Package entropy :: Module misc

Source Code for Module entropy.misc

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3   
   4      @author: Fabio Erculiani <lxnay@sabayonlinux.org> 
   5      @contact: lxnay@sabayonlinux.org 
   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  if sys.hexversion >= 0x3000000: 
  19      import urllib.request, urllib.error, urllib.parse 
  20      UrllibBaseHandler = urllib.request.BaseHandler 
  21  else: 
  22      import urllib2 
  23      UrllibBaseHandler = urllib2.BaseHandler 
  24  import threading 
  25  from entropy.const import etpConst, etpUi, const_isunicode, const_isstring, \ 
  26      const_isfileobj 
  27  from entropy.core.settings.base import SystemSettings 
  28   
29 -class Lifo(object):
30 31 """ 32 33 This class can be used to build LIFO buffers, also commonly 34 known as "stacks". I{Lifo} allows you to store and retrieve 35 Python objects from its stack, in a very smart way. 36 This implementation is much faster than the one provided 37 by Python (queue module) and more sofisticated. 38 39 Sample code: 40 41 >>> # load Lifo 42 >>> from entropy.misc import Lifo 43 >>> stack = Lifo() 44 >>> item1 = set([1,2,3]) 45 >>> item2 = ["a","b", "c"] 46 >>> item3 = None 47 >>> item4 = 1 48 >>> stack.push(item4) 49 >>> stack.push(item3) 50 >>> stack.push(item2) 51 >>> stack.push(item1) 52 >>> stack.is_filled() 53 True 54 # discarding all the item matching int(1) in the stack 55 >>> stack.discard(1) 56 >>> item3 is stack.pop() 57 True 58 >>> item2 is stack.pop() 59 True 60 >>> item1 is stack.pop() 61 True 62 >>> stack.pop() 63 ValueError exception (stack is empty) 64 >>> stack.is_filled() 65 False 66 >>> del stack 67 68 """ 69
70 - def __init__(self):
71 """ Lifo class constructor """ 72 object.__init__(self) 73 self.__buf = {}
74
75 - def __nonzero__(self):
76 """ 77 Return if stack is empty. 78 """ 79 return len(self.__buf) != 0
80
81 - def __len__(self):
82 """ 83 Return stack size. 84 """ 85 return len(self.__buf)
86
87 - def push(self, item):
88 """ 89 Push an object into the stack. 90 91 @param item: any Python object 92 @type item: Python object 93 @return: None 94 @rtype: None 95 """ 96 try: 97 idx = max(self.__buf)+1 98 except ValueError: 99 idx = 0 100 self.__buf[idx] = item
101
102 - def insert(self, item):
103 """ 104 Insert item at the bottom of the stack. 105 106 @param item: any Python object 107 @type item: Python object 108 @return: None 109 @rtype: None 110 """ 111 try: 112 idx = min(self.__buf)-1 113 except ValueError: 114 idx = -1 115 self.__buf[idx] = item
116
117 - def clear(self):
118 """ 119 Clear the stack. 120 121 @return: None 122 @rtype: None 123 """ 124 self.__buf.clear()
125
126 - def is_filled(self):
127 """ 128 Tell whether Lifo contains data that can be popped out. 129 130 @return: fill status 131 @rtype: bool 132 """ 133 if self.__buf: 134 return True 135 return False
136
137 - def discard(self, entry):
138 """ 139 Remove given object from stack. Any matching object, 140 through identity and == comparison will be removed. 141 142 @param entry: object in stack 143 @type entry: any Python object 144 @return: None 145 @rtype: None 146 """ 147 for key, buf_entry in self.__buf.items(): 148 # identity is generally faster, so try 149 # this first 150 if self.__buf is None: # shutting down py 151 break 152 if entry is buf_entry: 153 self.__buf.pop(key) 154 continue 155 if entry == buf_entry: 156 self.__buf.pop(key) 157 continue
158
159 - def pop(self):
160 """ 161 Pop the uppermost item of the stack out of it. 162 163 @return: object stored in the stack 164 @rtype: any Python object 165 @raise ValueError: if stack is empty 166 """ 167 try: 168 idx = max(self.__buf) 169 except (ValueError, TypeError,): 170 raise ValueError("Lifo is empty") 171 try: 172 return self.__buf.pop(idx) 173 except (KeyError, TypeError,): 174 raise ValueError("Lifo is empty")
175 176
177 -class TimeScheduled(threading.Thread):
178 179 """ 180 Multithreading class that wraps Python threading.Thread. 181 Specifically, this class implements the timed function execution 182 concept. It means that you can run timed functions (say every N 183 seconds) and control its execution through another (main?) thread. 184 185 It is possible to set arbitrary, variable, delays and decide if to delay 186 before or after the execution of the function provided at construction 187 time. 188 Timed function can be stopped by calling TimeScheduled.kill() method. 189 You may find the example below more exhaustive: 190 191 >>> from entropy.misc import TimeScheduled 192 >>> time_sched = TimeSheduled(5, print, "hello world", 123) 193 >>> time_sched.start() 194 hello world 123 # every 5 seconds 195 hello world 123 # every 5 seconds 196 hello world 123 # every 5 seconds 197 >>> time_sched.kill() 198 199 """ 200
201 - def __init__(self, delay, *args, **kwargs):
202 """ 203 TimeScheduled constructor. 204 205 @param delay: delay in seconds between a function call and another. 206 @type delay: float 207 @param *args: function as first magic arg and its arguments 208 @keyword *kwargs: keyword arguments of the function passed 209 @return: None 210 @rtype: None 211 """ 212 threading.Thread.__init__(self) 213 self.__f = args[0] 214 self.__delay = delay 215 self.__args = args[1:][:] 216 self.__kwargs = kwargs.copy() 217 # never enable this by default 218 # otherwise kill() and thread 219 # check will hang until 220 # time.sleep() is done 221 self.__accurate = False 222 self.__delay_before = False 223 self.__alive = 0 224 self.__paused = False 225 self.__paused_delay = 2
226
227 - def pause(self, pause):
228 """ 229 Pause current internal timer countdown. 230 231 @param pause: True to pause timer 232 @type pause: bool 233 """ 234 self.__paused = pause
235
236 - def set_delay(self, delay):
237 """ 238 Change current delay in seconds. 239 240 @param delay: new delay 241 @type delay: float 242 @return: None 243 @rtype: None 244 """ 245 self.__delay = delay
246
247 - def set_delay_before(self, delay_before):
248 """ 249 Set whether delay before the execution of the function or not. 250 251 @param delay_before: delay before boolean 252 @type delay_before: bool 253 @return: None 254 @rtype: None 255 """ 256 self.__delay_before = bool(delay_before)
257
258 - def set_accuracy(self, accuracy):
259 """ 260 Set whether delay function must be accurate or not. 261 262 @param accuracy: accuracy boolean 263 @type accuracy: bool 264 @return: None 265 @rtype: None 266 """ 267 self.__accurate = bool(accuracy)
268
269 - def run(self):
270 """ 271 This method is called automatically when start() is called. 272 Don't call this directly!!! 273 """ 274 self.__alive = 1 275 while self.__alive: 276 277 if self.__delay_before: 278 do_break = self.__do_delay() 279 if do_break: 280 break 281 282 if self.__f == None: 283 break 284 self.__f(*self.__args, **self.__kwargs) 285 286 if not self.__delay_before: 287 do_break = self.__do_delay() 288 if do_break: 289 break
290 291
292 - def __do_delay(self):
293 294 """ Executes the delay """ 295 while self.__paused: 296 if time == None: 297 return True 298 time.sleep(self.__paused_delay) 299 300 if not self.__accurate: 301 302 if float == None: 303 return True 304 mydelay = float(self.__delay) 305 t_frac = 0.3 306 while mydelay > 0.0: 307 if not self.__alive: 308 return True 309 if time == None: 310 return True # shut down? 311 time.sleep(t_frac) 312 mydelay -= t_frac 313 314 else: 315 316 if time == None: 317 return True # shut down? 318 time.sleep(self.__delay) 319 320 return False
321
322 - def kill(self):
323 """ Stop the execution of the timed function """ 324 self.__alive = 0
325
326 -class ParallelTask(threading.Thread):
327 328 """ 329 Multithreading class that wraps Python threading.Thread. 330 Specifically, this class makes possible to easily execute a function 331 on a separate thread. 332 333 Python threads can't be stopped, paused or more generically arbitrarily 334 controlled. 335 336 >>> from entropy.misc import ParallelTask 337 >>> parallel = ParallelTask(print, "hello world", 123) 338 >>> parallel.start() 339 hello world 123 340 >>> parallel.kill() 341 342 """ 343
344 - def __init__(self, *args, **kwargs):
345 """ 346 ParallelTask constructor 347 348 Provide a function and its arguments as arguments of this constructor. 349 """ 350 threading.Thread.__init__(self) 351 self.__function = args[0] 352 self.__args = args[1:][:] 353 self.__kwargs = kwargs.copy() 354 self.__rc = None
355
356 - def run(self):
357 """ 358 This method is called automatically when start() is called. 359 Don't call this directly!!! 360 """ 361 self.__rc = self.__function(*self.__args, **self.__kwargs)
362
363 - def get_function(self):
364 """ 365 Return the function passed to constructor that is going to be executed. 366 367 @return: parallel function 368 @rtype: Python callable object 369 """ 370 return self.__function
371
372 - def get_rc(self):
373 """ 374 Return result of the last parallel function call passed to constructor. 375 376 @return: parallel function result 377 @rtype: Python object 378 """ 379 return self.__rc
380
381 -class EmailSender:
382 383 """ 384 This class implements a very simple e-mail (through SMTP) sender. 385 It is used by the User Generated Content interface and something more. 386 387 You can swap the sender function at runtime, by redefining 388 EmailSender.default_sender. By default, default_sender is set to 389 EmailSender.smtp_send. 390 391 Sample code: 392 393 >>> sender = EmailSender() 394 >>> sender.send_text_email("me@test.com", ["him@test.com"], "hello!", 395 "this is the content") 396 ... 397 >>> sender = EmailSender() 398 >>> sender.send_mime_email("me@test.com", ["him@test.com"], "hello!", 399 "this is the content", ["/path/to/file1", "/path/to/file2"]) 400 401 """ 402
403 - def __init__(self):
404 405 """ EmailSender constructor """ 406 407 import smtplib 408 self.smtplib = smtplib 409 from email.mime.audio import MIMEAudio 410 from email.mime.image import MIMEImage 411 from email.mime.text import MIMEText 412 from email.mime.base import MIMEBase 413 from email.mime.multipart import MIMEMultipart 414 from email import encoders 415 from email.message import Message 416 import mimetypes 417 self.smtpuser = None 418 self.smtppassword = None 419 self.smtphost = 'localhost' 420 self.smtpport = 25 421 self.text = MIMEText 422 self.mimefile = MIMEBase 423 self.audio = MIMEAudio 424 self.image = MIMEImage 425 self.multipart = MIMEMultipart 426 self.default_sender = self.smtp_send 427 self.mimetypes = mimetypes 428 self.encoders = encoders 429 self.message = Message
430
431 - def smtp_send(self, sender, destinations, message):
432 """ 433 This is the default method for sending emails. 434 It uses Python's smtplib module. 435 You should not use this function directly. 436 437 @param sender: sender email address 438 @type sender: string 439 @param destinations: list of recipients 440 @type destinations: list of string 441 @param message: message to send 442 @type message: string 443 444 @return: None 445 @rtype: None 446 """ 447 s_srv = self.smtplib.SMTP(self.smtphost, self.smtpport) 448 if self.smtpuser and self.smtppassword: 449 s_srv.login(self.smtpuser, self.smtppassword) 450 s_srv.sendmail(sender, destinations, message) 451 s_srv.quit()
452
453 - def send_text_email(self, sender_email, destination_emails, subject, 454 content):
455 """ 456 This method exposes an easy way to send textual emails. 457 458 @param sender_email: sender email address 459 @type sender_email: string 460 @param destination_emails: list of recipients 461 @type destination_emails: list 462 @param subject: email subject 463 @type subject: string 464 @param content: email content 465 @type content: string 466 467 @return: None 468 @rtype: None 469 """ 470 # Create a text/plain message 471 if sys.hexversion < 0x3000000: 472 if const_isunicode(content): 473 content = content.encode('utf-8') 474 if const_isunicode(subject): 475 subject = subject.encode('utf-8') 476 else: 477 if not const_isunicode(content): 478 raise AttributeError("content must be unicode (str)") 479 if not const_isunicode(subject): 480 raise AttributeError("subject must be unicode (str)") 481 482 msg = self.text(content) 483 msg['Subject'] = subject 484 msg['From'] = sender_email 485 msg['To'] = ', '.join(destination_emails) 486 return self.default_sender(sender_email, destination_emails, 487 msg.as_string())
488
489 - def send_mime_email(self, sender_email, destination_emails, subject, 490 content, files):
491 """ 492 This method exposes an easy way to send complex emails (with 493 attachments). 494 495 @param sender_email: sender email address 496 @type sender_email: string 497 @param destination_emails: list of recipients 498 @type destination_emails: list of string 499 @param subject: email subject 500 @type subject: string 501 @param content: email content 502 @type content: string 503 @param files: list of valid file paths 504 @type files: list 505 506 @return: None 507 @rtype: None 508 """ 509 outer = self.multipart() 510 outer['Subject'] = subject 511 outer['From'] = sender_email 512 outer['To'] = ', '.join(destination_emails) 513 outer.preamble = subject 514 515 mymsg = self.text(content) 516 outer.attach(mymsg) 517 518 # attach files 519 for myfile in files: 520 if not (os.path.isfile(myfile) and os.access(myfile, os.R_OK)): 521 continue 522 523 ctype, encoding = self.mimetypes.guess_type(myfile) 524 if ctype is None or encoding is not None: 525 ctype = 'application/octet-stream' 526 maintype, subtype = ctype.split('/', 1) 527 528 if maintype == 'image': 529 img_f = open(myfile, 'rb') 530 msg = self.image(img_f.read(), _subtype = subtype) 531 img_f.close() 532 elif maintype == 'audio': 533 audio_f = open(myfile, 'rb') 534 msg = self.audio(audio_f.read(), _subtype = subtype) 535 audio_f.close() 536 else: 537 gen_f = open(myfile, 'rb') 538 msg = self.mimefile(maintype, subtype) 539 msg.set_payload(gen_f.read()) 540 gen_f.close() 541 self.encoders.encode_base64(msg) 542 543 msg.add_header('Content-Disposition', 'attachment', 544 filename = os.path.basename(myfile)) 545 outer.attach(msg) 546 547 composed = outer.as_string() 548 return self.default_sender(sender_email, destination_emails, composed)
549
550 -class EntropyGeoIP:
551 552 """ 553 Entropy geo-tagging interface containing useful methods to ease 554 metadata management and transformation. 555 It's a wrapper over GeoIP at the moment dev-python/geoip-python 556 required. 557 558 Sample code: 559 560 >>> geo = EntropyGeoIp("mygeoipdb.dat") 561 >>> geo.get_geoip_record_from_ip("123.123.123.123") 562 { dict() metadata } 563 564 """ 565
566 - def __init__(self, geoip_dbfile):
567 568 """ 569 EntropyGeoIP constructor. 570 571 @param geoip_dbfile: valid GeoIP (Maxmind) database file (.dat) path 572 (download from: 573 http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz) 574 @type geoip_dbfile: string 575 """ 576 577 import GeoIP 578 self.__geoip = GeoIP 579 # http://www.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz 580 if not (os.path.isfile(geoip_dbfile) and \ 581 os.access(geoip_dbfile, os.R_OK)): 582 raise AttributeError( 583 "expecting a valid filepath for geoip_dbfile, got: %s" % ( 584 repr(geoip_dbfile), 585 ) 586 ) 587 self.__geoip_dbfile = geoip_dbfile
588
589 - def __get_geo_ip_generic(self):
590 """ Private method """ 591 return self.__geoip.new(self.__geoip.GEOIP_MEMORY_CACHE)
592
593 - def __get_geo_ip_open(self):
594 """ Private method """ 595 return self.__geoip.open(self.__geoip_dbfile, 596 self.__geoip.GEOIP_STANDARD)
597
598 - def get_geoip_country_name_from_ip(self, ip_address):
599 """ 600 Get country name from IP address. 601 602 @param ip_address: ip address string 603 @type ip_address: string 604 @return: country name or None 605 @rtype: string or None 606 """ 607 gi_a = self.__get_geo_ip_generic() 608 return gi_a.country_name_by_addr(ip_address)
609
610 - def get_geoip_country_code_from_ip(self, ip_address):
611 """ 612 Get country code from IP address. 613 614 @param ip_address: ip address string 615 @type ip_address: string 616 @return: country code or None 617 @rtype: string or None 618 """ 619 gi_a = self.__get_geo_ip_generic() 620 return gi_a.country_code_by_addr(ip_address)
621
622 - def get_geoip_record_from_ip(self, ip_address):
623 """ 624 Get GeoIP record from IP address. 625 626 @param ip_address: ip address string 627 @type ip_address: string 628 @return: GeoIP record data 629 @rtype: dict 630 """ 631 go_a = self.__get_geo_ip_open() 632 return go_a.record_by_addr(ip_address)
633
634 - def get_geoip_record_from_hostname(self, hostname):
635 """ 636 Get GeoIP record from hostname. 637 638 @param hostname: ip address string 639 @type hostname: string 640 @return: GeoIP record data 641 @rtype: dict 642 """ 643 go_a = self.__get_geo_ip_open() 644 return go_a.record_by_name(hostname)
645 646
647 -class RSS:
648 649 """ 650 651 This is a base class for handling RSS (XML) files through Python's 652 xml.dom.minidom module. It produces 100% W3C-complaint code. 653 654 This class is meant to be used inside the Entropy world, it's not meant 655 for other tasks outside this codebase. 656 657 """ 658 659 # this is a relative import to avoid circular deps 660 import entropy.tools as entropyTools
661 - def __init__(self, filename, title, description, maxentries = 100):
662 663 """ 664 RSS constructor 665 666 @param filename: RSS file path (a new file will be created if not found) 667 @type filename: string 668 @param title: RSS feed title (used for new RSS files) 669 @type title: string 670 @param description: RSS feed description (used for new RSS files) 671 @type description: string 672 @keyword maxentries: max RSS feed entries 673 @type maxentries: int 674 """ 675 676 self.__system_settings = SystemSettings() 677 self.__feed_title = title 678 self.__feed_title = self.__feed_title.strip() 679 self.__feed_description = description 680 self.__feed_language = "en-EN" 681 self.__srv_settings_plugin_id = \ 682 etpConst['system_settings_plugins_ids']['server_plugin'] 683 srv_settings = self.__system_settings.get(self.__srv_settings_plugin_id) 684 if srv_settings is None: 685 self.__feed_editor = "N/A" 686 else: 687 self.__feed_editor = srv_settings['server']['rss']['editor'] 688 self.__feed_copyright = "%s - (C) %s" % ( 689 self.__system_settings['system']['name'], 690 self.entropyTools.get_year(), 691 ) 692 693 self.__file = filename 694 self.__items = {} 695 self.__itemscounter = 0 696 self.__maxentries = maxentries 697 from xml.dom import minidom 698 self.minidom = minidom 699 700 # sanity check 701 broken = False 702 if os.path.isfile(self.__file): 703 try: 704 self.xmldoc = self.minidom.parse(self.__file) 705 except: 706 broken = True 707 708 if not os.path.isfile(self.__file) or broken: 709 710 self.__title = self.__feed_title 711 self.__description = self.__feed_description 712 self.__language = self.__feed_language 713 self.__cright = self.__feed_copyright 714 self.__editor = self.__feed_editor 715 sys_set = self.__system_settings.get(self.__srv_settings_plugin_id) 716 if sys_set is None: 717 self.__link = etpConst['rss-website-url'] 718 else: 719 srv_set = sys_set['server'] 720 self.__link = srv_set['rss']['website_url'] 721 rss_f = open(self.__file, "w") 722 rss_f.write('') 723 rss_f.flush() 724 rss_f.close() 725 726 else: 727 728 self.__rssdoc = self.xmldoc.getElementsByTagName("rss")[0] 729 self.__channel = self.__rssdoc.getElementsByTagName("channel")[0] 730 title_obj = self.__channel.getElementsByTagName("title")[0] 731 self.__title = title_obj.firstChild.data.strip() 732 link_obj = self.__channel.getElementsByTagName("link")[0] 733 self.__link = link_obj.firstChild.data.strip() 734 desc_obj = self.__channel.getElementsByTagName("description")[0] 735 description = desc_obj.firstChild 736 if hasattr(description, "data"): 737 self.__description = description.data.strip() 738 else: 739 self.__description = '' 740 try: 741 lang_obj = self.__channel.getElementsByTagName("language")[0] 742 self.__language = lang_obj.firstChild.data.strip() 743 except IndexError: 744 self.__language = 'en' 745 try: 746 cright_obj = self.__channel.getElementsByTagName("copyright")[0] 747 self.__cright = cright_obj.firstChild.data.strip() 748 except IndexError: 749 self.__cright = '' 750 try: 751 e_obj = self.__channel.getElementsByTagName("managingEditor")[0] 752 self.__editor = e_obj.firstChild.data.strip() 753 except IndexError: 754 self.__editor = '' 755 entries = self.__channel.getElementsByTagName("item") 756 self.__itemscounter = len(entries) 757 if self.__itemscounter > self.__maxentries: 758 self.__itemscounter = self.__maxentries 759 mycounter = self.__itemscounter 760 for item in entries: 761 if mycounter == 0: # max entries reached 762 break 763 mycounter -= 1 764 self.__items[mycounter] = {} 765 title_obj = item.getElementsByTagName("title")[0] 766 self.__items[mycounter]['title'] = \ 767 title_obj.firstChild.data.strip() 768 desc_obj = item.getElementsByTagName("description") 769 description = None 770 if desc_obj: 771 description = desc_obj[0].firstChild 772 if description: 773 self.__items[mycounter]['description'] = \ 774 description.data.strip() 775 else: 776 self.__items[mycounter]['description'] = "" 777 778 link = item.getElementsByTagName("link")[0].firstChild 779 if link: 780 self.__items[mycounter]['link'] = link.data.strip() 781 else: 782 self.__items[mycounter]['link'] = "" 783 784 guid_obj = item.getElementsByTagName("guid")[0] 785 self.__items[mycounter]['guid'] = \ 786 guid_obj.firstChild.data.strip() 787 pub_date_obj = item.getElementsByTagName("pubDate")[0] 788 self.__items[mycounter]['pubDate'] = \ 789 pub_date_obj.firstChild.data.strip() 790 dcs = item.getElementsByTagName("dc:creator") 791 if dcs: 792 self.__items[mycounter]['dc:creator'] = \ 793 dcs[0].firstChild.data.strip()
794 795
796 - def add_item(self, title, link = '', description = '', pubDate = ''):
797 """ 798 Add new entry to RSS feed. 799 800 @param title: entry title 801 @type title: string 802 @keyword link: entry link 803 @type link: string 804 @keyword description: entry description 805 @type description: string 806 @keyword pubDate: entry publication date 807 @type pubDate: string 808 """ 809 810 self.__itemscounter += 1 811 self.__items[self.__itemscounter] = {} 812 self.__items[self.__itemscounter]['title'] = title 813 if pubDate: 814 self.__items[self.__itemscounter]['pubDate'] = pubDate 815 else: 816 self.__items[self.__itemscounter]['pubDate'] = \ 817 time.strftime("%a, %d %b %Y %X +0000") 818 self.__items[self.__itemscounter]['description'] = description 819 self.__items[self.__itemscounter]['link'] = link 820 if link: 821 self.__items[self.__itemscounter]['guid'] = link 822 else: 823 myguid = self.__system_settings['system']['name'].lower() 824 myguid = myguid.replace(" ", "") 825 self.__items[self.__itemscounter]['guid'] = myguid+"~" + \ 826 description + str(self.__itemscounter) 827 return self.__itemscounter
828
829 - def remove_entry(self, key):
830 """ 831 Remove entry from RSS feed through its index number. 832 833 @param key: entry index number. 834 @type key: int 835 @return: new entry count 836 @rtype: int 837 """ 838 if key in self.__items: 839 del self.__items[key] 840 self.__itemscounter -= 1 841 return self.__itemscounter
842
843 - def get_entries(self):
844 """ 845 Get entries and their total number. 846 847 @return: tuple composed by items (list of dict) and total items count 848 @rtype: tuple 849 """ 850 return self.__items, self.__itemscounter
851
852 - def write_changes(self, reverse = True):
853 """ 854 Writes changes to file. 855 856 @keyword reverse: write entries in reverse order. 857 @type reverse: bool 858 @return: None 859 @rtype: None 860 """ 861 862 # filter entries to fit in maxentries 863 if self.__itemscounter > self.__maxentries: 864 tobefiltered = self.__itemscounter - self.__maxentries 865 for index in range(tobefiltered): 866 try: 867 del self.__items[index] 868 except KeyError: 869 pass 870 871 doc = self.minidom.Document() 872 873 rss = doc.createElement("rss") 874 rss.setAttribute("version", "2.0") 875 rss.setAttribute("xmlns:atom", "http://www.w3.org/2005/Atom") 876 877 channel = doc.createElement("channel") 878 879 # title 880 title = doc.createElement("title") 881 title_text = doc.createTextNode(self.__title) 882 title.appendChild(title_text) 883 channel.appendChild(title) 884 # link 885 link = doc.createElement("link") 886 link_text = doc.createTextNode(self.__link) 887 link.appendChild(link_text) 888 channel.appendChild(link) 889 # description 890 description = doc.createElement("description") 891 desc_text = doc.createTextNode(self.__description) 892 description.appendChild(desc_text) 893 channel.appendChild(description) 894 # language 895 language = doc.createElement("language") 896 lang_text = doc.createTextNode(self.__language) 897 language.appendChild(lang_text) 898 channel.appendChild(language) 899 # copyright 900 cright = doc.createElement("copyright") 901 cr_text = doc.createTextNode(self.__cright) 902 cright.appendChild(cr_text) 903 channel.appendChild(cright) 904 # managingEditor 905 managing_editor = doc.createElement("managingEditor") 906 ed_text = doc.createTextNode(self.__editor) 907 managing_editor.appendChild(ed_text) 908 channel.appendChild(managing_editor) 909 910 keys = list(self.__items.keys()) 911 if reverse: 912 keys.reverse() 913 for key in keys: 914 915 # sanity check, you never know 916 if key not in self.__items: 917 self.remove_entry(key) 918 continue 919 k_error = False 920 for item in ('title', 'link', 'guid', 'description', 'pubDate',): 921 if item not in self.__items[key]: 922 k_error = True 923 break 924 if k_error: 925 self.remove_entry(key) 926 continue 927 928 # item 929 item = doc.createElement("item") 930 # title 931 item_title = doc.createElement("title") 932 item_title_text = doc.createTextNode( 933 self.__items[key]['title']) 934 item_title.appendChild(item_title_text) 935 item.appendChild(item_title) 936 # link 937 item_link = doc.createElement("link") 938 item_link_text = doc.createTextNode( 939 self.__items[key]['link']) 940 item_link.appendChild(item_link_text) 941 item.appendChild(item_link) 942 # guid 943 item_guid = doc.createElement("guid") 944 item_guid.setAttribute("isPermaLink", "true") 945 item_guid_text = doc.createTextNode( 946 self.__items[key]['guid']) 947 item_guid.appendChild(item_guid_text) 948 item.appendChild(item_guid) 949 # description 950 item_desc = doc.createElement("description") 951 item_desc_text = doc.createTextNode( 952 self.__items[key]['description']) 953 item_desc.appendChild(item_desc_text) 954 item.appendChild(item_desc) 955 # pubdate 956 item_date = doc.createElement("pubDate") 957 item_date_text = doc.createTextNode( 958 self.__items[key]['pubDate']) 959 item_date.appendChild(item_date_text) 960 item.appendChild(item_date) 961 962 # add item to channel 963 channel.appendChild(item) 964 965 # add channel to rss 966 rss.appendChild(channel) 967 doc.appendChild(rss) 968 rss_f = open(self.__file, "w") 969 rss_f.writelines(doc.toprettyxml(indent=" ").encode('utf-8')) 970 rss_f.flush() 971 rss_f.close()
972
973 -class LogFile:
974 975 """ Entropy simple logging interface, works as file object """ 976
977 - def __init__(self, level = 0, filename = None, header = "[LOG]"):
978 """ 979 LogFile constructor. 980 981 @keyword level: log level threshold which will trigger effective write 982 on log file 983 @type level: int 984 @keyword filename: log file path 985 @type filename: string 986 @keyword header: log line header 987 @type header: string 988 """ 989 self.handler = self.default_handler 990 self.level = level 991 self.header = header 992 self._logfile = None 993 self.open(filename) 994 self.__filename = filename
995
996 - def __del__(self):
997 self.close()
998
999 - def close(self):
1000 """ Close log file """ 1001 try: 1002 self._logfile.close() 1003 except (IOError, OSError,): 1004 pass
1005
1006 - def get_fpath(self):
1007 """ Get log file path """ 1008 return self.__filename
1009
1010 - def flush(self):
1011 """ Flush log file buffer to disk """ 1012 self._logfile.flush()
1013
1014 - def fileno(self):
1015 """ 1016 Get log file descriptor number 1017 1018 @return: file descriptor number 1019 @rtype: int 1020 """ 1021 return self.__get_file()
1022
1023 - def isatty(self):
1024 """ 1025 Return whether LogFile works like a tty 1026 1027 @return: is a tty? 1028 @rtype: bool 1029 """ 1030 return False
1031
1032 - def read(self, *args):
1033 """ 1034 Fake method (exposed for file object compatibility) 1035 1036 @return: empty string 1037 @rtype: string 1038 """ 1039 return ''
1040
1041 - def readline(self):
1042 """ 1043 Fake method (exposed for file object compatibility) 1044 1045 @return: empty string 1046 @rtype: string 1047 """ 1048 return ''
1049
1050 - def readlines(self):
1051 """ 1052 Fake method (exposed for file object compatibility) 1053 1054 @return: empty list 1055 @rtype: list 1056 """ 1057 return []
1058
1059 - def seek(self, offset):
1060 """ 1061 File object method, move file object cursor at offset 1062 1063 @return: new file object position 1064 @rtype: int 1065 """ 1066 return self._logfile.seek(offset)
1067
1068 - def tell(self):
1069 """ 1070 File object method, tell file object position 1071 1072 @return: file object position 1073 @rtype: int 1074 """ 1075 return self._logfile.tell()
1076
1077 - def truncate(self):
1078 """ 1079 File object method, truncate file buffer. 1080 """ 1081 return self._logfile.truncate()
1082
1083 - def open(self, file_path = None):
1084 """ 1085 Open log file, if possible, otherwise redirect to /dev/null or stderr. 1086 1087 @keyword file_path: path to file 1088 @type file_path: string 1089 """ 1090 if const_isstring(file_path): 1091 if not os.path.isfile(file_path) and os.access( 1092 os.path.dirname(file_path), os.W_OK): 1093 self._logfile = open(file_path, "a") 1094 else: 1095 if os.access(file_path, os.W_OK) and os.path.isfile(file_path): 1096 self._logfile = open(file_path, "a") 1097 elif os.path.exists("/dev/null"): 1098 self._logfile = open("/dev/null", "a") 1099 else: 1100 self._logfile = sys.stderr 1101 1102 elif hasattr(file_path, 'write'): 1103 self._logfile = file_path 1104 else: 1105 self._logfile = sys.stderr
1106
1107 - def __get_file(self):
1108 return self._logfile.fileno()
1109
1110 - def __call__(self, format, *args):
1111 self.handler (format % args)
1112
1113 - def default_handler(self, mystr):
1114 """ 1115 Default log file writer. This can be reimplemented. 1116 1117 @param mystr: log string to write 1118 @type mystr: string 1119 """ 1120 try: 1121 self._logfile.write ("* %s\n" % (mystr)) 1122 except UnicodeEncodeError: 1123 self._logfile.write ("* %s\n" % (mystr.encode('utf-8'),)) 1124 self._logfile.flush()
1125
1126 - def set_loglevel(self, level):
1127 """ 1128 Change logging threshold. 1129 1130 @param level: new logging threshold 1131 @type level: int 1132 """ 1133 self.level = level
1134
1135 - def log(self, messagetype, level, message):
1136 """ 1137 This is the effective function that LogFile consumers should use. 1138 1139 @param messagetype: message type (or tag) 1140 @type messagetype: string 1141 @param level: minimum logging threshold which should trigger the 1142 effective write 1143 @type level: int 1144 @param message: log message 1145 @type message: string 1146 """ 1147 if self.level >= level and not etpUi['nolog']: 1148 self.handler("%s %s %s %s" % (self.__get_header(), 1149 messagetype, self.header, message,))
1150
1151 - def write(self, mystr):
1152 """ 1153 File object method, write log message to file using the default 1154 handler set (LogFile.default_handler is the default). 1155 1156 @param mystr: log string to write 1157 @type mystr: string 1158 """ 1159 self.handler(mystr)
1160
1161 - def writelines(self, lst):
1162 """ 1163 File object method, write log message strings to file using the default 1164 handler set (LogFile.default_handler is the default). 1165 1166 @param lst: list of strings to write 1167 @type lst: list 1168 """ 1169 for line in lst: 1170 self.write(line)
1171
1172 - def __get_header(self):
1173 return time.strftime('[%H:%M:%S %d/%m/%Y %Z]')
1174
1175 -class Callable:
1176 """ 1177 Fake class wrapping any callable object into a callable class. 1178 """
1179 - def __init__(self, anycallable):
1180 """ 1181 Callable constructor. 1182 1183 @param anycallable: any callable object 1184 @type callable: callable 1185 """ 1186 self.__call__ = anycallable
1187
1188 -class MultipartPostHandler(UrllibBaseHandler):
1189 1190 """ 1191 Custom urllib2 opener used in the Entropy codebase. 1192 """ 1193 1194 # needs to run first 1195 if sys.hexversion >= 0x3000000: 1196 handler_order = urllib.request.HTTPHandler.handler_order - 10 1197 else: 1198 handler_order = urllib2.HTTPHandler.handler_order - 10 1199
1200 - def __init__(self):
1201 """ 1202 MultipartPostHandler constructor. 1203 """ 1204 pass
1205
1206 - def http_request(self, request):
1207 1208 """ 1209 Entropy codebase internal method. Not for re-use. 1210 1211 @param request: urllib2 HTTP request object 1212 """ 1213 1214 doseq = 1 1215 1216 data = request.get_data() 1217 if data is not None and not isinstance(data, str): 1218 v_files = [] 1219 v_vars = [] 1220 try: 1221 for (key, value) in list(data.items()): 1222 if const_isfileobj(value): 1223 v_files.append((key, value)) 1224 else: 1225 v_vars.append((key, value)) 1226 except TypeError: 1227 raise TypeError("not a valid non-string sequence" \ 1228 " or mapping object") 1229 1230 if len(v_files) == 0: 1231 if sys.hexversion >= 0x3000000: 1232 data = urllib.parse.urlencode(v_vars, doseq) 1233 else: 1234 import urllib 1235 data = urllib.urlencode(v_vars, doseq) 1236 else: 1237 boundary, data = self.multipart_encode(v_vars, v_files) 1238 contenttype = 'multipart/form-data; boundary=%s' % boundary 1239 request.add_unredirected_header('Content-Type', contenttype) 1240 1241 request.add_data(data) 1242 return request
1243
1244 - def multipart_encode(self, myvars, files, boundary = None, buf = None):
1245 1246 """ 1247 Does the effective multipart mime encoding. Entropy codebase internal 1248 method. Not for re-use. 1249 """ 1250 1251 from io import StringIO 1252 import mimetools, mimetypes 1253 #import stat 1254 1255 if boundary is None: 1256 boundary = mimetools.choose_boundary() 1257 if buf is None: 1258 buf = StringIO() 1259 for(key, value) in myvars: 1260 buf.write('--%s\r\n' % boundary) 1261 buf.write('Content-Disposition: form-data; name="%s"' % key) 1262 buf.write('\r\n\r\n' + value + '\r\n') 1263 for(key, fdesc) in files: 1264 #file_size = os.fstat(fdesc.fileno())[stat.ST_SIZE] 1265 filename = fdesc.name.split('/')[-1] 1266 contenttype = mimetypes.guess_type(filename)[0] or \ 1267 'application/octet-stream' 1268 buf.write('--%s\r\n' % boundary) 1269 buf.write('Content-Disposition: form-data; name="%s"; ' \ 1270 'filename="%s"\r\n' % (key, filename)) 1271 buf.write('Content-Type: %s\r\n' % contenttype) 1272 # buffer += 'Content-Length: %s\r\n' % file_size 1273 fdesc.seek(0) 1274 buf.write('\r\n' + fdesc.read() + '\r\n') 1275 buf.write('--' + boundary + '--\r\n\r\n') 1276 buf = buf.getvalue() 1277 return boundary, buf
1278 1279 multipart_encode = Callable(multipart_encode) 1280 1281 https_request = http_request
1282