for-15.05 opkg/packages: Show package size in list of available packages
[project/luci.git] / modules / luci-base / luasrc / model / ipkg.lua
1 -- Copyright 2008-2011 Jo-Philipp Wich <jow@openwrt.org>
2 -- Copyright 2008 Steven Barth <steven@midlink.org>
3 -- Licensed to the public under the Apache License 2.0.
4
5 local os = require "os"
6 local io = require "io"
7 local fs = require "nixio.fs"
8 local util = require "luci.util"
9
10 local type = type
11 local pairs = pairs
12 local error = error
13 local table = table
14
15 local ipkg = "opkg --force-removal-of-dependent-packages --force-overwrite --nocase"
16 local icfg = "/etc/opkg.conf"
17
18 module "luci.model.ipkg"
19
20
21 -- Internal action function
22 local function _action(cmd, ...)
23 local pkg = ""
24 for k, v in pairs({...}) do
25 pkg = pkg .. " '" .. v:gsub("'", "") .. "'"
26 end
27
28 local c = "%s %s %s >/tmp/opkg.stdout 2>/tmp/opkg.stderr" %{ ipkg, cmd, pkg }
29 local r = os.execute(c)
30 local e = fs.readfile("/tmp/opkg.stderr")
31 local o = fs.readfile("/tmp/opkg.stdout")
32
33 fs.unlink("/tmp/opkg.stderr")
34 fs.unlink("/tmp/opkg.stdout")
35
36 return r, o or "", e or ""
37 end
38
39 -- Internal parser function
40 local function _parselist(rawdata)
41 if type(rawdata) ~= "function" then
42 error("OPKG: Invalid rawdata given")
43 end
44
45 local data = {}
46 local c = {}
47 local l = nil
48
49 for line in rawdata do
50 if line:sub(1, 1) ~= " " then
51 local key, val = line:match("(.-): ?(.*)%s*")
52
53 if key and val then
54 if key == "Package" then
55 c = {Package = val}
56 data[val] = c
57 elseif key == "Status" then
58 c.Status = {}
59 for j in val:gmatch("([^ ]+)") do
60 c.Status[j] = true
61 end
62 else
63 c[key] = val
64 end
65 l = key
66 end
67 else
68 -- Multi-line field
69 c[l] = c[l] .. "\n" .. line
70 end
71 end
72
73 return data
74 end
75
76 -- Internal lookup function
77 local function _lookup(act, pkg)
78 local cmd = ipkg .. " " .. act
79 if pkg then
80 cmd = cmd .. " '" .. pkg:gsub("'", "") .. "'"
81 end
82
83 -- OPKG sometimes kills the whole machine because it sucks
84 -- Therefore we have to use a sucky approach too and use
85 -- tmpfiles instead of directly reading the output
86 local tmpfile = os.tmpname()
87 os.execute(cmd .. (" >%s 2>/dev/null" % tmpfile))
88
89 local data = _parselist(io.lines(tmpfile))
90 os.remove(tmpfile)
91 return data
92 end
93
94
95 function info(pkg)
96 return _lookup("info", pkg)
97 end
98
99 function status(pkg)
100 return _lookup("status", pkg)
101 end
102
103 function install(...)
104 return _action("install", ...)
105 end
106
107 function installed(pkg)
108 local p = status(pkg)[pkg]
109 return (p and p.Status and p.Status.installed)
110 end
111
112 function remove(...)
113 return _action("remove", ...)
114 end
115
116 function update()
117 return _action("update")
118 end
119
120 function upgrade()
121 return _action("upgrade")
122 end
123
124 -- List helper
125 function _list(action, pat, cb)
126 local fd = io.popen(ipkg .. " " .. action ..
127 (pat and (" '%s'" % pat:gsub("'", "")) or ""))
128
129 if fd then
130 local name, version, sz, desc
131 while true do
132 local line = fd:read("*l")
133 if not line then break end
134
135 name, version, sz, desc = line:match("^(.-) %- (.-) %- (.-) %- (.+)")
136
137 if not name then
138 name, version, sz = line:match("^(.-) %- (.-) %- (.+)")
139 desc = ""
140 end
141
142 if #version > 26 then
143 version = version:sub(1,21) .. ".." .. version:sub(-3,-1)
144 end
145
146 cb(name, version, sz, desc)
147
148 name = nil
149 version = nil
150 sz = nil
151 desc = nil
152 end
153
154 fd:close()
155 end
156 end
157
158 function list_all(pat, cb)
159 _list("list --size", pat, cb)
160 end
161
162 function list_installed(pat, cb)
163 _list("list_installed --size", pat, cb)
164 end
165
166 function find(pat, cb)
167 _list("find --size", pat, cb)
168 end
169
170
171 function overlay_root()
172 local od = "/"
173 local fd = io.open(icfg, "r")
174
175 if fd then
176 local ln
177
178 repeat
179 ln = fd:read("*l")
180 if ln and ln:match("^%s*option%s+overlay_root%s+") then
181 od = ln:match("^%s*option%s+overlay_root%s+(%S+)")
182
183 local s = fs.stat(od)
184 if not s or s.type ~= "dir" then
185 od = "/"
186 end
187
188 break
189 end
190 until not ln
191
192 fd:close()
193 end
194
195 return od
196 end