1 | #!/usr/bin/python -O |
1 | #!/usr/bin/python -O |
2 | """These functions mainly take ebuild info (grabbed from the database and |
2 | """These functions mainly take ebuild info (grabbed from the database and |
3 | convert it to HTML. See the "main" function at the bottom.""" |
3 | convert it to HTML. See the "main" function at the bottom.""" |
4 | |
4 | |
5 | __revision__ = "$Revision: 1.16 $" |
5 | __revision__ = "$Revision: 1.16.2.5 $" |
6 | # $Source: /var/cvsroot/gentoo/src/packages/gentoo.py,v $ |
6 | # $Source: /var/cvsroot/gentoo/src/packages/gentoo.py,v $ |
7 | |
7 | |
8 | import config |
8 | import config |
9 | import os |
9 | import os |
10 | import time |
10 | import time |
11 | import string |
|
|
12 | import sys |
11 | import sys |
13 | import ebuilddb |
12 | import ebuilddb |
14 | import bugs |
13 | import bugs |
15 | import changelogs |
14 | import changelogs |
16 | from cgi import escape |
15 | from cgi import escape |
17 | from urllib import quote |
16 | from urllib import quote |
18 | |
17 | from portage_versions import pkgcmp, pkgsplit |
19 | endversion={"pre":-2,"p":0,"alpha":-4,"beta":-3,"rc":-1} |
|
|
20 | # as there's no reliable way to set {}.keys() order |
|
|
21 | # netversion_keys will be used instead of endversion.keys |
|
|
22 | # to have fixed search order, so that "pre" is checked |
|
|
23 | # before "p" |
|
|
24 | endversion_keys = ["pre", "p", "alpha", "beta", "rc"] |
|
|
25 | |
18 | |
26 | def is_new(db, ebuild): |
19 | def is_new(db, ebuild): |
27 | """Check for newness.""" |
20 | """Check for newness.""" |
28 | |
21 | |
29 | c = db.cursor() |
22 | c = db.cursor() |
… | |
… | |
58 | if not license.strip(): return "?" |
51 | if not license.strip(): return "?" |
59 | license = license.replace('|',' ') |
52 | license = license.replace('|',' ') |
60 | license = license.replace('(', '') |
53 | license = license.replace('(', '') |
61 | license = license.replace(')', '') |
54 | license = license.replace(')', '') |
62 | pieces = license.split() |
55 | pieces = license.split() |
63 | html = ['<a href="http://www.gentoo.org/cgi-bin/viewcvs.cgi/*checkout*/' |
56 | html = ['<a href="http://sources.gentoo.org/viewcvs.py/*checkout*/' |
64 | 'licenses/%s">%s</a>' % (piece, piece) for piece in pieces] |
57 | 'gentoo-x86/licenses/%s">%s</a>' % (piece, piece) for piece in pieces] |
65 | return '<br>\n'.join(html) |
58 | return '<br>\n'.join(html) |
66 | |
59 | |
67 | def package_to_html(pkginfo, db, full=False): |
60 | def package_to_html(pkginfo, db, full=False): |
68 | """This function needs a database (db) connection because it performs a |
61 | """This function needs a database (db) connection because it performs a |
69 | query_to_dict on the package""" |
62 | query_to_dict on the package""" |
… | |
… | |
181 | """This actually will (should) take either a package or ebuild dict |
174 | """This actually will (should) take either a package or ebuild dict |
182 | as an argument""" |
175 | as an argument""" |
183 | |
176 | |
184 | import forums |
177 | import forums |
185 | |
178 | |
186 | changelogurl = ('http://www.gentoo.org/cgi-bin/viewcvs.cgi/*checkout*/' |
179 | changelogurl = ('http://sources.gentoo.org/viewcvs.py/*checkout*/' |
187 | '%s/%s/ChangeLog' % (pkg['category'],pkg['name'])) |
180 | 'gentoo-x86/%s/%s/ChangeLog' % (pkg['category'],pkg['name'])) |
188 | cat_header = '<th class="category">Category</th>' |
181 | cat_header = '<th class="category">Category</th>' |
189 | license_header = '<th class="license">License</th>' |
182 | license_header = '<th class="license">License</th>' |
190 | category = ('<td class="category">' |
183 | category = ('<td class="category">' |
191 | '<a href="%spackages/?category=%s">%s</a></td>' % (config.FEHOME, |
184 | '<a href="%spackages/?category=%s">%s</a></td>' % (config.FEHOME, |
192 | pkg['category'], pkg['category'])) |
185 | pkg['category'], pkg['category'])) |
… | |
… | |
198 | '<a href="%s">ChangeLog</a></td>' % changelogurl) |
191 | '<a href="%s">ChangeLog</a></td>' % changelogurl) |
199 | similar = ('<td class="similar" rowspan="2">' |
192 | similar = ('<td class="similar" rowspan="2">' |
200 | '%s</td>' % create_similar_pkgs_link(pkg)) |
193 | '%s</td>' % create_similar_pkgs_link(pkg)) |
201 | related_bugs = ('<td class="related_bugs" rowspan="2">' |
194 | related_bugs = ('<td class="related_bugs" rowspan="2">' |
202 | '%s</td>' % create_related_bugs_link(pkg)) |
195 | '%s</td>' % create_related_bugs_link(pkg)) |
203 | forums = ('<td class="forums" rowspan="2">' |
196 | forums_link = ('<td class="forums" rowspan="2">' |
204 | '%s</td>' % forums.create_forums_link(pkg)) |
197 | '%s</td>' % forums.create_forums_link(pkg)) |
205 | |
198 | |
206 | return '\n\t'.join(['<table class="general_info">', |
199 | return '\n\t'.join(['<table class="general_info">', |
207 | '<tr>', |
200 | '<tr>', |
208 | cat_header, |
201 | cat_header, |
209 | homepage, |
202 | homepage, |
210 | license_header, |
203 | license_header, |
211 | changelog, |
204 | changelog, |
212 | similar, |
205 | similar, |
213 | related_bugs, |
206 | related_bugs, |
214 | forums, |
207 | forums_link, |
215 | '</tr>', |
208 | '</tr>', |
216 | '<tr>', |
209 | '<tr>', |
217 | category, |
210 | category, |
218 | license, |
211 | license, |
219 | '</tr>', |
212 | '</tr>', |
220 | '</table>']) |
213 | '</table>']) |
221 | |
214 | |
222 | def create_similar_pkgs_link(pkg): |
215 | def create_similar_pkgs_link(pkg): |
223 | """Create a link to similar packages""" |
216 | """Create a link to similar packages""" |
224 | |
217 | |
225 | def strip_chars(mystring): |
218 | return '<a href="/similar/?package=%(category)s/%(name)s">Similar</a>' % pkg |
226 | newstring = '' |
|
|
227 | for char in mystring: |
|
|
228 | if char not in string.punctuation: |
|
|
229 | newstring = newstring + char |
|
|
230 | else: |
|
|
231 | newstring = newstring + ' ' |
|
|
232 | return newstring |
|
|
233 | |
|
|
234 | description = strip_chars(pkg['description'].lower()) |
|
|
235 | |
|
|
236 | words = [word for word in description.split() |
|
|
237 | if word and len(word)>2 and word not in config.EXCLUDED_FROM_SIMILAR] |
|
|
238 | words = words[:config.SIMILAR_MAX_WORDS] + [pkg['name']] |
|
|
239 | #query = ['[[:<:]]%s[[:>:]].*' % word for word in words] |
|
|
240 | query = ['[[:<:]]%s.*' % word for word in words] |
|
|
241 | query = '(%s){%s,}' % ('|'.join(query), config.SIMILAR_MIN_MATCHES) |
|
|
242 | url = '%ssearch/?sstring=%s' % (config.FEHOME, escape(query)) |
|
|
243 | return '<a href="%s">Similar</a>' % url |
|
|
244 | |
219 | |
245 | def create_related_bugs_link(pkg): |
220 | def create_related_bugs_link(pkg): |
246 | """Create a link to related bugs""" |
221 | """Create a link to related bugs""" |
247 | |
222 | |
248 | url = ('http://bugs.gentoo.org/buglist.cgi?query_format=' |
223 | url = ('http://bugs.gentoo.org/buglist.cgi?query_format=' |
… | |
… | |
297 | extra = ' AND ((%s) OR (%s)) ' % (stable_extra, testing_extra) |
272 | extra = ' AND ((%s) OR (%s)) ' % (stable_extra, testing_extra) |
298 | |
273 | |
299 | if new: |
274 | if new: |
300 | extra = ('%s AND package.new=1 ' % extra) |
275 | extra = ('%s AND package.new=1 ' % extra) |
301 | |
276 | |
302 | query = """SELECT ebuild.category,ebuild.name,version,when_found,description, |
277 | query = """SELECT ebuild.category,ebuild.name,version,ebuild.when_found,description, |
303 | changelog,arch,homepage,license,is_masked FROM ebuild,package WHERE ebuild.name=\ |
278 | changelog,arch,homepage,license,is_masked FROM ebuild,package WHERE ebuild.name=\ |
304 | package.name AND ebuild.category=package.category %s ORDER by when_found DESC \ |
279 | package.name AND ebuild.category=package.category %s ORDER by ebuild.when_found DESC \ |
305 | LIMIT %s""" % (extra,max) |
280 | LIMIT %s""" % (extra,max) |
306 | c.execute(query) |
281 | c.execute(query) |
307 | results = c.fetchall() |
282 | results = c.fetchall() |
308 | return results |
283 | return results |
309 | |
284 | |
… | |
… | |
348 | def cmp_ebuilds(a, b): |
323 | def cmp_ebuilds(a, b): |
349 | """Compare two ebuilds""" |
324 | """Compare two ebuilds""" |
350 | fields_a = pkgsplit('%s-%s' % (a['name'], a['version'])) |
325 | fields_a = pkgsplit('%s-%s' % (a['name'], a['version'])) |
351 | fields_b = pkgsplit('%s-%s' % (b['name'], b['version'])) |
326 | fields_b = pkgsplit('%s-%s' % (b['name'], b['version'])) |
352 | return pkgcmp(fields_a, fields_b) |
327 | return pkgcmp(fields_a, fields_b) |
353 | |
|
|
354 | pkgcache={} |
|
|
355 | |
|
|
356 | def pkgsplit(mypkg,silent=1): |
|
|
357 | try: |
|
|
358 | if not pkgcache[mypkg]: |
|
|
359 | return None |
|
|
360 | return pkgcache[mypkg][:] |
|
|
361 | except KeyError: |
|
|
362 | pass |
|
|
363 | myparts=string.split(mypkg,'-') |
|
|
364 | if len(myparts)<2: |
|
|
365 | if not silent: |
|
|
366 | print "!!! Name error in",mypkg+": missing a version or name part." |
|
|
367 | pkgcache[mypkg]=None |
|
|
368 | return None |
|
|
369 | for x in myparts: |
|
|
370 | if len(x)==0: |
|
|
371 | if not silent: |
|
|
372 | print "!!! Name error in",mypkg+": empty \"-\" part." |
|
|
373 | pkgcache[mypkg]=None |
|
|
374 | return None |
|
|
375 | #verify rev |
|
|
376 | revok=0 |
|
|
377 | myrev=myparts[-1] |
|
|
378 | if len(myrev) and myrev[0]=="r": |
|
|
379 | try: |
|
|
380 | int(myrev[1:]) |
|
|
381 | revok=1 |
|
|
382 | except SystemExit, e: |
|
|
383 | raise |
|
|
384 | except: |
|
|
385 | pass |
|
|
386 | if revok: |
|
|
387 | if ververify(myparts[-2]): |
|
|
388 | if len(myparts)==2: |
|
|
389 | pkgcache[mypkg]=None |
|
|
390 | return None |
|
|
391 | else: |
|
|
392 | for x in myparts[:-2]: |
|
|
393 | if ververify(x): |
|
|
394 | pkgcache[mypkg]=None |
|
|
395 | return None |
|
|
396 | #names can't have versiony looking parts |
|
|
397 | myval=[string.join(myparts[:-2],"-"),myparts[-2],myparts[-1]] |
|
|
398 | pkgcache[mypkg]=myval |
|
|
399 | return myval |
|
|
400 | else: |
|
|
401 | pkgcache[mypkg]=None |
|
|
402 | return None |
|
|
403 | |
|
|
404 | elif ververify(myparts[-1],silent=silent): |
|
|
405 | if len(myparts)==1: |
|
|
406 | if not silent: |
|
|
407 | print "!!! Name error in",mypkg+": missing name part." |
|
|
408 | pkgcache[mypkg]=None |
|
|
409 | return None |
|
|
410 | else: |
|
|
411 | for x in myparts[:-1]: |
|
|
412 | if ververify(x): |
|
|
413 | if not silent: |
|
|
414 | print "!!! Name error in",mypkg+": multiple version parts." |
|
|
415 | pkgcache[mypkg]=None |
|
|
416 | return None |
|
|
417 | myval=[string.join(myparts[:-1],"-"),myparts[-1],"r0"] |
|
|
418 | pkgcache[mypkg]=myval[:] |
|
|
419 | return myval |
|
|
420 | else: |
|
|
421 | pkgcache[mypkg]=None |
|
|
422 | return None |
|
|
423 | |
|
|
424 | vercache={} |
|
|
425 | def ververify(myorigval,silent=1): |
|
|
426 | try: |
|
|
427 | return vercache[myorigval] |
|
|
428 | except KeyError: |
|
|
429 | pass |
|
|
430 | if len(myorigval)==0: |
|
|
431 | if not silent: |
|
|
432 | print "!!! Name error: package contains empty \"-\" part." |
|
|
433 | return 0 |
|
|
434 | myval=string.split(myorigval,'.') |
|
|
435 | if len(myval)==0: |
|
|
436 | if not silent: |
|
|
437 | print "!!! Name error: empty version string." |
|
|
438 | vercache[myorigval]=0 |
|
|
439 | return 0 |
|
|
440 | #all but the last version must be a numeric |
|
|
441 | for x in myval[:-1]: |
|
|
442 | if not len(x): |
|
|
443 | if not silent: |
|
|
444 | print "!!! Name error in",myorigval+": two decimal points in a row" |
|
|
445 | vercache[myorigval]=0 |
|
|
446 | return 0 |
|
|
447 | try: |
|
|
448 | foo=int(x) |
|
|
449 | except SystemExit, e: |
|
|
450 | raise |
|
|
451 | except: |
|
|
452 | if not silent: |
|
|
453 | print "!!! Name error in",myorigval+": \""+x+"\" is not a valid version component." |
|
|
454 | vercache[myorigval]=0 |
|
|
455 | return 0 |
|
|
456 | if not len(myval[-1]): |
|
|
457 | if not silent: |
|
|
458 | print "!!! Name error in",myorigval+": two decimal points in a row" |
|
|
459 | vercache[myorigval]=0 |
|
|
460 | return 0 |
|
|
461 | try: |
|
|
462 | foo=int(myval[-1]) |
|
|
463 | vercache[myorigval]=1 |
|
|
464 | return 1 |
|
|
465 | except SystemExit, e: |
|
|
466 | raise |
|
|
467 | except: |
|
|
468 | pass |
|
|
469 | #ok, our last component is not a plain number or blank, let's continue |
|
|
470 | if myval[-1][-1] in string.lowercase: |
|
|
471 | try: |
|
|
472 | foo=int(myval[-1][:-1]) |
|
|
473 | vercache[myorigval]=1 |
|
|
474 | return 1 |
|
|
475 | # 1a, 2.0b, etc. |
|
|
476 | except SystemExit, e: |
|
|
477 | raise |
|
|
478 | except: |
|
|
479 | pass |
|
|
480 | #ok, maybe we have a 1_alpha or 1_beta2; let's see |
|
|
481 | #ep="endpart" |
|
|
482 | ep=string.split(myval[-1],"_") |
|
|
483 | if len(ep)!=2: |
|
|
484 | if not silent: |
|
|
485 | print "!!! Name error in",myorigval |
|
|
486 | vercache[myorigval]=0 |
|
|
487 | return 0 |
|
|
488 | try: |
|
|
489 | foo=int(ep[0][-1]) |
|
|
490 | chk=ep[0] |
|
|
491 | except SystemExit, e: |
|
|
492 | raise |
|
|
493 | except: |
|
|
494 | # because it's ok last char is not numeric. example: foo-1.0.0a_pre1 |
|
|
495 | chk=ep[0][:-1] |
|
|
496 | |
|
|
497 | try: |
|
|
498 | foo=int(chk) |
|
|
499 | except SystemExit, e: |
|
|
500 | raise |
|
|
501 | except: |
|
|
502 | #this needs to be numeric or numeric+single letter, |
|
|
503 | #i.e. the "1" in "1_alpha" or "1a_alpha" |
|
|
504 | if not silent: |
|
|
505 | print "!!! Name error in",myorigval+": characters before _ must be numeric or numeric+single letter" |
|
|
506 | vercache[myorigval]=0 |
|
|
507 | return 0 |
|
|
508 | for mye in endversion_keys: |
|
|
509 | if ep[1][0:len(mye)]==mye: |
|
|
510 | if len(mye)==len(ep[1]): |
|
|
511 | #no trailing numeric; ok |
|
|
512 | vercache[myorigval]=1 |
|
|
513 | return 1 |
|
|
514 | else: |
|
|
515 | try: |
|
|
516 | foo=int(ep[1][len(mye):]) |
|
|
517 | vercache[myorigval]=1 |
|
|
518 | return 1 |
|
|
519 | except SystemExit, e: |
|
|
520 | raise |
|
|
521 | except: |
|
|
522 | #if no endversions work, *then* we return 0 |
|
|
523 | pass |
|
|
524 | if not silent: |
|
|
525 | print "!!! Name error in",myorigval |
|
|
526 | vercache[myorigval]=0 |
|
|
527 | return 0 |
|
|
528 | |
|
|
529 | def relparse(myver): |
|
|
530 | "converts last version part into three components" |
|
|
531 | number=0 |
|
|
532 | suffix=0 |
|
|
533 | endtype=0 |
|
|
534 | endnumber=0 |
|
|
535 | |
|
|
536 | mynewver=string.split(myver,"_") |
|
|
537 | myver=mynewver[0] |
|
|
538 | |
|
|
539 | #normal number or number with letter at end |
|
|
540 | divider=len(myver)-1 |
|
|
541 | if myver[divider:] not in "1234567890": |
|
|
542 | #letter at end |
|
|
543 | suffix=ord(myver[divider:]) |
|
|
544 | number=string.atof(myver[0:divider]) |
|
|
545 | else: |
|
|
546 | number=string.atof(myver) |
|
|
547 | |
|
|
548 | if len(mynewver)==2: |
|
|
549 | #an endversion |
|
|
550 | for x in endversion_keys: |
|
|
551 | elen=len(x) |
|
|
552 | if mynewver[1][:elen] == x: |
|
|
553 | endtype=endversion[x] |
|
|
554 | try: |
|
|
555 | endnumber=string.atof(mynewver[1][elen:]) |
|
|
556 | except: |
|
|
557 | endnumber=0 |
|
|
558 | break |
|
|
559 | return [number,suffix,endtype,endnumber] |
|
|
560 | |
|
|
561 | # vercmp: |
|
|
562 | # ripped from portage.py to prevent having to import |
|
|
563 | vcmpcache={} |
|
|
564 | def vercmp(val1,val2): |
|
|
565 | if val1==val2: |
|
|
566 | #quick short-circuit |
|
|
567 | return 0 |
|
|
568 | valkey=val1+" "+val2 |
|
|
569 | try: |
|
|
570 | return vcmpcache[valkey] |
|
|
571 | try: |
|
|
572 | return -vcmpcache[val2+" "+val1] |
|
|
573 | except KeyError: |
|
|
574 | pass |
|
|
575 | except KeyError: |
|
|
576 | pass |
|
|
577 | |
|
|
578 | # consider 1_p2 vc 1.1 |
|
|
579 | # after expansion will become (1_p2,0) vc (1,1) |
|
|
580 | # then 1_p2 is compared with 1 before 0 is compared with 1 |
|
|
581 | # to solve the bug we need to convert it to (1,0_p2) |
|
|
582 | # by splitting _prepart part and adding it back _after_expansion |
|
|
583 | val1_prepart = val2_prepart = '' |
|
|
584 | if val1.count('_'): |
|
|
585 | val1, val1_prepart = val1.split('_', 1) |
|
|
586 | if val2.count('_'): |
|
|
587 | val2, val2_prepart = val2.split('_', 1) |
|
|
588 | |
|
|
589 | # replace '-' by '.' |
|
|
590 | # FIXME: Is it needed? can val1/2 contain '-'? |
|
|
591 | val1=string.split(val1,'-') |
|
|
592 | if len(val1)==2: |
|
|
593 | val1[0]=val1[0]+"."+val1[1] |
|
|
594 | val2=string.split(val2,'-') |
|
|
595 | if len(val2)==2: |
|
|
596 | val2[0]=val2[0]+"."+val2[1] |
|
|
597 | |
|
|
598 | val1=string.split(val1[0],'.') |
|
|
599 | val2=string.split(val2[0],'.') |
|
|
600 | |
|
|
601 | #add back decimal point so that .03 does not become "3" ! |
|
|
602 | for x in range(1,len(val1)): |
|
|
603 | if val1[x][0] == '0' : |
|
|
604 | val1[x]='.' + val1[x] |
|
|
605 | for x in range(1,len(val2)): |
|
|
606 | if val2[x][0] == '0' : |
|
|
607 | val2[x]='.' + val2[x] |
|
|
608 | |
|
|
609 | # extend version numbers |
|
|
610 | if len(val2)<len(val1): |
|
|
611 | val2.extend(["0"]*(len(val1)-len(val2))) |
|
|
612 | elif len(val1)<len(val2): |
|
|
613 | val1.extend(["0"]*(len(val2)-len(val1))) |
|
|
614 | |
|
|
615 | # add back _prepart tails |
|
|
616 | if val1_prepart: |
|
|
617 | val1[-1] += '_' + val1_prepart |
|
|
618 | if val2_prepart: |
|
|
619 | val2[-1] += '_' + val2_prepart |
|
|
620 | #The above code will extend version numbers out so they |
|
|
621 | #have the same number of digits. |
|
|
622 | for x in range(0,len(val1)): |
|
|
623 | cmp1=relparse(val1[x]) |
|
|
624 | cmp2=relparse(val2[x]) |
|
|
625 | for y in range(0,4): |
|
|
626 | myret=cmp1[y]-cmp2[y] |
|
|
627 | if myret != 0: |
|
|
628 | vcmpcache[valkey]=myret |
|
|
629 | return myret |
|
|
630 | vcmpcache[valkey]=0 |
|
|
631 | return 0 |
|
|
632 | |
|
|
633 | # pkgcmp: |
|
|
634 | # ripped from portage.py to prevent having to import |
|
|
635 | def pkgcmp(pkg1,pkg2): |
|
|
636 | """if returnval is less than zero, then pkg2 is newer than pkg1, zero if equal and positive if older.""" |
|
|
637 | if pkg1[0] != pkg2[0]: |
|
|
638 | return None |
|
|
639 | mycmp=vercmp(pkg1[1],pkg2[1]) |
|
|
640 | if mycmp>0: |
|
|
641 | return 1 |
|
|
642 | if mycmp<0: |
|
|
643 | return -1 |
|
|
644 | r1=int(pkg1[2][1:]) |
|
|
645 | r2=int(pkg2[2][1:]) |
|
|
646 | if r1>r2: |
|
|
647 | return 1 |
|
|
648 | if r2>r1: |
|
|
649 | return -1 |
|
|
650 | return 0 |
|
|
651 | |
328 | |
652 | def ebuilds_to_rss(fp, ebuilds, simple=False, subtitle=""): |
329 | def ebuilds_to_rss(fp, ebuilds, simple=False, subtitle=""): |
653 | """write out ebuild info to RSS file (fp)""" |
330 | """write out ebuild info to RSS file (fp)""" |
654 | |
331 | |
655 | # web link for RSS feed |
332 | # web link for RSS feed |
… | |
… | |
705 | ebuild['time'].strftime("%a, %d %b %Y %H:%M:%S +0000")) |
382 | ebuild['time'].strftime("%a, %d %b %Y %H:%M:%S +0000")) |
706 | ) |
383 | ) |
707 | |
384 | |
708 | fp.write("\n\ |
385 | fp.write("\n\ |
709 | <textInput>\n\ |
386 | <textInput>\n\ |
710 | <title>Search the Online Package Database</title>\n\ |
387 | <title>Search</title>\n\ |
711 | <link>%s/search/</link>\n\ |
388 | <link>%s/search/</link>\n\ |
712 | <description>emerge -Ss</description>\n\ |
389 | <description>Search the Online Package Database</description>\n\ |
713 | <name>sstring</name>\n\ |
390 | <name>sstring</name>\n\ |
714 | </textInput>\n\ |
391 | </textInput>\n\ |
715 | </channel>\n\ |
392 | </channel>\n\ |
716 | </rss>\n" % config.FEHOME) |
393 | </rss>\n" % config.FEHOME) |
717 | |
394 | |