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