Package entropy :: Module misc

Source Code for Module entropy.misc

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3   
   4      @author: Fabio Erculiani <[email protected]> 
   5      @contact: [email protected] 
   6      @copyright: Fabio Erculiani 
   7      @license: GPL-2 
   8   
   9      B{Entropy Framework miscellaneous module}. 
  10   
  11      This module contains miscellaneous classes, not directly 
  12      related with the "Entropy metaphor". 
  13   
  14  """ 
  15  import os 
  16  import sys 
  17  import time 
  18  import fcntl 
  19  import signal 
  20  import errno 
  21  import codecs 
  22  import contextlib 
  23   
  24  from entropy.const import const_is_python3 
  25   
  26  if const_is_python3(): 
  27      import urllib.request, urllib.error, urllib.parse 
  28      UrllibBaseHandler = urllib.request.BaseHandler 
  29  else: 
  30      import urllib 
  31      import urllib2 
  32      UrllibBaseHandler = urllib2.BaseHandler 
  33  import logging 
  34  import threading 
  35  from collections import deque 
  36   
  37  from entropy.const import etpConst, const_isunicode, \ 
  38      const_isfileobj, const_convert_log_level, const_setup_file 
  39  from entropy.exceptions import EntropyException 
  40   
  41  import entropy.tools 
42 43 44 -class Lifo(object):
45 46 """ 47 48 This class can be used to build LIFO buffers, also commonly 49 known as "stacks". I{Lifo} allows you to store and retrieve 50 Python objects from its stack, in a very smart way. 51 This implementation is much faster than the one provided 52 by Python (queue module) and more sofisticated. 53 54 Sample code: 55 56 >>> # load Lifo 57 >>> from entropy.misc import Lifo 58 >>> stack = Lifo() 59 >>> item1 = set([1,2,3]) 60 >>> item2 = ["a","b", "c"] 61 >>> item3 = None 62 >>> item4 = 1 63 >>> stack.push(item4) 64 >>> stack.push(item3) 65 >>> stack.push(item2) 66 >>> stack.push(item1) 67 >>> stack.is_filled() 68 True 69 # discarding all the item matching int(1) in the stack 70 >>> stack.discard(1) 71 >>> item3 is stack.pop() 72 True 73 >>> item2 is stack.pop() 74 True 75 >>> item1 is stack.pop() 76 True 77 >>> stack.pop() 78 ValueError exception (stack is empty) 79 >>> stack.is_filled() 80 False 81 >>> del stack 82 83 """ 84
85 - def __init__(self):
86 """ Lifo class constructor """ 87 object.__init__(self) 88 self.__buf = deque()
89
90 - def __nonzero__(self):
91 """ 92 Return if stack is empty. 93 """ 94 return len(self.__buf) != 0
95
96 - def __len__(self):
97 """ 98 Return stack size. 99 """ 100 return len(self.__buf)
101
102 - def push(self, item):
103 """ 104 Push an object into the stack. 105 106 @param item: any Python object 107 @type item: Python object 108 @return: None 109 @rtype: None 110 """ 111 self.__buf.append(item)
112
113 - def insert(self, item):
114 """ 115 Insert item at the bottom of the stack. 116 117 @param item: any Python object 118 @type item: Python object 119 @return: None 120 @rtype: None 121 """ 122 self.__buf.appendleft(item)
123
124 - def clear(self):
125 """ 126 Clear the stack. 127 128 @return: None 129 @rtype: None 130 """ 131 self.__buf.clear()
132
133 - def is_filled(self):
134 """ 135 Tell whether Lifo contains data that can be popped out. 136 137 @return: fill status 138 @rtype: bool 139 """ 140 if self.__buf: 141 return True 142 return False
143
144 - def discard(self, entry):
145 """ 146 Remove given object from stack. Any matching object, 147 through identity and == comparison will be removed. 148 149 @param entry: object in stack 150 @type entry: any Python object 151 @return: None 152 @rtype: None 153 """ 154 indexes = [] 155 while True: 156 try: 157 self.__buf.remove(entry) 158 except ValueError: 159 break
160
161 - def pop(self):
162 """ 163 Pop the uppermost item of the stack out of it. 164 165 @return: object stored in the stack 166 @rtype: any Python object 167 @raise ValueError: if stack is empty 168 """ 169 try: 170 return self.__buf.pop() 171 except IndexError: 172 raise ValueError("Lifo is empty")
173
174 175 -class TimeScheduled(threading.Thread):
176 177 """ 178 Multithreading class that wraps Python threading.Thread. 179 Specifically, this class implements the timed function execution 180 concept. It means that you can run timed functions (say every N 181 seconds) and control its execution through another (main?) thread. 182 183 It is possible to set arbitrary, variable, delays and decide if to delay 184 before or after the execution of the function provided at construction 185 time. 186 Timed function can be stopped by calling TimeScheduled.kill() method. 187 You may find the example below more exhaustive: 188 189 >>> from entropy.misc import TimeScheduled 190 >>> time_sched = TimeSheduled(5, print, "hello world", 123) 191 >>> time_sched.start() 192 hello world 123 # every 5 seconds 193 hello world 123 # every 5 seconds 194 hello world 123 # every 5 seconds 195 >>> time_sched.kill() 196 197 """ 198
199 - def __init__(self, delay, *args, **kwargs):
200 """ 201 TimeScheduled constructor. 202 203 @param delay: delay in seconds between a function call and another. 204 @type delay: float 205 @param *args: function as first magic arg and its arguments 206 @keyword *kwargs: keyword arguments of the function passed 207 @return: None 208 @rtype: None 209 """ 210 threading.Thread.__init__(self) 211 self.__f = args[0] 212 self.__delay = delay 213 self.__args = args[1:][:] 214 self.__kwargs = kwargs.copy() 215 # never enable this by default 216 # otherwise kill() and thread 217 # check will hang until 218 # time.sleep() is done 219 self.__accurate = False 220 self.__delay_before = False 221 self.__alive = 0 222 self.__paused = False 223 self.__paused_delay = 2 224 self.__state_sem = threading.Semaphore(0) 225 self.__killed = False 226 self.__kill_status = threading.Lock()
227
228 - def start(self):
229 """ 230 Override Thread.start() to handle the internal 231 state semaphore. 232 """ 233 self.__alive = 1 234 # send the signal to kill, now it can reliably change 235 # self.__alive 236 self.__state_sem.release() 237 return super(TimeScheduled, self).start()
238
239 - def pause(self, pause):
240 """ 241 Pause current internal timer countdown. 242 243 @param pause: True to pause timer 244 @type pause: bool 245 """ 246 self.__paused = pause
247
248 - def set_delay(self, delay):
249 """ 250 Change current delay in seconds. 251 252 @param delay: new delay 253 @type delay: float 254 @return: None 255 @rtype: None 256 """ 257 self.__delay = delay
258
259 - def set_delay_before(self, delay_before):
260 """ 261 Set whether delay before the execution of the function or not. 262 263 @param delay_before: delay before boolean 264 @type delay_before: bool 265 @return: None 266 @rtype: None 267 """ 268 self.__delay_before = bool(delay_before)
269
270 - def set_accuracy(self, accuracy):
271 """ 272 Set whether delay function must be accurate or not. 273 274 @param accuracy: accuracy boolean 275 @type accuracy: bool 276 @return: None 277 @rtype: None 278 """ 279 self.__accurate = bool(accuracy)
280
281 - def run(self):
282 """ 283 This method is called automatically when start() is called. 284 Don't call this directly!!! 285 """ 286 while self.__alive: 287 288 if self.__delay_before: 289 do_break = self.__do_delay() 290 if do_break: 291 break 292 293 if self.__f == None: 294 break 295 try: 296 self.__f(*self.__args, **self.__kwargs) 297 except KeyboardInterrupt: 298 break 299 300 if not self.__delay_before: 301 do_break = self.__do_delay() 302 if do_break: 303 break
304
305 - def __do_delay(self):
306 """ Executes the delay """ 307 while self.__paused: 308 if time == None: 309 return True 310 time.sleep(self.__paused_delay) 311 312 if not self.__accurate: 313 314 if float == None: 315 return True 316 mydelay = float(self.__delay) 317 t_frac = 0.3 318 while mydelay > 0.0: 319 if not self.__alive: 320 return True 321 if time == None: 322 return True # shut down? 323 time.sleep(t_frac) 324 mydelay -= t_frac 325 326 else: 327 328 if time == None: 329 return True # shut down? 330 time.sleep(self.__delay) 331 332 return False
333
334 - def kill(self):
335 """ Stop the execution of the timed function """ 336 if self.__alive == 0: 337 # never started? 338 return 339 with self.__kill_status: 340 if self.__killed: 341 # kill already called 342 return 343 self.__killed = True 344 self.__state_sem.acquire() 345 # at this point run() is called or start() hasn't been called 346 # we're allowed to kill 347 self.__alive = 0
348
349 350 -class DirectoryMonitor:
351 352 """ 353 DirectoryMonitor uses Linux dnotify facility to signal 354 file change events for the monitored directory. 355 However, this class attaches the event callback to SIGIO, 356 thus it is not safe to have multiple instances of it around 357 because there is no real event dispatching. 358 """ 359 360 # A File in the dir has been read 361 DN_ACCESS = fcntl.DN_ACCESS 362 # A File has been modified (w, t) 363 DN_MODIFY = fcntl.DN_MODIFY 364 # A File has been created 365 DN_CREATE = fcntl.DN_CREATE 366 # A File has been deleted 367 DN_DELETE = fcntl.DN_DELETE 368 # A File has been renamed 369 DN_RENAME = fcntl.DN_RENAME 370 # A file has got its attrs changed (perms, ownership) 371 DN_ATTRIB = fcntl.DN_ATTRIB 372 # Keep signaling until the handler is explicitly removed 373 DN_MULTISHOT = fcntl.DN_MULTISHOT 374
375 - def __init__(self, directory_paths, callback, event_flags=None):
376 """ 377 DirectoryMonitor constructor. 378 379 @param directory_paths: list of paths of the directories to monitor 380 @type directory_paths: list 381 @param callback: function called on events. The signature is: 382 void function() 383 @type callback: function 384 @keyword event_flags: specify an alternative flag mask, default is: 385 DN_ACCESS | DN_MODIFY | DN_CREATE | DN_DELETE | DN_RENAME 386 | DN_ATTRIB 387 @type event_flags: int 388 """ 389 self._directory_paths = directory_paths 390 self._signal_id = signal.SIGIO 391 self._callback = callback 392 if event_flags: 393 self._flags = event_flags 394 else: 395 self._flags = self.DN_ACCESS | self.DN_MODIFY | \ 396 self.DN_CREATE | self.DN_DELETE | self.DN_RENAME | \ 397 self.DN_ATTRIB 398 self._fds = [] 399 400 for directory_path in self._directory_paths: 401 fd = os.open(directory_path, os.O_RDONLY) 402 fcntl.fcntl(fd, fcntl.F_NOTIFY, self._flags) 403 self._fds.append(fd) 404 405 def _forward(signum, frame): 406 self._callback()
407 signal.signal(self._signal_id, _forward)
408
409 - def close(self):
410 """ 411 Terminate the listeners and release all the allocated resources. 412 """ 413 if self._fds: 414 signal.signal(self._signal_id, signal.SIG_DFL) 415 for fd in self._fds: 416 os.close(fd)
417
418 419 -class ParallelTask(threading.Thread):
420 421 """ 422 Multithreading class that wraps Python threading.Thread. 423 Specifically, this class makes possible to easily execute a function 424 on a separate thread. 425 426 Python threads can't be stopped, paused or more generically arbitrarily 427 controlled. 428 429 >>> from entropy.misc import ParallelTask 430 >>> parallel = ParallelTask(print, "hello world", 123) 431 >>> parallel.start() 432 hello world 123 433 >>> parallel.kill() 434 435 """ 436
437 - def __init__(self, *args, **kwargs):
438 """ 439 ParallelTask constructor 440 441 Provide a function and its arguments as arguments of this constructor. 442 """ 443 super(ParallelTask, self).__init__() 444 self.__function, self.__args = args[0], args[1:] 445 self.__kwargs = kwargs.copy() 446 self.__rc = None
447
448 - def run(self):
449 """ 450 This method is called automatically when start() is called. 451 Don't call this directly!!! 452 """ 453 self.__rc = self.__function(*self.__args, **self.__kwargs)
454
455 - def get_function(self):
456 """ 457 Return the function passed to constructor that is going to be executed. 458 459 @return: parallel function 460 @rtype: Python callable object 461 """ 462 return self.__function
463
464 - def get_rc(self):
465 """ 466 Return result of the last parallel function call passed to constructor. 467 468 @return: parallel function result 469 @rtype: Python object 470 """ 471 return self.__rc
472
473 474 -class ReadersWritersSemaphore(object):
475 476 """ 477 A simple Readers Writers Lock object. 478 Inspired by: 479 http://code.activestate.com/recipes/\ 480 577803-reader-writer-lock-with-priority-for-writers/ 481 and by: Mateusz Kobos 482 """ 483
484 - class SemaphoreWrapper(object):
485
486 - def __init__(self):
487 self.__counter = 0 488 self.__mutex = threading.Lock()
489
490 - def acquire(self, lock):
491 with self.__mutex: 492 self.__counter += 1 493 if self.__counter == 1: 494 lock.acquire()
495
496 - def try_acquire(self, lock):
497 with self.__mutex: 498 self.__counter += 1 499 acquired = True 500 if self.__counter == 1: 501 acquired = lock.acquire(False) 502 if not acquired: 503 self.__counter -= 1 504 return acquired
505
506 - def release(self, lock):
507 with self.__mutex: 508 self.__counter -= 1 509 if self.__counter == 0: 510 lock.release()
511
512 - def __init__(self):
513 self.__read_switch = self.SemaphoreWrapper() 514 self.__write_switch = self.SemaphoreWrapper() 515 self.__no_readers = threading.Semaphore() 516 self.__no_writers = threading.Semaphore() 517 self.__readers_queue = threading.Semaphore()
518
519 - def reader_acquire(self):
520 """ 521 Acquire the Reader end. 522 """ 523 with self.__readers_queue: 524 with self.__no_readers: 525 self.__read_switch.acquire(self.__no_writers)
526
527 - def try_reader_acquire(self):
528 """ 529 Acquire the Reader end in non-blocking mode. 530 """ 531 with self.__readers_queue: 532 acquired = self.__no_readers.acquire(False) 533 if acquired: 534 acquired = self.__read_switch.try_acquire( 535 self.__no_writers) 536 self.__no_readers.release() 537 return acquired
538
539 - def reader_release(self):
540 """ 541 Release the Reader end. 542 """ 543 self.__read_switch.release(self.__no_writers)
544
545 - def writer_acquire(self):
546 """ 547 Acquire the Writer end. 548 """ 549 self.__write_switch.acquire(self.__no_readers) 550 self.__no_writers.acquire()
551
552 - def try_writer_acquire(self):
553 """ 554 Acquire the Writer end in non-blocking mode. 555 """ 556 acquired = self.__write_switch.try_acquire(self.__no_readers) 557 if acquired: 558 acquired = self.__no_writers.acquire(False) 559 if not acquired: 560 self.__write_switch.release(self.__no_readers) 561 return acquired
562
563 - def writer_release(self):
564 """ 565 Release Writer end. 566 """ 567 self.__no_writers.release() 568 self.__write_switch.release(self.__no_readers)
569 570 @contextlib.contextmanager
571 - def reader(self):
572 """ 573 Acquire the Reader end. 574 """ 575 self.reader_acquire() 576 try: 577 yield 578 finally: 579 self.reader_release()
580 581 @contextlib.contextmanager
582 - def writer(self):
583 """ 584 Acquire the Writer end. 585 """ 586 self.writer_acquire() 587 try: 588 yield 589 finally: 590 self.writer_release()
591
592 593 -class FlockFile(object):
594 595 """ 596 Of flock() operations on a file. 597 """ 598
599 - class FlockFileInitFailure(EntropyException):
600 """ 601 FlockFile initialization failure exception. 602 Can be raised either because file path does 603 not exist (missing directory) or permissions 604 are not sufficient. 605 """
606
607 - def __init__(self, file_path, fd = None, fobj = None):
608 self._wait_msg_cb = None 609 self._acquired_msg_cb = None 610 611 self._path = file_path 612 if fobj: 613 self._f = fobj 614 elif fd: 615 self._f = os.fdopen(fd) 616 else: 617 try: 618 self._f = open(self._path, "a+") 619 except IOError as err: 620 if err.errno in (errno.ENOENT, errno.EACCES): 621 raise FlockFile.FlockFileInitFailure(err) 622 raise
623 624 @contextlib.contextmanager
625 - def shared(self):
626 """ 627 Acquire the lock in shared mode (context manager). 628 """ 629 acquired = False 630 try: 631 acquired = self.try_acquire_shared() 632 if not acquired: 633 if self._wait_msg_cb: 634 self._wait_msg_cb(self, False) 635 636 self.acquire_shared() 637 acquired = True 638 639 if self._acquired_msg_cb: 640 self._acquired_msg_cb(self, False) 641 642 yield 643 644 finally: 645 if acquired: 646 self.release()
647 648 @contextlib.contextmanager
649 - def exclusive(self):
650 """ 651 Acquire the lock in exclusive mode. 652 """ 653 acquired = False 654 try: 655 acquired = self.try_acquire_exclusive() 656 if not acquired: 657 if self._wait_msg_cb: 658 self._wait_msg_cb(self, True) 659 660 self.acquire_exclusive() 661 acquired = True 662 663 if self._acquired_msg_cb: 664 self._acquired_msg_cb(self, True) 665 666 yield 667 668 finally: 669 if acquired: 670 self.release()
671
672 - def acquire_shared(self):
673 """ 674 Acquire the lock in shared mode. 675 """ 676 flags = fcntl.LOCK_SH 677 while True: 678 try: 679 fcntl.flock(self._f.fileno(), flags) 680 except (IOError, OSError) as err: 681 if err.errno == errno.EINTR: 682 # interrupted system call 683 continue 684 self.close() 685 raise 686 break
687
688 - def try_acquire_shared(self):
689 """ 690 Acquire the lock in shared mode, non blocking. 691 692 @return: True, if lock acquired. 693 @rtype: bool 694 """ 695 flags = fcntl.LOCK_SH | fcntl.LOCK_NB 696 try: 697 fcntl.flock(self._f.fileno(), flags) 698 except (IOError, OSError) as err: 699 if err.errno == errno.EINTR: 700 return False 701 if err.errno not in (errno.EACCES, errno.EAGAIN,): 702 # ouch, wtf? 703 self.close() 704 raise 705 return False 706 return True
707
708 - def acquire_exclusive(self):
709 """ 710 Acquire the lock in exclusive mode. 711 """ 712 flags = fcntl.LOCK_EX 713 while True: 714 try: 715 fcntl.flock(self._f.fileno(), flags) 716 except (IOError, OSError) as err: 717 if err.errno == errno.EINTR: 718 # interrupted system call 719 continue 720 self.close() 721 raise 722 break
723
724 - def try_acquire_exclusive(self):
725 """ 726 Acquire the lock in exclusive mode, non blocking. 727 728 @return: True, if lock acquired. 729 @rtype: bool 730 """ 731 flags = fcntl.LOCK_EX | fcntl.LOCK_NB 732 try: 733 fcntl.flock(self._f.fileno(), flags) 734 except (IOError, OSError) as err: 735 if err.errno == errno.EINTR: 736 return False 737 if err.errno not in (errno.EACCES, errno.EAGAIN,): 738 # ouch, wtf? 739 self.close() 740 raise 741 return False 742 return True
743
744 - def promote(self):
745 """ 746 Promote a lock acquired in shared mode to exclusive mode. 747 """ 748 self.acquire_shared() 749 self.acquire_exclusive()
750
751 - def try_promote(self):
752 """ 753 Promote a lock acquired in shared mode to exclusive mode, 754 non blocking. 755 """ 756 acquired = self.try_acquire_shared() 757 if not acquired: 758 return False 759 acquired = self.try_acquire_exclusive() 760 if not acquired: 761 return False 762 return True
763
764 - def demote(self):
765 """ 766 Demote a lock acquired in exclusive mode to shared mode. 767 """ 768 self.release() 769 self.acquire_shared()
770
771 - def release(self):
772 """ 773 Release the lock previously acquired. 774 """ 775 fcntl.flock(self._f.fileno(), fcntl.LOCK_UN)
776
777 - def get_path(self):
778 """ 779 Return the file path associated with this instance. 780 """ 781 return self._path
782
783 - def get_file(self):
784 """ 785 Get the underlying File Object. 786 Use at your own risk. 787 """ 788 return self._f
789
790 - def close(self):
791 """ 792 Close the underlying file object. 793 """ 794 self._f.close()
795
796 797 -class EmailSender:
798 799 """ 800 This class implements a very simple e-mail (through SMTP) sender. 801 It is used by the User Generated Content interface and something more. 802 803 You can swap the sender function at runtime, by redefining 804 EmailSender.default_sender. By default, default_sender is set to 805 EmailSender.smtp_send. 806 807 Sample code: 808 809 >>> sender = EmailSender() 810 >>> sender.send_text_email("[email protected]", ["[email protected]"], "hello!", 811 "this is the content") 812 ... 813 >>> sender = EmailSender() 814 >>> sender.send_mime_email("[email protected]", ["[email protected]"], "hello!", 815 "this is the content", ["/path/to/file1", "/path/to/file2"]) 816 817 """ 818
819 - def __init__(self):
820 821 """ EmailSender constructor """ 822 823 import smtplib 824 self.smtplib = smtplib 825 from email.mime.audio import MIMEAudio 826 from email.mime.image import MIMEImage 827 from email.mime.text import MIMEText 828 from email.mime.base import MIMEBase 829 from email.mime.multipart import MIMEMultipart 830 from email import encoders 831 from email.message import Message 832 import mimetypes 833 self.smtpuser = None 834 self.smtppassword = None 835 self.smtphost = 'localhost' 836 self.smtpport = 25 837 self.text = MIMEText 838 self.mimefile = MIMEBase 839 self.audio = MIMEAudio 840 self.image = MIMEImage 841 self.multipart = MIMEMultipart 842 self.default_sender = self.smtp_send 843 self.mimetypes = mimetypes 844 self.encoders = encoders 845 self.message = Message
846
847 - def smtp_send(self, sender, destinations, message):
848 """ 849 This is the default method for sending emails. 850 It uses Python's smtplib module. 851 You should not use this function directly. 852 853 @param sender: sender email address 854 @type sender: string 855 @param destinations: list of recipients 856 @type destinations: list of string 857 @param message: message to send 858 @type message: string 859 860 @return: None 861 @rtype: None 862 """ 863 s_srv = self.smtplib.SMTP(self.smtphost, self.smtpport) 864 if self.smtpuser and self.smtppassword: 865 s_srv.login(self.smtpuser, self.smtppassword) 866 s_srv.sendmail(sender, destinations, message) 867 s_srv.quit()
868
869 - def send_text_email(self, sender_email, destination_emails, subject, 870 content):
871 """ 872 This method exposes an easy way to send textual emails. 873 874 @param sender_email: sender email address 875 @type sender_email: string 876 @param destination_emails: list of recipients 877 @type destination_emails: list 878 @param subject: email subject 879 @type subject: string 880 @param content: email content 881 @type content: string 882 883 @return: None 884 @rtype: None 885 """ 886 # Create a text/plain message 887 if not const_is_python3(): 888 if const_isunicode(content): 889 content = content.encode('utf-8') 890 if const_isunicode(subject): 891 subject = subject.encode('utf-8') 892 else: 893 if not const_isunicode(content): 894 raise AttributeError("content must be unicode (str)") 895 if not const_isunicode(subject): 896 raise AttributeError("subject must be unicode (str)") 897 898 msg = self.text(content) 899 msg['Subject'] = subject 900 msg['From'] = sender_email 901 msg['To'] = ', '.join(destination_emails) 902 return self.default_sender(sender_email, destination_emails, 903 msg.as_string())
904
905 - def send_mime_email(self, sender_email, destination_emails, subject, 906 content, files):
907 """ 908 This method exposes an easy way to send complex emails (with 909 attachments). 910 911 @param sender_email: sender email address 912 @type sender_email: string 913 @param destination_emails: list of recipients 914 @type destination_emails: list of string 915 @param subject: email subject 916 @type subject: string 917 @param content: email content 918 @type content: string 919 @param files: list of valid file paths 920 @type files: list 921 922 @return: None 923 @rtype: None 924 """ 925 outer = self.multipart() 926 outer['Subject'] = subject 927 outer['From'] = sender_email 928 outer['To'] = ', '.join(destination_emails) 929 outer.preamble = subject 930 931 # Create a text/plain message 932 if not const_is_python3(): 933 if const_isunicode(content): 934 content = content.encode('utf-8') 935 if const_isunicode(subject): 936 subject = subject.encode('utf-8') 937 else: 938 if not const_isunicode(content): 939 raise AttributeError("content must be unicode (str)") 940 if not const_isunicode(subject): 941 raise AttributeError("subject must be unicode (str)") 942 943 mymsg = self.text(content) 944 outer.attach(mymsg) 945 946 # attach files 947 for myfile in files: 948 949 try: 950 with open(myfile, "r") as my_f: 951 pass 952 except (OSError, IOError): 953 continue 954 955 ctype, encoding = self.mimetypes.guess_type(myfile) 956 if ctype is None or encoding is not None: 957 ctype = 'application/octet-stream' 958 maintype, subtype = ctype.split('/', 1) 959 960 if maintype == 'image': 961 img_f = open(myfile, "rb") 962 msg = self.image(img_f.read(), _subtype = subtype) 963 img_f.close() 964 elif maintype == 'audio': 965 audio_f = open(myfile, "rb") 966 msg = self.audio(audio_f.read(), _subtype = subtype) 967 audio_f.close() 968 else: 969 gen_f = open(myfile, "rb") 970 msg = self.mimefile(maintype, subtype) 971 msg.set_payload(gen_f.read()) 972 gen_f.close() 973 self.encoders.encode_base64(msg) 974 975 msg.add_header('Content-Disposition', 'attachment', 976 filename = os.path.basename(myfile)) 977 outer.attach(msg) 978 979 composed = outer.as_string() 980 return self.default_sender(sender_email, destination_emails, composed)
981
982 983 -class RSS:
984 985 """ 986 987 This is a base class for handling RSS (XML) files through Python's 988 xml.dom.minidom module. It produces 100% W3C-complaint code. 989 990 This class is meant to be used inside the Entropy world, it's not meant 991 for other tasks outside this codebase. 992 993 """ 994
995 - def __init__(self, filename, title, description, maxentries = 100):
996 997 """ 998 RSS constructor 999 1000 @param filename: RSS file path (a new file will be created if not found) 1001 @type filename: string 1002 @param title: RSS feed title (used for new RSS files) 1003 @type title: string 1004 @param description: RSS feed description (used for new RSS files) 1005 @type description: string 1006 @keyword maxentries: max RSS feed entries 1007 @type maxentries: int 1008 """ 1009 1010 from entropy.core.settings.base import SystemSettings 1011 self.__system_settings = SystemSettings() 1012 self.__feed_title = title 1013 self.__feed_title = self.__feed_title.strip() 1014 self.__feed_description = description 1015 self.__feed_language = "en-EN" 1016 self.__srv_settings_plugin_id = \ 1017 etpConst['system_settings_plugins_ids']['server_plugin'] 1018 srv_settings = self.__system_settings.get(self.__srv_settings_plugin_id) 1019 if srv_settings is None: 1020 self.__feed_editor = "N/A" 1021 else: 1022 self.__feed_editor = srv_settings['server']['rss']['editor'] 1023 self.__feed_copyright = "%s - (C) %s" % ( 1024 self.__system_settings['system']['name'], 1025 entropy.tools.get_year(), 1026 ) 1027 1028 self.__title = self.__feed_title 1029 self.__description = self.__feed_description 1030 self.__language = self.__feed_language 1031 self.__cright = self.__feed_copyright 1032 self.__editor = self.__feed_editor 1033 1034 sys_set = self.__system_settings.get(self.__srv_settings_plugin_id) 1035 if sys_set is None: 1036 self.__link = etpConst['rss-website-url'] 1037 else: 1038 srv_set = sys_set['server'] 1039 self.__link = srv_set['rss']['website_url'] 1040 1041 self.__file = filename 1042 self.__items = {} 1043 self.__itemscounter = 0 1044 self.__maxentries = maxentries 1045 from xml.dom import minidom 1046 self.minidom = minidom 1047 1048 if not os.path.isfile(self.__file): 1049 return 1050 1051 try: 1052 self.xmldoc = self.minidom.parse(self.__file) 1053 except Exception: 1054 entropy.tools.print_traceback() 1055 return 1056 1057 rssdocs = self.xmldoc.getElementsByTagName("rss") 1058 if not rssdocs: 1059 return 1060 1061 channels = rssdocs[0].getElementsByTagName("channel") 1062 if not channels: 1063 return 1064 1065 channel = channels[0] 1066 1067 title_obj = channel.getElementsByTagName("title")[0] 1068 self.__title = title_obj.firstChild.data.strip() 1069 1070 link_obj = channel.getElementsByTagName("link")[0] 1071 self.__link = link_obj.firstChild.data.strip() 1072 1073 desc_obj = channel.getElementsByTagName("description")[0] 1074 description = desc_obj.firstChild 1075 1076 if hasattr(description, "data"): 1077 self.__description = description.data.strip() 1078 else: 1079 self.__description = '' 1080 1081 try: 1082 lang_obj = channel.getElementsByTagName("language")[0] 1083 self.__language = lang_obj.firstChild.data.strip() 1084 except IndexError: 1085 self.__language = 'en' 1086 1087 try: 1088 cright_obj = channel.getElementsByTagName("copyright")[0] 1089 self.__cright = cright_obj.firstChild.data.strip() 1090 except IndexError: 1091 self.__cright = '' 1092 1093 try: 1094 e_obj = channel.getElementsByTagName("managingEditor")[0] 1095 self.__editor = e_obj.firstChild.data.strip() 1096 except IndexError: 1097 self.__editor = '' 1098 1099 entries = channel.getElementsByTagName("item") 1100 self.__itemscounter = len(entries) 1101 if self.__itemscounter > self.__maxentries: 1102 self.__itemscounter = self.__maxentries 1103 mycounter = self.__itemscounter 1104 1105 for item in entries: 1106 if mycounter == 0: # max entries reached 1107 break 1108 mycounter -= 1 1109 self.__items[mycounter] = {} 1110 title_obj = item.getElementsByTagName("title")[0] 1111 self.__items[mycounter]['title'] = \ 1112 title_obj.firstChild.data.strip() 1113 desc_obj = item.getElementsByTagName("description") 1114 description = None 1115 if desc_obj: 1116 description = desc_obj[0].firstChild 1117 if description: 1118 self.__items[mycounter]['description'] = \ 1119 description.data.strip() 1120 else: 1121 self.__items[mycounter]['description'] = "" 1122 1123 link = item.getElementsByTagName("link")[0].firstChild 1124 if link: 1125 self.__items[mycounter]['link'] = link.data.strip() 1126 else: 1127 self.__items[mycounter]['link'] = "" 1128 1129 guid_obj = item.getElementsByTagName("guid")[0] 1130 self.__items[mycounter]['guid'] = \ 1131 guid_obj.firstChild.data.strip() 1132 pub_date_obj = item.getElementsByTagName("pubDate")[0] 1133 self.__items[mycounter]['pubDate'] = \ 1134 pub_date_obj.firstChild.data.strip() 1135 dcs = item.getElementsByTagName("dc:creator") 1136 if dcs: 1137 self.__items[mycounter]['dc:creator'] = \ 1138 dcs[0].firstChild.data.strip()
1139 1140
1141 - def add_item(self, title, link = '', description = '', pubDate = ''):
1142 """ 1143 Add new entry to RSS feed. 1144 1145 @param title: entry title 1146 @type title: string 1147 @keyword link: entry link 1148 @type link: string 1149 @keyword description: entry description 1150 @type description: string 1151 @keyword pubDate: entry publication date 1152 @type pubDate: string 1153 """ 1154 1155 self.__itemscounter += 1 1156 self.__items[self.__itemscounter] = {} 1157 self.__items[self.__itemscounter]['title'] = title 1158 if pubDate: 1159 self.__items[self.__itemscounter]['pubDate'] = pubDate 1160 else: 1161 self.__items[self.__itemscounter]['pubDate'] = \ 1162 time.strftime("%a, %d %b %Y %X +0000") 1163 self.__items[self.__itemscounter]['description'] = description 1164 self.__items[self.__itemscounter]['link'] = link 1165 if link: 1166 self.__items[self.__itemscounter]['guid'] = link 1167 else: 1168 myguid = self.__system_settings['system']['name'].lower() 1169 myguid = myguid.replace(" ", "") 1170 self.__items[self.__itemscounter]['guid'] = myguid+"~" + \ 1171 description + str(self.__itemscounter) 1172 return self.__itemscounter
1173
1174 - def remove_entry(self, key):
1175 """ 1176 Remove entry from RSS feed through its index number. 1177 1178 @param key: entry index number. 1179 @type key: int 1180 @return: new entry count 1181 @rtype: int 1182 """ 1183 if key in self.__items: 1184 del self.__items[key] 1185 self.__itemscounter -= 1 1186 return self.__itemscounter
1187
1188 - def get_entries(self):
1189 """ 1190 Get entries and their total number. 1191 1192 @return: tuple composed by items (list of dict) and total items count 1193 @rtype: tuple 1194 """ 1195 return self.__items, self.__itemscounter
1196
1197 - def write_changes(self, reverse = True):
1198 """ 1199 Writes changes to file. 1200 1201 @keyword reverse: write entries in reverse order. 1202 @type reverse: bool 1203 @return: None 1204 @rtype: None 1205 """ 1206 1207 # filter entries to fit in maxentries 1208 if self.__itemscounter > self.__maxentries: 1209 tobefiltered = self.__itemscounter - self.__maxentries 1210 for index in range(tobefiltered): 1211 try: 1212 del self.__items[index] 1213 except KeyError: 1214 pass 1215 1216 doc = self.minidom.Document() 1217 1218 rss = doc.createElement("rss") 1219 rss.setAttribute("version", "2.0") 1220 rss.setAttribute("xmlns:atom", "http://www.w3.org/2005/Atom") 1221 1222 channel = doc.createElement("channel") 1223 1224 # title 1225 title = doc.createElement("title") 1226 title_text = doc.createTextNode(self.__title) 1227 title.appendChild(title_text) 1228 channel.appendChild(title) 1229 # link 1230 link = doc.createElement("link") 1231 link_text = doc.createTextNode(self.__link) 1232 link.appendChild(link_text) 1233 channel.appendChild(link) 1234 # description 1235 description = doc.createElement("description") 1236 desc_text = doc.createTextNode(self.__description) 1237 description.appendChild(desc_text) 1238 channel.appendChild(description) 1239 # language 1240 language = doc.createElement("language") 1241 lang_text = doc.createTextNode(self.__language) 1242 language.appendChild(lang_text) 1243 channel.appendChild(language) 1244 # copyright 1245 cright = doc.createElement("copyright") 1246 cr_text = doc.createTextNode(self.__cright) 1247 cright.appendChild(cr_text) 1248 channel.appendChild(cright) 1249 # managingEditor 1250 managing_editor = doc.createElement("managingEditor") 1251 ed_text = doc.createTextNode(self.__editor) 1252 managing_editor.appendChild(ed_text) 1253 channel.appendChild(managing_editor) 1254 1255 keys = list(self.__items.keys()) 1256 if reverse: 1257 keys.reverse() 1258 for key in keys: 1259 1260 # sanity check, you never know 1261 if key not in self.__items: 1262 self.remove_entry(key) 1263 continue 1264 k_error = False 1265 for item in ('title', 'link', 'guid', 'description', 'pubDate',): 1266 if item not in self.__items[key]: 1267 k_error = True 1268 break 1269 if k_error: 1270 self.remove_entry(key) 1271 continue 1272 1273 # item 1274 item = doc.createElement("item") 1275 # title 1276 item_title = doc.createElement("title") 1277 item_title_text = doc.createTextNode( 1278 self.__items[key]['title']) 1279 item_title.appendChild(item_title_text) 1280 item.appendChild(item_title) 1281 # link 1282 item_link = doc.createElement("link") 1283 item_link_text = doc.createTextNode( 1284 self.__items[key]['link']) 1285 item_link.appendChild(item_link_text) 1286 item.appendChild(item_link) 1287 # guid 1288 item_guid = doc.createElement("guid") 1289 item_guid.setAttribute("isPermaLink", "true") 1290 item_guid_text = doc.createTextNode( 1291 self.__items[key]['guid']) 1292 item_guid.appendChild(item_guid_text) 1293 item.appendChild(item_guid) 1294 # description 1295 item_desc = doc.createElement("description") 1296 item_desc_text = doc.createTextNode( 1297 self.__items[key]['description']) 1298 item_desc.appendChild(item_desc_text) 1299 item.appendChild(item_desc) 1300 # pubdate 1301 item_date = doc.createElement("pubDate") 1302 item_date_text = doc.createTextNode( 1303 self.__items[key]['pubDate']) 1304 item_date.appendChild(item_date_text) 1305 item.appendChild(item_date) 1306 1307 # add item to channel 1308 channel.appendChild(item) 1309 1310 # add channel to rss 1311 rss.appendChild(channel) 1312 doc.appendChild(rss) 1313 enc = etpConst['conf_encoding'] 1314 with codecs.open(self.__file, "w") as rss_f: 1315 rss_f.writelines(doc.toprettyxml(indent=" ")) 1316 rss_f.flush()
1317
1318 1319 -class FastRSS(object):
1320 1321 """ 1322 1323 This is a fast class for handling RSS files through Python's 1324 xml.dom.minidom module. It produces 100% W3C-complaint code. 1325 Any functionality exposed works in O(1) time (apart from commit() 1326 which is O(n)). 1327 """ 1328 1329 BASE_TITLE = "No title" 1330 BASE_DESCRIPTION = "No description" 1331 BASE_EDITOR = "No editor" 1332 BASE_URL = etpConst['distro_website_url'] 1333 MAX_ENTRIES = -1 1334 LANGUAGE = "en-EN" 1335
1336 - def __init__(self, rss_file_path):
1337 """ 1338 RSS constructor 1339 1340 @param rss_file_path: RSS file path (a new file will be created 1341 if not found) 1342 @type rss_file_path: string 1343 """ 1344 from entropy.core.settings.base import SystemSettings 1345 self.__system_settings = SystemSettings() 1346 self.__feed_title = FastRSS.BASE_TITLE 1347 self.__feed_description = FastRSS.BASE_DESCRIPTION 1348 self.__feed_language = FastRSS.LANGUAGE 1349 self.__feed_editor = FastRSS.BASE_EDITOR 1350 self.__system_name = self.__system_settings['system']['name'] 1351 self.__feed_year = entropy.tools.get_year() 1352 self.__title_changed = False 1353 self.__link_updated = False 1354 self.__description_updated = False 1355 self.__language_updated = False 1356 self.__year_updated = False 1357 self.__editor_updated = False 1358 self.__doc = None 1359 1360 self.__file = rss_file_path 1361 self.__items = [] 1362 self.__itemscounter = 0 1363 self.__maxentries = FastRSS.MAX_ENTRIES 1364 self.__link = FastRSS.BASE_URL 1365 1366 from xml.dom import minidom 1367 self.__minidom = minidom 1368 1369 newly_created = True 1370 try: 1371 with open(self.__file, "r") as f: 1372 newly_created = False 1373 except (OSError, IOError): 1374 pass 1375 self.__newly_created = newly_created
1376
1377 - def is_new(self):
1378 """ 1379 Return whether the file has been newly created or not 1380 1381 @return: True, if rss is new 1382 @rtype: bool 1383 """ 1384 return self.__newly_created
1385
1386 - def set_title(self, title):
1387 """ 1388 Set feed title 1389 1390 @param title: rss feed title 1391 @type title: string 1392 @return: this instance, for chaining 1393 """ 1394 self.__title_changed = True 1395 self.__feed_title = title 1396 return self
1397
1398 - def set_language(self, language):
1399 """ 1400 Set feed language 1401 1402 @param title: rss language 1403 @type title: string 1404 @return: this instance, for chaining 1405 """ 1406 self.__language_updated = True 1407 self.__feed_language = language 1408 return self
1409
1410 - def set_description(self, description):
1411 """ 1412 Set feed description 1413 1414 @param description: rss feed description 1415 @type description: string 1416 @return: this instance, for chaining 1417 """ 1418 self.__description_updated = True 1419 self.__feed_description = description 1420 return self
1421
1422 - def set_max_entries(self, max_entries):
1423 """ 1424 Set the maximum amount of rss feed entries, -1 for infinity 1425 1426 @param description: rss feed max entries value 1427 @type description: int 1428 @return: this instance, for chaining 1429 """ 1430 self.__maxentries = max_entries 1431 return self
1432
1433 - def set_editor(self, editor):
1434 """ 1435 Set rss feed editor name 1436 1437 @param editor: rss feed editor name 1438 @type editor: string 1439 @return: this instance, for chaining 1440 """ 1441 self.__editor_updated = True 1442 self.__feed_editor = editor 1443 return self
1444
1445 - def set_url(self, url):
1446 """ 1447 Set rss feed url name 1448 1449 @param url: rss feed url 1450 @type url: string 1451 @return: this instance, for chaining 1452 """ 1453 self.__link_updated = True 1454 self.__link = url 1455 return self
1456
1457 - def set_year(self, year):
1458 """ 1459 Set rss feed copyright year 1460 1461 @param url: rss feed copyright year 1462 @type url: string 1463 @return: this instance, for chaining 1464 """ 1465 self.__year_updated = True 1466 self.__feed_year = year 1467 return self
1468
1469 - def append(self, title, link, description, pub_date):
1470 """ 1471 Add new entry 1472 1473 @param title: entry title 1474 @type title: string 1475 @param link: entry link 1476 @type link: string 1477 @param description: entry description 1478 @type description: string 1479 @param pubDate: entry publication date 1480 @type pubDate: string 1481 """ 1482 meta = { 1483 "title": title, 1484 "pubDate": pub_date or time.strftime("%a, %d %b %Y %X +0000"), 1485 "description": description or "", 1486 "link": link or "", 1487 "guid": link or "", 1488 } 1489 self.__items.append(meta)
1490
1491 - def get(self):
1492 """ 1493 Return xml.minidom Document object. 1494 1495 @return: the Document object 1496 @rtype: xml.dom.Document object 1497 """ 1498 if self.__doc is not None: 1499 return self.__doc 1500 1501 is_new = self.is_new() 1502 1503 feed_copyright = "%s - (C) %s" % ( 1504 self.__system_name, self.__feed_year, 1505 ) 1506 1507 if not is_new: 1508 doc = self.__minidom.parse(self.__file) 1509 rss = doc.getElementsByTagName("rss")[0] 1510 channel = doc.getElementsByTagName("channel")[0] 1511 1512 titles = doc.getElementsByTagName("title") 1513 if not titles: 1514 title = doc.createElement("title") 1515 title.appendChild(doc.createTextNode(self.__feed_title)) 1516 channel.appendChild(title) 1517 else: 1518 title = titles[0] 1519 1520 links = doc.getElementsByTagName("link") 1521 if not links: 1522 link = doc.createElement("link") 1523 link.appendChild(doc.createTextNode(self.__link)) 1524 channel.appendChild(link) 1525 else: 1526 link = links[0] 1527 1528 descriptions = doc.getElementsByTagName("description") 1529 if not descriptions: 1530 description = doc.createElement("description") 1531 description.appendChild(doc.createTextNode( 1532 self.__feed_description)) 1533 channel.appendChild(description) 1534 else: 1535 description = descriptions[0] 1536 1537 languages = doc.getElementsByTagName("language") 1538 if not languages: 1539 language = doc.createElement("language") 1540 language.appendChild(doc.createTextNode(self.__feed_language)) 1541 channel.appendChild(language) 1542 else: 1543 language = languages[0] 1544 1545 crights = doc.getElementsByTagName("copyright") 1546 if not crights: 1547 cright = doc.createElement("copyright") 1548 cright.appendChild(doc.createTextNode(feed_copyright)) 1549 channel.appendChild(cright) 1550 else: 1551 cright = crights[0] 1552 1553 editors = doc.getElementsByTagName("managingEditor") 1554 if not editors: 1555 editor = doc.createElement("managingEditor") 1556 editor.appendChild(doc.createTextNode(self.__feed_editor)) 1557 channel.appendChild(editor) 1558 else: 1559 editor = editors[0] 1560 1561 # update title 1562 if self.__title_changed: 1563 title.removeChild(title.firstChild) 1564 title.appendChild(doc.createTextNode(self.__feed_title)) 1565 self.__title_changed = False 1566 1567 # update link 1568 if self.__link_updated: 1569 link.removeChild(link.firstChild) 1570 link.appendChild(doc.createTextNode(self.__link)) 1571 self.__link_updated = False 1572 1573 # update description 1574 if self.__description_updated: 1575 description.removeChild(description.firstChild) 1576 description.appendChild(doc.createTextNode( 1577 self.__feed_description)) 1578 self.__description_updated = False 1579 # update language 1580 if self.__language_updated: 1581 language.removeChild(language.firstChild) 1582 language.appendChild(doc.createTextNode(self.__feed_language)) 1583 self.__language_updated = False 1584 # update copyright 1585 if self.__year_updated: 1586 cright.removeChild(cright.firstChild) 1587 cright.appendChild(doc.createTextNode(feed_copyright)) 1588 self.__year_updated = False 1589 # update managingEditor, if required 1590 if self.__editor_updated: 1591 editor.removeChild(editor.firstChild) 1592 editor.appendChild(doc.createTextNode(self.__feed_editor)) 1593 self.__editor_updated = False 1594 else: 1595 doc = self.__minidom.Document() 1596 rss = doc.createElement("rss") 1597 rss.setAttribute("version", "2.0") 1598 rss.setAttribute("xmlns:atom", "http://www.w3.org/2005/Atom") 1599 channel = doc.createElement("channel") 1600 # title 1601 title = doc.createElement("title") 1602 title.appendChild(doc.createTextNode(self.__feed_title)) 1603 channel.appendChild(title) 1604 # link 1605 link = doc.createElement("link") 1606 link.appendChild(doc.createTextNode(self.__link)) 1607 channel.appendChild(link) 1608 # description 1609 description = doc.createElement("description") 1610 description.appendChild(doc.createTextNode(self.__feed_description)) 1611 channel.appendChild(description) 1612 # language 1613 language = doc.createElement("language") 1614 language.appendChild(doc.createTextNode(self.__feed_language)) 1615 channel.appendChild(language) 1616 # copyright 1617 cright = doc.createElement("copyright") 1618 cright.appendChild(doc.createTextNode(feed_copyright)) 1619 channel.appendChild(cright) 1620 # managingEditor 1621 editor = doc.createElement("managingEditor") 1622 editor.appendChild(doc.createTextNode(self.__feed_editor)) 1623 channel.appendChild(editor) 1624 1625 rss.appendChild(channel) 1626 doc.appendChild(rss) 1627 self.__doc = doc 1628 return doc
1629
1630 - def commit(self):
1631 """ 1632 Commit changes to file 1633 """ 1634 doc = self.get() 1635 channel = doc.getElementsByTagName("channel")[0] 1636 1637 # append new items at the bottom 1638 while self.__items: 1639 meta = self.__items.pop(0) 1640 item = doc.createElement("item") 1641 1642 for key in sorted(meta.keys()): 1643 obj = doc.createElement(key) 1644 obj.appendChild(doc.createTextNode(meta[key])) 1645 item.appendChild(obj) 1646 1647 channel.appendChild(item) 1648 1649 if self.__maxentries > 0: 1650 # drop older ones, from the top 1651 how_many = len(channel.childNodes) 1652 to_remove = how_many - self.__maxentries 1653 while to_remove > 0: 1654 child_nodes = channel.childNodes 1655 if not child_nodes: 1656 break 1657 node = child_nodes[0] 1658 channel.removeChild(node) 1659 node.unlink() 1660 to_remove -= 1 1661 1662 # considering enc == doc.toxml() encoding, cross fingers 1663 enc = etpConst['conf_encoding'] 1664 entropy.tools.atomic_write(self.__file, doc.toxml(), enc) 1665 const_setup_file(self.__file, etpConst['entropygid'], 0o664)
1666
1667 1668 -class LogFile:
1669 1670 """ Entropy simple logging interface, works as file object """ 1671 1672 LEVELS = { 1673 "debug": logging.DEBUG, 1674 "info": logging.INFO, 1675 "warning": logging.WARNING, 1676 "error": logging.ERROR, 1677 "critical": logging.CRITICAL 1678 } 1679 LOG_FORMAT = "%(asctime)s %(levelname)s: %(message)s" 1680 DATE_FORMAT = "[%H:%M:%S %d/%m/%Y %Z]" 1681
1682 - def __init__(self, level = None, filename = None, header = "[LOG]"):
1683 """ 1684 LogFile constructor. 1685 1686 @keyword level: any valid Entropy log level id (0, 1, 2). 1687 0: error logging, 1: normal logging, 2: debug logging 1688 @type level: int 1689 @keyword filename: log file path 1690 @type filename: string 1691 @keyword header: log line header 1692 @type header: string 1693 """ 1694 if level is not None: 1695 logger_level = const_convert_log_level(level) 1696 else: 1697 logger_level = logging.INFO 1698 self.__filename = filename 1699 self.__header = header 1700 1701 self.__logger = logging.getLogger(os.path.basename(self.__filename)) 1702 self.__level = LogFile.LEVELS.get(logger_level) 1703 self.__logger.setLevel(logging.DEBUG) 1704 1705 if self.__filename is not None: 1706 try: 1707 self.__handler = logging.FileHandler(self.__filename) 1708 except (IOError, OSError): 1709 self.__handler = logging.StreamHandler() 1710 else: 1711 self.__handler = logging.StreamHandler() 1712 if self.__level is not None: 1713 self.__handler.setLevel(self.__level) 1714 self.__handler.setFormatter(logging.Formatter(LogFile.LOG_FORMAT, 1715 LogFile.DATE_FORMAT)) 1716 self.__logger.addHandler(self.__handler)
1717
1718 - def __enter__(self):
1719 """ 1720 Just return self, configuration is done in __init__ 1721 """ 1722 return self
1723
1724 - def __exit__(self, exc_type, exc_value, traceback):
1725 """ 1726 Make sure any resource is closed. 1727 """ 1728 self.flush() 1729 self.close()
1730
1731 - def fileno(self):
1732 return self.__handler.stream.fileno()
1733
1734 - def isatty(self):
1735 return False
1736
1737 - def flush(self):
1738 """ Flush log buffer """ 1739 if hasattr(self.__handler, 'flush'): 1740 self.__handler.flush()
1741
1742 - def close(self):
1743 """ Close log file """ 1744 if self.__handler is not None: 1745 if hasattr(self.__handler, 'close'): 1746 self.__handler.close() 1747 self.__logger.removeHandler(self.__handler) 1748 self.__handler = None 1749 self.__logger = None
1750
1751 - def _handler(self, mystr):
1752 """ 1753 Default log file writer. This can be reimplemented. 1754 1755 @param mystr: log string to write 1756 @type mystr: string 1757 @param level: logging level 1758 @type level: string 1759 """ 1760 self.__get_logger() 1761 try: 1762 self.__get_logger()(mystr) 1763 except UnicodeEncodeError: 1764 self.__get_logger()(mystr.encode('utf-8'))
1765
1766 - def log(self, messagetype, level, message):
1767 """ 1768 This is the effective function that LogFile consumers should use. 1769 1770 @param messagetype: message type (or tag) 1771 @type messagetype: string 1772 @param level: minimum logging threshold which should trigger the 1773 effective write 1774 @type level: int 1775 @param message: log message 1776 @type message: string 1777 """ 1778 self._handler("%s %s %s" % (messagetype, self.__header, message,))
1779
1780 - def write(self, mystr):
1781 """ 1782 File object method, write log message to file using the default 1783 handler set (LogFile.default_handler is the default). 1784 1785 @param mystr: log string to write 1786 @type mystr: string 1787 """ 1788 self._handler(mystr)
1789
1790 - def writelines(self, lst):
1791 """ 1792 File object method, write log message strings to file using the default 1793 handler set (LogFile.default_handler is the default). 1794 1795 @param lst: list of strings to write 1796 @type lst: list 1797 """ 1798 for line in lst: 1799 self.write(line)
1800
1801 - def __get_logger(self):
1802 logger_map = { 1803 logging.INFO: self.__logger.info, 1804 logging.WARNING: self.__logger.warning, 1805 logging.DEBUG: self.__logger.debug, 1806 logging.ERROR: self.__logger.error, 1807 logging.CRITICAL: self.__logger.error, 1808 logging.NOTSET: self.__logger.info, 1809 } 1810 return logger_map.get(self.__level, self.__logger.info)
1811
1812 1813 -class Callable:
1814 """ 1815 Fake class wrapping any callable object into a callable class. 1816 """
1817 - def __init__(self, anycallable):
1818 """ 1819 Callable constructor. 1820 1821 @param anycallable: any callable object 1822 @type callable: callable 1823 """ 1824 self.__call__ = anycallable
1825
1826 -class MultipartPostHandler(UrllibBaseHandler):
1827 1828 """ 1829 Custom urllib2 opener used in the Entropy codebase. 1830 """ 1831 1832 # needs to run first 1833 if const_is_python3(): 1834 handler_order = urllib.request.HTTPHandler.handler_order - 10 1835 else: 1836 handler_order = urllib2.HTTPHandler.handler_order - 10 1837
1838 - def __init__(self):
1839 """ 1840 MultipartPostHandler constructor. 1841 """ 1842 pass
1843
1844 - def http_request(self, request):
1845 1846 """ 1847 Entropy codebase internal method. Not for re-use. 1848 1849 @param request: urllib2 HTTP request object 1850 """ 1851 1852 doseq = 1 1853 1854 data = request.get_data() 1855 if data is not None and not isinstance(data, str): 1856 v_files = [] 1857 v_vars = [] 1858 try: 1859 for (key, value) in list(data.items()): 1860 if const_isfileobj(value): 1861 v_files.append((key, value)) 1862 else: 1863 v_vars.append((key, value)) 1864 except TypeError: 1865 raise TypeError("not a valid non-string sequence" \ 1866 " or mapping object") 1867 1868 if len(v_files) == 0: 1869 if const_is_python3(): 1870 data = urllib.parse.urlencode(v_vars, doseq) 1871 else: 1872 data = urllib.urlencode(v_vars, doseq) 1873 else: 1874 boundary, data = self.multipart_encode(v_vars, v_files) 1875 contenttype = 'multipart/form-data; boundary=%s' % boundary 1876 request.add_unredirected_header('Content-Type', contenttype) 1877 1878 request.add_data(data) 1879 return request
1880
1881 - def multipart_encode(self, myvars, files, boundary = None, buf = None):
1882 1883 """ 1884 Does the effective multipart mime encoding. Entropy codebase internal 1885 method. Not for re-use. 1886 """ 1887 1888 from io import StringIO 1889 import mimetools, mimetypes 1890 #import stat 1891 1892 if boundary is None: 1893 boundary = mimetools.choose_boundary() 1894 if buf is None: 1895 buf = StringIO() 1896 for(key, value) in myvars: 1897 buf.write('--%s\r\n' % boundary) 1898 buf.write('Content-Disposition: form-data; name="%s"' % key) 1899 buf.write('\r\n\r\n' + value + '\r\n') 1900 for(key, fdesc) in files: 1901 #file_size = os.fstat(fdesc.fileno())[stat.ST_SIZE] 1902 filename = fdesc.name.split('/')[-1] 1903 contenttype = mimetypes.guess_type(filename)[0] or \ 1904 'application/octet-stream' 1905 buf.write('--%s\r\n' % boundary) 1906 buf.write('Content-Disposition: form-data; name="%s"; ' \ 1907 'filename="%s"\r\n' % (key, filename)) 1908 buf.write('Content-Type: %s\r\n' % contenttype) 1909 # buffer += 'Content-Length: %s\r\n' % file_size 1910 fdesc.seek(0) 1911 buf.write('\r\n' + fdesc.read() + '\r\n') 1912 buf.write('--' + boundary + '--\r\n\r\n') 1913 buf = buf.getvalue() 1914 return boundary, buf
1915 1916 multipart_encode = Callable(multipart_encode) 1917 1918 https_request = http_request
1919