diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 5ab6587..79e4294 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -134,6 +134,7 @@ function M.open_asset_store() end local dialog_component = editor.ui.component(function(props) + editor.prefs.set("druid.asset_install_folder", "./widget") -- State management local items, set_items = editor.ui.use_state(initial_items) local loading, set_loading = editor.ui.use_state(initial_loading) diff --git a/druid/editor_scripts/core/base64.lua b/druid/editor_scripts/core/base64.lua new file mode 100644 index 0000000..7353aa8 --- /dev/null +++ b/druid/editor_scripts/core/base64.lua @@ -0,0 +1,35 @@ +-- base64 encode/decode (http://lua-users.org/wiki/BaseSixtyFour) + +local M = {} + +local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +function M.encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +function M.decode(data) + data = string.gsub(data, '[^'..b..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(b:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +return M diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index beae5ee..5ba50fe 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -1,92 +1,83 @@ +local base64 = require("druid.editor_scripts.core.base64") --- Module for handling widget installation from zip files --- Downloads zip files and extracts them to the specified folder local M = {} -local DEFAULT_INSTALL_FOLDER = "/widget" +local DEFAULT_INSTALL_FOLDER = "./widget" + +---@class druid.core.item_info +---@field id string +---@field version string +---@field title string +---@field author string +---@field description string +---@field api string +---@field author_url string +---@field image string +---@field manifest_url string +---@field zip_url string +---@field json_zip_url string +---@field sha256 string +---@field size number +---@field depends string[] +---@field tags string[] ---Download a file from URL ---@param url string - The URL to download from ----@return string|nil, string|nil - Downloaded content or nil, error message or nil -local function download_file(url) - print("Downloading from:", url) +---@return string|nil, string|nil - Downloaded content or nil, filename or nil +local function download_file_zip_json(url) + local response = http.request(url, { as = "json" }) - -- Try different approaches for downloading binary data - local success, response = pcall(function() - -- First try without specifying 'as' parameter - return http.request(url) - end) + if response.status ~= 200 then + print("Failed to download file. HTTP status: " .. response.status) + return nil + end - -- If that fails, try with 'as = "string"' - if not success or not response or not response.body then - print("First attempt failed, trying with as='string'") - success, response = pcall(function() - return http.request(url, { - as = "string" - }) - end) - end + local data = response.body - if not success then - print("HTTP request failed:", response) - return nil, "HTTP request failed: " .. tostring(response) - end - - if not response then - print("No response received") - return nil, "No response received from server" - end - - print("Response status:", response.status) - print("Response body type:", type(response.body)) - print("Response body length:", response.body and #response.body or "nil") - if response.headers then - print("Response headers:", response.headers["content-type"] or "unknown") - print("Content length header:", response.headers["content-length"] or "unknown") - end - - if response.status ~= 200 then - return nil, "Failed to download file. HTTP status: " .. tostring(response.status) - end - - if not response.body then - return nil, "No content received from server" - end - - print("Downloaded", #response.body, "bytes") - return response.body, nil + return base64.decode(data.data), data.filename end ---Install a widget from a zip URL ----@param item table - Widget item data containing zip_url and id +---@param item druid.core.item_info - Widget item data containing zip_url and id ---@param install_folder string - Target folder to install to ---@return boolean, string - Success status and message function M.install_widget(item, install_folder) - if not item.zip_url or not item.id then - return false, "Invalid widget data: missing zip_url or id" - end + if not item.json_zip_url or not item.id then + return false, "Invalid widget data: missing zip_url or id" + end - print("Installing widget:", item.id) - print("Download URL:", item.zip_url) - print("Target folder:", install_folder) + -- Download the zip file + local zip_data, filename = download_file_zip_json(item.json_zip_url) + if not zip_data or not filename then + return false, "Failed to download widget: " .. filename + end - -- Download the zip file - local zip_data, download_error = download_file(item.zip_url) - if not zip_data then - return false, "Failed to download widget: " .. download_error - end - -- Create a simple success message for now - local success = true - local message = "Widget '" .. item.id .. "' downloaded successfully!" - message = message .. "\nDownload URL: " .. item.zip_url - message = message .. "\nSize: " .. tostring(#zip_data) .. " bytes" - message = message .. "\nTarget folder: " .. install_folder + local zip_file_path = install_folder .. "/" .. filename + local zip_file = io.open(zip_file_path, "wb") + if not zip_file then + return false, "Failed to open zip file: " .. zip_file_path + end - print("Successfully downloaded widget:", item.id) - return success, message + zip_file:write(zip_data) + zip_file:close() + print("Zip written to file: " .. zip_file_path) + + -- Unzip the zip file + local folder_name = item.id .. "-" .. item.version + zip.unpack(zip_file_path, install_folder .. "/" .. folder_name) + print("Widget unpacked successfully") + + -- Remove the zip file + os.remove(zip_file_path) + print("Zip file removed successfully") + + + return true, "Widget installed successfully" end @@ -95,15 +86,15 @@ end ---@param install_folder string - Install folder to check in ---@return boolean - True if widget is already installed function M.is_widget_installed(item, install_folder) - -- For now, assume widgets are not installed to avoid path issues - return false + -- For now, assume widgets are not installed to avoid path issues + return false end ---Get default installation folder ---@return string - Default installation folder path function M.get_default_install_folder() - return DEFAULT_INSTALL_FOLDER + return DEFAULT_INSTALL_FOLDER end