Changeset 3413 for pykota/trunk/bin
- Timestamp:
- 09/27/08 22:02:37 (16 years ago)
- Location:
- pykota/trunk/bin
- Files:
-
- 21 modified
Legend:
- Unmodified
- Added
- Removed
-
pykota/trunk/bin/autopykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 61 61 if printer.Exists : 62 62 self.logdebug("Printer %s created successfully." % printername) 63 else : 63 else : 64 64 self.logdebug("Impossible to create printer %s." % printername) 65 65 printernames = [printername] 66 else : 66 else : 67 67 printernames = [printer.Name] + [p.Name for p in self.storage.getParentPrinters(printer)] 68 68 69 69 user = self.storage.getUser(username) 70 70 if not user.Exists : … … 72 72 if (options.email is None) : 73 73 os.system('pkusers --add --limitby balance --balance "%s" --description "%s" "%s"' \ 74 % (options.initbalance, 74 % (options.initbalance, 75 75 _("user created with autopykota").encode(self.charset, "replace"), 76 76 username.encode(self.charset))) 77 77 else : 78 78 os.system('pkusers --add --limitby balance --balance "%s" --email "%s" --description "%s" "%s"' \ 79 % (options.initbalance, 80 options.email.encode(self.charset), 79 % (options.initbalance, 80 options.email.encode(self.charset), 81 81 _("user created with autopykota").encode(self.charset, "replace"), 82 82 username.encode(self.charset))) 83 83 84 84 user = self.storage.getUserFromBackend(username) 85 85 if user.Exists : 86 86 self.logdebug("User %s created successfully." % username) 87 else : 87 else : 88 88 self.logdebug("Impossible to create user %s." % username) 89 90 if user.Exists and printer.Exists : 89 90 if user.Exists and printer.Exists : 91 91 userpquota = self.storage.getUserPQuota(user, printer) 92 92 if not userpquota.Exists : … … 94 94 % (username, printernames)) 95 95 os.system('edpykota --add --printer "%s" "%s"' \ 96 % (','.join(printernames).encode(self.charset), 96 % (','.join(printernames).encode(self.charset), 97 97 username.encode(self.charset))) 98 userpquota = self.storage.getUserPQuotaFromBackend(user, 98 userpquota = self.storage.getUserPQuotaFromBackend(user, 99 99 printer) 100 100 if userpquota.Exists : … … 102 102 % (username, printername)) 103 103 return 0 104 else : 104 else : 105 105 self.logdebug("Impossible to create user %s's print quota entry on printer %s." \ 106 106 % (username, printername)) … … 110 110 % (username, printername)) 111 111 return 0 112 else : 112 else : 113 113 return -1 114 115 if __name__ == "__main__" : 114 115 if __name__ == "__main__" : 116 116 parser = PyKotaOptionParser(description=_("A tool to automate user account creation and initial balance setting. THIS TOOL MUST NOT BE USED FROM THE COMMAND LINE BUT ONLY AS PART OF AN external policy IN pykota.conf, AND MUST NOT BE USED IF YOU WANT TO LIMIT YOUR USERS BY PAGE QUOTA !"), 117 117 usage="autopykota { -i | --initbalance value } [options]") … … 124 124 dest="email", 125 125 help=_("Set the user's email address.")) 126 126 127 127 parser.add_example('--email="@example.com" --initbalance=10.0', 128 128 _("This would set the current user's email address to $PYKOTAUSERNAME@example.com, and would set the initial value of his account balance to 10.0 credits.")) 129 129 130 130 run(parser, AutoPyKota) -
pykota/trunk/bin/cupspykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 51 51 from pykota.accounter import openAccounter 52 52 from pykota import cups 53 54 class FakeObject : 53 54 class FakeObject : 55 55 """Fake object.""" 56 56 def __init__(self, name) : 57 57 """Fake init.""" 58 58 self.Name = name 59 60 class FakePrinter(FakeObject) : 59 60 class FakePrinter(FakeObject) : 61 61 """Fake printer instance.""" 62 62 pass 63 64 class FakeUser(FakeObject) : 63 64 class FakeUser(FakeObject) : 65 65 """Fake user instance.""" 66 66 def __init__(self, name) : … … 68 68 self.Email = name 69 69 FakeObject.__init__(self, name) 70 70 71 71 class CUPSBackend(PyKotaTool) : 72 72 """Base class for tools with no database access.""" … … 83 83 self.lockfilename = None 84 84 self.lockfile = None 85 86 def deferredInit(self) : 85 86 def deferredInit(self) : 87 87 """Deferred initialization.""" 88 88 PyKotaTool.deferredInit(self) … … 91 91 username = self.effectiveUserName 92 92 raise config.PyKotaConfigError, _("User %(username)s is not allowed to read ~pykota/pykotadmin.conf, you must check the permissions.") % locals() 93 94 def enableSigInt(self) : 93 94 def enableSigInt(self) : 95 95 """Enables the SIGINT signal (which raises KeyboardInterrupt).""" 96 96 signal.signal(signal.SIGINT, self.oldSigIntHandler) 97 98 def waitForLock(self) : 97 98 def waitForLock(self) : 99 99 """Waits until we can acquire the lock file.""" 100 100 self.logdebug("Waiting for lock %s to become available..." % self.lockfilename) … … 104 104 # open the lock file, optionally creating it if needed. 105 105 self.lockfile = open(self.lockfilename, "a+") 106 106 107 107 # we wait indefinitely for the lock to become available. 108 108 # works over NFS too. 109 109 fcntl.lockf(self.lockfile, fcntl.LOCK_EX) 110 110 haslock = True 111 111 112 112 self.logdebug("Lock %s acquired." % self.lockfilename) 113 113 114 114 # Here we save the PID in the lock file, but we don't use 115 115 # it, because the lock file may be in a directory shared … … 120 120 self.lockfile.write(str(self.pid)) 121 121 self.lockfile.flush() 122 except IOError, msg : 122 except IOError, msg : 123 123 self.logdebug("I/O Error while waiting for lock %s : %s" % (self.lockfilename, msg)) 124 124 time.sleep(0.25) 125 126 def discoverOtherBackends(self) : 125 126 def discoverOtherBackends(self) : 127 127 """Discovers the other CUPS backends. 128 128 129 129 Executes each existing backend in turn in device enumeration mode. 130 130 Returns the list of available backends. 131 131 132 132 Unfortunately this method can't output any debug information 133 133 to stdout or stderr, else CUPS considers that the device is … … 151 151 # process doesn't exist anymore 152 152 os.remove(lockfilename) 153 153 154 154 if not os.path.exists(lockfilename) : 155 155 lockfile = open(lockfilename, "w") … … 159 159 for b in os.listdir(directory) \ 160 160 if os.access(os.path.join(directory, b), os.X_OK) \ 161 and (b != myname)] 162 for backend in allbackends : 161 and (b != myname)] 162 for backend in allbackends : 163 163 answer = os.popen(backend, "r") 164 164 try : 165 165 devices = [line.strip() for line in answer.readlines()] 166 except : 166 except : 167 167 devices = [] 168 168 status = answer.close() 169 169 if status is None : 170 170 for d in devices : 171 # each line is of the form : 171 # each line is of the form : 172 172 # 'xxxx xxxx "xxxx xxx" "xxxx xxx"' 173 173 # so we have to decompose it carefully … … 186 186 try : 187 187 (devicetype, device, name, fullname) = arguments 188 except ValueError : 188 except ValueError : 189 189 pass # ignore this 'bizarre' device 190 else : 190 else : 191 191 if name.startswith('"') and name.endswith('"') : 192 192 name = name[1:-1] … … 202 202 % (self.myname, self.MyName, self.MyName)) 203 203 return available 204 205 def checkCUPSVersion(self) : 204 205 def checkCUPSVersion(self) : 206 206 """Checks if CUPS is not v1.3.4 or higher.""" 207 207 fullversion = os.environ.get("SOFTWARE", "") … … 210 210 try : 211 211 (major, minor, release) = [int(p) for p in vnum.split(".")] 212 except ValueError : 212 except ValueError : 213 213 pass 214 else : 214 else : 215 215 return (major > 1) \ 216 216 or ((major == 1) and (minor > 3)) \ 217 217 or ((major == 1) and (minor == 3) and (release >= 4)) 218 218 return False 219 220 def initBackendParameters(self) : 219 220 def initBackendParameters(self) : 221 221 """Initializes the backend's attributes.""" 222 # check that the DEVICE_URI environment variable's value is 222 # check that the DEVICE_URI environment variable's value is 223 223 # prefixed with self.myname otherwise don't touch it. 224 # If this is the case, we have to remove the prefix from 225 # the environment before launching the real backend 224 # If this is the case, we have to remove the prefix from 225 # the environment before launching the real backend 226 226 self.logdebug("Initializing backend...") 227 227 228 228 if not self.checkCUPSVersion() : 229 229 self.printInfo("BEWARE : CUPS is too old. You should use CUPS v1.3.4 or higher.", "error") 230 230 231 231 self.PrinterName = os.environ.get("PRINTER", "") 232 232 directories = [ self.config.getPrinterDirectory(self.PrinterName), … … 240 240 else : 241 241 self.printInfo("Insufficient permissions to access to temporary directory %s" % direc, "warn") 242 242 243 243 self.Action = "ALLOW" # job allowed by default 244 244 self.Reason = None … … 247 247 if copies < 1 : 248 248 raise ValueError 249 except (ValueError, TypeError) : 249 except (ValueError, TypeError) : 250 250 self.logdebug("Invalid number of copies '%s', using 1 instead." % sys.argv[4]) 251 251 copies = 1 252 252 if len(sys.argv) == 7 : 253 253 fname = sys.argv[6] # read job's datas from file 254 else : 255 fname = None # read job's datas from stdin 256 254 else : 255 fname = None # read job's datas from stdin 256 257 257 self.Ticket = cups.JobTicket(sys.argv[1].strip(), self.PrinterName, \ 258 258 copies, fname, sys.argv[5].strip()) 259 self.UserName = self.Ticket.OriginatingUserName 260 259 self.UserName = self.Ticket.OriginatingUserName 260 261 261 self.DataFile = (os.path.join(self.Directory, "%s-%s-%s-%s" % \ 262 262 (self.myname, self.PrinterName, self.UserName, self.Ticket.JobId))).encode(sys.getfilesystemencoding(), "replace") 263 263 264 264 muststartwith = "%s:" % self.myname 265 265 device_uri = os.environ.get("DEVICE_URI", "") … … 268 268 device_uri = fulldevice_uri[len(muststartwith):] 269 269 for i in range(2) : 270 if device_uri.startswith("/") : 270 if device_uri.startswith("/") : 271 271 device_uri = device_uri[1:] 272 272 try : 273 (backend, destination) = device_uri.split(":", 1) 274 except ValueError : 273 (backend, destination) = device_uri.split(":", 1) 274 except ValueError : 275 275 if not device_uri : 276 276 self.logdebug("Not attached to an existing print queue.") 277 277 backend = "" 278 278 printerhostname = "" 279 else : 279 else : 280 280 raise PyKotaToolError, "Invalid DEVICE_URI : %s\n" % device_uri 281 else : 281 else : 282 282 if backend == "hp" : 283 283 try : 284 284 printerhostname = destination.split("=")[1] # hp:/net/HP_LaserJet_8000_Series?ip=192.168.100.100 285 except IndexError : 285 except IndexError : 286 286 self.logdebug("Unsupported hplip URI %s" % device_uri) 287 printerhostname = "" 288 else : 287 printerhostname = "" 288 else : 289 289 while destination.startswith("/") : 290 290 destination = destination[1:] 291 checkauth = destination.split("@", 1) 291 checkauth = destination.split("@", 1) 292 292 if len(checkauth) == 2 : 293 293 destination = checkauth[1] 294 294 printerhostname = destination.split("/")[0].split(":")[0] 295 296 self.PrinterHostName = printerhostname 295 296 self.PrinterHostName = printerhostname 297 297 self.RealBackend = backend 298 298 self.DeviceURI = device_uri 299 299 300 300 if self.Ticket.BillingCode is None : 301 301 self.OriginalJobBillingCode = None 302 else : 302 else : 303 303 self.OriginalJobBillingCode = self.Ticket.BillingCode[:] 304 304 305 305 baselockfilename = self.DeviceURI.replace("/", ".") 306 306 baselockfilename = baselockfilename.replace(":", ".") … … 309 309 baselockfilename = baselockfilename.replace("@", ".") 310 310 self.lockfilename = os.path.join(self.Directory, "%s-%s..LCK" % (self.myname, baselockfilename)) 311 311 312 312 self.logdebug("Backend : %s" % self.RealBackend) 313 313 self.logdebug("DeviceURI : %s" % self.DeviceURI) … … 319 319 self.logdebug("Copies : %s" % self.Ticket.Copies) 320 320 self.logdebug("Options : %s" % self.Ticket.Options) 321 self.logdebug("Directory : %s" % self.Directory) 321 self.logdebug("Directory : %s" % self.Directory) 322 322 self.logdebug("DataFile : %s" % self.DataFile) 323 323 self.logdebug("JobBillingCode : %s" % self.Ticket.BillingCode) 324 324 self.logdebug("JobOriginatingHostName : %s" % self.Ticket.OriginatingHostName) 325 325 326 326 # fakes some entries to allow for external mailto 327 327 # before real entries are extracted from the database. 328 328 self.User = FakeUser(self.UserName) 329 329 self.Printer = FakePrinter(self.PrinterName) 330 330 331 331 self.enableSigInt() 332 332 self.logdebug("Backend initialized.") 333 333 334 334 def overwriteJobAttributes(self) : 335 335 """Overwrites some of the job's attributes if needed.""" … … 337 337 # First overwrite the job ticket 338 338 self.overwriteJobTicket() 339 339 340 340 # do we want to strip out the Samba/Winbind domain name ? 341 341 separator = self.config.getWinbindSeparator() 342 342 if separator is not None : 343 343 self.UserName = self.UserName.split(separator)[-1] 344 345 # this option is deprecated, and we want to tell people 344 345 # this option is deprecated, and we want to tell people 346 346 # this is the case. 347 347 tolower = self.config.getUserNameToLower() … … 352 352 if self.config.isTrue(tolower) : 353 353 self.UserName = self.UserName.lower() 354 355 # Now use the newer and more complete 'usernamecase' directive. 356 casechange = self.config.getUserNameCase() 354 355 # Now use the newer and more complete 'usernamecase' directive. 356 casechange = self.config.getUserNameCase() 357 357 if casechange != "native" : 358 358 self.UserName = getattr(self.UserName, casechange)() 359 360 # do we want to strip some prefix off of titles ? 359 360 # do we want to strip some prefix off of titles ? 361 361 stripprefix = self.config.getStripTitle(self.PrinterName) 362 362 if stripprefix : … … 365 365 % (stripprefix, self.Ticket.Title)) 366 366 self.Ticket.Title = self.Ticket.Title[len(stripprefix):] 367 367 368 368 self.logdebug("Username : %s" % self.UserName) 369 369 self.logdebug("BillingCode : %s" % self.Ticket.BillingCode) 370 370 self.logdebug("Title : %s" % self.Ticket.Title) 371 371 self.logdebug("Job's attributes sanitizing done.") 372 372 373 373 def didUserConfirm(self) : 374 374 """Asks for user confirmation through an external script. 375 375 376 376 returns False if the end user wants to cancel the job, else True. 377 377 """ 378 378 self.logdebug("Checking if we have to ask for user's confirmation...") 379 answer = None 379 answer = None 380 380 confirmationcommand = self.config.getAskConfirmation(self.PrinterName) 381 381 if confirmationcommand : … … 388 388 if answer == "CANCEL" : 389 389 break 390 except IOError, msg : 390 except IOError, msg : 391 391 self.logdebug("IOError while reading subprocess' output : %s" % msg) 392 inputfile.close() 392 inputfile.close() 393 393 self.logdebug("User's confirmation received : %s" % (((answer == "CANCEL") and "CANCEL") or "CONTINUE")) 394 else : 394 else : 395 395 self.logdebug("No need to ask for user's confirmation, job processing will continue.") 396 return (answer != "CANCEL") 397 398 def overwriteJobTicket(self) : 396 return (answer != "CANCEL") 397 398 def overwriteJobTicket(self) : 399 399 """Should we overwrite the job's ticket (username and billingcode) ?""" 400 400 self.logdebug("Checking if we need to overwrite the job ticket...") … … 414 414 self.logdebug("Seen CANCEL command.") 415 415 action = "CANCEL" 416 elif line.startswith("USERNAME=") : 416 elif line.startswith("USERNAME=") : 417 417 username = line.split("=", 1)[1].strip().decode(self.charset, "replace") 418 418 self.logdebug("Seen new username [%s]" % username) 419 elif line.startswith("BILLINGCODE=") : 419 elif line.startswith("BILLINGCODE=") : 420 420 billingcode = line.split("=", 1)[1].strip().decode(self.charset, "replace") 421 421 self.logdebug("Seen new billing code [%s]" % billingcode) … … 423 423 reason = line.split("=", 1)[1].strip().decode(self.charset, "replace") 424 424 self.logdebug("Seen new reason [%s]" % reason) 425 except IOError, msg : 425 except IOError, msg : 426 426 self.logdebug("IOError while reading subprocess' output : %s" % msg) 427 inputfile.close() 428 427 inputfile.close() 428 429 429 # now overwrite the job's ticket if new data was supplied 430 430 if action == "DENY" : … … 441 441 self.UserName = username 442 442 if billingcode is not None : 443 self.Ticket.BillingCode = billingcode 443 self.Ticket.BillingCode = billingcode 444 444 self.logdebug("Job ticket overwriting done.") 445 445 446 446 def saveDatasAndCheckSum(self) : 447 447 """Saves the input datas into a static file.""" 448 448 self.logdebug("Duplicating data stream into %s" % self.DataFile) 449 449 mustclose = 0 450 outfile = open(self.DataFile, "wb") 450 outfile = open(self.DataFile, "wb") 451 451 if self.Ticket.FileName is not None : 452 452 infile = open(self.Ticket.FileName, "rb") 453 453 self.logdebug("Reading input datas from %s" % self.Ticket.FileName) 454 454 mustclose = 1 455 else : 455 else : 456 456 infile = sys.stdin 457 457 self.logdebug("Reading input datas from stdin") … … 461 461 checksum = md5.new() 462 462 while 1 : 463 data = infile.read(CHUNK) 463 data = infile.read(CHUNK) 464 464 if not data : 465 465 break 466 sizeread += len(data) 466 sizeread += len(data) 467 467 outfile.write(data) 468 checksum.update(data) 468 checksum.update(data) 469 469 if not (dummy % 32) : # Only display every 2 Mb 470 470 self.logdebug("%s bytes saved..." % sizeread) 471 dummy += 1 472 if mustclose : 471 dummy += 1 472 if mustclose : 473 473 infile.close() 474 474 475 475 outfile.close() 476 self.JobSizeBytes = sizeread 476 self.JobSizeBytes = sizeread 477 477 self.JobMD5Sum = checksum.hexdigest() 478 478 479 479 self.logdebug("JobSizeBytes : %s" % self.JobSizeBytes) 480 480 self.logdebug("JobMD5Sum : %s" % self.JobMD5Sum) 481 481 self.logdebug("Data stream duplicated into %s" % self.DataFile) 482 482 483 483 def clean(self) : 484 484 """Cleans up the place.""" … … 487 487 try : 488 488 keep = self.config.getPrinterKeepFiles(self.PrinterName) 489 except AttributeError : 489 except AttributeError : 490 490 keep = False 491 491 if not keep : … … 497 497 else : 498 498 self.logdebug("Work file %s has been deleted." % self.DataFile) 499 else : 499 else : 500 500 self.logdebug("Work file %s will be kept." % self.DataFile) 501 PyKotaTool.clean(self) 501 PyKotaTool.clean(self) 502 502 if self.lockfile is not None : 503 503 self.logdebug("Unlocking %s..." % self.lockfilename) … … 505 505 fcntl.lockf(self.lockfile, fcntl.LOCK_UN) 506 506 self.lockfile.close() 507 except : 507 except : 508 508 self.printInfo("Problem while unlocking %s" % self.lockfilename, "error") 509 else : 509 else : 510 510 self.logdebug("%s unlocked." % self.lockfilename) 511 511 self.logdebug("Clean.") 512 513 def precomputeJobSize(self) : 512 513 def precomputeJobSize(self) : 514 514 """Computes the job size with a software method.""" 515 515 self.logdebug("Precomputing job's size...") … … 518 518 self.softwareJobSize = self.preaccounter.getJobSize(None) 519 519 self.logdebug("Precomputed job's size is %s pages." % self.softwareJobSize) 520 521 def precomputeJobPrice(self) : 520 521 def precomputeJobPrice(self) : 522 522 """Precomputes the job price with a software method.""" 523 523 self.logdebug("Precomputing job's price...") … … 525 525 self.logdebug("Precomputed job's price is %.3f credits." \ 526 526 % self.softwareJobPrice) 527 528 def exportJobInfo(self) : 527 528 def exportJobInfo(self) : 529 529 """Exports the actual job's attributes to the environment.""" 530 530 self.logdebug("Exporting job information to the environment...") … … 550 550 setenv("PYKOTAPRECOMPUTEDJOBSIZE", str(self.softwareJobSize), self.charset) 551 551 self.logdebug("Environment updated.") 552 552 553 553 def exportUserInfo(self) : 554 554 """Exports user information to the environment.""" … … 559 559 setenv("PYKOTALIFETIMEPAID", str(self.User.LifeTimePaid or 0.0), self.charset) 560 560 setenv("PYKOTAUSERDESCRIPTION", self.User.Description or "", self.charset) 561 561 562 562 setenv("PYKOTAPAGECOUNTER", str(self.UserPQuota.PageCounter or 0), self.charset) 563 563 setenv("PYKOTALIFEPAGECOUNTER", str(self.UserPQuota.LifePageCounter or 0), self.charset) … … 566 566 setenv("PYKOTADATELIMIT", str(self.UserPQuota.DateLimit), self.charset) 567 567 setenv("PYKOTAWARNCOUNT", str(self.UserPQuota.WarnCount), self.charset) 568 568 569 569 # TODO : move this elsewhere once software accounting is done only once. 570 570 setenv("PYKOTAPRECOMPUTEDJOBPRICE", str(self.softwareJobPrice), self.charset) 571 571 572 572 self.logdebug("Environment updated.") 573 573 574 574 def exportPrinterInfo(self) : 575 575 """Exports printer information to the environment.""" … … 584 584 setenv("PYKOTAPRICEPERJOB", str(self.Printer.PricePerJob or 0), self.charset) 585 585 self.logdebug("Environment updated.") 586 586 587 587 def exportPhaseInfo(self, phase) : 588 588 """Exports phase information to the environment.""" … … 590 590 setenv("PYKOTAPHASE", phase, self.charset) 591 591 self.logdebug("Environment updated.") 592 592 593 593 def exportJobSizeAndPrice(self) : 594 594 """Exports job's size and price information to the environment.""" … … 597 597 setenv("PYKOTAJOBPRICE", str(self.JobPrice), self.charset) 598 598 self.logdebug("Environment updated.") 599 599 600 600 def exportReason(self) : 601 601 """Exports the job's action status and optional reason.""" … … 605 605 setenv("PYKOTAREASON", self.Reason or "", self.charset) 606 606 self.logdebug("Environment updated.") 607 608 def acceptJob(self) : 607 608 def acceptJob(self) : 609 609 """Returns the appropriate exit code to tell CUPS all is OK.""" 610 610 return 0 611 612 def removeJob(self) : 611 612 def removeJob(self) : 613 613 """Returns the appropriate exit code to let CUPS think all is OK. 614 614 615 615 Returning 0 (success) prevents CUPS from stopping the print queue. 616 """ 616 """ 617 617 return 0 618 618 619 619 def launchPreHook(self) : 620 620 """Allows plugging of an external hook before the job gets printed.""" … … 624 624 retcode = os.system(prehook) 625 625 self.logdebug("pre-hook exited with status %s." % retcode) 626 626 627 627 def launchPostHook(self) : 628 628 """Allows plugging of an external hook after the job gets printed and/or denied.""" … … 632 632 retcode = os.system(posthook) 633 633 self.logdebug("post-hook exited with status %s." % retcode) 634 635 def improveMessage(self, message) : 634 635 def improveMessage(self, message) : 636 636 """Improves a message by adding more informations in it if possible.""" 637 637 try : … … 640 640 self.Ticket.JobId, \ 641 641 message) 642 except : 642 except : 643 643 return message 644 645 def logdebug(self, message) : 644 645 def logdebug(self, message) : 646 646 """Improves the debug message before outputting it.""" 647 647 PyKotaTool.logdebug(self, self.improveMessage(message)) 648 649 def printInfo(self, message, level="info") : 648 649 def printInfo(self, message, level="info") : 650 650 """Improves the informational message before outputting it.""" 651 651 self.logger.log_message(self.improveMessage(message), level) 652 652 653 653 def startingBanner(self, withaccounting) : 654 654 """Retrieves a starting banner for current printer and returns its content.""" … … 656 656 self.printBanner(self.config.getStartingBanner(self.PrinterName), withaccounting) 657 657 self.logdebug("Starting banner retrieved.") 658 658 659 659 def endingBanner(self, withaccounting) : 660 660 """Retrieves an ending banner for current printer and returns its content.""" … … 662 662 self.printBanner(self.config.getEndingBanner(self.PrinterName), withaccounting) 663 663 self.logdebug("Ending banner retrieved.") 664 664 665 665 def printBanner(self, bannerfileorcommand, withaccounting) : 666 666 """Reads a banner or generates one through an external command. 667 667 668 668 Returns the banner's content in a format which MUST be accepted 669 669 by the printer. … … 691 691 try : 692 692 fh = open(bannerfileorcommand, 'rb') 693 except IOError, msg : 693 except IOError, msg : 694 694 self.printInfo("Impossible to open %s : %s" \ 695 695 % (bannerfileorcommand, msg), "error") 696 else : 696 else : 697 697 self.runOriginalBackend(fh, isBanner=1) 698 698 fh.close() … … 701 701 self.BannerSize += 1 # TODO : fix this by passing the banner's content through software accounting 702 702 self.logdebug("Banner printed...") 703 703 704 704 def handleBanner(self, bannertype, withaccounting) : 705 705 """Handles the banner with or without accounting.""" 706 706 if withaccounting : 707 707 acc = "with" 708 else : 708 else : 709 709 acc = "without" 710 710 self.logdebug("Handling %s banner %s accounting..." % (bannertype, acc)) … … 735 735 if (avoidduplicatebanners == "YES") : 736 736 printbanner = False 737 else : 738 # avoidduplicatebanners is an integer, since NO, 737 else : 738 # avoidduplicatebanners is an integer, since NO, 739 739 # YES and 0 are already handled 740 740 now = DateTime.now() … … 746 746 self.logdebug("Difference with previous job : %.2f seconds. Try to avoid banners for : %.2f seconds." % (difference, avoidduplicatebanners)) 747 747 if difference < avoidduplicatebanners : 748 self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners) 748 self.logdebug("Duplicate banner avoided because previous banner is less than %.2f seconds old." % avoidduplicatebanners) 749 749 printbanner = False 750 750 else : … … 753 753 getattr(self, "%sBanner" % bannertype)(withaccounting) 754 754 self.logdebug("%s banner done." % bannertype.title()) 755 756 def sanitizeJobSize(self) : 755 756 def sanitizeJobSize(self) : 757 757 """Sanitizes the job's size if needed.""" 758 758 # TODO : there's a difficult to see bug here when banner accounting is activated and hardware accounting is used. … … 772 772 if replacement == "PRECOMPUTED" : 773 773 self.JobSize = self.softwareJobSize 774 else : 774 else : 775 775 self.JobSize = replacement 776 776 self.logdebug("Job's size sanitized.") 777 778 def getPrinterUserAndUserPQuota(self) : 777 778 def getPrinterUserAndUserPQuota(self) : 779 779 """Returns a tuple (policy, printer, user, and user print quota) on this printer. 780 780 781 781 "OK" is returned in the policy if both printer, user and user print quota 782 782 exist in the Quota Storage. 783 783 Otherwise, the policy as defined for this printer in pykota.conf is returned. 784 784 785 785 If policy was set to "EXTERNAL" and one of printer, user, or user print quota 786 786 doesn't exist in the Quota Storage, then an external command is launched, as … … 789 789 or users, for example, and finally extracting printer, user and user print 790 790 quota from the Quota Storage is tried a second time. 791 791 792 792 "EXTERNALERROR" is returned in case policy was "EXTERNAL" and an error status 793 793 was returned by the external command. … … 802 802 break 803 803 (policy, args) = self.config.getPrinterPolicy(self.PrinterName) 804 if policy == "EXTERNAL" : 804 if policy == "EXTERNAL" : 805 805 commandline = self.formatCommandLine(args, user, printer) 806 806 if not printer.Exists : … … 814 814 policy = "EXTERNALERROR" 815 815 break 816 else : 816 else : 817 817 if not printer.Exists : 818 818 self.printInfo(_("Printer %s not registered in the PyKota system, applying default policy (%s)") % (self.PrinterName, policy)) … … 822 822 self.printInfo(_("User %s doesn't have quota on printer %s in the PyKota system, applying default policy (%s)") % (self.UserName, self.PrinterName, policy)) 823 823 break 824 825 if policy == "EXTERNAL" : 824 825 if policy == "EXTERNAL" : 826 826 if not printer.Exists : 827 827 self.printInfo(_("Printer %s still not registered in the PyKota system, job will be rejected") % self.PrinterName) … … 830 830 if not userpquota.Exists : 831 831 self.printInfo(_("User %s still doesn't have quota on printer %s in the PyKota system, job will be rejected") % (self.UserName, self.PrinterName)) 832 self.Policy = policy 832 self.Policy = policy 833 833 self.Printer = printer 834 834 self.User = user 835 835 self.UserPQuota = userpquota 836 836 self.logdebug("Retrieval of printer, user and user print quota entry done.") 837 838 def getBillingCode(self) : 837 838 def getBillingCode(self) : 839 839 """Extracts the billing code from the database. 840 840 841 841 An optional script is launched to notify the user when 842 842 the billing code is unknown and PyKota was configured to … … 858 858 if self.BillingCode.Exists : 859 859 self.logdebug(msg + "has been created.") 860 else : 860 else : 861 861 self.printInfo(msg + "couldn't be created.", "error") 862 else : 862 else : 863 863 self.logdebug(msg + "job will be denied.") 864 864 self.Action = newaction 865 if script is not None : 865 if script is not None : 866 866 self.logdebug(msg + "launching subprocess [%s] to notify user." % script) 867 867 os.system(script) 868 868 self.logdebug("Retrieval of billing code information done.") 869 870 def checkIfDupe(self) : 869 870 def checkIfDupe(self) : 871 871 """Checks if the job is a duplicate, and handles the situation.""" 872 872 self.logdebug("Checking if the job is a duplicate...") … … 889 889 self.logdebug("Duplicate job allowed because previous one is more than %.2f seconds old." % duplicatesdelay) 890 890 else : 891 # TODO : use the current user's last job instead of 891 # TODO : use the current user's last job instead of 892 892 # TODO : the current printer's last job. This would be 893 893 # TODO : better but requires an additional database query 894 # TODO : with SQL, and is much more complex with the 894 # TODO : with SQL, and is much more complex with the 895 895 # TODO : actual LDAP schema. Maybe this is not very 896 896 # TODO : important, because usually duplicate jobs are sucessive. … … 900 900 self.Action = "DENY" 901 901 self.Reason = _("Duplicate print jobs are not allowed on printer %s.") % self.PrinterName 902 else : 902 else : 903 903 self.logdebug("Launching subprocess [%s] to see if duplicate jobs should be allowed or not." % denyduplicates) 904 904 fanswer = os.popen(denyduplicates, "r") 905 905 self.Action = fanswer.read().strip().upper() 906 906 fanswer.close() 907 if self.Action == "DENY" : 907 if self.Action == "DENY" : 908 908 self.printInfo("%s : %s." % (msg, _("Subprocess denied printing of a dupe")), "warn") 909 909 self.Reason = _("Duplicate print jobs are not allowed on printer %s at this time.") % self.PrinterName 910 else : 910 else : 911 911 self.printInfo("%s : %s." % (msg, _("Subprocess allowed printing of a dupe")), "warn") 912 else : 912 else : 913 913 self.logdebug("Job doesn't seem to be a duplicate.") 914 914 self.logdebug("Checking if the job is a duplicate done.") 915 915 916 916 def tellUser(self) : 917 917 """Sends a message to an user.""" 918 self.logdebug("Sending some feedback to user %s..." % self.UserName) 918 self.logdebug("Sending some feedback to user %s..." % self.UserName) 919 919 if not self.Reason : 920 920 self.logdebug("No feedback to send to user %s." % self.UserName) 921 else : 921 else : 922 922 (mailto, arguments) = self.config.getMailTo(self.PrinterName) 923 923 if mailto == "EXTERNAL" : 924 924 # TODO : clean this again 925 925 self.externalMailTo(arguments, self.Action, self.User, self.Printer, self.Reason) 926 else : 926 else : 927 927 # TODO : clean this again 928 928 admin = self.config.getAdmin(self.PrinterName) … … 934 934 if mailto in ("BOTH", "ADMIN") : 935 935 destination.append(adminmail) 936 if mailto in ("BOTH", "USER") : 936 if mailto in ("BOTH", "USER") : 937 937 destination.append(usermail) 938 938 939 939 fullmessage = self.Reason + (_("\n\nYour system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)) 940 try : 940 try : 941 941 server = smtplib.SMTP(self.smtpserver) 942 except socket.error, msg : 942 except socket.error, msg : 943 943 self.printInfo(_("Impossible to connect to SMTP server : %s") % msg, "error") 944 944 else : … … 951 951 if mailto == "BOTH" : 952 952 msg["Cc"] = adminmail 953 else : 953 else : 954 954 msg["To"] = adminmail 955 955 msg["Date"] = email.Utils.formatdate(localtime=True) 956 956 server.sendmail(adminmail, destination, msg.as_string()) 957 except smtplib.SMTPException, answer : 957 except smtplib.SMTPException, answer : 958 958 try : 959 959 for (k, v) in answer.recipients.items() : … … 963 963 server.quit() 964 964 self.logdebug("Feedback sent to user %s." % self.UserName) 965 966 def mainWork(self) : 965 966 def mainWork(self) : 967 967 """Main work is done here.""" 968 968 if not self.JobSizeBytes : … … 974 974 self.tellUser() 975 975 return self.removeJob() 976 976 977 977 self.getPrinterUserAndUserPQuota() 978 978 if self.Policy == "EXTERNALERROR" : … … 989 989 self.tellUser() 990 990 return self.removeJob() 991 elif self.Policy == "DENY" : 991 elif self.Policy == "DENY" : 992 992 # Either printer, user or user print quota doesn't exist, 993 993 # and the job should be rejected. … … 1007 1007 # be allowed if current user is allowed to print on this printer 1008 1008 return self.doWork() 1009 else : 1009 else : 1010 1010 self.Reason = _("Invalid policy %s for printer %s") % (self.Policy, self.PrinterName) 1011 1011 self.printInfo(self.Reason, "error") 1012 1012 self.tellUser() 1013 1013 return self.removeJob() 1014 1015 def doWork(self) : 1014 1015 def doWork(self) : 1016 1016 """The accounting work is done here.""" 1017 1017 self.precomputeJobPrice() … … 1019 1019 self.exportPrinterInfo() 1020 1020 self.exportPhaseInfo("BEFORE") 1021 1022 if self.Action not in ("DENY", "CANCEL") : 1021 1022 if self.Action not in ("DENY", "CANCEL") : 1023 1023 if self.Printer.MaxJobSize and (self.softwareJobSize > self.Printer.MaxJobSize) : 1024 1024 # This printer was set to refuse jobs this large. … … 1028 1028 # because in case of error the user could complain :-) 1029 1029 self.Reason = _("You are not allowed to print so many pages on printer %s at this time.") % self.PrinterName 1030 1030 1031 1031 if self.Action not in ("DENY", "CANCEL") : 1032 1032 if self.User.LimitBy == "noprint" : … … 1034 1034 self.Action = "DENY" 1035 1035 self.Reason = _("Your account settings forbid you to print at this time.") 1036 1036 1037 1037 if self.Action not in ("DENY", "CANCEL") : 1038 1038 # If printing is still allowed at this time, we … … 1041 1041 # save some database queries. 1042 1042 self.getBillingCode() 1043 1043 1044 1044 if self.Action not in ("DENY", "CANCEL") : 1045 1045 # If printing is still allowed at this time, we … … 1048 1048 # save some database queries. 1049 1049 self.checkIfDupe() 1050 1050 1051 1051 if self.Action not in ("DENY", "CANCEL") : 1052 1052 # If printing is still allowed at this time, we … … 1056 1056 if self.User.LimitBy in ('noquota', 'nochange') : 1057 1057 self.logdebug("User %s is allowed to print with no limit, no need to check quota." % self.UserName) 1058 elif self.Printer.PassThrough : 1058 elif self.Printer.PassThrough : 1059 1059 self.logdebug("Printer %s is in PassThrough mode, no need to check quota." % self.PrinterName) 1060 1060 else : … … 1067 1067 self.printInfo(_("Print Quota exceeded for user %s on printer %s") % (self.UserName, self.PrinterName)) 1068 1068 self.Reason = self.config.getHardWarn(self.PrinterName) 1069 elif self.Action == "WARN" : 1069 elif self.Action == "WARN" : 1070 1070 self.printInfo(_("Print Quota low for user %s on printer %s") % (self.UserName, self.PrinterName)) 1071 if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 1071 if self.User.LimitBy and (self.User.LimitBy.lower() == "balance") : 1072 1072 self.Reason = self.config.getPoorWarn() 1073 else : 1073 else : 1074 1074 self.Reason = self.config.getSoftWarn(self.PrinterName) 1075 1076 # If job still allowed to print, should we ask for confirmation ? 1077 if self.Action not in ("DENY", "CANCEL") : 1075 1076 # If job still allowed to print, should we ask for confirmation ? 1077 if self.Action not in ("DENY", "CANCEL") : 1078 1078 if not self.didUserConfirm() : 1079 1079 self.Action = "CANCEL" 1080 1080 self.Reason = _("Print job cancelled.") 1081 1081 setenv("PYKOTASTATUS", "CANCELLED", self.charset) 1082 1082 1083 1083 # exports some new environment variables 1084 1084 self.exportReason() 1085 1085 1086 1086 # now tell the user if he needs to know something 1087 1087 self.tellUser() 1088 1088 1089 1089 # launches the pre hook 1090 1090 self.launchPreHook() 1091 1091 1092 1092 # handle starting banner pages without accounting 1093 1093 self.BannerSize = 0 … … 1095 1095 if (self.Action != "CANCEL") and accountbanner in ["ENDING", "NONE"] : 1096 1096 self.handleBanner("starting", 0) 1097 1097 1098 1098 if self.Action == "DENY" : 1099 1099 self.printInfo(_("Job denied, no accounting will be done.")) 1100 elif self.Action == "CANCEL" : 1100 elif self.Action == "CANCEL" : 1101 1101 self.printInfo(_("Job cancelled, no accounting will be done.")) 1102 1102 else : 1103 1103 self.printInfo(_("Job accounting begins.")) 1104 1104 self.accounter.beginJob(self.Printer) 1105 1105 1106 1106 # handle starting banner pages with accounting 1107 1107 if (self.Action != "CANCEL") and accountbanner in ["STARTING", "BOTH"] : 1108 1108 self.handleBanner("starting", 1) 1109 1110 # pass the job's data to the real backend if needed 1109 1110 # pass the job's data to the real backend if needed 1111 1111 if self.Action in ("ALLOW", "WARN") : 1112 1112 retcode = self.printJobDatas() 1113 else : 1113 else : 1114 1114 retcode = self.removeJob() 1115 1115 1116 1116 # indicate phase change 1117 1117 self.exportPhaseInfo("AFTER") 1118 1118 1119 1119 # handle ending banner pages with accounting 1120 1120 if (self.Action != "CANCEL") and accountbanner in ["ENDING", "BOTH"] : 1121 1121 self.handleBanner("ending", 1) 1122 1122 1123 1123 # stops accounting 1124 1124 if self.Action == "DENY" : 1125 1125 self.printInfo(_("Job denied, no accounting has been done.")) 1126 elif self.Action == "CANCEL" : 1126 elif self.Action == "CANCEL" : 1127 1127 self.printInfo(_("Job cancelled, no accounting has been done.")) 1128 1128 else : 1129 1129 self.accounter.endJob(self.Printer) 1130 1130 self.printInfo(_("Job accounting ends.")) 1131 1132 # Do all these database changes within a single transaction 1131 1132 # Do all these database changes within a single transaction 1133 1133 # NB : we don't enclose ALL the changes within a single transaction 1134 1134 # because while waiting for the printer to answer its internal page … … 1147 1147 self.JobSize = 0 1148 1148 self.printInfo(_("Job size forced to 0 because the real CUPS backend failed. No accounting will be done."), "warn") 1149 else : 1149 else : 1150 1150 self.printInfo(_("The real CUPS backend failed, but the job will be accounted for anyway."), "warn") 1151 1152 # retrieve the job size 1151 1152 # retrieve the job size 1153 1153 if self.Action == "DENY" : 1154 1154 self.JobSize = 0 1155 1155 self.printInfo(_("Job size forced to 0 because printing is denied.")) 1156 elif self.Action == "CANCEL" : 1156 elif self.Action == "CANCEL" : 1157 1157 self.JobSize = 0 1158 1158 self.printInfo(_("Job size forced to 0 because printing was cancelled.")) 1159 else : 1159 else : 1160 1160 self.UserPQuota.resetDenyBannerCounter() 1161 if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) : 1161 if (self.Action != "PROBLEM") or ("CHARGE" in onbackenderror) : 1162 1162 self.JobSize = self.accounter.getJobSize(self.Printer) 1163 1163 self.sanitizeJobSize() 1164 1164 self.JobSize += self.BannerSize 1165 1165 self.printInfo(_("Job size : %i") % self.JobSize) 1166 1166 1167 1167 if ((self.Action == "PROBLEM") and ("NOCHARGE" in onbackenderror)) or \ 1168 1168 (self.Action in ("DENY", "CANCEL")) : … … 1173 1173 self.JobPrice = 0.0 1174 1174 else : 1175 # update the quota for the current user on this printer 1175 # update the quota for the current user on this printer 1176 1176 self.printInfo(_("Updating user %s's quota on printer %s") % (self.UserName, self.PrinterName)) 1177 1177 self.JobPrice = self.UserPQuota.increasePagesUsage(self.JobSize, self.accounter.inkUsage) 1178 1179 # adds the current job to history 1178 1179 # adds the current job to history 1180 1180 self.Printer.addJobToHistory(self.Ticket.JobId, self.User, self.accounter.getLastPageCounter(), \ 1181 1181 self.Action, self.JobSize, self.JobPrice, self.Ticket.FileName, \ … … 1184 1184 self.softwareJobSize, self.softwareJobPrice) 1185 1185 self.printInfo(_("Job added to history.")) 1186 1186 1187 1187 if hasattr(self, "BillingCode") and self.BillingCode and self.BillingCode.Exists : 1188 1188 if (self.Action in ("ALLOW", "WARN")) or \ … … 1190 1190 self.BillingCode.consume(self.JobSize, self.JobPrice) 1191 1191 self.printInfo(_("Billing code %s was updated.") % self.BillingCode.BillingCode) 1192 except : 1192 except : 1193 1193 self.storage.rollbackTransaction() 1194 1194 raise 1195 else : 1195 else : 1196 1196 self.storage.commitTransaction() 1197 1197 1198 1198 # exports some new environment variables 1199 1199 self.exportJobSizeAndPrice() 1200 1200 1201 1201 # then re-export user information with new values 1202 1202 self.exportUserInfo() 1203 1203 1204 1204 # handle ending banner pages without accounting 1205 1205 if (self.Action != "CANCEL") and accountbanner in ["STARTING", "NONE"] : 1206 1206 self.handleBanner("ending", 0) 1207 1207 1208 1208 self.launchPostHook() 1209 1210 return retcode 1211 1212 def printJobDatas(self) : 1209 1210 return retcode 1211 1212 def printJobDatas(self) : 1213 1213 """Sends the job's datas to the real backend.""" 1214 1214 self.logdebug("Sending job's datas to real backend...") 1215 1215 1216 1216 delay = 0 1217 1217 number = 1 … … 1222 1222 if (number < 0) or (delay < 0) : 1223 1223 raise ValueError 1224 except ValueError : 1224 except ValueError : 1225 1225 self.printInfo(_("Incorrect value for the 'onbackenderror' directive in section [%s]") % self.PrinterName, "error") 1226 1226 delay = 0 1227 1227 number = 1 1228 else : 1228 else : 1229 1229 break 1230 loopcnt = 1 1231 while True : 1230 loopcnt = 1 1231 while True : 1232 1232 if self.Ticket.FileName is None : 1233 1233 infile = open(self.DataFile, "rb") 1234 else : 1234 else : 1235 1235 infile = None 1236 1236 retcode = self.runOriginalBackend(infile) … … 1244 1244 time.sleep(delay) 1245 1245 loopcnt += 1 1246 else : 1246 else : 1247 1247 break 1248 1248 1249 1249 self.logdebug("Job's datas sent to real backend.") 1250 1250 return retcode 1251 1251 1252 1252 def runOriginalBackend(self, filehandle=None, isBanner=0) : 1253 1253 """Launches the original backend.""" … … 1255 1255 if not isBanner : 1256 1256 arguments = [os.environ["DEVICE_URI"]] + [a.encode("UTF-8") for a in sys.argv[1:]] 1257 else : 1257 else : 1258 1258 # For banners, we absolutely WANT 1259 1259 # to remove any filename from the command line ! … … 1264 1264 # TODO : do something about the job title : if we are printing a banner and the backend 1265 1265 # TODO : uses the job's title to name an output file (cups-pdf:// for example), we're stuck ! 1266 1266 1267 1267 self.logdebug("Starting original backend %s with args %s" % (originalbackend, " ".join(['"%s"' % a.decode("UTF-8") for a in arguments]))) 1268 1268 pid = os.fork() … … 1277 1277 except OSError, msg : 1278 1278 self.logdebug("execve() failed: %s" % msg) 1279 self.logdebug("We shouldn't be there !!!") 1279 self.logdebug("We shouldn't be there !!!") 1280 1280 os._exit(-1) 1281 1282 self.logdebug("Waiting for original backend to exit...") 1281 1282 self.logdebug("Waiting for original backend to exit...") 1283 1283 killed = False 1284 1284 status = -1 … … 1289 1289 if err == 4 : 1290 1290 killed = True 1291 1291 1292 1292 if os.WIFEXITED(status) : 1293 1293 status = os.WEXITSTATUS(status) … … 1297 1297 level = "error" 1298 1298 self.Reason = message 1299 else : 1299 else : 1300 1300 level = "info" 1301 1301 self.printInfo(message, level) … … 1309 1309 self.printInfo(self.Reason, "warn") 1310 1310 return 1 1311 1312 if __name__ == "__main__" : 1311 1312 if __name__ == "__main__" : 1313 1313 # This is a CUPS backend, we should act and die like a CUPS backend 1314 1314 wrapper = CUPSBackend() 1315 1315 if len(sys.argv) == 1 : 1316 1316 print "\n".join(wrapper.discoverOtherBackends()) 1317 sys.exit(0) 1318 elif len(sys.argv) not in (6, 7) : 1317 sys.exit(0) 1318 elif len(sys.argv) not in (6, 7) : 1319 1319 logerr("ERROR: %s job-id user title copies options [file]\n"\ 1320 1320 % sys.argv[0]) 1321 1321 sys.exit(1) 1322 else : 1322 else : 1323 1323 os.environ["PATH"] = "%s:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin" % os.environ.get("PATH", "") 1324 1324 try : … … 1333 1333 wrapper.accounter = openAccounter(wrapper) 1334 1334 wrapper.precomputeJobSize() 1335 wrapper.exportJobInfo() # exports a first time to give hints to external scripts 1335 wrapper.exportJobInfo() # exports a first time to give hints to external scripts 1336 1336 wrapper.overwriteJobAttributes() 1337 1337 wrapper.exportJobInfo() # re-exports in case it was overwritten 1338 1338 retcode = wrapper.mainWork() 1339 except KeyboardInterrupt : 1339 except KeyboardInterrupt : 1340 1340 wrapper.printInfo(_("Job %s interrupted by the administrator !") % wrapper.Ticket.JobId, "warn") 1341 1341 retcode = 0 1342 except SystemExit, err : 1342 except SystemExit, err : 1343 1343 retcode = err.code 1344 except : 1344 except : 1345 1345 try : 1346 1346 wrapper.crashed("cupspykota backend failed") 1347 except : 1347 except : 1348 1348 crashed("cupspykota backend failed") 1349 1349 retcode = 1 1350 finally : 1350 finally : 1351 1351 wrapper.clean() 1352 1352 sys.exit(retcode) -
pykota/trunk/bin/dumpykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 33 33 from pykota.dumper import DumPyKota 34 34 35 if __name__ == "__main__" : 35 if __name__ == "__main__" : 36 36 parser = PyKotaOptionParser(description=_("Data Dumper for PyKota."), 37 37 usage="dumpykota [options] [filterexpr]") 38 39 parser.add_option("-d", "--data", 38 39 parser.add_option("-d", "--data", 40 40 dest="data", 41 41 help=_("Select the type of datas to dump. This option is mandatory. Supported data types are : history, payments, billingcodes, users, groups, printers, upquotas, gpquotas, umembers, pmembers, and all. The 'all' value forces the output format to XML.")) 42 parser.add_option("-f", "--format", 42 parser.add_option("-f", "--format", 43 43 default="csv", 44 44 dest="format", 45 45 help=_("Select the output format, the default being comma separated values. Supported formats are : csv, ssv, tsv, xml and cups. The 'cups' output format only works when dumping the history, and produces CUPS' page_log compatible output.")) 46 parser.add_option("-o", "--output", 46 parser.add_option("-o", "--output", 47 47 dest="output", 48 48 default=u"-", 49 49 help=_("The name of the file the data dump will be written to. The default value is '-', which tells dumpykota to write the dump to stdout.")) 50 parser.add_option("-O", "--orderby", 50 parser.add_option("-O", "--orderby", 51 51 dest="orderby", 52 52 help=_("Change the ordering of the output based on a comma separated list of ordering statements. For example '-username,+printername' would sort the output by descending order of user names and ascending order of printer names. Not all expressions are supported, and you should not use this if you don't know the internal structure of PyKota's database." )) 53 parser.add_option("-s", "--sum", 53 parser.add_option("-s", "--sum", 54 54 dest="sum", 55 55 action="store_true", 56 56 default=False, 57 57 help=_("Summarize the output. Only available when dumping the printing history or the payments.")) 58 58 59 59 parser.add_filterexpression("username", _("User's name")) 60 60 parser.add_filterexpression("groupname", _("Users group's name")) … … 66 66 parser.add_filterexpression("start", _("Job's date of printing")) 67 67 parser.add_filterexpression("end", _("Job's date of printing")) 68 69 parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 68 69 parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 70 70 _("This would generate a PDF document containing invoices for all users who have spent some credits last month. Amounts would be in EURO and not VAT information would be included.")) 71 71 72 72 parser.add_example("--data history --format csv >myfile.csv", 73 73 _("This would dump the whole printing history to stdout in the CSV format, and redirect the output to a file.")) 74 74 75 75 parser.add_example("--data users --format xml -o users.xml", 76 76 _("This would dump all users into the 'users.xml' file in the XML format.")) 77 77 78 78 parser.add_example("--data history printername=HP2100 username=jerome", 79 79 _("This would dump jerome's printing history on printer HP2100.")) 80 80 81 81 parser.add_example("--data history start=200503 end=20050730234615", 82 82 _("This would dump all jobs printer between March 1st 2008 at midnight and July 30th 2008 at 23 hours 46 minutes and 15 seconds, included.")) 83 run(parser, DumPyKota) 83 run(parser, DumPyKota) -
pykota/trunk/bin/edpykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 38 38 39 39 edpykota [options] user1 user2 ... userN 40 40 41 41 edpykota [options] group1 group2 ... groupN 42 42 … … 45 45 -v | --version Prints edpykota's version number then exits. 46 46 -h | --help Prints this message then exits. 47 47 48 48 -a | --add Adds users or groups print quota entries if 49 49 they don't exist in database. 50 50 51 51 -d | --delete Deletes users or groups print quota entries. 52 52 Users or groups are never deleted, you have … … 54 54 The history will be purge from all matching 55 55 jobs, unless -g | --groups is used. 56 56 57 57 -P | --printer p Edit quotas on printer p only. Actually p can 58 58 use wildcards characters to select only 59 59 some printers. The default value is *, meaning 60 all printers. 61 You can specify several names or wildcards, 60 all printers. 61 You can specify several names or wildcards, 62 62 by separating them with commas. 63 64 -g | --groups Edit groups print quota entries instead of 63 64 -g | --groups Edit groups print quota entries instead of 65 65 users print quota entries. 66 66 67 67 -L | --list Lists users or groups print quota entries. 68 68 69 69 -n | --noquota Sets both soft and hard limits to None for users 70 70 or groups print quota entries. 71 71 72 72 -r | --reset Resets the actual page counter for the user 73 or group to zero on the specified printers. 73 or group to zero on the specified printers. 74 74 The life time page counter is kept unchanged. 75 75 76 76 -R | --hardreset Resets the actual and life time page counters 77 for the user or group to zero on the specified 77 for the user or group to zero on the specified 78 78 printers. This is a shortcut for '--used 0'. 79 79 80 80 -s | --skipexisting In combination with the --add option above, tells 81 81 edpykota to not modify existing print quota entries. 82 83 -S | --softlimit sl Sets the quota soft limit to sl pages. 84 82 83 -S | --softlimit sl Sets the quota soft limit to sl pages. 84 85 85 -H | --hardlimit hl Sets the quota hard limit to hl pages. 86 86 87 87 -I | --increase v Increase existing Soft and Hard limits by the value 88 88 of v. You can prefix v with + or -, if no sign is … … 104 104 user1 through userN and group1 through groupN can use wildcards 105 105 if the --add option is not set. 106 107 examples : 106 107 examples : 108 108 109 109 $ edpykota --add john paul george ringo 110 110 111 111 This will create print quota entries for users john, paul, george 112 112 and ringo on all printers. These print quota entries will have no 113 113 limit set. 114 114 115 115 $ edpykota --printer lp -S 50 -H 60 jerome 116 116 117 117 This will set jerome's print quota on the lp printer to a soft limit 118 118 of 50 pages, and a hard limit of 60 pages. Both user jerome and … … 120 120 commands, respectively. 121 121 122 $ edpykota -g -S 500 -H 550 financial support 123 122 $ edpykota -g -S 500 -H 550 financial support 123 124 124 This will set print quota soft limit to 500 pages and hard limit 125 125 to 550 pages for groups financial and support on all printers. 126 126 127 127 $ edpykota --reset jerome "jo*" 128 128 129 129 This will reset jerome's page counter to zero on all printers, as 130 130 well as every user whose name begins with 'jo'. … … 132 132 You can also reset the life time page counters by using the 133 133 --hardreset | -R command line option. 134 134 135 135 $ edpykota --printer hpcolor --noquota jerome 136 137 This will tell PyKota to not limit jerome when printing on the 138 hpcolor printer. All his jobs will be allowed on this printer, but 136 137 This will tell PyKota to not limit jerome when printing on the 138 hpcolor printer. All his jobs will be allowed on this printer, but 139 139 accounting of the pages he prints will still be kept. 140 140 Print Quotas for jerome on other printers are unchanged. 141 141 142 142 $ edpykota --delete --printer "HP*,XER*" jerome rachel 143 143 144 144 This will delete users jerome and rachel's print quota 145 145 entries on all printers which name begin with 'HP' or 146 146 'XER'. The jobs printed by these users on these printers 147 147 will be deleted from the history. 148 """) 149 150 class EdPyKota(PyKotaTool) : 148 """) 149 150 class EdPyKota(PyKotaTool) : 151 151 """A class for edpykota.""" 152 152 def modifyPQEntry(self, pqkey, pqentry, noquota, softlimit, hardlimit, increase, reset, hardreset, suffix, used) : … … 155 155 pqentry.setLimits(softlimit, hardlimit) 156 156 if increase : 157 newsoft = (pqentry.SoftLimit or 0) + increase 158 newhard = (pqentry.HardLimit or 0) + increase 157 newsoft = (pqentry.SoftLimit or 0) + increase 158 newhard = (pqentry.HardLimit or 0) + increase 159 159 if (newsoft >= 0) and (newhard >= 0) : 160 160 pqentry.setLimits(newsoft, newhard) 161 else : 161 else : 162 162 self.printInfo(_("You can't set negative limits for %s") % pqkey, "error") 163 163 if reset : 164 164 pqentry.reset() 165 if hardreset : 165 if hardreset : 166 166 pqentry.hardreset() 167 167 if suffix == "User" : 168 168 if used : 169 169 pqentry.setUsage(used) 170 170 171 171 def main(self, names, options) : 172 172 """Edit user or group quotas.""" 173 173 names = self.sanitizeNames(options, names) 174 suffix = (options["groups"] and "Group") or "User" 174 suffix = (options["groups"] and "Group") or "User" 175 175 printernames = options["printer"].split(",") 176 176 177 177 if not options["list"] : 178 178 percent = Percent(self) … … 182 182 if not options["list"] : 183 183 percent.setSize(len(printers) * len(entries)) 184 184 185 185 if options["list"] : 186 186 for printer in printers : … … 198 198 print " %s" % (_("Warning banners printed : %s") % pqentry.WarnCount) 199 199 print 200 elif options["delete"] : 200 elif options["delete"] : 201 201 percent.display("\n%s..." % _("Deletion")) 202 202 getattr(self.storage, "deleteMany%sPQuotas" % suffix)(printers, entries) … … 211 211 except ValueError : 212 212 raise PyKotaCommandLineError, _("Invalid used value %s.") % used 213 213 214 214 increase = options["increase"] 215 215 if increase : … … 218 218 except ValueError : 219 219 raise PyKotaCommandLineError, _("Invalid increase value %s.") % increase 220 220 221 221 noquota = options["noquota"] 222 reset = options["reset"] 222 reset = options["reset"] 223 223 hardreset = options["hardreset"] 224 224 softlimit = hardlimit = None … … 229 229 if softlimit < 0 : 230 230 raise ValueError 231 except ValueError : 231 except ValueError : 232 232 raise PyKotaCommandLineError, _("Invalid softlimit value %s.") % options["softlimit"] 233 233 if options["hardlimit"] : … … 236 236 if hardlimit < 0 : 237 237 raise ValueError 238 except ValueError : 238 except ValueError : 239 239 raise PyKotaCommandLineError, _("Invalid hardlimit value %s.") % options["hardlimit"] 240 if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) : 240 if (softlimit is not None) and (hardlimit is not None) and (hardlimit < softlimit) : 241 241 # error, exchange them 242 242 self.printInfo(_("Hard limit %i is less than soft limit %i, values will be exchanged.") % (hardlimit, softlimit)) 243 243 (softlimit, hardlimit) = (hardlimit, softlimit) 244 if hardlimit is None : 244 if hardlimit is None : 245 245 hardlimit = softlimit 246 246 if hardlimit is not None : 247 247 self.printInfo(_("Undefined hard limit set to soft limit (%s).") % str(hardlimit)) 248 if softlimit is None : 248 if softlimit is None : 249 249 softlimit = hardlimit 250 250 if softlimit is not None : 251 251 self.printInfo(_("Undefined soft limit set to hard limit (%s).") % str(softlimit)) 252 253 self.storage.beginTransaction() 252 253 self.storage.beginTransaction() 254 254 try : 255 255 if options["add"] : 256 256 percent.display("\n%s...\n" % _("Creation")) 257 if not entries : 257 if not entries : 258 258 self.printInfo(_("No entry matches %s. Please use pkusers to create them first.") % (" ".join(names)), "warn") 259 259 260 260 factory = globals()["Storage%sPQuota" % suffix] 261 261 for printer in printers : … … 270 270 hardreset, suffix, used) 271 271 oldpqentry = getattr(self.storage, "add%sPQuota" % suffix)(pqentry) 272 if oldpqentry is not None : 272 if oldpqentry is not None : 273 273 if skipexisting : 274 274 self.logdebug("%s print quota entry %s@%s already exists, skipping." % (suffix, ename, pname)) 275 else : 275 else : 276 276 self.logdebug("%s print quota entry %s@%s already exists, will be modified." % (suffix, ename, pname)) 277 277 self.modifyPQEntry(pqkey, oldpqentry, noquota, \ … … 279 279 increase, reset, \ 280 280 hardreset, suffix, used) 281 oldpqentry.save() 281 oldpqentry.save() 282 282 percent.oneMore() 283 else : 283 else : 284 284 percent.display("\n%s...\n" % _("Modification")) 285 285 for printer in printers : … … 287 287 pqkey = "%s@%s" % (entry.Name, printer.Name) 288 288 pqentry = getattr(self.storage, "get%sPQuota" % suffix)(entry, printer) 289 if pqentry.Exists : 289 if pqentry.Exists : 290 290 self.modifyPQEntry(pqkey, pqentry, noquota, \ 291 291 softlimit, hardlimit, \ 292 292 increase, reset, \ 293 293 hardreset, suffix, used) 294 pqentry.save() 294 pqentry.save() 295 295 percent.oneMore() 296 except : 296 except : 297 297 self.storage.rollbackTransaction() 298 298 raise 299 else : 299 else : 300 300 self.storage.commitTransaction() 301 301 302 302 if not options["list"] : 303 303 percent.done() 304 305 if __name__ == "__main__" : 304 305 if __name__ == "__main__" : 306 306 retcode = 0 307 307 try : … … 316 316 "printer=", "softlimit=", "hardlimit=", \ 317 317 "increase=", "used=", "skipexisting"] 318 318 319 319 # Initializes the command line tool 320 320 manager = EdPyKota(doc=__doc__) 321 321 manager.deferredInit() 322 322 323 323 # parse and checks the command line 324 324 (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options) 325 325 326 326 # sets long options 327 327 options["help"] = options["h"] or options["help"] … … 331 331 options["printer"] = options["P"] or options["printer"] or defaults["printer"] 332 332 options["softlimit"] = options["S"] or options["softlimit"] 333 options["hardlimit"] = options["H"] or options["hardlimit"] 334 options["reset"] = options["r"] or options["reset"] 333 options["hardlimit"] = options["H"] or options["hardlimit"] 334 options["reset"] = options["r"] or options["reset"] 335 335 options["noquota"] = options["n"] or options["noquota"] 336 options["delete"] = options["d"] or options["delete"] 337 options["hardreset"] = options["R"] or options["hardreset"] 336 options["delete"] = options["d"] or options["delete"] 337 options["hardreset"] = options["R"] or options["hardreset"] 338 338 options["used"] = options["U"] or options["used"] 339 339 options["increase"] = options["I"] or options["increase"] 340 340 options["list"] = options["L"] or options["list"] 341 341 options["skipexisting"] = options["s"] or options["skipexisting"] 342 342 343 343 if options["help"] : 344 344 manager.display_usage_and_quit() … … 354 354 else : 355 355 retcode = manager.main(args, options) 356 except KeyboardInterrupt : 356 except KeyboardInterrupt : 357 357 logerr("\nInterrupted with Ctrl+C !\n") 358 358 retcode = -3 359 except PyKotaCommandLineError, msg : 359 except PyKotaCommandLineError, msg : 360 360 logerr("%s : %s\n" % (sys.argv[0], msg)) 361 361 retcode = -2 362 except SystemExit : 362 except SystemExit : 363 363 pass 364 364 except : 365 365 try : 366 366 manager.crashed("edpykota failed") 367 except : 367 except : 368 368 crashed("edpykota failed") 369 369 retcode = -1 … … 371 371 try : 372 372 manager.storage.close() 373 except (TypeError, NameError, AttributeError) : 373 except (TypeError, NameError, AttributeError) : 374 374 pass 375 376 sys.exit(retcode) 375 376 sys.exit(retcode) -
pykota/trunk/bin/mailandpopup.sh
r3275 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 37 37 $MESSAGE 38 38 EOF1 39 # 39 # 40 40 # Send some information to root as well 41 41 mail -s "Print Quota problem on printer $PNAME" root <<EOF2 … … 43 43 EOF2 44 44 # 45 # Launch WinPopup on user's host (may need a real Samba or NT domain) 45 # Launch WinPopup on user's host (may need a real Samba or NT domain) 46 46 # In some cases the username does not suffice for smbclient to send a message; 47 47 # we must also supply the IP address. This will use smbstatus to get all IPs -
pykota/trunk/bin/papwaitprinter.sh
r3275 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 21 21 PATH=$PATH:/bin:/usr/bin:/usr/local/bin:/opt/bin:/sbin:/usr/sbin 22 22 sleep 5; 23 until papstatus -p "$1" | grep -i idle >/dev/null ; do 24 sleep 1 ; 23 until papstatus -p "$1" | grep -i idle >/dev/null ; do 24 sleep 1 ; 25 25 done -
pykota/trunk/bin/pkbanner
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 22 22 # 23 23 24 """A banner generator for PyKota""" 24 """A banner generator for PyKota""" 25 25 26 26 import sys … … 34 34 import reportlab.lib 35 35 from reportlab.lib.units import cm 36 except ImportError : 36 except ImportError : 37 37 hasRL = False 38 else : 38 else : 39 39 hasRL = True 40 40 41 41 try : 42 import PIL.Image 43 except ImportError : 42 import PIL.Image 43 except ImportError : 44 44 hasPIL = False 45 else : 45 else : 46 46 hasPIL = True 47 47 48 48 import pykota.appinit 49 49 from pykota.utils import run … … 56 56 from pykota import version 57 57 58 class PyKotaBanner(Tool) : 58 class PyKotaBanner(Tool) : 59 59 """A class for pkbanner.""" 60 def getVar(self, varname) : 60 def getVar(self, varname) : 61 61 """Extracts a variable from the environment and returns its value or 'Unknown' in the current locale.""" 62 62 return os.environ.get(varname) or _("Unknown") 63 63 64 64 def printVar(self, canvas, x, y, label, value, size, savetoner) : 65 65 """Outputs a variable onto the PDF canvas. 66 66 67 67 Returns the number of points to substract to current Y coordinate. 68 """ 68 """ 69 69 canvas.saveState() 70 70 canvas.setFont("Helvetica-Bold", size) … … 79 79 canvas.restoreState() 80 80 return (size + 4) 81 81 82 82 def genPDF(self, pagesize, logo, url, text, savetoner) : 83 83 """Generates the banner in PDF format, return the PDF document as a string.""" 84 84 document = cStringIO.StringIO() 85 85 c = canvas.Canvas(document, pagesize=pagesize, pageCompression=1) 86 86 87 87 c.setAuthor(self.effectiveUserName) 88 88 c.setTitle(_("PyKota generated Banner")) 89 89 c.setSubject(_("This is a print banner generated with PyKota")) 90 90 91 91 xcenter = pagesize[0] / 2.0 92 92 ycenter = pagesize[1] / 2.0 93 94 ypos = pagesize[1] - (2 * cm) 95 93 94 ypos = pagesize[1] - (2 * cm) 95 96 96 if logo : 97 try : 97 try : 98 98 imglogo = PIL.Image.open(logo) 99 except : 99 except : 100 100 self.printInfo("Unable to open image %s" % logo, "warn") 101 101 else : 102 102 (width, height) = imglogo.size 103 multi = float(width) / (8 * cm) 103 multi = float(width) / (8 * cm) 104 104 width = float(width) / multi 105 105 height = float(height) / multi … … 107 107 ypos -= height 108 108 c.drawImage(logo, xpos, ypos, width, height) 109 109 110 110 # New top 111 111 xpos = pagesize[0] / 5.0 112 112 ypos -= (1 * cm) + 20 113 113 114 114 printername = self.getVar("PYKOTAPRINTERNAME") 115 115 username = self.getVar("PYKOTAUSERNAME") 116 116 accountbanner = self.config.getAccountBanner(printername) 117 117 118 118 # Outputs the username 119 ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 120 121 # Text 119 ypos -= self.printVar(c, xcenter, ypos, _("Username"), username, 20, savetoner) 120 121 # Text 122 122 if text : 123 ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 124 123 ypos -= self.printVar(c, xcenter, ypos, _("More Info"), text, 20, savetoner) 124 125 125 # Printer and Job Id 126 126 job = "%s - %s" % (printername, self.getVar("PYKOTAJOBID")) 127 ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 128 127 ypos -= self.printVar(c, xcenter, ypos, _("Job"), job, 14, savetoner) 128 129 129 # Current date (TODO : at the time the banner was printed ! Change this to job's submission date) 130 130 datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace") 131 ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 132 131 ypos -= self.printVar(c, xcenter, ypos, _("Date"), datetime, 14, savetoner) 132 133 133 # Result of the print job 134 134 action = self.getVar("PYKOTAACTION") 135 135 if action == "ALLOW" : 136 136 action = _("Allowed") 137 elif action == "DENY" : 137 elif action == "DENY" : 138 138 action = _("Denied") 139 elif action == "WARN" : 139 elif action == "WARN" : 140 140 action = _("Allowed with Warning") 141 elif action == "PROBLEM" : 141 elif action == "PROBLEM" : 142 142 # should never occur 143 143 action = _("Problem") 144 elif action == "CANCEL" : 144 elif action == "CANCEL" : 145 145 # should never occur 146 146 action = _("Cancelled") 147 ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 148 147 ypos -= self.printVar(c, xcenter, ypos, _("Result"), action, 14, savetoner) 148 149 149 # skip some space 150 150 ypos -= 20 151 151 152 152 # Outputs title and filename 153 153 # We put them at x=0.25*pagewidth so that the line is long enough to hold them 154 154 title = self.getVar("PYKOTATITLE") 155 ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 156 155 ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Title"), title, 10, savetoner) 156 157 157 filename = self.getVar("PYKOTAFILENAME") 158 ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 159 158 ypos -= self.printVar(c, xcenter / 2.0, ypos, _("Filename"), filename, 10, savetoner) 159 160 160 # skip some space 161 161 ypos -= 20 162 162 163 163 # Now outputs the user's account balance or page counter 164 ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 164 ypos -= self.printVar(c, xcenter, ypos, _("Pages printed so far on %s") % printername, self.getVar("PYKOTAPAGECOUNTER"), 14, savetoner) 165 165 limitby = self.getVar("PYKOTALIMITBY") 166 if limitby == "balance" : 167 ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 166 if limitby == "balance" : 167 ypos -= self.printVar(c, xcenter, ypos, _("Account balance"), self.getVar("PYKOTABALANCE"), 14, savetoner) 168 168 elif limitby == "quota" : 169 ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 170 ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 171 ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 169 ypos -= self.printVar(c, xcenter, ypos, _("Soft Limit"), self.getVar("PYKOTASOFTLIMIT"), 14, savetoner) 170 ypos -= self.printVar(c, xcenter, ypos, _("Hard Limit"), self.getVar("PYKOTAHARDLIMIT"), 14, savetoner) 171 ypos -= self.printVar(c, xcenter, ypos, _("Date Limit"), self.getVar("PYKOTADATELIMIT"), 14, savetoner) 172 172 else : 173 173 if limitby == "noquota" : 174 174 msg = _("No Limit") 175 elif limitby == "nochange" : 175 elif limitby == "nochange" : 176 176 msg = _("No Accounting") 177 elif limitby == "noprint" : 177 elif limitby == "noprint" : 178 178 msg = _("Forbidden") 179 else : 179 else : 180 180 msg = _("Unknown") 181 181 ypos -= self.printVar(c, xcenter, ypos, _("Printing Mode"), msg, 14, savetoner) 182 182 183 183 # URL 184 184 if url : … … 189 189 c.drawCentredString(xcenter, 2 * cm, url) 190 190 c.restoreState() 191 191 192 192 c.showPage() 193 193 c.save() 194 194 return document.getvalue() 195 195 196 196 def main(self, arguments, options) : 197 197 """Generates a banner.""" … … 200 200 if not hasPIL : 201 201 raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads" 202 203 self.logdebug("Generating the banner in PDF format...") 202 203 self.logdebug("Generating the banner in PDF format...") 204 204 doc = self.genPDF(getPageSize(options.pagesize), 205 options.logo.strip().encode(sys.getfilesystemencoding(), "replace"), 206 options.url.strip(), 207 " ".join(arguments).strip(), 205 options.logo.strip().encode(sys.getfilesystemencoding(), "replace"), 206 options.url.strip(), 207 " ".join(arguments).strip(), 208 208 options.savetoner / 100.0) 209 210 self.logdebug("Converting the banner to PostScript...") 209 210 self.logdebug("Converting the banner to PostScript...") 211 211 command = "gs -q -dNOPAUSE -dBATCH -dPARANOIDSAFER -sDEVICE=pswrite -sOutputFile=- -" 212 212 subpr = subprocess.Popen(command, … … 215 215 stdout=subprocess.PIPE, 216 216 stderr=subprocess.PIPE) 217 try : 217 try : 218 218 (out, err) = subpr.communicate(doc) 219 except OSError, msg : 219 except OSError, msg : 220 220 raise PyKotaToolError, _("Impossible to execute '%(command)s'") % locals() 221 221 status = subpr.wait() … … 255 255 parser.add_example('--logo="" --savetoner=75', 256 256 _("This would generate a banner in the default page size, with no logo, and text luminosity would be increased by 75%.")) 257 257 258 258 run(parser, PyKotaBanner) -
pykota/trunk/bin/pkbcodes
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 35 35 from pykota.storage import StorageBillingCode 36 36 37 class PKBcodes(PyKotaTool) : 37 class PKBcodes(PyKotaTool) : 38 38 """A class for a billing codes manager.""" 39 39 def modifyBillingCode(self, billingcode, reset, description) : 40 40 """Modifies a billing code.""" 41 41 if reset : 42 billingcode.reset() 42 billingcode.reset() 43 43 if description is not None : # NB : "" is allowed ! 44 44 billingcode.setDescription(description) 45 45 46 46 def main(self, names, options) : 47 47 """Manage billing codes.""" 48 48 if options.action != "list" : 49 49 self.adminOnly() 50 50 51 51 islist = (options.action == "list") 52 52 isadd = (options.action == "add") 53 53 isdelete = (options.action == "delete") 54 54 55 55 if not names : 56 56 if isdelete or isadd : 57 57 raise PyKotaCommandLineError, _("You must specify billing codes on the command line.") 58 names = [u"*"] 59 58 names = [u"*"] 59 60 60 if not islist : 61 61 percent = Percent(self) 62 62 63 63 if not isadd : 64 64 if not islist : … … 73 73 if not islist : 74 74 percent.setSize(len(billingcodes)) 75 75 76 76 if islist : 77 77 for billingcode in billingcodes : … … 83 83 billingcode.Balance, \ 84 84 _("credits"))) 85 elif isdelete : 85 elif isdelete : 86 86 percent.display("\n%s..." % _("Deletion")) 87 87 self.storage.deleteManyBillingCodes(billingcodes) … … 91 91 if description : 92 92 description = description.strip() 93 93 94 94 self.storage.beginTransaction() 95 95 try : 96 if isadd : 96 if isadd : 97 97 percent.display("%s...\n" % _("Creation")) 98 98 percent.setSize(len(names)) 99 99 for bname in names : 100 100 billingcode = StorageBillingCode(self.storage, bname) 101 self.modifyBillingCode(billingcode, 102 options.reset, 101 self.modifyBillingCode(billingcode, 102 options.reset, 103 103 description) 104 104 oldbillingcode = self.storage.addBillingCode(billingcode) … … 106 106 if options.skipexisting : 107 107 self.logdebug(_("Billing code '%(bname)s' already exists, skipping.") % locals()) 108 else : 108 else : 109 109 self.logdebug(_("Billing code '%(bname)s' already exists, will be modified.") % locals()) 110 self.modifyBillingCode(oldbillingcode, 111 options.reset, 110 self.modifyBillingCode(oldbillingcode, 111 options.reset, 112 112 description) 113 113 oldbillingcode.save() 114 114 percent.oneMore() 115 else : 115 else : 116 116 percent.display("\n%s...\n" % _("Modification")) 117 117 for billingcode in billingcodes : 118 self.modifyBillingCode(billingcode, 119 options.reset, 118 self.modifyBillingCode(billingcode, 119 options.reset, 120 120 description) 121 billingcode.save() 121 billingcode.save() 122 122 percent.oneMore() 123 except : 123 except : 124 124 self.storage.rollbackTransaction() 125 125 raise 126 else : 126 else : 127 127 self.storage.commitTransaction() 128 128 129 129 if not islist : 130 130 percent.done() 131 132 if __name__ == "__main__" : 131 132 if __name__ == "__main__" : 133 133 parser = PyKotaOptionParser(description=_("A billing codes manager for PyKota."), 134 134 usage="pkbcodes [options] code1 code2 ... codeN") … … 159 159 dest="skipexisting", 160 160 help=_("If --add is used, ensure that existing billing codes won't be modified.")) 161 161 162 162 parser.add_example('-D "Financial Department" financial', 163 163 _("Would create a billing code labelled 'financial' with the specified textual description.")) 164 parser.add_example('--delete "fin*"', 164 parser.add_example('--delete "fin*"', 165 165 _("Would delete all billing codes which label begins with 'fin'. Matching jobs in the printing history wouldn't be deleted though.")) 166 166 parser.add_example("--list", 167 167 _("Would display details about all existing billing codes.")) 168 168 169 169 (options, arguments) = parser.parse_args() 170 run(parser, PKBcodes) 170 run(parser, PKBcodes) -
pykota/trunk/bin/pkinvoice
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 34 34 from reportlab.lib import pagesizes 35 35 from reportlab.lib.units import cm 36 except ImportError : 36 except ImportError : 37 37 hasRL = False 38 else : 38 else : 39 39 hasRL = True 40 40 41 41 try : 42 import PIL.Image 43 except ImportError : 42 import PIL.Image 43 except ImportError : 44 44 hasPIL = False 45 else : 45 else : 46 46 hasPIL = True 47 47 … … 56 56 from pykota.tool import Percent, PyKotaTool 57 57 58 class PKInvoice(PyKotaTool) : 58 class PKInvoice(PyKotaTool) : 59 59 """A class for invoice generator.""" 60 60 validfilterkeys = [ "username", … … 66 66 "end", 67 67 ] 68 68 69 69 def printVar(self, label, value, size) : 70 70 """Outputs a variable onto the PDF canvas. 71 71 72 72 Returns the number of points to substract to current Y coordinate. 73 """ 73 """ 74 74 xcenter = (self.pagesize[0] / 2.0) - 1*cm 75 75 self.canvas.saveState() … … 82 82 self.canvas.restoreState() 83 83 self.ypos -= (size + 4) 84 84 85 85 def pagePDF(self, invoicenumber, name, values, unit, vat) : 86 86 """Generates a new page in the PDF document.""" … … 96 96 datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace") 97 97 self.printVar(_("Edited on"), datetime, 14) 98 98 99 99 self.ypos -= 20 100 100 self.printVar(_("Number of jobs printed"), str(values["nbjobs"]), 18) … … 107 107 self.canvas.showPage() 108 108 return 1 109 return 0 110 109 return 0 110 111 111 def initPDF(self, logo) : 112 112 """Initializes the PDF document.""" 113 self.pdfDocument = cStringIO.StringIO() 113 self.pdfDocument = cStringIO.StringIO() 114 114 self.canvas = c = canvas.Canvas(self.pdfDocument, \ 115 115 pagesize=self.pagesize, \ 116 116 pageCompression=1) 117 117 118 118 c.setAuthor(self.effectiveUserName) 119 119 c.setTitle(_("PyKota invoices")) 120 120 c.setSubject(_("Invoices generated with PyKota")) 121 121 122 122 self.canvas.beginForm("background") 123 123 self.canvas.saveState() 124 125 self.ypos = self.pagesize[1] - (2 * cm) 126 124 125 self.ypos = self.pagesize[1] - (2 * cm) 126 127 127 xcenter = self.pagesize[0] / 2.0 128 128 if logo : 129 try : 129 try : 130 130 imglogo = PIL.Image.open(logo) 131 except IOError : 131 except IOError : 132 132 self.printInfo("Unable to open image %s" % logo, "warn") 133 133 else : 134 134 (width, height) = imglogo.size 135 multi = float(width) / (8 * cm) 135 multi = float(width) / (8 * cm) 136 136 width = float(width) / multi 137 137 height = float(height) / multi … … 140 140 self.ypos, \ 141 141 width, height) 142 142 143 143 self.ypos -= (cm + 20) 144 144 self.canvas.setFont("Helvetica-Bold", 14) … … 146 146 msg = _("Here's the invoice for your printouts") 147 147 self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg) 148 148 149 149 self.yorigine = self.ypos 150 150 self.canvas.restoreState() 151 151 self.canvas.endForm() 152 153 def endPDF(self, fname) : 152 153 def endPDF(self, fname) : 154 154 """Flushes the PDF generator.""" 155 155 self.canvas.save() 156 if fname != "-" : 156 if fname != "-" : 157 157 outfile = open(fname, "w") 158 158 outfile.write(self.pdfDocument.getvalue()) 159 159 outfile.close() 160 else : 160 else : 161 161 sys.stdout.write(self.pdfDocument.getvalue()) 162 162 sys.stdout.flush() 163 163 164 164 def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) : 165 165 """Generates the invoices file.""" … … 168 168 if outfname != "-" : 169 169 percent.display("%s...\n" % _("Generating invoices")) 170 170 171 171 self.initPDF(logo) 172 172 number = firstnumber … … 175 175 if outfname != "-" : 176 176 percent.oneMore() 177 177 178 178 if number > firstnumber : 179 179 self.endPDF(outfname) 180 180 181 181 if outfname != "-" : 182 182 percent.done() 183 183 184 184 def main(self, arguments, options) : 185 185 """Generate invoices.""" … … 188 188 if not hasPIL : 189 189 raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads" 190 190 191 191 self.adminOnly() 192 192 193 193 self.pagesize = getPageSize(options.pagesize) 194 194 195 195 extractonly = {} 196 196 for filterexp in arguments : … … 200 200 filterkey = filterkey.lower() 201 201 if filterkey not in self.validfilterkeys : 202 raise ValueError 203 except ValueError : 202 raise ValueError 203 except ValueError : 204 204 raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp 205 else : 205 else : 206 206 extractonly.update({ filterkey : filtervalue }) 207 207 208 208 percent = Percent(self) 209 209 outfname = options.output.strip().encode(sys.getfilesystemencoding()) 210 210 if outfname != "-" : 211 211 percent.display("%s..." % _("Extracting datas")) 212 213 username = extractonly.get("username") 212 213 username = extractonly.get("username") 214 214 if username : 215 215 user = self.storage.getUser(username) 216 216 else : 217 217 user = None 218 219 printername = extractonly.get("printername") 218 219 printername = extractonly.get("printername") 220 220 if printername : 221 221 printer = self.storage.getPrinter(printername) 222 else : 222 else : 223 223 printer = None 224 224 225 225 start = extractonly.get("start") 226 226 end = extractonly.get("end") 227 227 (start, end) = self.storage.cleanDates(start, end) 228 229 jobs = self.storage.retrieveHistory(user=user, 230 printer=printer, 228 229 jobs = self.storage.retrieveHistory(user=user, 230 printer=printer, 231 231 hostname=extractonly.get("hostname"), 232 232 billingcode=extractonly.get("billingcode"), … … 235 235 end=end, 236 236 limit=0) 237 238 peruser = {} 239 nbjobs = 0 240 nbpages = 0 237 238 peruser = {} 239 nbjobs = 0 240 nbpages = 0 241 241 nbcredits = 0.0 242 242 percent.setSize(len(jobs)) 243 243 if outfname != "-" : 244 244 percent.display("\n") 245 for job in jobs : 245 for job in jobs : 246 246 if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) : 247 247 nbpages += job.JobSize … … 256 256 if outfname != "-" : 257 257 percent.done() 258 self.genInvoices(peruser, 259 options.logo.strip().encode(sys.getfilesystemencoding()), 260 outfname, 261 options.number, 262 options.unit or _("Credits"), 258 self.genInvoices(peruser, 259 options.logo.strip().encode(sys.getfilesystemencoding()), 260 outfname, 261 options.number, 262 options.unit or _("Credits"), 263 263 options.vat) 264 if outfname != "-" : 264 if outfname != "-" : 265 265 nbusers = len(peruser) 266 266 print _("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \ 267 267 % locals() 268 269 if __name__ == "__main__" : 268 269 if __name__ == "__main__" : 270 270 parser = PyKotaOptionParser(description=_("Invoice generator for PyKota."), 271 271 usage="pkinvoice [options] [filterexpr]") … … 281 281 default=u"A4", 282 282 help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default.")) 283 parser.add_option("-n", "--number", 283 parser.add_option("-n", "--number", 284 284 dest="number", 285 285 type="int", … … 293 293 default=u"-", 294 294 help=_("The name of the file to which the PDF invoices will be written. If not set or set to '%default', the PDF document will be sent to the standard output.")) 295 295 296 296 # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861 297 297 # TODO : we can't use 'default=_("Credits")' for this option 298 parser.add_option("-u", "--unit", 298 parser.add_option("-u", "--unit", 299 299 dest="unit", 300 300 type="string", 301 301 help=_("The name of the unit to use on the invoices. The default value is 'Credits' or its locale translation.")) 302 303 parser.add_option("-V", "--vat", 302 303 parser.add_option("-V", "--vat", 304 304 dest="vat", 305 305 type="float", … … 308 308 default=0.0, 309 309 help=_("The value in percent of the applicable VAT to be exposed. The default is %default, meaning no VAT.")) 310 311 parser.add_filterexpression("username", _("User's name")) 312 parser.add_filterexpression("printername", _("Printer's name")) 313 parser.add_filterexpression("hostname", _("Host's name")) 314 parser.add_filterexpression("jobid", _("Job's id")) 310 311 parser.add_filterexpression("username", _("User's name")) 312 parser.add_filterexpression("printername", _("Printer's name")) 313 parser.add_filterexpression("hostname", _("Host's name")) 314 parser.add_filterexpression("jobid", _("Job's id")) 315 315 parser.add_filterexpression("billingcode", _("Job's billing code")) 316 parser.add_filterexpression("start", _("Job's date of printing")) 317 parser.add_filterexpression("end", _("Job's date of printing")) 318 319 parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 316 parser.add_filterexpression("start", _("Job's date of printing")) 317 parser.add_filterexpression("end", _("Job's date of printing")) 318 319 parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', 320 320 _("This would generate a PDF document containing invoices for all users who have spent some credits last month. Amounts would be in EURO and not VAT information would be included.")) 321 322 run(parser, PKInvoice) 321 322 run(parser, PKInvoice) -
pykota/trunk/bin/pknotify
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 30 30 try : 31 31 import PAM 32 except ImportError : 32 except ImportError : 33 33 hasPAM = False 34 else : 34 else : 35 35 hasPAM = True 36 36 … … 53 53 -v | --version Prints pknotify's version number then exits. 54 54 -h | --help Prints this message then exits. 55 55 56 56 -d | --destination h[:p] Sets the destination hostname and optional 57 57 port onto which contact the remote PyKotIcon 58 58 application. This option is mandatory. 59 59 When not specified, the port defaults to 7654. 60 60 61 61 -a | --ask Tells pknotify to ask something to the end 62 62 user. Then pknotify will output the result. 63 63 64 64 -C | --checkauth When --ask is used and both an 'username' and a 65 65 'password' are asked to the end user, then … … 70 70 be printed. If the user is authenticated, then 71 71 "USERNAME=xxxx" will be printed as well. 72 73 -c | --confirm Tells pknotify to ask for either a confirmation 72 73 -c | --confirm Tells pknotify to ask for either a confirmation 74 74 or abortion. 75 76 -D | --denyafter N With --checkauth above, makes pknotify loop 75 76 -D | --denyafter N With --checkauth above, makes pknotify loop 77 77 up to N times if the password is incorrect. 78 78 After having reached the limit, "DENY" will … … 80 80 The default value of N is 1, meaning the job 81 81 is denied after the first unsuccessful try. 82 82 83 83 -N | --noremote action If it's impossible to connect to the remote 84 84 PyKotIcon machine, do this action instead. 85 Allowed actions are 'CONTINUE' and 'CANCEL', 85 Allowed actions are 'CONTINUE' and 'CANCEL', 86 86 which will respectively allow the processing 87 87 of the print job to continue, or the job to 88 88 be cancelled. The default value is CANCEL. 89 89 90 90 -n | --notify Tells pknotify to send an informational message 91 91 to the end user. 92 92 93 93 -q | --quit Tells pknotify to send a message asking the 94 94 PyKotIcon application to exit. This option can 95 95 be combined with the other ones to make PyKotIcon 96 96 exit after having sent the answer from the dialog. 97 97 98 98 -t | --timeout T Tells pknotify to ignore the end user's answer if 99 99 it comes past T seconds after the dialog box being 100 opened. The default value is 0 seconds, which 100 opened. The default value is 0 seconds, which 101 101 tells pknotify to wait indefinitely. 102 102 Use this option to avoid having an user who 103 103 leaved his computer stall a whole print queue. 104 104 105 105 You MUST specify either --ask, --confirm, --notify or --quit. 106 106 107 arguments : 108 107 arguments : 108 109 109 -a | --ask : Several arguments are accepted, of the form 110 110 "label:varname:defaultvalue". The result will … … 119 119 it is not printed, it will be used to check if 120 120 authentication is valid if you specify --checkauth. 121 121 122 122 -c | --confirm : A single argument is expected, representing the 123 123 message to display. If the dialog is confirmed 124 124 then pknotify will print OK, else CANCEL. 125 126 -n | --notify : A single argument is expected, representing the 125 126 -n | --notify : A single argument is expected, representing the 127 127 message to display. In this case pknotify will 128 128 always print OK. 129 130 examples : 129 130 examples : 131 131 132 132 pknotify -d client:7654 --noremote CONTINUE --confirm "This job costs 10 credits" 133 133 134 134 Would display the cost of the print job and asks for confirmation. 135 135 If the end user doesn't have PyKotIcon running and accepting connections 136 136 from the print server, PyKota will consider that the end user accepted 137 137 to print this job. 138 138 139 139 pknotify --destination $PYKOTAJOBORIGINATINGHOSTNAME:7654 \\ 140 140 --checkauth --ask "Your name:username:" "Your password:password:" 141 142 Asks an username and password, and checks if they are valid. 141 142 Asks an username and password, and checks if they are valid. 143 143 NB : The PYKOTAJOBORIGINATINGHOSTNAME environment variable is 144 144 only set if you launch pknotify from cupspykota through a directive 145 145 in ~pykota/pykota.conf 146 146 147 147 The TCP port you'll use must be reachable on the client from the 148 148 print server. 149 149 """) 150 151 class TimeoutError(Exception) : 150 151 class TimeoutError(Exception) : 152 152 """An exception for timeouts.""" 153 153 def __init__(self, message = ""): … … 157 157 return self.message 158 158 __str__ = __repr__ 159 160 class PyKotaNotify(Tool) : 159 160 class PyKotaNotify(Tool) : 161 161 """A class for pknotify.""" 162 162 def UTF8ToUserCharset(self, text) : … … 164 164 if text is None : 165 165 return None 166 else : 167 return text.decode("UTF-8", "replace").encode(self.charset, "replace") 168 166 else : 167 return text.decode("UTF-8", "replace").encode(self.charset, "replace") 168 169 169 def userCharsetToUTF8(self, text) : 170 170 """Converts from user's charset to UTF-8.""" 171 171 if text is None : 172 172 return None 173 else : 174 return text.decode(self.charset, "replace").encode("UTF-8", "replace") 175 173 else : 174 return text.decode(self.charset, "replace").encode("UTF-8", "replace") 175 176 176 def sanitizeMessage(self, msg) : 177 177 """Replaces \\n and returns a messagee in xmlrpclib Binary format.""" 178 178 return xmlrpclib.Binary(self.userCharsetToUTF8(msg.replace("\\n", "\n"))) 179 179 180 180 def convPAM(self, auth, queries=[], userdata=None) : 181 181 """Prepares PAM datas.""" … … 191 191 return response 192 192 193 def checkAuth(self, username, password) : 193 def checkAuth(self, username, password) : 194 194 """Checks if we could authenticate an username with a password.""" 195 if not hasPAM : 195 if not hasPAM : 196 196 raise PyKotaToolError, _("You MUST install PyPAM for this functionnality to work !") 197 else : 197 else : 198 198 retcode = False 199 199 self.password = password … … 213 213 retcode = True 214 214 return retcode 215 216 def alarmHandler(self, signum, frame) : 215 216 def alarmHandler(self, signum, frame) : 217 217 """Alarm handler.""" 218 218 raise TimeoutError, _("The end user at %s:%i didn't answer within %i seconds. The print job will be cancelled.") % (self.destination, self.port, self.timeout) 219 219 220 220 def main(self, arguments, options) : 221 221 """Notifies or asks questions to end users through PyKotIcon.""" … … 226 226 self.destination = options["destination"] 227 227 self.port = 7654 228 228 229 229 try : 230 230 denyafter = int(options["denyafter"]) 231 231 if denyafter < 1 : 232 232 raise ValueError 233 except (ValueError, TypeError) : 233 except (ValueError, TypeError) : 234 234 denyafter = 1 235 236 try : 235 236 try : 237 237 self.timeout = int(options["timeout"]) 238 238 if self.timeout < 0 : … … 240 240 except (ValueError, TypeError) : 241 241 self.timeout = 0 242 242 243 243 if self.timeout : 244 244 signal.signal(signal.SIGALRM, self.alarmHandler) 245 245 signal.alarm(self.timeout) 246 247 try : 248 try : 246 247 try : 248 try : 249 249 server = xmlrpclib.ServerProxy("http://%s:%s" % (self.destination, self.port)) 250 250 if options["ask"] : … … 253 253 if denyafter < 1 : 254 254 raise ValueError 255 except (ValueError, TypeError) : 255 except (ValueError, TypeError) : 256 256 denyafter = 1 257 257 labels = [] … … 261 261 try : 262 262 (label, varname, varvalue) = arg.split(":", 2) 263 except ValueError : 263 except ValueError : 264 264 raise PyKotaCommandLineError, "argument '%s' is invalid !" % arg 265 265 labels.append(self.sanitizeMessage(label)) … … 267 267 varnames.append(varname) 268 268 varvalues[varname] = self.sanitizeMessage(varvalue) 269 270 passnumber = 1 269 270 passnumber = 1 271 271 authok = None 272 272 while (authok != "AUTH=YES") and (passnumber <= denyafter) : 273 result = server.askDatas(labels, varnames, varvalues) 273 result = server.askDatas(labels, varnames, varvalues) 274 274 if not options["checkauth"] : 275 275 break 276 276 if result["isValid"] : 277 277 if ("username" in varnames) and ("password" in varnames) : 278 if self.checkAuth(self.UTF8ToUserCharset(result["username"].data[:]), 278 if self.checkAuth(self.UTF8ToUserCharset(result["username"].data[:]), 279 279 self.UTF8ToUserCharset(result["password"].data[:])) : 280 280 authok = "AUTH=YES" 281 else : 281 else : 282 282 authok = "AUTH=NO" 283 else : 284 authok = "AUTH=IMPOSSIBLE" 285 passnumber += 1 286 283 else : 284 authok = "AUTH=IMPOSSIBLE" 285 passnumber += 1 286 287 287 if options["checkauth"] and options["denyafter"] \ 288 288 and (passnumber > denyafter) \ … … 294 294 and ((varname != "username") or (authok in (None, "AUTH=YES"))) : 295 295 print "%s=%s" % (varname.upper(), self.UTF8ToUserCharset(result[varname].data[:])) 296 if authok is not None : 297 print authok 296 if authok is not None : 297 print authok 298 298 elif options["confirm"] : 299 299 print server.showDialog(self.sanitizeMessage(arguments[0]), True) 300 300 elif options["notify"] : 301 301 print server.showDialog(self.sanitizeMessage(arguments[0]), False) 302 303 if options["quit"] : 302 303 if options["quit"] : 304 304 server.quitApplication() 305 305 except (xmlrpclib.ProtocolError, socket.error, socket.gaierror), msg : … … 309 309 #except (AttributeError, IndexError) : 310 310 # pass 311 #else : 311 #else : 312 312 # if errnum == errno.ECONNREFUSED : 313 313 # raise PyKotaToolError, "%s : %s" % (str(msg), (_("Are you sure that PyKotIcon is running and accepting incoming connections on %s:%s ?") % (self.destination, self.port))) 314 314 self.printInfo("%s : %s" % (_("Connection error"), str(msg)), "warn") 315 except TimeoutError, msg : 315 except TimeoutError, msg : 316 316 self.printInfo(msg, "warn") 317 317 print "CANCEL" # Timeout occured : job is cancelled. 318 finally : 319 if self.timeout : 320 signal.alarm(0) 321 318 finally : 319 if self.timeout : 320 signal.alarm(0) 321 322 322 if __name__ == "__main__" : 323 323 retcode = 0 … … 331 331 "timeout=", "ask", "checkauth", "confirm", "notify", \ 332 332 "quit", "noremote=" ] 333 333 334 334 # Initializes the command line tool 335 335 notifier = PyKotaNotify(doc=__doc__) 336 336 notifier.deferredInit() 337 337 338 338 # parse and checks the command line 339 339 (options, args) = notifier.parseCommandline(sys.argv[1:], short_options, long_options) 340 340 341 341 # sets long options 342 342 options["help"] = options["h"] or options["help"] … … 351 351 options["timeout"] = options["t"] or options["timeout"] or defaults["timeout"] 352 352 options["noremote"] = (options["N"] or options["noremote"] or defaults["noremote"]).upper() 353 353 354 354 if options["help"] : 355 355 notifier.display_usage_and_quit() … … 370 370 else : 371 371 retcode = notifier.main(args, options) 372 except KeyboardInterrupt : 372 except KeyboardInterrupt : 373 373 logerr("\nInterrupted with Ctrl+C !\n") 374 374 retcode = -3 375 except PyKotaCommandLineError, msg : 375 except PyKotaCommandLineError, msg : 376 376 logerr("%s : %s\n" % (sys.argv[0], msg)) 377 377 print "CANCEL" # Forces the cancellation of the print job if a command line switch is incorrect 378 378 retcode = -2 379 except SystemExit : 379 except SystemExit : 380 380 pass 381 381 except : 382 382 try : 383 383 notifier.crashed("%s failed" % sys.argv[0]) 384 except : 384 except : 385 385 crashed("%s failed" % sys.argv[0]) 386 386 retcode = -1 387 388 sys.exit(retcode) 387 388 sys.exit(retcode) -
pykota/trunk/bin/pkprinters
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 47 47 -v | --version Prints pkprinters's version number then exits. 48 48 -h | --help Prints this message then exits. 49 50 -a | --add Adds printers if they don't exist on the Quota 49 50 -a | --add Adds printers if they don't exist on the Quota 51 51 Storage Server. If they exist, they are modified 52 52 unless -s|--skipexisting is also used. 53 53 54 54 -d | --delete Deletes printers from the quota storage. 55 55 56 56 -D | --description d Adds a textual description to printers. 57 57 … … 62 62 If both are to be set, separate them with a comma. 63 63 Floating point and negative values are allowed. 64 65 -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer 64 65 -g | --groups pg1[,pg2...] Adds or Remove the printer(s) to the printer 66 66 groups pg1, pg2, etc... which must already exist. 67 67 A printer group is just like a normal printer, 68 68 only that it is usually unknown from the printing 69 69 system. Create printer groups exactly the same 70 way that you create printers, then add other 70 way that you create printers, then add other 71 71 printers to them with this option. 72 72 Accounting is done on a printer and on all … … 74 74 is done on a printer and on all the printer groups 75 75 it belongs to. 76 If the --remove option below is not used, the 76 If the --remove option below is not used, the 77 77 default action is to add printers to the specified 78 78 printer groups. 79 79 80 80 -l | --list List informations about the printer(s) and the 81 81 printers groups it is a member of. 82 83 -r | --remove In combination with the --groups option above, 82 83 -r | --remove In combination with the --groups option above, 84 84 remove printers from the specified printers groups. 85 85 86 86 -s | --skipexisting In combination with the --add option above, tells 87 87 pkprinters to not modify existing printers. 88 88 89 89 -m | --maxjobsize s Sets the maximum job size allowed on the printer 90 90 to s pages. 91 91 92 92 -p | --passthrough Activate passthrough mode for the printer. In this 93 93 mode, users are allowed to print without any impact 94 94 on their quota or account balance. 95 95 96 96 -n | --nopassthrough Deactivate passthrough mode for the printer. 97 Without -p or -n, printers are created in 97 Without -p or -n, printers are created in 98 98 normal mode, i.e. no passthrough. 99 100 printer1 through printerN can contain wildcards if the --add option 99 100 printer1 through printerN can contain wildcards if the --add option 101 101 is not set. 102 103 examples : 102 103 examples : 104 104 105 105 $ pkprinters --add -D "HP Printer" --charge 0.05,0.1 hp2100 hp2200 hp8000 106 106 107 107 Will create three printers named hp2100, hp2200 and hp8000. 108 108 Their price per page will be set at 0.05 unit, and their price … … 110 110 or whatever you want them to mean. 111 111 All of their descriptions will be set to the string "HP Printer". 112 If any of these printers already exists, it will also be modified 112 If any of these printers already exists, it will also be modified 113 113 unless the -s|--skipexisting command line option is also used. 114 114 115 115 $ pkprinters --delete "*" 116 116 117 117 This will completely delete all printers and associated quota information, 118 118 as well as their job history. USE WITH CARE ! 119 119 120 120 $ pkprinters --groups Laser,HP "hp*" 121 122 This will put all printers which name matches "hp*" into printers groups 121 122 This will put all printers which name matches "hp*" into printers groups 123 123 Laser and HP, which MUST already exist. 124 124 125 125 $ pkprinters --groups LexMark --remove hp2200 126 126 127 127 This will remove the hp2200 printer from the LexMark printer group. 128 128 """) 129 130 class PKPrinters(PyKotaTool) : 129 130 class PKPrinters(PyKotaTool) : 131 131 """A class for a printers manager.""" 132 132 def modifyPrinter(self, printer, charges, perpage, perjob, description, passthrough, nopassthrough, maxjobsize) : 133 133 if charges : 134 printer.setPrices(perpage, perjob) 134 printer.setPrices(perpage, perjob) 135 135 if description is not None : # NB : "" is allowed ! 136 136 printer.setDescription(description) 137 if nopassthrough : 137 if nopassthrough : 138 138 printer.setPassThrough(False) 139 if passthrough : 139 if passthrough : 140 140 printer.setPassThrough(True) 141 141 if maxjobsize is not None : 142 142 printer.setMaxJobSize(maxjobsize) 143 144 def managePrintersGroups(self, pgroups, printer, remove) : 143 144 def managePrintersGroups(self, pgroups, printer, remove) : 145 145 """Manage printer group membership.""" 146 146 for pgroup in pgroups : … … 148 148 pgroup.delPrinterFromGroup(printer) 149 149 else : 150 pgroup.addPrinterToGroup(printer) 151 152 def getPrinterDeviceURI(self, printername) : 150 pgroup.addPrinterToGroup(printer) 151 152 def getPrinterDeviceURI(self, printername) : 153 153 """Returns the Device URI attribute for a particular printer.""" 154 154 if not printername : … … 159 159 try : 160 160 return cups.doRequest(req).printer["device-uri"][0][1] 161 except : 161 except : 162 162 self.printInfo(_("Impossible to retrieve %(printername)s's DeviceURI") % locals(), "warn") 163 163 return "" 164 164 165 165 def isPrinterCaptured(self, printername=None, deviceuri=None) : 166 166 """Returns True if the printer is already redirected through PyKota's backend, else False.""" 167 167 if (deviceuri or self.getPrinterDeviceURI(printername)).find("cupspykota:") != -1 : 168 168 return True 169 else : 169 else : 170 170 return False 171 172 def reroutePrinterThroughPyKota(self, printer) : 171 172 def reroutePrinterThroughPyKota(self, printer) : 173 173 """Reroutes a CUPS printer through PyKota.""" 174 174 uri = self.getPrinterDeviceURI(printer.Name) … … 177 177 os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri)) 178 178 self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri)) 179 180 def deroutePrinterFromPyKota(self, printer) : 179 180 def deroutePrinterFromPyKota(self, printer) : 181 181 """Deroutes a PyKota printer through CUPS only.""" 182 182 uri = self.getPrinterDeviceURI(printer.Name) … … 187 187 os.system('lpadmin -p "%s" -v "%s"' % (printer.Name, newuri)) 188 188 self.logdebug("Printer %s rerouted to %s" % (printer.Name, newuri)) 189 189 190 190 def main(self, names, options) : 191 191 """Manage printers.""" 192 192 if not options["list"] : 193 193 self.adminOnly() 194 194 195 195 docups = options["cups"] 196 197 if not options["list"] : 196 197 if not options["list"] : 198 198 percent = Percent(self) 199 199 200 200 if not options["add"] : 201 201 if not options["list"] : … … 208 208 percent.display("\n") 209 209 raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names) 210 if not options["list"] : 210 if not options["list"] : 211 211 percent.setSize(len(printers)) 212 212 213 213 if options["list"] : 214 214 for printer in printers : … … 220 220 print " %s" % (_("Maximum job size : %s") % ((printer.MaxJobSize and (_("%s pages") % printer.MaxJobSize)) or _("Unlimited"))) 221 221 print " %s" % (_("Routed through PyKota : %s") % ((self.isPrinterCaptured(printer.Name) and _("YES")) or _("NO"))) 222 if parents : 222 if parents : 223 223 print " %s %s" % (_("in"), parents) 224 print 225 elif options["delete"] : 224 print 225 elif options["delete"] : 226 226 percent.display("\n%s..." % _("Deletion")) 227 227 self.storage.deleteManyPrinters(printers) … … 233 233 percent.oneMore() 234 234 else : 235 if options["groups"] : 235 if options["groups"] : 236 236 printersgroups = self.storage.getMatchingPrinters(options["groups"]) 237 237 if not printersgroups : 238 238 raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(',')) 239 else : 239 else : 240 240 printersgroups = [] 241 241 242 242 if options["charge"] : 243 243 try : 244 244 charges = [float(part) for part in options["charge"].split(',', 1)] 245 except ValueError : 245 except ValueError : 246 246 raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"] 247 else : 247 else : 248 248 if len(charges) > 2 : 249 249 charges = charges[:2] … … 251 251 charges = [charges[0], None] 252 252 (perpage, perjob) = charges 253 else : 253 else : 254 254 charges = perpage = perjob = None 255 256 if options["maxjobsize"] : 255 256 if options["maxjobsize"] : 257 257 try : 258 258 maxjobsize = int(options["maxjobsize"]) 259 259 if maxjobsize < 0 : 260 260 raise ValueError 261 except ValueError : 261 except ValueError : 262 262 raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"] 263 else : 263 else : 264 264 maxjobsize = None 265 265 266 266 description = options["description"] 267 267 if description : 268 268 description = description.strip() 269 270 nopassthrough = options["nopassthrough"] 269 270 nopassthrough = options["nopassthrough"] 271 271 passthrough = options["passthrough"] 272 272 remove = options["remove"] … … 274 274 self.storage.beginTransaction() 275 275 try : 276 if options["add"] : 276 if options["add"] : 277 277 percent.display("%s...\n" % _("Creation")) 278 278 percent.setSize(len(names)) … … 283 283 description, passthrough, \ 284 284 nopassthrough, maxjobsize) 285 oldprinter = self.storage.addPrinter(printer) 286 285 oldprinter = self.storage.addPrinter(printer) 286 287 287 if docups : 288 288 self.reroutePrinterThroughPyKota(printer) 289 289 290 290 if oldprinter is not None : 291 291 if skipexisting : 292 292 self.logdebug(_("Printer %s already exists, skipping.") % pname) 293 else : 293 else : 294 294 self.logdebug(_("Printer %s already exists, will be modified.") % pname) 295 295 self.modifyPrinter(oldprinter, charges, \ … … 297 297 passthrough, nopassthrough, \ 298 298 maxjobsize) 299 oldprinter.save() 299 oldprinter.save() 300 300 self.managePrintersGroups(printersgroups, oldprinter, remove) 301 elif printersgroups : 301 elif printersgroups : 302 302 self.managePrintersGroups(printersgroups, \ 303 303 self.storage.getPrinter(pname), \ 304 304 remove) 305 else : 305 else : 306 306 raise PyKotaCommandLineError, _("Invalid printer name %s") % pname 307 307 percent.oneMore() 308 else : 308 else : 309 309 percent.display("\n%s...\n" % _("Modification")) 310 for printer in printers : 310 for printer in printers : 311 311 self.modifyPrinter(printer, charges, perpage, perjob, \ 312 312 description, passthrough, \ 313 313 nopassthrough, maxjobsize) 314 printer.save() 314 printer.save() 315 315 self.managePrintersGroups(printersgroups, printer, remove) 316 316 if docups : 317 317 self.reroutePrinterThroughPyKota(printer) 318 318 percent.oneMore() 319 except : 319 except : 320 320 self.storage.rollbackTransaction() 321 321 raise 322 else : 322 else : 323 323 self.storage.commitTransaction() 324 324 325 325 if not options["list"] : 326 326 percent.done() 327 328 if __name__ == "__main__" : 327 328 if __name__ == "__main__" : 329 329 retcode = 0 330 330 try : … … 334 334 "skipexisting", "passthrough", "nopassthrough", \ 335 335 "maxjobsize="] 336 336 337 337 # Initializes the command line tool 338 338 manager = PKPrinters(doc=__doc__) 339 339 manager.deferredInit() 340 340 341 341 # parse and checks the command line 342 342 (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options) 343 343 344 344 # sets long options 345 345 options["help"] = options["h"] or options["help"] … … 349 349 options["charge"] = options["c"] or options["charge"] 350 350 options["description"] = options["D"] or options["description"] 351 options["delete"] = options["d"] or options["delete"] 351 options["delete"] = options["d"] or options["delete"] 352 352 options["groups"] = options["g"] or options["groups"] 353 353 options["list"] = options["l"] or options["list"] … … 357 357 options["passthrough"] = options["p"] or options["passthrough"] 358 358 options["nopassthrough"] = options["n"] or options["nopassthrough"] 359 359 360 360 if options["help"] : 361 361 manager.display_usage_and_quit() … … 374 374 else : 375 375 retcode = manager.main(args, options) 376 except KeyboardInterrupt : 376 except KeyboardInterrupt : 377 377 logerr("\nInterrupted with Ctrl+C !\n") 378 378 retcode = -3 379 except PyKotaCommandLineError, msg : 379 except PyKotaCommandLineError, msg : 380 380 logerr("%s : %s\n" % (sys.argv[0], msg)) 381 381 retcode = -2 382 except SystemExit : 382 except SystemExit : 383 383 pass 384 384 except : 385 385 try : 386 386 manager.crashed("pkprinters failed") 387 except : 387 except : 388 388 crashed("pkprinters failed") 389 389 retcode = -1 … … 391 391 try : 392 392 manager.storage.close() 393 except (TypeError, NameError, AttributeError) : 393 except (TypeError, NameError, AttributeError) : 394 394 pass 395 396 sys.exit(retcode) 395 396 sys.exit(retcode) -
pykota/trunk/bin/pkrefund
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 34 34 from reportlab.lib import pagesizes 35 35 from reportlab.lib.units import cm 36 except ImportError : 36 except ImportError : 37 37 hasRL = False 38 else : 38 else : 39 39 hasRL = True 40 40 41 41 try : 42 import PIL.Image 43 except ImportError : 42 import PIL.Image 43 except ImportError : 44 44 hasPIL = False 45 else : 45 else : 46 46 hasPIL = True 47 47 … … 54 54 from pykota.errors import PyKotaToolError, PyKotaCommandLineError 55 55 from pykota.tool import Percent, PyKotaTool 56 57 class PKRefund(PyKotaTool) : 56 57 class PKRefund(PyKotaTool) : 58 58 """A class for refund manager.""" 59 59 validfilterkeys = [ "username", … … 65 65 "end", 66 66 ] 67 67 68 68 def printVar(self, label, value, size) : 69 69 """Outputs a variable onto the PDF canvas. 70 70 71 71 Returns the number of points to substract to current Y coordinate. 72 """ 72 """ 73 73 xcenter = (self.pagesize[0] / 2.0) - 1*cm 74 74 self.canvas.saveState() … … 81 81 self.canvas.restoreState() 82 82 self.ypos -= (size + 4) 83 83 84 84 def pagePDF(self, receiptnumber, name, values, unit, reason) : 85 85 """Generates a new page in the PDF document.""" … … 92 92 datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace") 93 93 self.printVar(_("Edited on"), datetime, 14) 94 94 95 95 self.ypos -= 20 96 96 self.printVar(_("Jobs refunded"), str(values["nbjobs"]), 18) … … 101 101 self.canvas.showPage() 102 102 return 1 103 return 0 104 103 return 0 104 105 105 def initPDF(self, logo) : 106 106 """Initializes the PDF document.""" 107 self.pdfDocument = cStringIO.StringIO() 107 self.pdfDocument = cStringIO.StringIO() 108 108 self.canvas = c = canvas.Canvas(self.pdfDocument, \ 109 109 pagesize=self.pagesize, \ 110 110 pageCompression=1) 111 111 112 112 c.setAuthor(self.effectiveUserName) 113 113 c.setTitle(_("PyKota print job refunding receipts")) 114 114 c.setSubject(_("Print job refunding receipts generated with PyKota")) 115 115 116 116 self.canvas.beginForm("background") 117 117 self.canvas.saveState() 118 119 self.ypos = self.pagesize[1] - (2 * cm) 120 118 119 self.ypos = self.pagesize[1] - (2 * cm) 120 121 121 xcenter = self.pagesize[0] / 2.0 122 122 if logo : 123 try : 123 try : 124 124 imglogo = PIL.Image.open(logo) 125 except IOError : 125 except IOError : 126 126 self.printInfo("Unable to open image %s" % logo, "warn") 127 127 else : 128 128 (width, height) = imglogo.size 129 multi = float(width) / (8 * cm) 129 multi = float(width) / (8 * cm) 130 130 width = float(width) / multi 131 131 height = float(height) / multi … … 134 134 self.ypos, \ 135 135 width, height) 136 136 137 137 self.ypos -= (cm + 20) 138 138 self.canvas.setFont("Helvetica-Bold", 14) … … 140 140 msg = _("Here's the receipt for the refunding of your print jobs") 141 141 self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg) 142 142 143 143 self.yorigine = self.ypos 144 144 self.canvas.restoreState() 145 145 self.canvas.endForm() 146 147 def endPDF(self, fname) : 146 147 def endPDF(self, fname) : 148 148 """Flushes the PDF generator.""" 149 149 self.canvas.save() 150 if fname != "-" : 150 if fname != "-" : 151 151 outfile = open(fname, "w") 152 152 outfile.write(self.pdfDocument.getvalue()) 153 153 outfile.close() 154 else : 154 else : 155 155 sys.stdout.write(self.pdfDocument.getvalue()) 156 156 sys.stdout.flush() 157 157 158 158 def genReceipts(self, peruser, logo, outfname, firstnumber, reason, unit) : 159 159 """Generates the receipts file.""" … … 162 162 if outfname != "-" : 163 163 percent.display("%s...\n" % _("Generating receipts")) 164 164 165 165 self.initPDF(logo) 166 166 number = firstnumber … … 169 169 if outfname != "-" : 170 170 percent.oneMore() 171 171 172 172 if number > firstnumber : 173 173 self.endPDF(outfname) 174 174 175 175 if outfname != "-" : 176 176 percent.done() 177 177 178 178 def main(self, arguments, options) : 179 179 """Refunds jobs.""" … … 182 182 if not hasPIL : 183 183 raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads" 184 185 self.adminOnly() 186 184 185 self.adminOnly() 186 187 187 self.pagesize = getPageSize(options.pagesize) 188 188 189 189 if (not options.reason) or (not options.reason.strip()) : 190 190 raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.") 191 191 192 192 extractonly = {} 193 193 for filterexp in arguments : … … 197 197 filterkey = filterkey.lower() 198 198 if filterkey not in self.validfilterkeys : 199 raise ValueError 200 except ValueError : 199 raise ValueError 200 except ValueError : 201 201 raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp 202 else : 202 else : 203 203 extractonly.update({ filterkey : filtervalue }) 204 204 205 205 percent = Percent(self) 206 206 outfname = options.output.strip().encode(sys.getfilesystemencoding()) 207 207 if outfname != "-" : 208 208 percent.display("%s..." % _("Extracting datas")) 209 else : 209 else : 210 210 options.force = True 211 211 self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn") 212 213 username = extractonly.get("username") 212 213 username = extractonly.get("username") 214 214 if username : 215 215 user = self.storage.getUser(username) 216 216 else : 217 217 user = None 218 219 printername = extractonly.get("printername") 218 219 printername = extractonly.get("printername") 220 220 if printername : 221 221 printer = self.storage.getPrinter(printername) 222 else : 222 else : 223 223 printer = None 224 224 225 225 start = extractonly.get("start") 226 226 end = extractonly.get("end") 227 227 (start, end) = self.storage.cleanDates(start, end) 228 229 jobs = self.storage.retrieveHistory(user=user, 230 printer=printer, 228 229 jobs = self.storage.retrieveHistory(user=user, 230 printer=printer, 231 231 hostname=extractonly.get("hostname"), 232 232 billingcode=extractonly.get("billingcode"), … … 235 235 end=end, 236 236 limit=0) 237 238 peruser = {} 239 nbjobs = 0 240 nbpages = 0 237 238 peruser = {} 239 nbjobs = 0 240 nbpages = 0 241 241 nbcredits = 0.0 242 242 percent.setSize(len(jobs)) 243 243 if outfname != "-" : 244 244 percent.display("\n") 245 for job in jobs : 245 for job in jobs : 246 246 if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) : 247 247 if options.force : … … 256 256 if outfname != "-" : 257 257 percent.oneMore() 258 else : 258 else : 259 259 print _("Date : %s") % str(job.JobDate)[:19] 260 260 print _("Printer : %s") % job.PrinterName … … 266 266 print _("Pages : %i") % job.JobSize 267 267 print _("Credits : %.3f") % job.JobPrice 268 269 while True : 268 269 while True : 270 270 answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper() 271 271 if answer == _("Y") : … … 278 278 counters["nbjobs"] += 1 279 279 nbjobs += 1 280 break281 elif answer == _("N") :282 280 break 283 print 281 elif answer == _("N") : 282 break 283 print 284 284 if outfname != "-" : 285 285 percent.done() 286 self.genReceipts(peruser, 287 options.logo.strip().encode(sys.getfilesystemencoding()), 288 outfname, 289 options.number, 290 options.reason, 286 self.genReceipts(peruser, 287 options.logo.strip().encode(sys.getfilesystemencoding()), 288 outfname, 289 options.number, 290 options.reason, 291 291 options.unit or _("Credits")) 292 if outfname != "-" : 292 if outfname != "-" : 293 293 nbusers = len(peruser) 294 294 print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \ 295 295 % locals() 296 297 if __name__ == "__main__" : 296 297 if __name__ == "__main__" : 298 298 parser = PyKotaOptionParser(description=_("Refunding tool for PyKota."), 299 299 usage="pkrefund [options] [filterexpr]") … … 313 313 default=u"A4", 314 314 help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default.")) 315 parser.add_option("-n", "--number", 315 parser.add_option("-n", "--number", 316 316 dest="number", 317 317 type="int", … … 329 329 type="string", 330 330 help=_("The reason why there was a refund.")) 331 331 332 332 # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861 333 333 # TODO : we can't use 'default=_("Credits")' for this option 334 parser.add_option("-u", "--unit", 334 parser.add_option("-u", "--unit", 335 335 dest="unit", 336 336 type="string", 337 337 help=_("The name of the unit to use on the receipts. The default value is 'Credits' or its locale translation.")) 338 338 339 339 parser.add_filterexpression("username", _("User's name")) 340 340 parser.add_filterexpression("printername", _("Printer's name")) … … 344 344 parser.add_filterexpression("start", _("Job's date of printing")) 345 345 parser.add_filterexpression("end", _("Job's date of printing")) 346 346 347 347 parser.add_example('--output /tmp/receipts.pdf jobid=503', 348 348 _("This would refund all jobs which Id is 503. A confirmation would be asked for each job to refund, and a PDF file named /tmp/receipts.pdf would be created containing printable receipts. BEWARE of job ids rolling over if you reset CUPS' history.")) 349 349 350 350 parser.add_example('--reason "Hardware problem" jobid=503 start=today-7', 351 351 _("This would refund all jobs which id is 503 but which would have been printed during the past week. The reason would be marked as being an hardware problem.")) 352 352 353 353 parser.add_example('--force username=jerome printername=HP2100', 354 354 _("This would refund all jobs printed by user jerome on printer HP2100. No confirmation would be asked.")) 355 355 356 356 parser.add_example('--force printername=HP2100 start=200602 end=yesterday', 357 357 _("This would refund all jobs printed on printer HP2100 between February 1st 2006 and yesterday. No confirmation would be asked.")) 358 358 359 359 (options, arguments) = parser.parse_args() 360 run(parser, PKRefund) 360 run(parser, PKRefund) -
pykota/trunk/bin/pksetup
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 60 60 # IMPORTANT : many more directives can be used, and some of the directives 61 61 # below accept different and/or more complex parameters. Please read 62 # /usr/share/pykota/conf/pykota.conf.sample for more details about the 62 # /usr/share/pykota/conf/pykota.conf.sample for more details about the 63 63 # numerous possibilities allowed. 64 64 # … … 122 122 onaccountererror : stop 123 123 124 # Who will receive warning messages ? 124 # Who will receive warning messages ? 125 125 # both means admin and user. 126 126 mailto : both … … 136 136 poorman : 1.0 137 137 138 # Warning messages to use 139 poorwarn : Your Print Quota account balance is low. 138 # Warning messages to use 139 poorwarn : Your Print Quota account balance is low. 140 140 Soon you'll not be allowed to print anymore. 141 141 142 142 softwarn : Your Print Quota Soft Limit is reached. 143 143 This means that you may still be allowed to print for some 144 time, but you must contact your administrator to purchase 144 time, but you must contact your administrator to purchase 145 145 more print quota. 146 146 … … 150 150 as soon as possible to solve the problem. 151 151 152 # Number of banners allowed to be printed by users 152 # Number of banners allowed to be printed by users 153 153 # who are over quota 154 154 maxdenybanners : 0 … … 181 181 cupsrestart = "/etc/init.d/cupsys restart" # overload it if needed 182 182 adduser = "adduser --system --group --home /etc/pykota --gecos PyKota pykota" # overload it if needed 183 packages = [ "wget", 184 "bzip2", 185 "subversion", 186 "postgresql", 183 packages = [ "wget", 184 "bzip2", 185 "subversion", 186 "postgresql", 187 187 "postgresql-client", 188 188 "cupsys", … … 200 200 "python-pam", 201 201 "pkpgcounter" ] 202 202 203 203 otherpackages = [ 204 204 { "name" : "pkipplib", … … 219 219 ], 220 220 }, 221 ] 222 223 def __init__(self) : 221 ] 222 223 def __init__(self) : 224 224 """Initializes instance specific datas.""" 225 225 self.launched = [] 226 227 def yesno(self, message) : 226 227 def yesno(self, message) : 228 228 """Asks the end user some question and returns the answer.""" 229 229 try : 230 230 return raw_input("\n%s ? " % message).strip().upper()[0] == 'Y' 231 except IndexError : 231 except IndexError : 232 232 return False 233 234 def confirmCommand(self, message, command, record=True) : 233 234 def confirmCommand(self, message, command, record=True) : 235 235 """Asks for confirmation before a command is launched, and launches it if needed.""" 236 236 if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) : … … 241 241 else : 242 242 return False 243 244 def confirmPipe(self, message, command) : 243 244 def confirmPipe(self, message, command) : 245 245 """Asks for confirmation before a command is launched in a pipe, launches it if needed, and returns the result.""" 246 246 if self.yesno("The following command will be launched %(message)s :\n%(command)s\nDo you agree" % locals()) : … … 251 251 else : 252 252 return False 253 253 254 254 def listPrinters(self) : 255 255 """Returns a list of tuples (queuename, deviceuri) for all existing print queues.""" … … 263 263 queuename = begin.split()[-1] 264 264 printers.append((queuename, deviceuri)) 265 return printers 266 267 def downloadOtherPackages(self) : 265 return printers 266 267 def downloadOtherPackages(self) : 268 268 """Downloads and install additional packages from http://www.pykota.com or other websites""" 269 269 olddirectory = os.getcwd() … … 278 278 if url.startswith("svn://") : 279 279 download = 'svn export "%(url)s" %(name)s' % locals() 280 else : 280 else : 281 281 download = 'wget --user-agent=pksetup "%(url)s"' % locals() 282 282 if self.confirmCommand("to download %(name)s" % locals(), download) : 283 283 self.confirmCommand("to install %(name)s" % locals(), commands) 284 self.confirmCommand("to remove the temporary directory %(directory)s" % locals(), 284 self.confirmCommand("to remove the temporary directory %(directory)s" % locals(), 285 285 "rm -fr %(directory)s" % locals(), 286 286 record=False) 287 os.chdir(olddirectory) 288 287 os.chdir(olddirectory) 288 289 289 def waitPrintersOnline(self) : 290 290 """Asks the admin to switch all printers ON.""" 291 291 while not self.yesno("First you MUST switch ALL your printers ON. Are ALL your printers ON") : 292 292 pass 293 293 294 294 def setupDatabase(self) : 295 295 """Creates the database.""" 296 296 pykotadirectory = self.pykotadirectory 297 297 self.confirmCommand("to create PyKota's database in PostgreSQL", 'su - postgres -c "psql -f %(pykotadirectory)s/postgresql/pykota-postgresql.sql template1"' % locals()) 298 298 299 299 def configurePostgreSQL(self) : 300 300 """Configures PostgreSQL for PyKota to work.""" … … 314 314 if tcpip is False : 315 315 tcpip = answer.startswith("listen_addresses") 316 else : 316 else : 317 317 tcpip = False 318 if tcpip : 318 if tcpip : 319 319 conflines.insert(2, "host\tpykota\tpykotaadmin,pykotauser\t127.0.0.1\t255.255.255.255\tmd5") 320 320 else : … … 333 333 return (tcpip, port) 334 334 return (None, None) 335 335 336 336 def genConfig(self, adminname, adminemail, dnsdomain, smtpserver, home, tcpip, port) : 337 337 """Generates minimal configuration files for PyKota.""" 338 338 if tcpip : 339 339 storageserver = "localhost:%i" % port 340 else : 340 else : 341 341 storageserver = "" 342 342 conf = pykotaconf % locals() … … 356 356 if begin is None : 357 357 begin = i 358 else : 358 else : 359 359 end = i 360 360 361 361 if (begin is not None) and (end is not None) : 362 362 suffix = "\n".join(lines[begin+1:end]) 363 363 self.confirmCommand("to improve PyKota's configuration wrt your existing printers", 'echo "%(suffix)s" >>%(home)s/pykota.conf' % locals()) 364 364 365 365 def addPyKotaUser(self) : 366 366 """Adds a system user named pykota, returns its home directory or None""" 367 367 try : 368 368 user = pwd.getpwnam("pykota") 369 except KeyError : 369 except KeyError : 370 370 if self.confirmCommand("to create a system user named 'pykota'", self.adduser) : 371 try : 371 try : 372 372 return pwd.getpwnam("pykota")[5] 373 except KeyError : 373 except KeyError : 374 374 return None 375 else : 375 else : 376 376 return None 377 else : 377 else : 378 378 return user[5] 379 379 380 380 def setupBackend(self) : 381 381 """Installs the cupspykota backend.""" … … 385 385 self.confirmCommand("to make PyKota known to CUPS", "ln -s %(realbackend)s %(backend)s" % locals()) 386 386 self.confirmCommand("to restart CUPS for the changes to take effect", self.cupsrestart) 387 388 def managePrinters(self, printers) : 387 388 def managePrinters(self, printers) : 389 389 """For each printer, asks if it should be managed with PyKota or not.""" 390 390 for (queuename, deviceuri) in printers : 391 391 command = 'pkprinters --add --cups --description "Printer created with pksetup" "%(queuename)s"' % locals() 392 392 self.confirmCommand("to import the %(queuename)s print queue into PyKota's database and reroute it through PyKota" % locals(), command) 393 393 394 394 def installPyKotaFiles(self) : 395 395 """Installs PyKota files through Python's Distutils mechanism.""" … … 399 399 if os.path.exists(setuppy) : 400 400 self.confirmCommand("to install PyKota files on your system", "python %(setuppy)s install" % locals()) 401 401 402 402 def setup(self) : 403 403 """Installation procedure.""" … … 411 411 if homedirectory is None : 412 412 logerr("Installation can't proceed. You MUST create a system user named 'pykota'.\n") 413 else : 413 else : 414 414 self.upgradeSystem() 415 415 self.setupPackages() … … 422 422 print nowready 423 423 print "The script %s can be used to reinstall in unattended mode.\n" % self.genInstaller() 424 425 def genInstaller(self) : 424 425 def genInstaller(self) : 426 426 """Generates an installer script.""" 427 427 scriptname = "/tmp/pykota-installer.sh" 428 commands = [ "#! /bin/sh", 428 commands = [ "#! /bin/sh", 429 429 "#", 430 "# PyKota installer script.", 431 "#", 430 "# PyKota installer script.", 431 "#", 432 432 "# This script was automatically generated.", 433 433 "#", 434 434 ] + self.launched 435 script = open(scriptname, "w") 435 script = open(scriptname, "w") 436 436 script.write("\n".join(commands)) 437 437 script.close() 438 438 os.chmod(scriptname, \ 439 439 stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 440 return scriptname 441 442 443 class Debian(PyKotaSetup) : 440 return scriptname 441 442 443 class Debian(PyKotaSetup) : 444 444 """Class for Debian installer.""" 445 def setupPackages(self) : 445 def setupPackages(self) : 446 446 """Installs missing Debian packages.""" 447 447 self.confirmCommand("to install missing dependencies", "apt-get install %s" % " ".join(self.packages)) 448 448 449 449 def upgradeSystem(self) : 450 450 """Upgrades the Debian setup.""" 451 451 if self.confirmCommand("to grab an up-to-date list of available packages", "apt-get update") : 452 452 self.confirmCommand("to put your system up-to-date", "apt-get -y dist-upgrade") 453 454 class Ubuntu(Debian) : 453 454 class Ubuntu(Debian) : 455 455 """Class for Ubuntu installer.""" 456 456 pass 457 458 if __name__ == "__main__" : 457 458 if __name__ == "__main__" : 459 459 retcode = 0 460 460 if (len(sys.argv) != 2) or (sys.argv[1] == "-h") or (sys.argv[1] == "--help") : 461 461 print "pksetup v0.1 (c) 2003-2008 Jerome Alet - alet@librelogiciel.com\n\nusage : pksetup distribution\n\ne.g. : pksetup debian\n\nIMPORTANT : only Debian and Ubuntu are currently supported." 462 elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") : 462 elif (sys.argv[1] == "-v") or (sys.argv[1] == "--version") : 463 463 print "0.1" # pksetup's own version number 464 else : 464 else : 465 465 classname = sys.argv[1].strip().title() 466 466 try : 467 467 installer = globals()[classname]() 468 except KeyError : 468 except KeyError : 469 469 logerr("There's currently no support for the %s distribution, sorry.\n" % sys.argv[1]) 470 470 retcode = -1 471 else : 471 else : 472 472 try : 473 473 retcode = installer.setup() 474 except KeyboardInterrupt : 474 except KeyboardInterrupt : 475 475 logerr("\n\n\nWARNING : Setup was aborted at user's request !\n\n") 476 476 retcode = -1 -
pykota/trunk/bin/pkturnkey
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 51 51 -v | --version Prints pkturnkey version number then exits. 52 52 -h | --help Prints this message then exits. 53 53 54 54 -c | --doconf Give hints about what to put into pykota.conf 55 55 56 56 -d | --dousers Manages users accounts as well. 57 57 58 58 -D | --dogroups Manages users groups as well. 59 59 Implies -d | --dousers. 60 60 61 61 -e | --emptygroups Includes empty groups. 62 62 63 63 -f | --force Modifies the database instead of printing what 64 64 it would do. 65 65 66 66 -u | --uidmin uid Only adds users whose uid is greater than or equal to 67 67 uid. You can pass an username there as well, and its … … 69 69 If not set, 0 will be used automatically. 70 70 Implies -d | --dousers. 71 71 72 72 -U | --uidmax uid Only adds users whose uid is lesser than or equal to 73 73 uid. You can pass an username there as well, and its … … 81 81 If not set, 0 will be used automatically. 82 82 Implies -D | --dogroups. 83 83 84 84 -G | --gidmax gid Only adds groups whose gid is lesser than or equal to 85 85 gid. You can pass a groupname there as well, and its … … 88 88 Implies -D | --dogroups. 89 89 90 examples : 90 examples : 91 91 92 92 $ pkturnkey --dousers --uidmin jerome … … 95 95 printers and print accounts for all users whose uid is greater than 96 96 or equal to jerome's one. Won't manage any users group. 97 97 98 98 To REALLY initialize the database instead of simulating it, please 99 99 use the -f | --force command line switch. 100 100 101 101 You can limit the initialization to only a subset of the existing 102 102 printers, by passing their names at the end of the command line. 103 103 """) 104 104 105 105 class PKTurnKey(Tool) : 106 106 """A class for an initialization tool.""" … … 118 118 if self.matchString(queuename, namestomatch) : 119 119 printers.append((queuename, deviceuri)) 120 else : 120 else : 121 121 self.printInfo("Print queue %s skipped." % queuename) 122 return printers 123 124 def listUsers(self, uidmin, uidmax) : 122 return printers 123 124 def listUsers(self, uidmin, uidmax) : 125 125 """Returns a list of users whose uids are between uidmin and uidmax.""" 126 126 self.printInfo("Extracting all users whose uid is between %s and %s." % (uidmin, uidmax)) 127 127 return [(entry[0], entry[3]) for entry in pwd.getpwall() if uidmin <= entry[2] <= uidmax] 128 128 129 129 def listGroups(self, gidmin, gidmax, users) : 130 130 """Returns a list of groups whose gids are between gidmin and gidmax.""" … … 135 135 for u in users : 136 136 gidusers.setdefault(u[1], []).append(u[0]) 137 usersgid.setdefault(u[0], []).append(u[1]) 138 139 membership = {} 137 usersgid.setdefault(u[0], []).append(u[1]) 138 139 membership = {} 140 140 for g in range(len(groups)) : 141 141 (gname, gid, members) = groups[g] … … 145 145 try : 146 146 usernames = gidusers[gid] 147 except KeyError : 147 except KeyError : 148 148 pass 149 else : 149 else : 150 150 for username in usernames : 151 151 if not newmembers.has_key(username) : 152 152 newmembers[username] = username 153 for member in newmembers.keys() : 153 for member in newmembers.keys() : 154 154 if not usersgid.has_key(member) : 155 155 del newmembers[member] 156 156 membership[gname] = newmembers.keys() 157 157 return membership 158 159 def runCommand(self, command, dryrun) : 158 159 def runCommand(self, command, dryrun) : 160 160 """Launches an external command.""" 161 161 self.printInfo("%s" % command) 162 if not dryrun : 162 if not dryrun : 163 163 os.system(command) 164 165 def createPrinters(self, printers, dryrun=0) : 164 165 def createPrinters(self, printers, dryrun=0) : 166 166 """Creates all printers in PyKota's database.""" 167 167 if printers : … … 171 171 args.close() 172 172 self.runCommand("pkprinters --arguments /tmp/pkprinters.args", dryrun) 173 173 174 174 def createUsers(self, users, printers, dryrun=0) : 175 175 """Creates all users in PyKota's database.""" … … 180 180 args.close() 181 181 self.runCommand("pkusers --arguments /tmp/pkusers.users.args", dryrun) 182 182 183 183 printersnames = [p[0] for p in printers] 184 184 args = open("/tmp/edpykota.users.args", "w") … … 188 188 args.close() 189 189 self.runCommand("edpykota --arguments /tmp/edpykota.users.args", dryrun) 190 190 191 191 def createGroups(self, groups, printers, dryrun=0) : 192 192 """Creates all groups in PyKota's database.""" … … 197 197 args.close() 198 198 self.runCommand("pkusers --arguments /tmp/pkusers.groups.args", dryrun) 199 199 200 200 printersnames = [p[0] for p in printers] 201 201 args = open("/tmp/edpykota.groups.args", "w") … … 205 205 args.close() 206 206 self.runCommand("edpykota --arguments /tmp/edpykota.groups.args", dryrun) 207 207 208 208 revmembership = {} 209 209 for (groupname, usernames) in groups.items() : 210 210 for username in usernames : 211 211 revmembership.setdefault(username, []).append(groupname) 212 commands = [] 213 for (username, groupnames) in revmembership.items() : 212 commands = [] 213 for (username, groupnames) in revmembership.items() : 214 214 commands.append('pkusers --ingroups %s "%s"' \ 215 215 % (",".join(['"%s"' % g for g in groupnames]), username)) 216 216 for command in commands : 217 217 self.runCommand(command, dryrun) 218 218 219 219 def supportsSNMP(self, hostname, community) : 220 220 """Returns 1 if the printer accepts SNMP queries, else 0.""" … … 222 222 try : 223 223 from pysnmp.entity.rfc3413.oneliner import cmdgen 224 except ImportError : 224 except ImportError : 225 225 hasV4 = False 226 226 try : … … 228 228 from pysnmp.mapping.udp.role import Manager 229 229 from pysnmp.proto.api import alpha 230 except ImportError : 230 except ImportError : 231 231 logerr("pysnmp doesn't seem to be installed. SNMP checks will be ignored !\n") 232 232 return 0 233 else : 233 else : 234 234 hasV4 = True 235 236 if hasV4 : 235 236 if hasV4 : 237 237 def retrieveSNMPValues(hostname, community) : 238 238 """Retrieves a printer's internal page counter and status via SNMP.""" … … 241 241 cmdgen.UdpTransportTarget((hostname, 161)), \ 242 242 tuple([int(i) for i in pageCounterOID.split('.')])) 243 if errorIndication : 243 if errorIndication : 244 244 raise "No SNMP !" 245 elif errorStatus : 245 elif errorStatus : 246 246 raise "No SNMP !" 247 else : 247 else : 248 248 self.SNMPOK = True 249 249 else : 250 def retrieveSNMPValues(hostname, community) : 250 def retrieveSNMPValues(hostname, community) : 251 251 """Retrieves a printer's internal page counter and status via SNMP.""" 252 252 ver = alpha.protoVersions[alpha.protoVersionId1] … … 260 260 (hostname, 161), \ 261 261 (handleAnswer, req)) 262 except : 262 except : 263 263 raise "No SNMP !" 264 264 tsp.close() 265 265 266 266 def handleAnswer(wholemsg, notusedhere, req): 267 267 """Decodes and handles the SNMP answer.""" … … 270 270 try : 271 271 rsp.berDecode(wholemsg) 272 except TypeMismatchError, msg : 272 except TypeMismatchError, msg : 273 273 raise "No SNMP !" 274 274 else : … … 281 281 for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList(): 282 282 self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value) 283 try : 283 try : 284 284 pagecounter = self.values[0] 285 285 except : 286 286 raise "No SNMP !" 287 else : 287 else : 288 288 self.SNMPOK = 1 289 289 return 1 290 290 291 291 self.SNMPOK = 0 292 292 try : 293 293 retrieveSNMPValues(hostname, community) 294 except : 294 except : 295 295 self.SNMPOK = 0 296 296 return self.SNMPOK 297 297 298 298 def supportsPJL(self, hostname, port) : 299 299 """Returns 1 if the printer accepts PJL queries over TCP, else 0.""" 300 300 def alarmHandler(signum, frame) : 301 301 raise "Timeout !" 302 302 303 303 pjlsupport = 0 304 304 signal.signal(signal.SIGALRM, alarmHandler) … … 311 311 if not answer.startswith("@PJL") : 312 312 raise "No PJL !" 313 except : 313 except : 314 314 pass 315 else : 315 else : 316 316 pjlsupport = 1 317 317 s.close() … … 319 319 signal.signal(signal.SIGALRM, signal.SIG_IGN) 320 320 return pjlsupport 321 322 def hintConfig(self, printers) : 321 322 def hintConfig(self, printers) : 323 323 """Gives some hints about what to put into pykota.conf""" 324 324 if not printers : 325 325 return 326 sys.stderr.flush() # ensure outputs don't mix 327 print 326 sys.stderr.flush() # ensure outputs don't mix 327 print 328 328 print "--- CUT ---" 329 329 print "# Here are some lines that we suggest you add at the end" … … 338 338 try : 339 339 uri = uri.split("cupspykota:", 2)[-1] 340 except (ValueError, IndexError) : 340 except (ValueError, IndexError) : 341 341 pass 342 else : 342 else : 343 343 while uri and uri.startswith("/") : 344 344 uri = uri[1:] 345 345 try : 346 (backend, destination) = uri.split(":", 1) 346 (backend, destination) = uri.split(":", 1) 347 347 if backend not in ("ipp", "http", "https", "lpd", "socket") : 348 348 raise ValueError 349 except ValueError : 349 except ValueError : 350 350 pass 351 else : 351 else : 352 352 while destination.startswith("/") : 353 353 destination = destination[1:] 354 checkauth = destination.split("@", 1) 354 checkauth = destination.split("@", 1) 355 355 if len(checkauth) == 2 : 356 356 destination = checkauth[1] … … 362 362 except ValueError : 363 363 port = 9100 364 else : 364 else : 365 365 (hostname, port) = parts[0], 9100 366 366 367 367 if self.supportsSNMP(hostname, "public") : 368 368 accounter = "hardware(snmp)" … … 371 371 elif self.supportsPJL(hostname, 9101) : 372 372 accounter = "hardware(pjl:9101)" 373 elif self.supportsPJL(hostname, port) : 373 elif self.supportsPJL(hostname, port) : 374 374 accounter = "hardware(pjl:%s)" % port 375 376 print "preaccounter : software()" 375 376 print "preaccounter : software()" 377 377 print "accounter : %s" % accounter 378 378 print 379 379 print "--- CUT ---" 380 380 381 381 def main(self, names, options) : 382 382 """Intializes PyKota's database.""" 383 383 self.adminOnly() 384 384 385 385 if not names : 386 386 names = ["*"] 387 387 388 388 self.printInfo(_("Please be patient...")) 389 389 dryrun = not options["force"] 390 390 if dryrun : 391 391 self.printInfo(_("Don't worry, the database WILL NOT BE MODIFIED.")) 392 else : 392 else : 393 393 self.printInfo(_("Please WORRY NOW, the database WILL BE MODIFIED.")) 394 395 if options["dousers"] : 396 if not options["uidmin"] : 394 395 if options["dousers"] : 396 if not options["uidmin"] : 397 397 self.printInfo(_("System users will have a print account as well !"), "warn") 398 398 uidmin = 0 399 else : 399 else : 400 400 try : 401 401 uidmin = int(options["uidmin"]) 402 except : 402 except : 403 403 try : 404 404 uidmin = pwd.getpwnam(options["uidmin"])[2] 405 except KeyError, msg : 405 except KeyError, msg : 406 406 raise PyKotaCommandLineError, _("Unknown username %s : %s") \ 407 407 % (options["uidmin"], msg) 408 409 if not options["uidmax"] : 408 409 if not options["uidmax"] : 410 410 uidmax = sys.maxint 411 else : 411 else : 412 412 try : 413 413 uidmax = int(options["uidmax"]) 414 except : 414 except : 415 415 try : 416 416 uidmax = pwd.getpwnam(options["uidmax"])[2] 417 except KeyError, msg : 417 except KeyError, msg : 418 418 raise PyKotaCommandLineError, _("Unknown username %s : %s") \ 419 419 % (options["uidmax"], msg) 420 421 if uidmin > uidmax : 420 421 if uidmin > uidmax : 422 422 (uidmin, uidmax) = (uidmax, uidmin) 423 423 users = self.listUsers(uidmin, uidmax) 424 else : 424 else : 425 425 users = [] 426 427 if options["dogroups"] : 428 if not options["gidmin"] : 426 427 if options["dogroups"] : 428 if not options["gidmin"] : 429 429 self.printInfo(_("System groups will have a print account as well !"), "warn") 430 430 gidmin = 0 431 else : 431 else : 432 432 try : 433 433 gidmin = int(options["gidmin"]) 434 except : 434 except : 435 435 try : 436 436 gidmin = grp.getgrnam(options["gidmin"])[2] 437 except KeyError, msg : 437 except KeyError, msg : 438 438 raise PyKotaCommandLineError, _("Unknown groupname %s : %s") \ 439 439 % (options["gidmin"], msg) 440 441 if not options["gidmax"] : 440 441 if not options["gidmax"] : 442 442 gidmax = sys.maxint 443 else : 443 else : 444 444 try : 445 445 gidmax = int(options["gidmax"]) 446 except : 446 except : 447 447 try : 448 448 gidmax = grp.getgrnam(options["gidmax"])[2] 449 except KeyError, msg : 449 except KeyError, msg : 450 450 raise PyKotaCommandLineError, _("Unknown groupname %s : %s") \ 451 451 % (options["gidmax"], msg) 452 453 if gidmin > gidmax : 452 453 if gidmin > gidmax : 454 454 (gidmin, gidmax) = (gidmax, gidmin) 455 455 groups = self.listGroups(gidmin, gidmax, users) … … 458 458 if not members : 459 459 del groups[groupname] 460 else : 460 else : 461 461 groups = [] 462 462 463 463 printers = self.listPrinters(names) 464 464 if printers : … … 466 466 self.createUsers([entry[0] for entry in users], printers, dryrun) 467 467 self.createGroups(groups, printers, dryrun) 468 468 469 469 if dryrun : 470 470 self.printInfo(_("Simulation terminated.")) 471 else : 471 else : 472 472 self.printInfo(_("Database initialized !")) 473 474 if options["doconf"] : 473 474 if options["doconf"] : 475 475 self.hintConfig(printers) 476 477 478 if __name__ == "__main__" : 476 477 478 if __name__ == "__main__" : 479 479 retcode = 0 480 480 try : … … 483 483 "emptygroups", "force", "uidmin=", "uidmax=", \ 484 484 "gidmin=", "gidmax=", "doconf"] 485 485 486 486 # Initializes the command line tool 487 487 manager = PKTurnKey(doc=__doc__) 488 488 manager.deferredInit() 489 489 490 490 # parse and checks the command line 491 491 (options, args) = manager.parseCommandline(sys.argv[1:], \ … … 493 493 long_options, \ 494 494 allownothing=1) 495 495 496 496 # sets long options 497 497 options["help"] = options["h"] or options["help"] … … 506 506 options["gidmax"] = options["G"] or options["gidmax"] 507 507 options["doconf"] = options["c"] or options["doconf"] 508 508 509 509 if options["uidmin"] or options["uidmax"] : 510 510 if not options["dousers"] : 511 511 manager.printInfo(_("The --uidmin or --uidmax command line option implies --dousers as well."), "warn") 512 options["dousers"] = 1 513 512 options["dousers"] = 1 513 514 514 if options["gidmin"] or options["gidmax"] : 515 515 if not options["dogroups"] : 516 516 manager.printInfo(_("The --gidmin or --gidmax command line option implies --dogroups as well."), "warn") 517 517 options["dogroups"] = 1 518 518 519 519 if options["dogroups"] : 520 520 if not options["dousers"] : 521 521 manager.printInfo(_("The --dogroups command line option implies --dousers as well."), "warn") 522 options["dousers"] = 1 523 522 options["dousers"] = 1 523 524 524 if options["help"] : 525 525 manager.display_usage_and_quit() … … 528 528 else : 529 529 retcode = manager.main(args, options) 530 except KeyboardInterrupt : 530 except KeyboardInterrupt : 531 531 logerr("\nInterrupted with Ctrl+C !\n") 532 532 retcode = -3 533 except PyKotaCommandLineError, msg : 533 except PyKotaCommandLineError, msg : 534 534 logerr("%s : %s\n" % (sys.argv[0], msg)) 535 535 retcode = -2 536 except SystemExit : 536 except SystemExit : 537 537 pass 538 538 except : 539 539 try : 540 540 manager.crashed("pkturnkey failed") 541 except : 541 except : 542 542 crashed("pkturnkey failed") 543 543 retcode = -1 … … 545 545 try : 546 546 manager.storage.close() 547 except (TypeError, NameError, AttributeError) : 547 except (TypeError, NameError, AttributeError) : 548 548 pass 549 550 sys.exit(retcode) 549 550 sys.exit(retcode) -
pykota/trunk/bin/pkusers
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 40 40 41 41 pkusers [options] user1 user2 user3 ... userN 42 43 or : 42 43 or : 44 44 45 45 pkusers --groups [options] group1 group2 group3 ... groupN … … 49 49 -v | --version Prints pkusers's version number then exits. 50 50 -h | --help Prints this message then exits. 51 51 52 52 -a | --add Adds users if they don't exist on the database. 53 53 If they exist, they are modified unless 54 54 -s|--skipexisting is also used. 55 55 56 56 -d | --delete Deletes users from the quota storage. 57 57 … … 62 62 63 63 -D | --description d Adds a textual description to users or groups. 64 64 65 65 -g | --groups Edit users groups instead of users. 66 67 -o | --overcharge f Sets the overcharging factor applied to the user 68 when computing the cost of a print job. Positive or 66 67 -o | --overcharge f Sets the overcharging factor applied to the user 68 when computing the cost of a print job. Positive or 69 69 negative floating point values are allowed, 70 70 this allows you to do some really creative … … 74 74 cost of the job for a particular user. 75 75 Only users have such a coefficient. 76 76 77 77 -i | --ingroups g1[,g2...] Puts the users into each of the groups 78 78 listed, separated by commas. The groups 79 79 must already exist in the Quota Storage. 80 80 81 81 -L | --list Lists users or groups. 82 83 -l | --limitby l Choose if the user/group is limited in printing 82 83 -l | --limitby l Choose if the user/group is limited in printing 84 84 by its account balance or by its page quota. 85 85 The default value is 'quota'. Allowed values 86 are 'quota' 'balance' 'noquota' 'noprint' 86 are 'quota' 'balance' 'noquota' 'noprint' 87 87 and 'nochange' : 88 88 89 89 - quota : limit by number of pages per printer. 90 90 - balance : limit by number of credits in account. 91 91 - noquota : no limit, accounting still done. 92 - nochange : no limit, accounting not done. 93 - noprint : printing is denied. 92 - nochange : no limit, accounting not done. 93 - noprint : printing is denied. 94 94 NB : nochange and noprint are not supported for groups. 95 96 -b | --balance b Sets the user's account balance to b. 95 96 -b | --balance b Sets the user's account balance to b. 97 97 Account balance may be increase or decreased 98 98 if b is prefixed with + or -. … … 102 102 Groups don't have a real balance, but the 103 103 sum of their users' account balance. 104 104 105 105 -C | --comment txt Defines some informational text to be associated 106 106 with a change to an user's account balance. 107 107 Only meaningful if -b | --balance is also used. 108 109 110 -r | --remove In combination with the --ingroups option above, 108 109 110 -r | --remove In combination with the --ingroups option above, 111 111 remove users from the specified users groups. 112 112 113 113 -s | --skipexisting In combination with the --add option above, tells 114 114 pkusers to not modify existing users. 115 115 116 116 user1 through userN and group1 through groupN can use wildcards 117 117 if the --add option is not set. 118 119 examples : 118 119 examples : 120 120 121 121 $ pkusers --add john paul george ringo/ringo@example.com 122 122 123 123 This will add users john, paul, george and ringo to the quota 124 database. User ringo's email address will also be set to 124 database. User ringo's email address will also be set to 125 125 'ringo@example.com' 126 126 127 127 $ pkusers --ingroups coders,it jerome 128 128 129 129 User jerome is put into the groups "coders" and "it" which must 130 130 already exist in the quota database. 131 131 132 132 $ pkusers --limitby balance jerome 133 133 134 134 This will tell PyKota to limit jerome by his account's balance 135 135 when printing. 136 136 137 137 $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome 138 138 139 139 This will increase jerome's account balance by 10.0 (in your 140 140 own currency). You can decrease the account balance with a 141 141 dash prefix, and set it to a fixed amount with no prefix. 142 142 A comment will be stored for this balance change. 143 143 144 144 $ pkusers --delete jerome rachel 145 145 146 146 This will completely delete jerome and rachel from the quota 147 147 database. All their quotas and jobs will be deleted too. 148 148 149 149 $ pkusers --overcharge 2.5 poorstudent 150 150 151 151 This will overcharge the poorstudent user by a factor of 2.5. 152 152 153 153 $ pkusers --overcharge -1 jerome 154 154 155 155 User jerome will actually earn money whenever he prints. 156 156 157 157 $ pkusers --overcharge 0 boss 158 158 159 159 User boss can print at will, it won't cost him anything because the 160 160 cost of each print job will be multiplied by zero before charging … … 165 165 This will set the email address for each user to username@example.com 166 166 """) 167 168 class PKUsers(PyKotaTool) : 167 168 class PKUsers(PyKotaTool) : 169 169 """A class for a users and users groups manager.""" 170 170 def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) : … … 172 172 if description is not None : # NB : "" is allowed ! 173 173 entry.setDescription(description) 174 if limitby : 174 if limitby : 175 175 entry.setLimitBy(limitby) 176 176 if not groups : … … 181 181 raise PyKotaCommandLineError, _("Invalid email address %s") % email 182 182 entry.setEmail(email) 183 if overcharge is not None : # NB : 0 is allowed ! 183 if overcharge is not None : # NB : 0 is allowed ! 184 184 entry.setOverChargeFactor(overcharge) 185 185 if balance : … … 192 192 newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff 193 193 entry.setAccountBalance(balancevalue, newlifetimepaid, comment) 194 195 def manageUsersGroups(self, ugroups, user, remove) : 194 195 def manageUsersGroups(self, ugroups, user, remove) : 196 196 """Manage user group membership.""" 197 197 for ugroup in ugroups : … … 200 200 else : 201 201 ugroup.addUserToGroup(user) 202 202 203 203 def main(self, names, options) : 204 204 """Manage users or groups.""" 205 205 names = self.sanitizeNames(options, names) 206 suffix = (options["groups"] and "Group") or "User" 207 206 suffix = (options["groups"] and "Group") or "User" 207 208 208 if not options["list"] : 209 209 percent = Percent(self) 210 210 211 211 if not options["add"] : 212 212 if not options["list"] : … … 219 219 percent.display("\n") 220 220 raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names)) 221 if not options["list"] : 221 if not options["list"] : 222 222 percent.setSize(len(entries)) 223 223 224 224 if options["list"] : 225 225 if suffix == "User" : … … 229 229 email = entry.Email 230 230 if not email : 231 if maildomain : 231 if maildomain : 232 232 email = "%s@%s" % (entry.Name, maildomain) 233 elif smtpserver : 233 elif smtpserver : 234 234 email = "%s@%s" % (entry.Name, smtpserver) 235 else : 235 else : 236 236 email = "%s@%s" % (entry.Name, "localhost") 237 237 msg = "%s - <%s>" % (entry.Name, email) 238 238 if entry.Description : 239 239 msg += " - %s" % entry.Description 240 print msg 240 print msg 241 241 print " %s" % (_("Limited by : %s") % entry.LimitBy) 242 242 print " %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0)) … … 244 244 print " %s" % (_("Overcharging factor : %.2f") % entry.OverCharge) 245 245 print 246 else : 246 else : 247 247 for entry in entries : 248 248 msg = "%s" % entry.Name 249 249 if entry.Description : 250 250 msg += " - %s" % entry.Description 251 print msg 251 print msg 252 252 print " %s" % (_("Limited by : %s") % entry.LimitBy) 253 253 print " %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0)) 254 254 print " %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0)) 255 255 print 256 elif options["delete"] : 256 elif options["delete"] : 257 257 percent.display("\n%s..." % _("Deletion")) 258 258 getattr(self.storage, "deleteMany%ss" % suffix)(entries) … … 266 266 'noprint', 'nochange') : 267 267 raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"] 268 if (limitby in ('nochange', 'noprint')) and options["groups"] : 268 if (limitby in ('nochange', 'noprint')) and options["groups"] : 269 269 raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"] 270 270 271 271 overcharge = options["overcharge"] 272 272 if overcharge : 273 273 try : 274 274 overcharge = float(overcharge.strip()) 275 except (ValueError, AttributeError) : 275 except (ValueError, AttributeError) : 276 276 raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"] 277 277 278 278 balance = options["balance"] 279 279 if balance : … … 281 281 try : 282 282 balancevalue = float(balance) 283 except ValueError : 283 except ValueError : 284 284 raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"] 285 else : 285 else : 286 286 balancevalue = None 287 287 288 288 if options["ingroups"] : 289 289 usersgroups = self.storage.getMatchingGroups(options["ingroups"]) 290 290 if not usersgroups : 291 291 raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(',')) 292 else : 292 else : 293 293 usersgroups = [] 294 294 295 295 description = options["description"] 296 296 if description : 297 297 description = description.strip() 298 298 299 299 comment = options["comment"] 300 300 if comment : 301 301 comment = comment.strip() 302 email = options["email"] 302 email = options["email"] 303 303 if email : 304 304 email = email.strip() 305 skipexisting = options["skipexisting"] 305 skipexisting = options["skipexisting"] 306 306 groups = options["groups"] 307 307 remove = options["remove"] 308 308 self.storage.beginTransaction() 309 try : 310 if options["add"] : 311 rejectunknown = self.config.getRejectUnknown() 309 try : 310 if options["add"] : 311 rejectunknown = self.config.getRejectUnknown() 312 312 percent.display("%s...\n" % _("Creation")) 313 313 percent.setSize(len(names)) … … 325 325 try : 326 326 grp.getgrnam(ename) 327 except KeyError : 327 except KeyError : 328 328 self.printInfo(_("Unknown group %s") % ename, "error") 329 329 reject = 1 330 else : 330 else : 331 331 try : 332 332 pwd.getpwnam(ename) 333 except KeyError : 333 except KeyError : 334 334 self.printInfo(_("Unknown user %s") % ename, "error") 335 335 reject = 1 336 if not reject : 336 if not reject : 337 337 entry = globals()["Storage%s" % suffix](self.storage, ename) 338 338 if groups : 339 339 self.modifyEntry(entry, groups, limitby, \ 340 340 description) 341 else : 341 else : 342 342 self.modifyEntry(entry, groups, limitby, \ 343 343 description, overcharge,\ … … 348 348 if skipexisting : 349 349 self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename)) 350 else : 350 else : 351 351 self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename)) 352 352 if groups : … … 373 373 if groups : 374 374 self.modifyEntry(entry, groups, limitby, description) 375 else : 375 else : 376 376 self.modifyEntry(entry, groups, limitby, description, \ 377 377 overcharge, balance, balancevalue, \ 378 378 comment, email) 379 self.manageUsersGroups(usersgroups, entry, remove) 380 entry.save() 379 self.manageUsersGroups(usersgroups, entry, remove) 380 entry.save() 381 381 percent.oneMore() 382 except : 382 except : 383 383 self.storage.rollbackTransaction() 384 384 raise 385 else : 385 else : 386 386 self.storage.commitTransaction() 387 387 388 388 if not options["list"] : 389 389 percent.done() 390 391 if __name__ == "__main__" : 390 391 if __name__ == "__main__" : 392 392 retcode = 0 393 393 try : … … 401 401 "ingroups=", "limitby=", "balance=", "comment=", \ 402 402 ] 403 404 403 404 405 405 # Initializes the command line tool 406 406 manager = PKUsers(doc=__doc__) 407 407 manager.deferredInit() 408 408 409 409 # parse and checks the command line 410 410 (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options) 411 411 412 412 # sets long options 413 413 options["help"] = options["h"] or options["help"] … … 415 415 options["add"] = options["a"] or options["add"] 416 416 options["description"] = options["D"] or options["description"] 417 options["delete"] = options["d"] or options["delete"] 417 options["delete"] = options["d"] or options["delete"] 418 418 options["groups"] = options["g"] or options["groups"] 419 419 options["list"] = options["L"] or options["list"] … … 421 421 options["skipexisting"] = options["s"] or options["skipexisting"] 422 422 options["limitby"] = options["l"] or options["limitby"] 423 options["balance"] = options["b"] or options["balance"] 423 options["balance"] = options["b"] or options["balance"] 424 424 options["ingroups"] = options["i"] or options["ingroups"] 425 425 options["overcharge"] = options["o"] or options["overcharge"] 426 426 options["comment"] = options["C"] or options["comment"] or defaults["comment"] 427 427 options["email"] = options["e"] or options["email"] 428 428 429 429 if options["help"] : 430 430 manager.display_usage_and_quit() … … 436 436 or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) : 437 437 raise PyKotaCommandLineError, _("incompatible options, see help.") 438 elif options["remove"] and not options["ingroups"] : 438 elif options["remove"] and not options["ingroups"] : 439 439 raise PyKotaCommandLineError, _("You have to pass user groups names on the command line") 440 440 elif (not args) and (options["add"] or options["delete"]) : … … 442 442 else : 443 443 retcode = manager.main(args, options) 444 except KeyboardInterrupt : 444 except KeyboardInterrupt : 445 445 logerr("\nInterrupted with Ctrl+C !\n") 446 446 retcode = -3 447 except PyKotaCommandLineError, msg : 447 except PyKotaCommandLineError, msg : 448 448 logerr("%s : %s\n" % (sys.argv[0], msg)) 449 449 retcode = -2 450 except SystemExit : 450 except SystemExit : 451 451 pass 452 452 except : 453 453 try : 454 454 manager.crashed("pkusers failed") 455 except : 455 except : 456 456 crashed("pkusers failed") 457 457 retcode = -1 … … 459 459 try : 460 460 manager.storage.close() 461 except (TypeError, NameError, AttributeError) : 461 except (TypeError, NameError, AttributeError) : 462 462 pass 463 464 sys.exit(retcode) 463 464 sys.exit(retcode) -
pykota/trunk/bin/pykosd
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 31 31 try : 32 32 import pyosd 33 except ImportError : 33 except ImportError : 34 34 sys.stderr.write("Sorry ! You need both xosd and the Python OSD library (pyosd) for this software to work.\n") 35 35 sys.exit(-1) … … 53 53 if not user.Exists : 54 54 raise PyKotaCommandLineError, _("You %(username)s don't have a PyKota printing account. Please contact your administrator.") % locals() 55 if user.LimitBy == "quota" : 55 if user.LimitBy == "quota" : 56 56 printers = self.storage.getMatchingPrinters("*") 57 57 upquotas = [ self.storage.getUserPQuotaFromBackend(user, p) for p in printers ] # don't use cache 58 58 nblines = len(upquotas) 59 display = pyosd.osd(font=options.font, 60 colour=color, 61 timeout=options.duration, 62 shadow=2, 59 display = pyosd.osd(font=options.font, 60 colour=color, 61 timeout=options.duration, 62 shadow=2, 63 63 lines=nblines) 64 64 for line in range(nblines) : … … 67 67 if upq.SoftLimit is None : 68 68 percent = "%i" % upq.PageCounter 69 else : 69 else : 70 70 percent = "%i%%" % min((upq.PageCounter * 100) / upq.SoftLimit, 100) 71 else : 71 else : 72 72 percent = "%i%%" % min((upq.PageCounter * 100) / upq.HardLimit, 100) 73 printername = upq.Printer.Name 73 printername = upq.Printer.Name 74 74 msg = _("Pages used on %(printername)s : %(percent)s") % locals() 75 display.display(msg.encode(self.charset, "replace"), 76 type=pyosd.TYPE_STRING, 75 display.display(msg.encode(self.charset, "replace"), 76 type=pyosd.TYPE_STRING, 77 77 line=line) 78 78 elif user.LimitBy == "balance" : 79 79 if user.AccountBalance <= self.config.getBalanceZero() : 80 80 color = "#FF0000" 81 display = pyosd.osd(font=options.font, 82 colour=color, 83 timeout=options.duration, 81 display = pyosd.osd(font=options.font, 82 colour=color, 83 timeout=options.duration, 84 84 shadow=2) 85 balance = user.AccountBalance 85 balance = user.AccountBalance 86 86 msg = _("PyKota Units left : %(balance).2f") % locals() 87 display.display(msg.encode(self.charset, "replace"), 87 display.display(msg.encode(self.charset, "replace"), 88 88 type=pyosd.TYPE_STRING) 89 elif user.LimitBy == "noprint" : 90 display = pyosd.osd(font=options.font, 91 colour="#FF0000", 92 timeout=options.duration, 89 elif user.LimitBy == "noprint" : 90 display = pyosd.osd(font=options.font, 91 colour="#FF0000", 92 timeout=options.duration, 93 93 shadow=2) 94 94 msg = _("Printing denied.") 95 display.display(msg.encode(self.charset, "replace"), 95 display.display(msg.encode(self.charset, "replace"), 96 96 type=pyosd.TYPE_STRING) 97 elif user.LimitBy == "noquota" : 98 display = pyosd.osd(font=options.font, 99 colour=savecolor, 100 timeout=options.duration, 97 elif user.LimitBy == "noquota" : 98 display = pyosd.osd(font=options.font, 99 colour=savecolor, 100 timeout=options.duration, 101 101 shadow=2) 102 102 msg = _("Printing not limited.") 103 display.display(msg.encode(self.charset, "replace"), 103 display.display(msg.encode(self.charset, "replace"), 104 104 type=pyosd.TYPE_STRING) 105 elif user.LimitBy == "nochange" : 106 display = pyosd.osd(font=options.font, 107 colour=savecolor, 108 timeout=options.duration, 105 elif user.LimitBy == "nochange" : 106 display = pyosd.osd(font=options.font, 107 colour=savecolor, 108 timeout=options.duration, 109 109 shadow=2) 110 110 msg = _("Printing not limited, no accounting.") 111 display.display(msg.encode(self.charset, "replace"), 111 display.display(msg.encode(self.charset, "replace"), 112 112 type=pyosd.TYPE_STRING) 113 else : 113 else : 114 114 limitby = repr(user.LimitBy) 115 115 raise PyKotaToolError, "Incorrect limitation factor %(limitby)s for user %(username)s" % locals() 116 116 117 117 time.sleep(options.duration + 1) 118 118 if loop : … … 120 120 if not loop : 121 121 break 122 time.sleep(options.sleep) 123 124 return 0 125 122 time.sleep(options.sleep) 123 124 return 0 125 126 126 if __name__ == "__main__" : 127 def checkandset_positiveint(option, opt, value, optionparser) : 127 def checkandset_positiveint(option, opt, value, optionparser) : 128 128 """Checks and sets positive integer values.""" 129 129 if value < 0 : 130 130 loginvalidparam(opt, value, option.default) 131 131 setattr(optionparser.values, option.dest, option.default) 132 else : 132 else : 133 133 setattr(optionparser.values, option.dest, value) 134 135 def checkandset_color(option, opt, value, optionparser) : 134 135 def checkandset_color(option, opt, value, optionparser) : 136 136 """Checks and sets the color value.""" 137 137 if not value.startswith("#") : 138 138 value = "#%s" % value 139 try : 139 try : 140 140 int(value[1:], 16) 141 except (ValueError, TypeError) : 141 except (ValueError, TypeError) : 142 142 error = True 143 else : 143 else : 144 144 error = False 145 145 if (len(value) != 7) or error : 146 146 loginvalidparam(opt, value, option.default) 147 147 setattr(optionparser.values, option.dest, option.default) 148 else : 148 else : 149 149 setattr(optionparser.values, option.dest, value) 150 150 151 151 parser = PyKotaOptionParser(description=_("An On Screen Display (OSD) monitor for PyKota's end users.")) 152 152 parser.add_option("-c", "--color", "--colour", … … 182 182 default=180, 183 183 help=_("Set the sleeping time in seconds between two refreshes. Defaults to %default seconds.")) 184 184 185 185 parser.add_example('-s 60 --loop 5', 186 186 _("This would tell pykosd to display the current user's status for 3 seconds (the default) every 60 seconds, and exit after 5 iterations.")) 187 187 188 188 run(parser, PyKOSD) -
pykota/trunk/bin/pykotme
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 34 34 from pykota.tool import PyKotaTool 35 35 from pykota.accounter import openAccounter 36 37 class PyKotMe(PyKotaTool) : 36 37 class PyKotMe(PyKotaTool) : 38 38 """A class for pykotme.""" 39 39 def main(self, files, options) : … … 46 46 # TODO : over the second printer below. 47 47 files.append("-") 48 48 49 49 printers = self.storage.getMatchingPrinters(options.printer) 50 50 if not printers : 51 51 raise PyKotaCommandLineError, _("There's no printer matching %s") % options.printer 52 52 53 53 username = pwd.getpwuid(os.getuid())[0] 54 54 if options.user : 55 55 if not self.config.isAdmin : 56 56 self.printInfo(_("The --user command line option will be ignored because you are not a PyKota Administrator."), "warn") 57 else : 57 else : 58 58 username = options.user 59 59 60 60 user = self.storage.getUser(username) 61 61 if not user.Exists : 62 62 self.printInfo(_("There's no user matching '%(username)s'.") \ 63 63 % locals(), 64 "error") 65 else : 64 "error") 65 else : 66 66 if user.LimitBy and (user.LimitBy.lower() == "balance"): 67 67 self.display("%s\n" % (_("Your account balance : %.2f") % (user.AccountBalance or 0.0))) 68 68 69 69 sizeprinted = False 70 70 done = {} 71 71 for printer in printers : 72 # Now fake some values. TODO : improve API to not need this anymore 72 # Now fake some values. TODO : improve API to not need this anymore 73 73 printername = printer.Name 74 74 self.PrinterName = printer.Name … … 77 77 key = self.preaccounter.name + self.preaccounter.arguments 78 78 if not done.has_key(key) : 79 totalsize = 0 79 totalsize = 0 80 80 inkusage = [] 81 for filename in files : 81 for filename in files : 82 82 self.DataFile = filename 83 83 self.preaccounter.beginJob(None) … … 85 85 totalsize += self.preaccounter.getJobSize(None) 86 86 inkusage.extend(self.preaccounter.inkUsage) 87 done[key] = (totalsize, inkusage) 88 (totalsize, inkusage) = done[key] 89 if not sizeprinted : 87 done[key] = (totalsize, inkusage) 88 (totalsize, inkusage) = done[key] 89 if not sizeprinted : 90 90 self.display("%s\n" % (_("Job size : %i pages") % totalsize)) 91 91 sizeprinted = True … … 94 94 if printer.MaxJobSize and (totalsize > printer.MaxJobSize) : 95 95 self.display("%s\n" % (_("User %(username)s is not allowed to print so many pages on printer %(printername)s at this time.") % locals())) 96 else : 96 else : 97 97 cost = userpquota.computeJobPrice(totalsize, inkusage) 98 98 msg = _("Cost on printer %s : %.2f") % (printer.Name, cost) 99 99 if printer.PassThrough : 100 100 msg = "%s (%s)" % (msg, _("won't be charged, printer is in passthrough mode")) 101 elif user.LimitBy == "nochange" : 101 elif user.LimitBy == "nochange" : 102 102 msg = "%s (%s)" % (msg, _("won't be charged, account is immutable")) 103 103 self.display("%s\n" % msg) 104 104 if user.LimitBy == "noprint" : 105 105 self.display("%s\n" % (_("User %(username)s is forbidden to print at this time.") % locals())) 106 107 if __name__ == "__main__" : 106 107 if __name__ == "__main__" : 108 108 parser = PyKotaOptionParser(description=_("Generates print quotes for end users."), 109 109 usage="pykotme [options] [files]") … … 116 116 help=_("Acts on this user only. Only one username can be specified this way. The default value is the name of the user who launched this command. This option is ignored when the command is not launched by a PyKota Administrator.")) 117 117 118 parser.add_example("--printer apple file1.ps <file2.pclxl", 118 parser.add_example("--printer apple file1.ps <file2.pclxl", 119 119 _("This would show the number of pages needed to print these two files, as well as the cost of printing them to the 'apple' printer for the user who launched this command.")) 120 120 parser.add_example("--user john", -
pykota/trunk/bin/README
r3275 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. -
pykota/trunk/bin/repykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 36 36 from pykota.tool import PyKotaTool 37 37 from pykota import reporter 38 39 class RePyKota(PyKotaTool) : 38 39 class RePyKota(PyKotaTool) : 40 40 """A class for repykota.""" 41 41 def main(self, ugnames, options) : … … 43 43 if options.ingroups and options.groups : 44 44 raise PyKotaCommandLineError, _("Incompatible options, see help.") 45 45 46 46 if self.config.isAdmin : 47 47 # PyKota administrator … … 49 49 # no username, means all usernames 50 50 ugnames = [ "*" ] 51 51 52 52 if options.ingroups : 53 53 groupsnames = options.ingroups.split(",") … … 57 57 if not group.Exists : 58 58 self.printInfo("Group %s doesn't exist." % group.Name, "warn") 59 else : 59 else : 60 60 for user in self.storage.getGroupMembers(group) : 61 61 members[user.Name] = user 62 62 ugnames = [ m for m in members.keys() if self.matchString(m, ugnames) ] 63 else : 63 else : 64 64 # reports only the current user 65 65 if options.ingroups : 66 66 raise PyKotaCommandLineError, _("Option --ingroups is reserved to PyKota Administrators.") 67 67 68 68 username = pwd.getpwuid(os.geteuid())[0] 69 69 if options.groups : … … 71 71 if user.Exists : 72 72 ugnames = [ g.Name for g in self.storage.getUserGroups(user) ] 73 else : 73 else : 74 74 ugnames = [ ] 75 75 else : 76 76 ugnames = [ username ] 77 77 78 78 printers = self.storage.getMatchingPrinters(options.printer) 79 79 if not printers : 80 80 raise PyKotaCommandLineError, _("There's no printer matching %s") % options.printer 81 81 82 82 self.reportingtool = reporter.openReporter(self, "text", printers, ugnames, options.groups) 83 83 print self.reportingtool.generateReport() 84 85 if __name__ == "__main__" : 84 85 if __name__ == "__main__" : 86 86 parser = PyKotaOptionParser(description=_("Minimalist print accounting reports for PyKota. If not launched by a PyKota administrator, additionnal arguments representing users or groups names are ignored, limiting the scope of the reports to the current user."), 87 87 usage="repykota [options] [usernames|groupnames]") … … 97 97 default="*", 98 98 help=_("Acts on this printer only. You can specify several printer names by separating them with commas. The default value is '%default', which means all printers.")) 99 99 100 100 parser.add_example('', 101 101 _("This would generate a report for all users on all printers.")) … … 104 104 parser.add_example('--printer "laser*,*pson" jerome "jo*"', 105 105 _("This would generate a report for all users named 'jerome' or whose name begins with 'jo', on all printers which name begins with 'laser' or ends with 'pson'.")) 106 106 107 107 run(parser, RePyKota) -
pykota/trunk/bin/waitprinter.sh
r3389 r3413 8 8 # the Free Software Foundation, either version 3 of the License, or 9 9 # (at your option) any later version. 10 # 10 # 11 11 # This program is distributed in the hope that it will be useful, 12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 14 # GNU General Public License for more details. 15 # 15 # 16 16 # You should have received a copy of the GNU General Public License 17 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. -
pykota/trunk/bin/warnpykota
r3411 r3413 9 9 # the Free Software Foundation, either version 3 of the License, or 10 10 # (at your option) any later version. 11 # 11 # 12 12 # This program is distributed in the hope that it will be useful, 13 13 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. … … 25 25 notifications to users or groups who have reached the limit of their 26 26 printing quota.""" 27 27 28 28 import sys 29 29 import os … … 41 41 from pykota.tool import PyKotaTool 42 42 43 class WarnPyKota(PyKotaTool) : 43 class WarnPyKota(PyKotaTool) : 44 44 """A class for warnpykota.""" 45 45 def sendMessage(self, adminmail, touser, fullmessage) : 46 46 """Sends an email message containing headers to some user.""" 47 47 smtpserver = self.smtpserver 48 try : 48 try : 49 49 server = smtplib.SMTP(smtpserver) 50 except socket.error, msg : 50 except socket.error, msg : 51 51 self.printInfo(_("Impossible to connect to SMTP server : %(smtpserver)s") \ 52 52 % locals(), \ … … 55 55 try : 56 56 server.sendmail(adminmail, [touser], fullmessage) 57 except smtplib.SMTPException, answer : 57 except smtplib.SMTPException, answer : 58 58 for (k, v) in answer.recipients.items() : 59 59 errormsg = v[0] … … 63 63 "error") 64 64 server.quit() 65 65 66 66 def sendMessageToUser(self, admin, adminmail, user, subject, message) : 67 67 """Sends an email message to a user.""" … … 76 76 msg["Date"] = email.Utils.formatdate(localtime=True) 77 77 self.sendMessage(adminmail, usermail, msg.as_string()) 78 78 79 79 def sendMessageToAdmin(self, adminmail, subject, message) : 80 80 """Sends an email message to the Print Quota administrator.""" … … 86 86 msg["To"] = adminmail 87 87 self.sendMessage(adminmail, adminmail, msg.as_string()) 88 88 89 89 def warnGroupPQuota(self, grouppquota) : 90 90 """Checks a group quota and send messages if quota is exceeded on current printer.""" … … 98 98 if group.LimitBy in ("noquota", "nochange") : 99 99 action = "ALLOW" 100 else : 100 else : 101 101 action = self.checkGroupPQuota(grouppquota) 102 102 if action.startswith("POLICY_") : … … 111 111 if mailto != "EXTERNAL" : 112 112 self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printername)) 113 else : 113 else : 114 114 self.externalMailTo(arguments, action, user, printer, self.config.getHardWarn(printername)) 115 elif action == "WARN" : 115 elif action == "WARN" : 116 116 adminmessage = _("Print Quota low for group %(groupname)s on printer %(printername)s") % locals() 117 117 self.printInfo(adminmessage) 118 118 if mailto in [ "BOTH", "ADMIN" ] : 119 119 self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) 120 if group.LimitBy and (group.LimitBy.lower() == "balance") : 120 if group.LimitBy and (group.LimitBy.lower() == "balance") : 121 121 message = self.config.getPoorWarn() 122 else : 122 else : 123 123 message = self.config.getSoftWarn(printername) 124 124 if mailto in [ "BOTH", "USER", "EXTERNAL" ] : … … 126 126 if mailto != "EXTERNAL" : 127 127 self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message) 128 else : 128 else : 129 129 self.externalMailTo(arguments, action, user, printer, message) 130 return action 131 130 return action 131 132 132 def warnUserPQuota(self, userpquota) : 133 133 """Checks a user quota and send him a message if quota is exceeded on current printer.""" … … 139 139 adminmail = self.config.getAdminMail(printername) 140 140 (mailto, arguments) = self.config.getMailTo(printername) 141 141 142 142 if user.LimitBy in ("noquota", "nochange") : 143 143 action = "ALLOW" … … 149 149 if mailto != "EXTERNAL" : 150 150 self.sendMessageToUser(admin, adminmail, user, _("Printing denied."), message) 151 else : 151 else : 152 152 self.externalMailTo(arguments, action, user, printer, message) 153 153 if mailto in [ "BOTH", "ADMIN" ] : … … 157 157 if action.startswith("POLICY_") : 158 158 action = action[7:] 159 159 160 160 if action == "DENY" : 161 161 adminmessage = _("Print Quota exceeded for user %(username)s on printer %(printername)s") % locals() … … 165 165 if mailto != "EXTERNAL" : 166 166 self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message) 167 else : 167 else : 168 168 self.externalMailTo(arguments, action, user, printer, message) 169 169 if mailto in [ "BOTH", "ADMIN" ] : 170 170 self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) 171 elif action == "WARN" : 171 elif action == "WARN" : 172 172 adminmessage = _("Print Quota low for user %(username)s on printer %(printername)s") % locals() 173 173 self.printInfo(adminmessage) 174 174 if mailto in [ "BOTH", "USER", "EXTERNAL" ] : 175 if user.LimitBy and (user.LimitBy.lower() == "balance") : 175 if user.LimitBy and (user.LimitBy.lower() == "balance") : 176 176 message = self.config.getPoorWarn() 177 else : 177 else : 178 178 message = self.config.getSoftWarn(printername) 179 if mailto != "EXTERNAL" : 179 if mailto != "EXTERNAL" : 180 180 self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message) 181 else : 181 else : 182 182 self.externalMailTo(arguments, action, user, printer, message) 183 183 if mailto in [ "BOTH", "ADMIN" ] : 184 184 self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage) 185 return action 186 185 return action 186 187 187 def main(self, ugnames, options) : 188 188 """Warn users or groups over print quota.""" … … 192 192 # no username, means all usernames 193 193 ugnames = [ "*" ] 194 else : 194 else : 195 195 # not a PyKota administrator 196 196 # warns only the current user … … 203 203 if user.Exists : 204 204 ugnames = [ g.Name for g in self.storage.getUserGroups(user) ] 205 else : 205 else : 206 206 ugnames = [ ] 207 207 else : 208 208 ugnames = [ username ] 209 209 210 210 printername = options.printer 211 211 printers = self.storage.getMatchingPrinters(printername) … … 231 231 if not done : 232 232 alreadydone[user.Name] = (action in ('WARN', 'DENY')) 233 234 if __name__ == "__main__" : 233 234 if __name__ == "__main__" : 235 235 parser = PyKotaOptionParser(description=_("A tool to warn users and groups who have reached the limit of their printing quota."), 236 236 usage="warnpykota [options] [usernames|groupnames]") … … 243 243 default="*", 244 244 help=_("Acts on this printer only. You can specify several printer names by separating them with commas. The default value is '%default', which means all printers.")) 245 245 246 246 parser.add_example('', 247 247 _("This would notify all users who have reached the limit of their printing quota on any printer.")) … … 250 250 parser.add_example('--groups --printer "HP*,XER*" "dev*"', 251 251 _("This would notify all users of the groups whose name begins with 'dev' and for which the printing quota limit is reached on any printer whose name begins with 'HP' or 'XER'.")) 252 252 253 253 run(parser, WarnPyKota)