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 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 entry is buf_entry:
118 self.__buf.pop(key)
119 continue
120 if entry == buf_entry:
121 self.__buf.pop(key)
122 continue
123
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
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
182
183
184
185 self.__accurate = False
186 self.__delay_before = False
187 self.__alive = 0
188
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
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
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
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
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
260 time.sleep(t_frac)
261 mydelay -= t_frac
262
263 else:
264
265 if time == None:
266 return True
267 time.sleep(self.__delay)
268
269 return False
270
272 """ Stop the execution of the timed function """
273 self.__alive = 0
274
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
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
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
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
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
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
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
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
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
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
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
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
533 """ Private method """
534 return self.__geoip.new(self.__geoip.GEOIP_MEMORY_CACHE)
535
537 """ Private method """
538 return self.__geoip.open(self.__geoip_dbfile,
539 self.__geoip.GEOIP_STANDARD)
540
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
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
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
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
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
603 import entropy.tools as entropyTools
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
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:
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
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
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
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
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
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
823 title = doc.createElement("title")
824 title_text = doc.createTextNode(unicode(self.__title))
825 title.appendChild(title_text)
826 channel.appendChild(title)
827
828 link = doc.createElement("link")
829 link_text = doc.createTextNode(unicode(self.__link))
830 link.appendChild(link_text)
831 channel.appendChild(link)
832
833 description = doc.createElement("description")
834 desc_text = doc.createTextNode(unicode(self.__description))
835 description.appendChild(desc_text)
836 channel.appendChild(description)
837
838 language = doc.createElement("language")
839 lang_text = doc.createTextNode(unicode(self.__language))
840 language.appendChild(lang_text)
841 channel.appendChild(language)
842
843 cright = doc.createElement("copyright")
844 cr_text = doc.createTextNode(unicode(self.__cright))
845 cright.appendChild(cr_text)
846 channel.appendChild(cright)
847
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
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
872 item = doc.createElement("item")
873
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
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
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
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
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
906 channel.appendChild(item)
907
908
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
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
941
943 """ Close log file """
944 try:
945 self._logfile.close()
946 except (IOError, OSError,):
947 pass
948
950 """ Get log file path """
951 return self.__filename
952
954 """ Flush log file buffer to disk """
955 self._logfile.flush()
956
958 """
959 Get log file descriptor number
960
961 @return: file descriptor number
962 @rtype: int
963 """
964 return self.__get_file()
965
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
985 """
986 Fake method (exposed for file object compatibility)
987
988 @return: empty string
989 @rtype: string
990 """
991 return ''
992
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
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
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
1048 return self._logfile.fileno()
1049
1051 self.handler (format % args)
1052
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
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
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
1113 return time.strftime('[%H:%M:%S %d/%m/%Y %Z]')
1114
1116 """
1117 Fake class wrapping any callable object into a callable class.
1118 """
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
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
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
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
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