1
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
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
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
85 """
86 Clear the stack.
87
88 @return: None
89 @rtype: None
90 """
91 self.__buf.clear()
92
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
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
116
117 if self.__buf is None:
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
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
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
184
185
186
187 self.__accurate = False
188 self.__delay_before = False
189 self.__alive = 0
190
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
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
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
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
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
262 time.sleep(t_frac)
263 mydelay -= t_frac
264
265 else:
266
267 if time == None:
268 return True
269 time.sleep(self.__delay)
270
271 return False
272
274 """ Stop the execution of the timed function """
275 self.__alive = 0
276
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
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
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
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
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
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
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
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
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
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
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
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
535 """ Private method """
536 return self.__geoip.new(self.__geoip.GEOIP_MEMORY_CACHE)
537
539 """ Private method """
540 return self.__geoip.open(self.__geoip_dbfile,
541 self.__geoip.GEOIP_STANDARD)
542
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
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
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
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
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
605 import entropy.tools as entropyTools
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
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:
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
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
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
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
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
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
825 title = doc.createElement("title")
826 title_text = doc.createTextNode(unicode(self.__title))
827 title.appendChild(title_text)
828 channel.appendChild(title)
829
830 link = doc.createElement("link")
831 link_text = doc.createTextNode(unicode(self.__link))
832 link.appendChild(link_text)
833 channel.appendChild(link)
834
835 description = doc.createElement("description")
836 desc_text = doc.createTextNode(unicode(self.__description))
837 description.appendChild(desc_text)
838 channel.appendChild(description)
839
840 language = doc.createElement("language")
841 lang_text = doc.createTextNode(unicode(self.__language))
842 language.appendChild(lang_text)
843 channel.appendChild(language)
844
845 cright = doc.createElement("copyright")
846 cr_text = doc.createTextNode(unicode(self.__cright))
847 cright.appendChild(cr_text)
848 channel.appendChild(cright)
849
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
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
874 item = doc.createElement("item")
875
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
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
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
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
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
908 channel.appendChild(item)
909
910
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
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
943
945 """ Close log file """
946 try:
947 self._logfile.close()
948 except (IOError, OSError,):
949 pass
950
952 """ Get log file path """
953 return self.__filename
954
956 """ Flush log file buffer to disk """
957 self._logfile.flush()
958
960 """
961 Get log file descriptor number
962
963 @return: file descriptor number
964 @rtype: int
965 """
966 return self.__get_file()
967
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
987 """
988 Fake method (exposed for file object compatibility)
989
990 @return: empty string
991 @rtype: string
992 """
993 return ''
994
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
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
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
1050 return self._logfile.fileno()
1051
1053 self.handler (format % args)
1054
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
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
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
1115 return time.strftime('[%H:%M:%S %d/%m/%Y %Z]')
1116
1118 """
1119 Fake class wrapping any callable object into a callable class.
1120 """
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
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
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
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
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