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