local Ansi = require('opus.ansi') local SHA = require('opus.crypto.sha2') local UI = require('opus.ui') local Util = require('opus.util') local fs = _G.fs local http = _G.http local multishell = _ENV.multishell local os = _G.os local shell = _ENV.shell local colors = _G.colors local REGISTRY_DIR = 'usr/.registry' -- FIX SOMEDAY local function registerApp(app, key) app.key = SHA.compute(key) Util.writeTable(fs.combine(REGISTRY_DIR, app.key), app) os.queueEvent('os_register_app') end local function unregisterApp(key) local filename = fs.combine(REGISTRY_DIR, SHA.compute(key)) if fs.exists(filename) then fs.delete(filename) os.queueEvent('os_register_app') end end multishell.setTitle(multishell.getCurrent(), 'App Store') UI:configure('Appstore', ...) local APP_DIR = 'usr/apps' local source = { text = "STD Default", event = 'source', url = "https://github.com/LDDestroier/STD-GUI/raw/master/list.lua", } shell.setDir(APP_DIR) local function downloadApp(app) local h if type(app.url) == "table" then h = contextualGet(app.url[1]) else h = http.get(app.url) end if h then local contents = h.readAll() h:close() return contents end end local function runApp(app, checkExists, ...) local env = shell.makeEnv(_ENV) local path, fn local args = { ... } if checkExists and fs.exists(fs.combine(APP_DIR, app.name)) then path = fs.combine(APP_DIR, app.name) else local program = downloadApp(app) fn = function() if not program then error('Failed to download') end fn = _G.loadstring(program, app.name) if not fn then error('Failed to download') end _G.setfenv(fn, env) fn(table.unpack(args)) end end multishell.openTab(_ENV, { title = app.name, path = path, fn = fn, focused = true, }) return true, 'Running program' end local installApp = function(app) local program = downloadApp(app) if not program then return false, "Failed to download" end local fullPath = fs.combine(APP_DIR, app.name) Util.writeFile(fullPath, program) return true, 'Installed as ' .. fullPath end local viewApp = function(app) local program = downloadApp(app) if not program then return false, "Failed to download" end Util.writeFile('/.source', program) shell.openForegroundTab('edit /.source') fs.delete('/.source') return true end local getSourceListing = function() local contents = http.get(source.url) if contents then local fn = _G.loadstring(contents.readAll(), source.text) contents.close() local env = { std = { } } setmetatable(env, { __index = _G }) _G.setfenv(fn, env) fn() if env.contextualGet then contextualGet = env.contextualGet end source.storeURLs = env.std.storeURLs source.storeCatagoryNames = env.std.storeCatagoryNames if source.storeURLs and source.storeCatagoryNames then for k,v in pairs(source.storeURLs) do if source.generateName then v.name = v.title:match('(%w+)') if not v.name or #v.name == 0 then v.name = tostring(k) else v.name = v.name:lower() end else v.name = k end v.categoryName = source.storeCatagoryNames[v.catagory] v.ltitle = v.title:lower() end end end end getSourceListing() if not source.storeURLs then error('Unable to download application list') end local buttons = { } for k,v in Util.spairs(source.storeCatagoryNames, function(a, b) return a:lower() < b:lower() end) do if v ~= 'Operating System' then table.insert(buttons, { text = v, event = 'category', index = k, }) end end source.index, source.name = Util.first(source.storeCatagoryNames) local appPage = UI.Page { menuBar = UI.MenuBar { buttons = { { text = '\027', event = 'back' }, { text = 'Install', event = 'install' }, { text = 'Run', event = 'run' }, { text = 'View', event = 'view' }, { text = 'Remove', event = 'uninstall', name = 'removeButton' }, }, }, container = UI.Window { x = 2, y = 3, ex = -2, ey = -3, viewport = UI.Viewport(), }, notification = UI.Notification(), accelerators = { [ 'control-q' ] = 'back', backspace = 'back', }, } function appPage.container.viewport:draw() local app = self.parent.parent.app local str = string.format( '%s \nBy: %s \nCategory: %s\nFile name: %s\n\n%s', Ansi.yellow .. app.title .. Ansi.reset, app.creator, app.categoryName, app.name, Ansi.yellow .. app.description .. Ansi.reset) self:clear() self:print(str) if appPage.notification.enabled then appPage.notification:draw() end end function appPage:enable(app) self.source = source self.app = app UI.Page.enable(self) self.container.viewport:setScrollPosition(0) if fs.exists(fs.combine(APP_DIR, app.name)) then self.menuBar.removeButton:enable('Remove') else self.menuBar.removeButton:disable('Remove') end end function appPage:eventHandler(event) if event.type == 'back' then UI:setPreviousPage() elseif event.type == 'run' then self.notification:info('Running program', 3) self:sync() runApp(self.app, true) elseif event.type == 'view' then self.notification:info('Downloading program', 3) self:sync() viewApp(self.app) elseif event.type == 'uninstall' then if self.app.runOnly then runApp(self.app, false, 'uninstall') else fs.delete(fs.combine(APP_DIR, self.app.name)) self.notification:success("Uninstalled " .. self.app.name, 3) self:focusFirst(self) self.menuBar.removeButton:disable('Remove') self.menuBar:draw() unregisterApp(self.app.creator .. '.' .. self.app.name) end elseif event.type == 'install' then self.notification:info("Installing", 3) self:sync() local s, m if self.app.runOnly then s,m = runApp(self.app, false) else s,m = installApp(self.app) end if s then self.notification:success(m, 3) if not self.app.runOnly then self.menuBar.removeButton:enable('Remove') self.menuBar:draw() local category = 'Apps' if self.app.catagoryName == 'Game' then category = 'Games' end registerApp({ run = fs.combine(APP_DIR, self.app.name), title = self.app.title, category = category, icon = self.app.icon, }, self.app.creator .. '.' .. self.app.name) end else self.notification:error(m, 3) end else return UI.Page.eventHandler(self, event) end return true end local categoryPage = UI.Page { menuBar = UI.MenuBar { buttons = { { text = 'Category', name = 'categoryButton', dropdown = buttons }, }, }, grid = UI.ScrollingGrid { y = 2, ey = -2, columns = { { heading = 'Title', key = 'title' }, }, sortColumn = 'title', }, statusBar = UI.StatusBar(), accelerators = { l = 'lua', [ 'control-q' ] = 'quit', }, source = source, } function categoryPage:setCategory(name, index) self.grid.values = { } for _,v in pairs(source.storeURLs) do if index == 0 or index == v.catagory then table.insert(self.grid.values, v) end end self.statusBar:setStatus(string.format('%s: %s', self.source.text or '', name)) self.grid:update() self.grid:setIndex(1) end function categoryPage.grid:sortCompare(a, b) return a.ltitle < b.ltitle end function categoryPage.grid:getRowTextColor(row, selected) if fs.exists(fs.combine(APP_DIR, row.name)) then return colors.orange end return UI.Grid:getRowTextColor(row, selected) end function categoryPage:eventHandler(event) if event.type == 'grid_select' or event.type == 'select' then UI:setPage(appPage, self.grid:getSelected()) elseif event.type == 'category' then self:setCategory(event.button.text, event.button.index) self:setFocus(self.grid) self:draw() elseif event.type == 'source' then self:setFocus(self.grid) self:setSource(event.button) self:draw() elseif event.type == 'quit' then UI:quit() else return UI.Page.eventHandler(self, event) end return true end print("Retrieving catalog list") categoryPage:setCategory(source.name, source.index) UI:setPage(categoryPage) UI:start()