1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
-- Copyright 2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
-- Licensed under the Apache License, Version 2.0
module("luci.controller.radicale", package.seeall)
local NX = require("nixio")
local NXFS = require("nixio.fs")
local DISP = require("luci.dispatcher")
local HTTP = require("luci.http")
local I18N = require("luci.i18n") -- not globally avalible here
local IPKG = require("luci.model.ipkg")
local UTIL = require("luci.util")
local SYS = require("luci.sys")
local srv_name = "radicale"
local srv_ver_min = "1.1" -- minimum version of service required
local srv_ver_cmd = [[/usr/bin/radicale --version]]
local app_name = "luci-app-radicale"
local app_title = I18N.translate("Radicale CalDAV/CardDAV Server")
local app_version = "1.1.0-1"
function index()
entry( {"admin", "services", "radicale"}, alias("admin", "services", "radicale", "edit"), _("CalDAV/CardDAV"), 58).acl_depends = { "luci-app-radicale" }
entry( {"admin", "services", "radicale", "edit"}, cbi("radicale") ).leaf = true
entry( {"admin", "services", "radicale", "logview"}, call("_logread") ).leaf = true
entry( {"admin", "services", "radicale", "startstop"}, post("_startstop") ).leaf = true
entry( {"admin", "services", "radicale", "status"}, call("_status") ).leaf = true
end
-- Application / Service specific information functions
function app_description()
return I18N.translate("The Radicale Project is a complete CalDAV (calendar) and CardDAV (contact) server solution.") .. [[<br />]]
.. I18N.translate("Calendars and address books are available for both local and remote access, possibly limited through authentication policies.") .. [[<br />]]
.. I18N.translate("They can be viewed and edited by calendar and contact clients on mobile phones or computers.")
end
function app_title_main()
return [[<a href="javascript:alert(']]
.. I18N.translate("Version Information")
.. [[\n\n]] .. app_name
.. [[\n\t]] .. I18N.translate("Version") .. [[:\t]] .. app_version
.. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("required") .. [[:]]
.. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
.. srv_ver_min .. [[ ]] .. I18N.translate("or higher")
.. [[\n\n]] .. srv_name .. [[ ]] .. I18N.translate("installed") .. [[:]]
.. [[\n\t]] .. I18N.translate("Version") .. [[:\t]]
.. (service_version() or I18N.translate("NOT installed"))
.. [[\n\n]]
.. [[')">]]
.. I18N.translate(app_title)
.. [[</a>]]
end
function app_title_back()
return [[<a href="]]
.. DISP.build_url("admin", "services", "radicale")
.. [[">]]
.. I18N.translate(app_title)
.. [[</a>]]
end
function app_err_value()
if not service_version() then
return [[<h3><strong><br /><font color="red"> ]]
.. I18N.translate("Software package '%s' is not installed." % srv_name)
.. [[</font><br /><br /> ]]
.. I18N.translate("required") .. [[: ]] .. srv_name .. [[ ]] .. srv_ver_min
.. [[<br /><br /> ]]
.. [[<a href="]] .. DISP.build_url("admin", "system", "packages") ..[[">]]
.. I18N.translate("Please install current version !")
.. [[</a><br /> </strong></h3>]]
else
return [[<h3><strong><br /><font color="red"> ]]
.. I18N.translate("Software package '%s' is outdated." % srv_name)
.. [[</font><br /><br /> ]]
.. I18N.translate("installed") .. [[: ]] .. srv_name .. [[ ]] .. service_version()
.. [[<br /> ]]
.. I18N.translate("required") .. [[: ]] .. srv_name .. [[ ]] .. srv_ver_min
.. [[<br /><br /> ]]
.. [[<a href="]] .. DISP.build_url("admin", "system", "packages") ..[[">]]
.. I18N.translate("Please update to current version !")
.. [[</a><br /> </strong></h3>]]
end
end
function service_version()
local ver = nil
IPKG.list_installed(srv_name, function(n, v, d)
if v and (#v > 0) then ver = v end
end
)
if not ver or (#ver == 0) then
ver = UTIL.exec(srv_ver_cmd)
if #ver == 0 then ver = nil end
end
return ver
end
function service_ok()
return IPKG.compare_versions((service_version() or "0"), ">=", srv_ver_min)
end
-- called by XHR.get from detail_logview.htm
function _logread()
-- read application settings
local uci = UCI.cursor()
local logfile = uci:get("radicale", "radicale", "logfile") or "/var/log/radicale"
uci:unload("radicale")
local ldata=NXFS.readfile(logfile)
if not ldata or #ldata == 0 then
ldata="_nodata_"
end
HTTP.write(ldata)
end
-- called by XHR.get from detail_startstop.htm
function _startstop()
local pid = get_pid()
if pid > 0 then
SYS.call("/etc/init.d/radicale stop")
NX.nanosleep(1) -- sleep a second
if NX.kill(pid, 0) then -- still running
NX.kill(pid, 9) -- send SIGKILL
end
pid = 0
else
SYS.call("/etc/init.d/radicale start")
NX.nanosleep(1) -- sleep a second
pid = get_pid()
if pid > 0 and not NX.kill(pid, 0) then
pid = 0 -- process did not start
end
end
HTTP.write(tostring(pid)) -- HTTP needs string not number
end
-- called by XHR.poll from detail_startstop.htm
function _status()
local pid = get_pid()
HTTP.write(tostring(pid)) -- HTTP needs string not number
end
--return pid of running process
function get_pid()
return tonumber(SYS.exec([[ps | grep "[p]ython.*[r]adicale" 2>/dev/null | awk '{print $1}']])) or 0
end
-- replacement of build-in parse of "Value"
-- modified AbstractValue.parse(self, section, novld) from cbi.lua
-- validate is called if rmempty/optional true or false
-- before write check if forcewrite, value eq default, and more
function value_parse(self, section, novld)
local fvalue = self:formvalue(section)
local fexist = ( fvalue and (#fvalue > 0) ) -- not "nil" and "not empty"
local cvalue = self:cfgvalue(section)
local rm_opt = ( self.rmempty or self.optional )
local eq_cfg -- flag: equal cfgvalue
-- If favlue and cvalue are both tables and have the same content
-- make them identical
if type(fvalue) == "table" and type(cvalue) == "table" then
eq_cfg = (#fvalue == #cvalue)
if eq_cfg then
for i=1, #fvalue do
if cvalue[i] ~= fvalue[i] then
eq_cfg = false
end
end
end
if eq_cfg then
fvalue = cvalue
end
end
-- removed parameter "section" from function call because used/accepted nowhere
-- also removed call to function "transfer"
local vvalue, errtxt = self:validate(fvalue)
-- error handling; validate return "nil"
if not vvalue then
if novld then -- and "novld" set
return -- then exit without raising an error
end
if fexist then -- and there is a formvalue
self:add_error(section, "invalid", errtxt)
return -- so data are invalid
elseif not rm_opt then -- and empty formvalue but NOT (rmempty or optional) set
self:add_error(section, "missing", errtxt)
return -- so data is missing
elseif errtxt then
self:add_error(section, "invalid", errtxt)
return
end
-- error ("\n option: " .. self.option ..
-- "\n fvalue: " .. tostring(fvalue) ..
-- "\n fexist: " .. tostring(fexist) ..
-- "\n cvalue: " .. tostring(cvalue) ..
-- "\n vvalue: " .. tostring(vvalue) ..
-- "\n vexist: " .. tostring(vexist) ..
-- "\n rm_opt: " .. tostring(rm_opt) ..
-- "\n eq_cfg: " .. tostring(eq_cfg) ..
-- "\n eq_def: " .. tostring(eq_def) ..
-- "\n novld : " .. tostring(novld) ..
-- "\n errtxt: " .. tostring(errtxt) )
end
-- lets continue with value returned from validate
eq_cfg = ( vvalue == cvalue ) -- update equal_config flag
local vexist = ( vvalue and (#vvalue > 0) ) and true or false -- not "nil" and "not empty"
local eq_def = ( vvalue == self.default ) -- equal_default flag
-- (rmempty or optional) and (no data or equal_default)
if rm_opt and (not vexist or eq_def) then
if self:remove(section) then -- remove data from UCI
self.section.changed = true -- and push events
end
return
end
-- not forcewrite and no changes, so nothing to write
if not self.forcewrite and eq_cfg then
return
end
-- we should have a valid value here
assert (vvalue, "\n option: " .. self.option ..
"\n fvalue: " .. tostring(fvalue) ..
"\n fexist: " .. tostring(fexist) ..
"\n cvalue: " .. tostring(cvalue) ..
"\n vvalue: " .. tostring(vvalue) ..
"\n vexist: " .. tostring(vexist) ..
"\n rm_opt: " .. tostring(rm_opt) ..
"\n eq_cfg: " .. tostring(eq_cfg) ..
"\n eq_def: " .. tostring(eq_def) ..
"\n errtxt: " .. tostring(errtxt) )
-- write data to UCI; raise event only on changes
if self:write(section, vvalue) and not eq_cfg then
self.section.changed = true
end
end
|