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