|
|
|
1 |
#!/usr/bin/python2.6 |
2 |
# -*- coding: utf-8 -*- |
3 |
# |
4 |
# Univention Management Console |
5 |
# module: updater |
6 |
# |
7 |
# Copyright 2011 Univention GmbH |
8 |
# |
9 |
# http://www.univention.de/ |
10 |
# |
11 |
# All rights reserved. |
12 |
# |
13 |
# The source code of this program is made available |
14 |
# under the terms of the GNU Affero General Public License version 3 |
15 |
# (GNU AGPL V3) as published by the Free Software Foundation. |
16 |
# |
17 |
# Binary versions of this program provided by Univention to you as |
18 |
# well as other copyrighted, protected or trademarked materials like |
19 |
# Logos, graphics, fonts, specific documentations and configurations, |
20 |
# cryptographic keys etc. are subject to a license agreement between |
21 |
# you and Univention and not subject to the GNU AGPL V3. |
22 |
# |
23 |
# In the case you use this program under the terms of the GNU AGPL V3, |
24 |
# the program is provided in the hope that it will be useful, |
25 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 |
# GNU Affero General Public License for more details. |
28 |
# |
29 |
# You should have received a copy of the GNU Affero General Public |
30 |
# License with the Debian GNU/Linux or Univention distribution in file |
31 |
# /usr/share/common-licenses/AGPL-3; if not, see |
32 |
# <http://www.gnu.org/licenses/>. |
33 |
|
34 |
import pprint |
35 |
import subprocess |
36 |
import univention.management.console as umc |
37 |
import univention.management.console.modules as umcm |
38 |
import univention.config_registry |
39 |
|
40 |
from fnmatch import * |
41 |
import re |
42 |
import string |
43 |
import subprocess |
44 |
|
45 |
from univention.management.console.log import MODULE |
46 |
from univention.management.console.protocol.definitions import * |
47 |
|
48 |
_ = umc.Translation('univention-management-console-module-printers').translate |
49 |
|
50 |
class Instance(umcm.Base): |
51 |
|
52 |
def init(self): |
53 |
|
54 |
self.ucr = univention.config_registry.ConfigRegistry() |
55 |
self.ucr.load() |
56 |
|
57 |
self._hostname = self.ucr.get('hostname') |
58 |
|
59 |
|
60 |
def list_printers(self,request): |
61 |
""" Lists the printers for the overview grid. """ |
62 |
|
63 |
# ----------- DEBUG ----------------- |
64 |
MODULE.info("printers/query invoked with:") |
65 |
pp = pprint.PrettyPrinter(indent=4) |
66 |
st = pp.pformat(request.options).split("\n") |
67 |
for s in st: |
68 |
MODULE.info(" << %s" % s) |
69 |
# ----------------------------------- |
70 |
|
71 |
key = request.options.get('key','printer') |
72 |
pattern = request.options.get('pattern','*') |
73 |
|
74 |
quota = self._quota_enabled() # we need it later |
75 |
|
76 |
result = [] |
77 |
plist = self._list_printers() |
78 |
for element in plist: |
79 |
try: |
80 |
printer = element['printer'] |
81 |
data = self._printer_details(printer) |
82 |
for field in data: |
83 |
element[field] = data[field] |
84 |
# filter according to query |
85 |
if fnmatch(element[key],pattern): |
86 |
if printer in quota: |
87 |
element['quota'] = quota[printer] |
88 |
else: |
89 |
element['quota'] = False |
90 |
result.append(element) |
91 |
except: |
92 |
pass |
93 |
|
94 |
# ---------- DEBUG -------------- |
95 |
MODULE.info("printers/query returns:") |
96 |
pp = pprint.PrettyPrinter(indent=4) |
97 |
st = '' |
98 |
if len(result) > 5: |
99 |
tmp = result[0:5] |
100 |
MODULE.info(" >> %d entries, first 5 are:" % len(result)) |
101 |
st = pp.pformat(tmp).split("\n") |
102 |
else: |
103 |
st = pp.pformat(result).split("\n") |
104 |
for s in st: |
105 |
MODULE.info(" >> %s" % s) |
106 |
# -------------------------------- |
107 |
|
108 |
self.finished(request.id,result) |
109 |
|
110 |
def get(self,request): |
111 |
""" gets detail data for one printer. """ |
112 |
|
113 |
# ----------- DEBUG ----------------- |
114 |
MODULE.info("printers/get invoked with:") |
115 |
pp = pprint.PrettyPrinter(indent=4) |
116 |
st = pp.pformat(request.options).split("\n") |
117 |
for s in st: |
118 |
MODULE.info(" << %s" % s) |
119 |
# ----------------------------------- |
120 |
|
121 |
printer = request.options.get('printer','') |
122 |
result = self._printer_details(printer) |
123 |
result['printer'] = printer |
124 |
result['status'] = self._printer_status(printer) |
125 |
result['quota'] = self._quota_enabled(printer) |
126 |
|
127 |
# ---------- DEBUG -------------- |
128 |
MODULE.info("printers/get returns:") |
129 |
pp = pprint.PrettyPrinter(indent=4) |
130 |
st = pp.pformat(result).split("\n") |
131 |
for s in st: |
132 |
MODULE.info(" >> %s" % s) |
133 |
# -------------------------------- |
134 |
|
135 |
self.finished(request.id,result) |
136 |
|
137 |
def list_jobs(self,request): |
138 |
""" returns list of jobs for one printer. """ |
139 |
|
140 |
# ----------- DEBUG ----------------- |
141 |
MODULE.info("printers/jobs/query invoked with:") |
142 |
pp = pprint.PrettyPrinter(indent=4) |
143 |
st = pp.pformat(request.options).split("\n") |
144 |
for s in st: |
145 |
MODULE.info(" << %s" % s) |
146 |
# ----------------------------------- |
147 |
|
148 |
result = self._job_list(request.options.get('printer','')) |
149 |
|
150 |
# ---------- DEBUG -------------- |
151 |
MODULE.info("printers/jobs/query returns:") |
152 |
pp = pprint.PrettyPrinter(indent=4) |
153 |
st = '' |
154 |
if len(result) > 5: |
155 |
tmp = result[0:5] |
156 |
MODULE.info(" >> %d entries, first 5 are:" % len(result)) |
157 |
st = pp.pformat(tmp).split("\n") |
158 |
else: |
159 |
st = pp.pformat(result).split("\n") |
160 |
for s in st: |
161 |
MODULE.info(" >> %s" % s) |
162 |
# -------------------------------- |
163 |
|
164 |
self.finished(request.id,result) |
165 |
|
166 |
def list_quota(self,request): |
167 |
""" lists all quota entries related to this printer. """ |
168 |
|
169 |
# fill a dummy result table. |
170 |
printer = request.options.get('printer','') |
171 |
result = [] |
172 |
|
173 |
self.finished(request.id,result) |
174 |
|
175 |
def enable(self,request): |
176 |
""" can enable or disable a printer, depending on args. |
177 |
returns empty string on success, else error message. |
178 |
""" |
179 |
|
180 |
# ----------- DEBUG ----------------- |
181 |
MODULE.info("printers/enable invoked with:") |
182 |
pp = pprint.PrettyPrinter(indent=4) |
183 |
st = pp.pformat(request.options).split("\n") |
184 |
for s in st: |
185 |
MODULE.info(" << %s" % s) |
186 |
# ----------------------------------- |
187 |
|
188 |
printer = request.options.get('printer','') |
189 |
on = request.options.get('on',False) |
190 |
|
191 |
result = self._enable_printer(printer,on) |
192 |
|
193 |
# ---------- DEBUG -------------- |
194 |
MODULE.info("printers/enable returns:") |
195 |
pp = pprint.PrettyPrinter(indent=4) |
196 |
st = pp.pformat(result).split("\n") |
197 |
for s in st: |
198 |
MODULE.info(" >> %s" % s) |
199 |
# -------------------------------- |
200 |
|
201 |
self.finished(request.id, result) |
202 |
|
203 |
def cancel(self,request): |
204 |
""" cancels one or more print jobs. Job IDs are passed |
205 |
as an array that can be directly passed on to the |
206 |
_shell_command() method |
207 |
""" |
208 |
|
209 |
# ----------- DEBUG ----------------- |
210 |
MODULE.info("printers/jobs/cancel invoked with:") |
211 |
pp = pprint.PrettyPrinter(indent=4) |
212 |
st = pp.pformat(request.options).split("\n") |
213 |
for s in st: |
214 |
MODULE.info(" << %s" % s) |
215 |
# ----------------------------------- |
216 |
|
217 |
jobs = request.options['jobs'] |
218 |
printer = request.options.get('printer','') |
219 |
result = self._cancel_jobs(printer,jobs) |
220 |
|
221 |
# ---------- DEBUG -------------- |
222 |
MODULE.info("printers/jobs/cancel returns:") |
223 |
pp = pprint.PrettyPrinter(indent=4) |
224 |
st = pp.pformat(result).split("\n") |
225 |
for s in st: |
226 |
MODULE.info(" >> %s" % s) |
227 |
# -------------------------------- |
228 |
|
229 |
self.finished(request.id, result) |
230 |
|
231 |
|
232 |
|
233 |
# ----------------------- Internal functions ------------------------- |
234 |
|
235 |
def _job_list(self,printer): |
236 |
""" lists jobs for a given printer, directly suitable for the grid """ |
237 |
|
238 |
# *** NOTE *** we don't set language to 'neutral' since it is useful |
239 |
# to get localized date/time strings. |
240 |
|
241 |
result = [] |
242 |
(stdout,stderr,status) = self._shell_command(['/usr/bin/lpstat','-o',printer]) |
243 |
expr = re.compile('\s*(\S+)\s+(\S+)\s+(\d+)\s*(.*?)$') |
244 |
if status == 0: |
245 |
for line in stdout.split("\n"): |
246 |
mobj = expr.match(line) |
247 |
if mobj: |
248 |
entry = { |
249 |
'job': mobj.group(1), |
250 |
'owner': mobj.group(2), |
251 |
'size': mobj.group(3), |
252 |
'date': mobj.group(4) |
253 |
} |
254 |
result.append(entry) |
255 |
return result |
256 |
|
257 |
def _list_printers(self): |
258 |
""" returns a list of printers, along with their 'enabled' status. """ |
259 |
|
260 |
result = [] |
261 |
expr = re.compile('printer\s+(\S+)\s.*?(\S+abled)') |
262 |
(stdout,stderr,status) = self._shell_command(['/usr/bin/lpstat','-p'],{'LANG':'C'}) |
263 |
if status == 0: |
264 |
for line in stdout.split("\n"): |
265 |
mobj = expr.match(line) |
266 |
if mobj: |
267 |
entry = { 'printer' : mobj.group(1), 'status': mobj.group(2) } |
268 |
result.append(entry) |
269 |
return result |
270 |
|
271 |
def _printer_status(self,printer): |
272 |
""" returns the 'enabled' status of a printer """ |
273 |
|
274 |
(stdout,stderr,status) = self._shell_command(['/usr/bin/lpstat','-p',printer],{'LANG':'C'}) |
275 |
if status == 0: |
276 |
if ' enabled ' in stdout: |
277 |
return 'enabled' |
278 |
if ' disabled ' in stdout: |
279 |
return 'disabled' |
280 |
return 'unknown' |
281 |
|
282 |
def _printer_details(self,printer): |
283 |
""" returns as much as possible details about a printer. """ |
284 |
|
285 |
result = {} |
286 |
expr = re.compile('\s+([^\s\:]+)\:\s*(.*?)$') |
287 |
(stdout,stderr,status) = self._shell_command(['/usr/bin/lpstat','-l','-p',printer],{'LANG':'C'}) |
288 |
if status == 0: |
289 |
for line in stdout.split("\n"): |
290 |
mobj = expr.match(line) |
291 |
if mobj: |
292 |
result[mobj.group(1).lower()] = mobj.group(2) |
293 |
result['server'] = self._hostname |
294 |
return result |
295 |
|
296 |
def _enable_printer(self,printer,on): |
297 |
""" internal function that enables/disables a printer. |
298 |
returns empty string or error message. |
299 |
""" |
300 |
|
301 |
cmd = 'univention-cups-enable' if on else 'univention-cups-disable' |
302 |
(stdout,stderr,status) = self._shell_command([cmd,printer]) |
303 |
|
304 |
if status: |
305 |
return stderr |
306 |
|
307 |
# Q: What do these tools return if the cups command being called returns with error? |
308 |
# A: They return zero, the exit code meant for success. |
309 |
# |
310 |
# Q: Which is the channel where these tools print the ERROR message? |
311 |
# A: On STDOUT, as the name suggests. |
312 |
# |
313 |
# Q: What do these tools print on success? |
314 |
# A: Two newlines, instead of nothing. |
315 |
if re.search('\S',stdout): |
316 |
return stdout |
317 |
|
318 |
return '' |
319 |
|
320 |
def _quota_enabled(self,printer=None): |
321 |
""" returns a dictionary with printer names and their 'quota active' status. |
322 |
if printer is specified, returns only quota status for this printer. |
323 |
""" |
324 |
|
325 |
result = {} |
326 |
expr = re.compile('device for (\S+)\:\s*(\S+)$') |
327 |
(stdout,stderr,status) = self._shell_command(['/usr/bin/lpstat','-v'],{'LANG':'C'}) |
328 |
if status == 0: |
329 |
for line in stdout.split("\n"): |
330 |
match = expr.match(line) |
331 |
if match: |
332 |
quota = False |
333 |
if match.group(2).startswith('cupspykota'): |
334 |
quota = True |
335 |
result[match.group(1)] = quota |
336 |
# No printer specified: return the whole list. |
337 |
if printer == None: |
338 |
return result |
339 |
|
340 |
# Printer specified: return its quota value or False if not found. |
341 |
if printer in result: |
342 |
return result[printer] |
343 |
return False |
344 |
|
345 |
def _cancel_jobs(self,printer,jobs): |
346 |
""" internal function that cancels a list of jobs. |
347 |
returns empty string or error message. |
348 |
""" |
349 |
|
350 |
args = ['/usr/bin/cancel','-U','%s$' % self._hostname] |
351 |
for job in jobs: |
352 |
args.append(job) |
353 |
args.append(printer) |
354 |
(stdout,stderr,status) = self._shell_command(args) |
355 |
|
356 |
if status: |
357 |
return stderr |
358 |
return '' |
359 |
|
360 |
def _shell_command(self,args,env=None): |
361 |
|
362 |
proc = subprocess.Popen(args=args, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) |
363 |
outputs = proc.communicate() |
364 |
|
365 |
return (outputs[0],outputs[1],proc.returncode) |
366 |
|