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