Changeset 3413 for pykota/trunk/pykota/tool.py
- Timestamp:
- 09/27/08 22:02:37 (16 years ago)
- Files:
-
- 1 modified
Legend:
- Unmodified
- Added
- Removed
-
pykota/trunk/pykota/tool.py
r3411 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/>. … … 53 53 self.previous = None 54 54 self.before = time.time() 55 56 def setSize(self, size) : 55 56 def setSize(self, size) : 57 57 """Sets the total size.""" 58 58 self.number = 0 … … 60 60 if size : 61 61 self.factor = 100.0 / float(size) 62 63 def display(self, msg) : 62 63 def display(self, msg) : 64 64 """Displays the value.""" 65 65 self.app.display(msg) 66 67 def oneMore(self) : 66 67 def oneMore(self) : 68 68 """Increments internal counter.""" 69 69 if self.size : … … 73 73 self.display("\r%s%%" % percent) 74 74 self.previous = percent 75 76 def done(self) : 75 76 def done(self) : 77 77 """Displays the 'done' message.""" 78 78 after = time.time() … … 80 80 try : 81 81 speed = self.size / ((after - self.before) + 0.00000000001) # adds an epsilon to avoid an user's problem I can't reproduce... 82 except ZeroDivisionError : 82 except ZeroDivisionError : 83 83 speed = 1 # Fake value in case of division by zero, shouldn't happen anyway with the epsilon above... 84 84 self.display("\r100.00%%\r \r%s. %s : %.2f %s.\n" \ 85 85 % (_("Done"), _("Average speed"), speed, _("entries per second"))) 86 else : 86 else : 87 87 self.display("\r100.00%%\r \r%s.\n" % _("Done")) 88 88 89 89 class Tool : 90 90 """Base class for tools with no database access.""" … … 93 93 self.debug = True # in case of early failure 94 94 self.logger = logger.openLogger("stderr") 95 95 96 96 # Saves a copy of the locale settings 97 97 (self.language, self.charset) = locale.getlocale() … … 100 100 if not self.charset : 101 101 self.charset = "UTF-8" 102 102 103 103 # pykota specific stuff 104 104 self.documentation = doc 105 105 106 106 # Extract the effective username 107 107 uid = os.geteuid() 108 108 try : 109 109 self.effectiveUserName = pwd.getpwuid(uid)[0] 110 except (KeyError, IndexError), msg : 110 except (KeyError, IndexError), msg : 111 111 self.printInfo(_("Strange problem with uid(%s) : %s") % (uid, msg), "warn") 112 112 self.effectiveUserName = os.getlogin() 113 114 def deferredInit(self) : 113 114 def deferredInit(self) : 115 115 """Deferred initialization.""" 116 116 confdir = os.environ.get("PYKOTA_HOME") … … 123 123 self.pykotauser = pwd.getpwnam("pykota") 124 124 confdir = self.pykotauser[5] 125 except KeyError : 125 except KeyError : 126 126 self.pykotauser = None 127 127 confdir = "/etc/pykota" 128 128 missingUser = True 129 129 130 130 self.config = config.PyKotaConfig(confdir) 131 131 self.debug = self.config.getDebug() … … 133 133 self.maildomain = self.config.getMailDomain() 134 134 self.logger = logger.openLogger(self.config.getLoggingBackend()) 135 136 # TODO : We NEED this here, even when not in an accounting filter/backend 135 136 # TODO : We NEED this here, even when not in an accounting filter/backend 137 137 self.softwareJobSize = 0 138 138 self.softwareJobPrice = 0.0 139 139 140 140 if environHome : 141 141 self.printInfo("PYKOTA_HOME environment variable is set. Configuration files were searched in %s" % confdir, "info") 142 142 else : 143 if missingUser : 143 if missingUser : 144 144 self.printInfo("The 'pykota' system account is missing. Configuration files were searched in %s instead." % confdir, "warn") 145 145 146 146 self.logdebug("Language in use : %s" % self.language) 147 147 self.logdebug("Charset in use : %s" % self.charset) 148 148 149 149 arguments = " ".join(['"%s"' % arg for arg in sys.argv]) 150 150 self.logdebug("Command line arguments : %s" % arguments) 151 151 152 152 def display(self, message) : 153 153 """Display a message but only if stdout is a tty.""" … … 156 156 "replace")) 157 157 sys.stdout.flush() 158 159 def logdebug(self, message) : 158 159 def logdebug(self, message) : 160 160 """Logs something to debug output if debug is enabled.""" 161 161 if self.debug : … … 163 163 "replace"), \ 164 164 "debug") 165 166 def printInfo(self, message, level="info") : 165 166 def printInfo(self, message, level="info") : 167 167 """Sends a message to standard error.""" 168 168 sys.stderr.write("%s: %s\n" % (level.upper(), \ … … 170 170 "replace"))) 171 171 sys.stderr.flush() 172 172 173 173 def adminOnly(self, restricted=True) : 174 174 """Raises an exception if the user is not a PyKota administrator.""" 175 175 if restricted and not self.config.isAdmin : 176 176 raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command.")) 177 177 178 178 def matchString(self, s, patterns) : 179 179 """Returns True if the string s matches one of the patterns, else False.""" 180 180 if not patterns : 181 181 return True # No pattern, always matches. 182 else : 182 else : 183 183 for pattern in patterns : 184 184 if fnmatch.fnmatchcase(s, pattern) : 185 185 return True 186 186 return False 187 187 188 188 def sanitizeNames(self, options, names) : 189 189 """Ensures that an user can only see the datas he is allowed to see, by modifying the list of names.""" … … 198 198 return [ g.Name for g in self.storage.getUserGroups(user) ] 199 199 return [ username ] 200 elif not names : 200 elif not names : 201 201 return ["*"] 202 else : 202 else : 203 203 return names 204 204 205 205 def display_version_and_quit(self) : 206 206 """Displays version number, then exists successfully.""" 207 207 try : 208 208 self.clean() 209 except AttributeError : 209 except AttributeError : 210 210 pass 211 211 print __version__ 212 212 sys.exit(0) 213 213 214 214 def display_usage_and_quit(self) : 215 215 """Displays command line usage, then exists successfully.""" 216 216 try : 217 217 self.clean() 218 except AttributeError : 218 except AttributeError : 219 219 pass 220 220 print _(self.documentation) % globals() … … 223 223 print _("Please report bugs to :"), __author__ 224 224 sys.exit(0) 225 226 def crashed(self, message="Bug in PyKota") : 225 226 def crashed(self, message="Bug in PyKota") : 227 227 """Outputs a crash message, and optionally sends it to software author.""" 228 228 msg = utils.crashed(message) … … 248 248 self.printInfo("PyKota double crash !", "error") 249 249 raise 250 return fullmessage 251 250 return fullmessage 251 252 252 def parseCommandline(self, argv, short, long, allownothing=0) : 253 253 """Parses the command line, controlling options.""" … … 269 269 withoutarg.append(short[i]) 270 270 i = ii 271 271 272 272 for option in long : 273 273 if option[-1] == '=' : … … 277 277 # doesn't need an argument 278 278 withoutarg.append(option) 279 279 280 280 # then we parse the command line 281 281 done = 0 … … 309 309 except getopt.error, msg : 310 310 raise PyKotaCommandLineError, str(msg) 311 else : 311 else : 312 312 if parsed["arguments"] or parsed["A"] : 313 313 # arguments are in a file, we ignore all other arguments … … 321 321 if argi.startswith('"') and argi.endswith('"') : 322 322 argv[i] = argi[1:-1] 323 else : 323 else : 324 324 done = 1 325 325 return (parsed, args) 326 327 class PyKotaTool(Tool) : 326 327 class PyKotaTool(Tool) : 328 328 """Base class for all PyKota command line tools.""" 329 def deferredInit(self) : 329 def deferredInit(self) : 330 330 """Deferred initialization.""" 331 331 Tool.deferredInit(self) … … 333 333 if self.config.isAdmin : # TODO : We don't know this before, fix this ! 334 334 self.logdebug("Beware : running as a PyKota administrator !") 335 else : 335 else : 336 336 self.logdebug("Don't Panic : running as a mere mortal !") 337 338 def clean(self) : 337 338 def clean(self) : 339 339 """Ensures that the database is closed.""" 340 340 try : 341 341 self.storage.close() 342 except (TypeError, NameError, AttributeError) : 342 except (TypeError, NameError, AttributeError) : 343 343 pass 344 344 345 345 def isValidName(self, name) : 346 346 """Checks if a user or printer name is valid.""" … … 349 349 if c in name : 350 350 return 0 351 return 1 352 353 def _checkUserPQuota(self, userpquota) : 351 return 1 352 353 def _checkUserPQuota(self, userpquota) : 354 354 """Checks the user quota on a printer and deny or accept the job.""" 355 355 # then we check the user's own quota … … 361 361 (policy, dummy) = self.config.getPrinterPolicy(userpquota.Printer.Name) 362 362 if not userpquota.Exists : 363 # Unknown userquota 363 # Unknown userquota 364 364 if policy == "ALLOW" : 365 365 action = "POLICY_ALLOW" 366 else : 366 else : 367 367 action = "POLICY_DENY" 368 368 self.printInfo(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action)) 369 else : 369 else : 370 370 pagecounter = int(userpquota.PageCounter or 0) 371 371 if enforcement == "STRICT" : … … 375 375 if pagecounter < softlimit : 376 376 action = "ALLOW" 377 else : 377 else : 378 378 if userpquota.HardLimit is None : 379 379 # only a soft limit, this is equivalent to having only a hard limit 380 380 action = "DENY" 381 else : 381 else : 382 382 hardlimit = int(userpquota.HardLimit) 383 if softlimit <= pagecounter < hardlimit : 383 if softlimit <= pagecounter < hardlimit : 384 384 now = DateTime.now() 385 385 if userpquota.DateLimit is not None : … … 390 390 if now < datelimit : 391 391 action = "WARN" 392 else : 392 else : 393 393 action = "DENY" 394 else : 394 else : 395 395 action = "DENY" 396 else : 396 else : 397 397 if userpquota.HardLimit is not None : 398 398 # no soft limit, only a hard one. … … 400 400 if pagecounter < hardlimit : 401 401 action = "ALLOW" 402 else : 402 else : 403 403 action = "DENY" 404 404 else : … … 406 406 action = "ALLOW" 407 407 return action 408 409 def checkGroupPQuota(self, grouppquota) : 408 409 def checkGroupPQuota(self, grouppquota) : 410 410 """Checks the group quota on a printer and deny or accept the job.""" 411 411 group = grouppquota.Group … … 413 413 enforcement = self.config.getPrinterEnforcement(printer.Name) 414 414 self.logdebug("Checking group %s's quota on printer %s" % (group.Name, printer.Name)) 415 if group.LimitBy and (group.LimitBy.lower() == "balance") : 415 if group.LimitBy and (group.LimitBy.lower() == "balance") : 416 416 val = group.AccountBalance or 0.0 417 if enforcement == "STRICT" : 417 if enforcement == "STRICT" : 418 418 val -= self.softwareJobPrice # use precomputed size. 419 419 balancezero = self.config.getBalanceZero() 420 420 if val <= balancezero : 421 421 action = "DENY" 422 elif val <= self.config.getPoorMan() : 422 elif val <= self.config.getPoorMan() : 423 423 action = "WARN" 424 else : 424 else : 425 425 action = "ALLOW" 426 426 if (enforcement == "STRICT") and (val == balancezero) : … … 434 434 if val < softlimit : 435 435 action = "ALLOW" 436 else : 436 else : 437 437 if grouppquota.HardLimit is None : 438 438 # only a soft limit, this is equivalent to having only a hard limit 439 439 action = "DENY" 440 else : 440 else : 441 441 hardlimit = int(grouppquota.HardLimit) 442 if softlimit <= val < hardlimit : 442 if softlimit <= val < hardlimit : 443 443 now = DateTime.now() 444 444 if grouppquota.DateLimit is not None : … … 449 449 if now < datelimit : 450 450 action = "WARN" 451 else : 451 else : 452 452 action = "DENY" 453 else : 453 else : 454 454 action = "DENY" 455 else : 455 else : 456 456 if grouppquota.HardLimit is not None : 457 457 # no soft limit, only a hard one. … … 459 459 if val < hardlimit : 460 460 action = "ALLOW" 461 else : 461 else : 462 462 action = "DENY" 463 463 else : … … 465 465 action = "ALLOW" 466 466 return action 467 467 468 468 def checkUserPQuota(self, userpquota) : 469 469 """Checks the user quota on a printer and all its parents and deny or accept the job.""" 470 470 user = userpquota.User 471 471 printer = userpquota.Printer 472 472 473 473 # indicates that a warning needs to be sent 474 warned = 0 475 474 warned = 0 475 476 476 # first we check any group the user is a member of 477 477 for group in self.storage.getUserGroups(user) : … … 485 485 if action == "DENY" : 486 486 return action 487 elif action == "WARN" : 487 elif action == "WARN" : 488 488 warned = 1 489 489 490 490 # Then we check the user's account balance 491 491 # if we get there we are sure that policy is not EXTERNAL 492 492 (policy, dummy) = self.config.getPrinterPolicy(printer.Name) 493 if user.LimitBy and (user.LimitBy.lower() == "balance") : 493 if user.LimitBy and (user.LimitBy.lower() == "balance") : 494 494 self.logdebug("Checking account balance for user %s" % user.Name) 495 495 if user.AccountBalance is None : 496 496 if policy == "ALLOW" : 497 497 action = "POLICY_ALLOW" 498 else : 498 else : 499 499 action = "POLICY_DENY" 500 500 self.printInfo(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name)) 501 return action 502 else : 501 return action 502 else : 503 503 if user.OverCharge == 0.0 : 504 504 self.printInfo(_("User %s will not be charged for printing.") % user.Name) … … 507 507 val = float(user.AccountBalance or 0.0) 508 508 enforcement = self.config.getPrinterEnforcement(printer.Name) 509 if enforcement == "STRICT" : 509 if enforcement == "STRICT" : 510 510 val -= self.softwareJobPrice # use precomputed size. 511 balancezero = self.config.getBalanceZero() 511 balancezero = self.config.getBalanceZero() 512 512 if val <= balancezero : 513 513 action = "DENY" 514 elif val <= self.config.getPoorMan() : 514 elif val <= self.config.getPoorMan() : 515 515 action = "WARN" 516 516 else : … … 518 518 if (enforcement == "STRICT") and (val == balancezero) : 519 519 action = "WARN" # we can still print until account is 0 520 return action 521 else : 522 # Then check the user quota on current printer and all its parents. 520 return action 521 else : 522 # Then check the user quota on current printer and all its parents. 523 523 policyallowed = 0 524 for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota : 524 for upquota in [ userpquota ] + userpquota.ParentPrintersUserPQuota : 525 525 action = self._checkUserPQuota(upquota) 526 526 if action in ("DENY", "POLICY_DENY") : 527 527 return action 528 elif action == "WARN" : 528 elif action == "WARN" : 529 529 warned = 1 530 elif action == "POLICY_ALLOW" : 530 elif action == "POLICY_ALLOW" : 531 531 policyallowed = 1 532 if warned : 532 if warned : 533 533 return "WARN" 534 elif policyallowed : 535 return "POLICY_ALLOW" 536 else : 534 elif policyallowed : 535 return "POLICY_ALLOW" 536 else : 537 537 return "ALLOW" 538 538 539 539 def externalMailTo(self, cmd, action, user, printer, message) : 540 540 """Warns the user with an external command.""" … … 545 545 email = "%s@%s" % (email, self.maildomain or self.smtpserver) 546 546 os.system(cmd % locals()) 547 547 548 548 def formatCommandLine(self, cmd, user, printer) : 549 549 """Executes an external command.""" … … 551 551 printername = printer.Name 552 552 return cmd % locals() 553 553