Skip to content

Commit ecc8d8d

Browse files
authored
fix(plugin): downloader needs to check for files and move them (#48)
1 parent 94267f0 commit ecc8d8d

3 files changed

Lines changed: 138 additions & 42 deletions

File tree

lua/snap/backend.lua

Lines changed: 77 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,34 @@ local function download_file_async(url, output_path, progress_callback, callback
113113
local last_progress = 0
114114
local download_completed = false
115115
local final_stats = nil
116+
local progress_100_reported = false
117+
local debounce_timer = nil
118+
local pending_progress = nil
119+
120+
-- Debounced progress callback to prevent rapid updates
121+
local function report_progress(progress_data)
122+
pending_progress = progress_data
123+
-- Clear existing timer
124+
if debounce_timer then
125+
vim.fn.timer_stop(debounce_timer)
126+
end
127+
-- Set new timer to report after 100ms
128+
debounce_timer = vim.fn.timer_start(100, function()
129+
if pending_progress and progress_callback then
130+
-- Only report 100% once
131+
if pending_progress.progress == 100 then
132+
if not progress_100_reported then
133+
progress_callback(pending_progress)
134+
progress_100_reported = true
135+
download_completed = true
136+
end
137+
else
138+
progress_callback(pending_progress)
139+
end
140+
pending_progress = nil
141+
end
142+
end)
143+
end
116144

117145
local job_id = vim.fn.jobstart(cmd, {
118146
env = vim.fn.environ(),
@@ -138,16 +166,15 @@ local function download_file_async(url, output_path, progress_callback, callback
138166
}
139167

140168
-- Only report if we haven't completed yet and have valid data
141-
if size_total > 0 and progress_callback and not download_completed then
169+
if size_total > 0 and not download_completed then
142170
local progress = math.floor((size_download / size_total) * 100)
171+
-- Cap at 99% to avoid showing 100% multiple times (let on_exit handle final 100%)
172+
progress = math.min(99, progress)
143173
if progress ~= last_progress then
144174
local speed_mb = speed / 1024 / 1024
145175
local message = string.format("Downloading backend... %d%% (%.2f MB/s)", progress, speed_mb)
146-
progress_callback({ progress = progress, message = message })
176+
report_progress({ progress = progress, message = message })
147177
last_progress = progress
148-
if progress >= 100 then
149-
download_completed = true
150-
end
151178
end
152179
end
153180
end
@@ -156,7 +183,7 @@ local function download_file_async(url, output_path, progress_callback, callback
156183
on_stderr = vim.schedule_wrap(function(_, data, _)
157184
-- Parse curl's -# progress bar from stderr for real-time updates
158185
-- Format: "##..." where each # represents ~2% progress (50 # = 100%)
159-
if data and #data > 0 and progress_callback and not download_completed then
186+
if data and #data > 0 and not download_completed then
160187
for _, line in ipairs(data) do
161188
if line ~= "" then
162189
-- Count # characters in the line
@@ -166,10 +193,10 @@ local function download_file_async(url, output_path, progress_callback, callback
166193
end
167194
-- Simple progress bar has ~50 # characters for 100%
168195
if hash_count > 0 then
169-
local estimated_progress = math.min(100, math.floor((hash_count / 50) * 100))
196+
local estimated_progress = math.min(99, math.floor((hash_count / 50) * 100))
170197
-- Only update if progress changed and we haven't completed
171198
if estimated_progress ~= last_progress and estimated_progress < 100 then
172-
progress_callback({
199+
report_progress({
173200
progress = estimated_progress,
174201
message = string.format("Downloading backend... %d%%", estimated_progress),
175202
})
@@ -181,6 +208,17 @@ local function download_file_async(url, output_path, progress_callback, callback
181208
end
182209
end),
183210
on_exit = vim.schedule_wrap(function(_, exit_code, _)
211+
-- Stop any pending debounce timer
212+
if debounce_timer then
213+
vim.fn.timer_stop(debounce_timer)
214+
debounce_timer = nil
215+
end
216+
-- Flush any pending progress immediately
217+
if pending_progress and progress_callback and pending_progress.progress ~= 100 then
218+
progress_callback(pending_progress)
219+
pending_progress = nil
220+
end
221+
184222
if exit_code ~= 0 then
185223
Logger.error("Download failed with exit code: " .. tostring(exit_code))
186224
if callback then
@@ -201,13 +239,15 @@ local function download_file_async(url, output_path, progress_callback, callback
201239
f:close()
202240
make_executable(output_path)
203241
-- Report completion only if we haven't already reported 100%
204-
if progress_callback and not download_completed then
242+
if progress_callback and not progress_100_reported then
205243
if final_stats and final_stats.size_total > 0 then
206244
local speed_mb = (final_stats.speed or 0) / 1024 / 1024
207245
progress_callback({ progress = 100, message = string.format("Download completed! (%.2f MB/s)", speed_mb) })
208246
else
209247
progress_callback({ progress = 100, message = "Download completed!" })
210248
end
249+
progress_100_reported = true
250+
download_completed = true
211251
end
212252
Logger.notify("Snap.nvim backend downloaded successfully!", Logger.LoggerLogLevels.info)
213253
if callback then
@@ -482,45 +522,41 @@ local function move_extracted_files(temp_dir, bin_dir)
482522
local ext = IS_WINDOWS and ".exe" or ""
483523
local release_bin_name = "snap-nvim-" .. plat .. ext
484524
local bin_path = M.get_bin_path()
485-
local playwright_dir = join_paths(bin_dir, "playwright")
486-
487-
local found_binary = false
488-
local found_playwright = false
489-
490-
-- Look for binary at root of temp directory (archive extracts to root)
491-
local temp_binary = join_paths(temp_dir, release_bin_name)
492-
if vim.fn.filereadable(temp_binary) == 1 then
493-
-- Move binary to final location
494-
vim.fn.rename(temp_binary, bin_path)
495-
make_executable(bin_path)
496-
found_binary = true
497-
end
498525

499-
-- Look for playwright directory at root of temp directory
500-
local temp_playwright = join_paths(temp_dir, "playwright")
501-
if vim.fn.isdirectory(temp_playwright) == 1 then
502-
-- Remove old playwright directory if it exists
503-
if vim.fn.isdirectory(playwright_dir) == 1 then
504-
vim.fn.delete(playwright_dir, "rf")
526+
local lookup_temp_paths = {
527+
join_paths(temp_dir, release_bin_name),
528+
join_paths(temp_dir, "node_modules"),
529+
join_paths(temp_dir, "playwright"),
530+
}
531+
local lookup_bin_paths = {
532+
bin_path,
533+
join_paths(bin_dir, "node_modules"),
534+
join_paths(bin_dir, "playwright"),
535+
}
536+
537+
for i, temp_lookup in ipairs(lookup_temp_paths) do
538+
if vim.fn.filereadable(temp_lookup) == 0 and vim.fn.isdirectory(temp_lookup) == 0 then
539+
Logger.error("Expected file or directory not found in extracted archive: " .. temp_lookup)
540+
return false
541+
end
542+
local bin_lookup = lookup_bin_paths[i]
543+
if vim.fn.filereadable(bin_lookup) == 1 then
544+
vim.fn.rename(temp_lookup, bin_lookup)
545+
make_executable(bin_lookup)
546+
end
547+
if vim.fn.isdirectory(temp_lookup) == 1 then
548+
-- Remove old directory if it exists
549+
if vim.fn.isdirectory(bin_lookup) == 1 then
550+
vim.fn.delete(bin_lookup, "rf")
551+
end
552+
-- Move directory to final location
553+
vim.fn.rename(temp_lookup, bin_lookup)
505554
end
506-
-- Move playwright directory to final location
507-
vim.fn.rename(temp_playwright, playwright_dir)
508-
found_playwright = true
509555
end
510556

511557
-- Clean up temp directory
512558
vim.fn.delete(temp_dir, "rf")
513559

514-
if not found_binary then
515-
Logger.error("Binary not found in extracted archive")
516-
return false
517-
end
518-
519-
-- Playwright directory is optional (for backwards compatibility)
520-
if found_playwright then
521-
Logger.notify("Playwright bundled with backend", Logger.LoggerLogLevels.info)
522-
end
523-
524560
return true
525561
end
526562

lua/snap/export.lua

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,41 @@ local function install_backend(progress_callback, completion_callback)
9898
-- Use jobstart for real-time output streaming
9999
local final_result = nil
100100
local stdout_buffer = ""
101+
local last_progress_status = nil
102+
local last_progress_value = nil
103+
local progress_100_reported = false
104+
local debounce_timer = nil
105+
local pending_progress = nil
106+
107+
-- Debounced progress callback to prevent rapid updates
108+
local function report_progress(progress_data)
109+
pending_progress = progress_data
110+
-- Clear existing timer
111+
if debounce_timer then
112+
vim.fn.timer_stop(debounce_timer)
113+
end
114+
-- Set new timer to report after 150ms
115+
debounce_timer = vim.fn.timer_start(150, function()
116+
if pending_progress and progress_callback then
117+
-- Only report 100% or "completed" once
118+
if (pending_progress.progress == 100 or pending_progress.status == "completed") then
119+
if not progress_100_reported then
120+
progress_callback(pending_progress)
121+
progress_100_reported = true
122+
end
123+
else
124+
-- Only report if status or progress value changed
125+
if pending_progress.status ~= last_progress_status or
126+
(pending_progress.progress and pending_progress.progress ~= last_progress_value) then
127+
progress_callback(pending_progress)
128+
last_progress_status = pending_progress.status
129+
last_progress_value = pending_progress.progress
130+
end
131+
end
132+
pending_progress = nil
133+
end
134+
end)
135+
end
101136

102137
local job_id = vim.fn.jobstart(system_args, {
103138
cwd = cwd,
@@ -124,8 +159,16 @@ local function install_backend(progress_callback, completion_callback)
124159
if res.data and res.data.type == "install" then
125160
if res.data.status == "completed" then
126161
final_result = res
162+
-- Report completion only once
163+
if not progress_100_reported and progress_callback then
164+
report_progress({
165+
status = res.data.status,
166+
message = res.data.message,
167+
progress = res.data.progress or 100,
168+
})
169+
end
127170
elseif progress_callback then
128-
progress_callback({
171+
report_progress({
129172
status = res.data.status,
130173
message = res.data.message,
131174
progress = res.data.progress,
@@ -145,6 +188,22 @@ local function install_backend(progress_callback, completion_callback)
145188
end
146189
end),
147190
on_exit = vim.schedule_wrap(function(_, exit_code, _)
191+
-- Stop any pending debounce timer
192+
if debounce_timer then
193+
vim.fn.timer_stop(debounce_timer)
194+
debounce_timer = nil
195+
end
196+
-- Flush any pending progress immediately
197+
if pending_progress and progress_callback then
198+
if not (pending_progress.progress == 100 or pending_progress.status == "completed") or not progress_100_reported then
199+
progress_callback(pending_progress)
200+
if pending_progress.progress == 100 or pending_progress.status == "completed" then
201+
progress_100_reported = true
202+
end
203+
end
204+
pending_progress = nil
205+
end
206+
148207
if exit_code ~= 0 then
149208
Logger.error("Install failed with exit code: " .. tostring(exit_code))
150209
if completion_callback then

scripts/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ echo " 🧹 Cleaning up temporary files..."
275275
echo
276276
# Remove copied playwright and node_modules to keep dist clean
277277
rm -rf "$PLAYWRIGHT_BROWSERS_PATH" \
278+
"./dist/node_modules" \
278279
"./dist/playwright" \
279280
"./dist/$BINARY_NAME"
280281
echo " ✅ Cleanup completed."

0 commit comments

Comments
 (0)