Annotation of ECHO_content/ECHO_collection.py, revision 1.4
1.1 casties 1:
2: """Echo collection provides the classes for the ECHO content web-site.
3:
4: class ECHO_collection is the basis class for an ECHO collection.
5:
6: class ECHO_resource contains information on ECHO resources (e.g. an Display environment for Metadata
7:
8: class ECHO_externalLink contains information on externalLinks
9:
10:
11:
12: """
13: import string
14: import OFS.Image
15: from types import *
16: from OFS.Image import Image
17: from Globals import DTMLFile
18: from OFS.Folder import Folder
19: from OFS.SimpleItem import SimpleItem
20: from AccessControl import ClassSecurityInfo
21: from Globals import InitializeClass
22: from Globals import DTMLFile
23: from Products.PageTemplates.PageTemplateFile import PageTemplateFile
24: from Products.PageTemplates.PageTemplate import PageTemplate
25: from Globals import Persistent
26: from Acquisition import Implicit
27:
28:
29: import urllib
30: import xml.dom.minidom
31:
32:
33: def toList(field):
34: """Einzelfeld in Liste umwandeln"""
35: if type(field)==StringType:
36: return [field]
37: else:
38: return field
39:
40: def getText(nodelist):
41:
42: rc = ""
43: for node in nodelist:
44: if node.nodeType == node.TEXT_NODE:
45: rc = rc + node.data
46: return rc
47:
48:
49: def readMetadata(url):
50: """Methoden zum Auslesen der Metadateninformation zu einer Resource
51: Vorerst noch Typ bib"""
52:
53: metadict={}
54: try:
55: geturl=""
56: for line in urllib.urlopen(url).readlines():
57: geturl=geturl+line
58:
59:
60: except:
61: return (None,"Cannot open: "+url)
62:
63: try:
64: dom=xml.dom.minidom.parseString(geturl)
65: except:
66: return (None,"Cannot parse: "+url+"<br>"+geturl)
67:
68: metanode=dom.getElementsByTagName('bib')
69: metadict['bib_type']='Book'
70: if len(metanode)==0:
71: metanode=dom.getElementsByTagName('archimedes')
72: metadict['bib_type']='Archimedes'
73: #print "HELLO"
74:
75: if not len(metanode)==0:
76: metacontent=metanode[0].childNodes
77:
78: try:
79: metadict['bib_type']=getText(dom.getElementsByTagName('bib')[0].attributes['type'].childNodes)
80: except:
81: """nothing"""
82:
83: for node in metacontent:
84: try:
85: metadict[node.tagName.lower()]=getText(node.childNodes)
86: except:
87: """nothing"""
88:
89: #print metadict
90: return metadict,""
91:
92:
93: def setECHO_CollectionInformation(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,coords):
94:
95: """Allegemeine Informationen zu einer ECHO Collection"""
96:
97: self.label = label
98: self.title=title
99: self.description=description
100: self.content_type=content_type
101: self.responsible=responsible
102: self.credits=toList(credits)
103: self.weight=weight
104:
105: self.scientific_Information.source_type=source_type
106: self.scientific_Information.period=period
107: self.scientific_Information.scientific_Classification.context=context
108: self.scientific_Information.scientific_Classification.science=science
109: self.scientific_Information.scientific_Classification.practice=practice
110:
111:
112: #coordinates of for rectangles
113: self.coords=coords
114:
115:
116: class scientificClassification(SimpleItem,Persistent,Implicit):
117: """subclass"""
118: security=ClassSecurityInfo()
119:
120: def __init__(self,context,science,practice):
121: self.context=context
122: self.science=science
123: self.practice=practice
124: self.id="scientific_Classification"
125:
126: security.declarePublic('get_context')
127: def get_context(self):
128: return self.context
129:
130: security.declarePublic('get_science')
131: def get_science(self):
132: return self.science
133:
134: security.declarePublic('get_practice')
135: def get_practice(self):
136: return self.practice
137:
138:
139: class scientificInformation(Folder,Persistent,Implicit):
140: """subclass scientificInformation"""
141: security=ClassSecurityInfo()
142:
143:
144:
145: def __init__(self,source_type,period):
146:
147: self.id="scientific_Information"
148: self.source_type=source_type
149: self.period=period
150:
151:
152:
153: security.declarePublic('get_source_type')
154: def get_source_type(self):
155: return self.source_type
156:
157: security.declarePublic('get_period')
158: def get_period(self):
159: return self.period
160:
161:
162: class ECHO_resource(Folder):
163: """ECHO Ressource"""
164: meta_type='ECHO_resource'
165:
166:
167: def __init__(self,id,link,metalink,title,label,description,content_type,responsible,credits,weight,coords):
168:
169: self.id = id
170: """Festlegen der ID"""
171:
172: self.label = label
173: self.link= link
174: self.metalink=metalink
175: self.title=title
176: self.weight=weight
177: self.credits=toList(credits)
178: self.description=description
179: self.content_type=content_type
180: self.responsible=responsible
181: coordsnew=[ string.split(x,",") for x in coords]
182: self.coords=coordsnew
183:
1.3 dwinter 184:
185: def getCoords(self):
186: try:
1.4 ! dwinter 187: print
! 188: return [string.join(x,",") for x in self.coords]
1.3 dwinter 189: except:
190: return []
191:
1.1 casties 192:
193: def ECHO_resource_config(self):
194: """Main configuration"""
195:
196: if not hasattr(self,'weight'):
197: self.weight=""
198: if not hasattr(self,'coords'):
199: self.coords=[]
200:
201: pt=PageTemplateFile('Products/ECHO_content/ChangeECHO_resource.zpt').__of__(self)
202: return pt()
203:
204:
205: def changeECHO_resource(self,metalink,link,context,science,practice,source_type,period,title,label,description,content_type,responsible,credits,weight,coords,RESPONSE=None):
206:
207: """Änderung der Properties"""
208:
209:
210: setECHO_CollectionInformation(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,coords)
211:
212:
213: self.link=link
214: self.metalink=metalink
215:
216: if RESPONSE is not None:
217: RESPONSE.redirect('manage_main')
218:
219:
220: manage_options = Folder.manage_options+(
221: {'label':'Main Config','action':'ECHO_resource_config'},
222: {'label':'Metadata','action':'ECHO_getResourceMD'},
1.3 dwinter 223: {'label':'Graphics','action':'ECHO_graphicEntry'},
1.1 casties 224: )
225:
1.3 dwinter 226: def ECHO_graphicEntry(self):
227: """DO nothing"""
228: if 'overview' in self.aq_parent.__dict__.keys():
229: pt=PageTemplateFile('Products/ECHO_content/ECHO_draw.zpt').__of__(self)
230: return pt()
231: else:
232: return "NO OVERVIEW GRAPHICS"
233:
1.4 ! dwinter 234: def ECHO_enterCoords(self,coordstr,angle="",RESPONSE=None):
1.3 dwinter 235: """Enter coords"""
236: coords=self.coords
1.4 ! dwinter 237: temco=coordstr.split(",")
! 238: temco.append(angle)
! 239: coords.append(temco)
! 240:
1.3 dwinter 241: self.coords=coords[0:]
242: #pt=PageTemplateFile('Products/ECHO_content/ECHO_draw.zpt').__of__(self)
243: if RESPONSE is not None:
244: RESPONSE.redirect('ECHO_graphicEntry')
245:
1.1 casties 246: def ECHO_getResourceMD(self,template="yes"):
247: """Einlesen der Metadaten und Anlegen dieser Metadaten als Informationen zur Resource"""
248: (metadict, error)=readMetadata(self.metalink)
249:
250: #print "BLA"
251:
252: if not error=="": #Fehler beim Auslesen des Metafiles
253: return "ERROR:",error
254: for key in metadict.keys():#Hinzufügen der Felder
255:
256: setattr(self,key,metadict[key].encode('ascii','replace'))
257:
258:
259: self.metadata=metadict.keys()
260: #return "BLUccssB"
261: self.label=self.generate_label()
262:
263: if template=="yes":
264: pt=PageTemplateFile('Products/ECHO_content/ECHO_resourceMD.zpt').__of__(self)
265: return pt()
266:
267: def ECHO_getMD(self,item):
268: """Ausgabe der MD"""
269: return getattr(self,item)
270:
271: def index_html(self):
272: """standard page"""
273:
274: return self.REQUEST.RESPONSE.redirect(self.link)
275:
276: def generate_label(self):
277: """Erzeugt_standard_Label aus Template"""
278: pt=getattr(self,"label_template_"+self.bib_type)
279: #return pt
280: #pt.content_type="text/html; charset=utf-8"
281: return pt()
282:
283: def manage_AddECHO_resourceForm(self):
284: """Nothing yet"""
285: pt=PageTemplateFile('Products/ECHO_content/AddECHO_resourceForm.zpt').__of__(self)
286: return pt()
287:
288:
289: def manage_AddECHO_resource(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,link,metalink,credits,weight,coords,RESPONSE=None):
290:
291: """nothing yet"""
292: scientificClassificationObj=scientificClassification(context,science,practice)
293:
294: scientificInformationObj=scientificInformation(source_type,period)
295:
296:
297: newObj=ECHO_resource(id,link,metalink,title,label,description,content_type,responsible,credits,weight,coords)
298:
299: self._setObject(id,newObj)
300: getattr(self,id)._setObject('scientific_Information',scientificInformationObj)
301: getattr(self,id).scientific_Information._setObject('scientific_Classification',scientificClassificationObj)
302: if RESPONSE is not None:
303: RESPONSE.redirect('manage_main')
304:
305:
306: class ECHO_externalLink(Folder):
307: """Link zu einer externen Ressource"""
308: security=ClassSecurityInfo()
309: meta_type='ECHO_externalLink'
310:
311:
312: def __init__(self,id,link,title,label,description,content_type,responsible,credits,weight,coords):
313:
314: self.id = id
315: """Festlegen der ID"""
316:
317: self.credits=toList(credits)
318: self.label = label
319: self.link= link
320: self.title=title
321: self.weight=weight
322: self.description=description
323: self.content_type=content_type
324: self.responsible=responsible
325: coordsnew=[ string.split(x,",") for x in coords]
326: self.coords=coordsnew
327:
328: def ECHO_externalLink_config(self):
329: """Main configuration"""
330:
331: if not hasattr(self,'weight'):
332: self.weight=""
333: if not hasattr(self,'coords'):
334: print "HI"
335: self.coords=['']
336: print "G",self.coords
337:
338: pt=PageTemplateFile('Products/ECHO_content/ChangeECHO_externalLink.zpt').__of__(self)
339: return pt()
340:
341:
342: def changeECHO_externalLink(self,link,context,science,practice,source_type,period,title,label,description,content_type,responsible,credits,weight,coords,RESPONSE=None):
343:
344: """Änderung der Properties"""
345:
346:
347: setECHO_CollectionInformation(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,coords)
348:
349:
350: self.link=link
351: if RESPONSE is not None:
352: RESPONSE.redirect('manage_main')
353:
354:
355: manage_options = Folder.manage_options+(
356: {'label':'Main Config','action':'ECHO_externalLink_config'},
357: )
358:
359: def index_html(self):
360: """standard page"""
361:
362: return self.REQUEST.RESPONSE.redirect(self.link)
363:
364: def manage_AddECHO_externalLinkForm(self):
365: """Nothing yet"""
366: pt=PageTemplateFile('Products/ECHO_content/AddECHO_externalLinkForm.zpt').__of__(self)
367: return pt()
368:
369:
370: def manage_AddECHO_externalLink(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,link,credits,weight,coords,RESPONSE=None):
371:
372: """nothing yet"""
373: scientificClassificationObj=scientificClassification(context,science,practice)
374:
375: scientificInformationObj=scientificInformation(source_type,period)
376:
377:
378: newObj=ECHO_externalLink(id,link,title,label,description,content_type,responsible,credits,weight,coords)
379:
380: self._setObject(id,newObj)
381: getattr(self,id)._setObject('scientific_Information',scientificInformationObj)
382: getattr(self,id).scientific_Information._setObject('scientific_Classification',scientificClassificationObj)
383: if RESPONSE is not None:
384: RESPONSE.redirect('manage_main')
385:
386:
387: class ECHO_collection(Folder, Persistent, Implicit):
388: """ECHO Collection"""
389: security=ClassSecurityInfo()
390: meta_type='ECHO_collection'
391:
392:
393:
394: security.declarePublic('getCreditObject')
395: def getCreditObject(self,name):
396: """credit id to credititem"""
397: return getattr(self.partners,name)
398:
399: security.declarePublic('ECHO_generateNavBar')
400: def ECHO_generateNavBar(self):
401: """Erzeuge Navigationsbar"""
402: link=""
403: object="self"
404: ret=[]
405: path=self.getPhysicalPath()
406: for element in path:
407:
408:
409: if not element=="":
410: object+="."+element
411:
412: label=eval(object).label
413: link+="/"+element
414: if not label=="":
415: ret.append((label,link))
416: return ret
417:
418: security.declarePublic('ECHO_rerenderLinksMD')
419: def ECHO_rerenderLinksMD(self):
420: """Rerender all Links"""
421: #print "HI"
422: #return "OK"
423: for entry in self.__dict__.keys():
424: object=getattr(self,entry)
425:
426:
427: try:
428:
429: if object.meta_type == 'ECHO_resource':
430:
431: object.ECHO_getResourceMD(template="no")
432:
433: except:
434: """nothing"""
435:
436: return "Rerenderd all links to resources in: "+self.title
437:
438:
439:
440: security.declarePublic('printall')
441: def printall(self):
442: return self.scientific_information.__dict__.keys()
443:
444:
445: def getCoords(self):
446: try:
1.4 ! dwinter 447: print self.coords
! 448: return [string.join(x,",") for x in self.coords]
! 449:
! 450:
1.1 casties 451: except:
452: return []
1.3 dwinter 453:
1.1 casties 454: def __init__(self,id,title,label,description,content_type,responsible,credits,weight,sortfield,coords):
455: print "CO",coords
456:
457: self.id = id
458: """Festlegen der ID"""
459: self.credits=toList(credits)
460: self.label = label
461: self.title=title
462: self.description=description
463: self.content_type=content_type
464: self.responsible=responsible
465:
466: self.weight=weight
467: self.sortfield=sortfield
468: coordsnew=[ string.split(x,",") for x in coords]
469: self.coords=coordsnew
470:
471:
472: manage_options = Folder.manage_options+(
473: {'label':'Main Config','action':'ECHO_Collection_config'},
474: {'label':'Rerender Links','action':'ECHO_rerenderLinksMD'},
475: {'label':'Graphics','action':'ECHO_graphicEntry'},
476:
477: )
478:
479: def ECHO_graphicEntry(self):
480: """DO nothing"""
481: if 'overview' in self.aq_parent.__dict__.keys():
482: pt=PageTemplateFile('Products/ECHO_content/ECHO_draw.zpt').__of__(self)
483: return pt()
484: else:
485: return "NO OVERVIEW GRAPHICS"
486:
1.4 ! dwinter 487: def ECHO_enterCoords(self,coordstr,angle="",RESPONSE=None):
1.1 casties 488: """Enter coords"""
1.2 dwinter 489: coords=self.coords
1.4 ! dwinter 490: temco=coordstr.split(",")
! 491: temco.append(angle)
! 492: coords.append(temco)
1.2 dwinter 493: self.coords=coords[0:]
494: #pt=PageTemplateFile('Products/ECHO_content/ECHO_draw.zpt').__of__(self)
495: if RESPONSE is not None:
496: RESPONSE.redirect('ECHO_graphicEntry')
1.1 casties 497:
498:
499: security.declarePublic('ECHO_Collection_config')
500: def ECHO_Collection_config(self):
501: """Main configuration"""
502:
503: if not hasattr(self,'weight'):
504: self.weight=""
505:
506: if not hasattr(self,'sortfield'):
507: self.sortfield="weight"
508: #print "HI"
509: if not hasattr(self,'coords'):
510: self.coords=[]
511:
512: pt=PageTemplateFile('Products/ECHO_content/ChangeECHO_Collection.zpt').__of__(self)
513: return pt()
514:
515:
516: security.declarePublic('changeECHO_Collection')
517:
518: def changeECHO_Collection(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,coords,sortfield="weight",RESPONSE=None):
519:
520: """Änderung der Properties"""
521:
522: coordsnew=[ string.split(x,",") for x in coords]
523: setECHO_CollectionInformation(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,coordsnew)
524:
525: self.sortfield=sortfield
526:
527: if RESPONSE is not None:
528: RESPONSE.redirect('manage_main')
529:
530: security.declarePublic('index_html')
531:
532: showOverview=DTMLFile('ECHO_content_overview',globals())
533:
534:
535: def index_html(self):
536: """standard page"""
537: #print self.objectIDs()
538:
539: if 'index.html' in self.__dict__.keys():
540: return getattr(self,'index.html')()
541: elif 'overview' in self.__dict__.keys():
1.3 dwinter 542: #print "HI"
1.1 casties 543: return self.showOverview()
544:
545:
546: pt=PageTemplateFile('Products/ECHO_content/ECHO_content_standard.zpt').__of__(self)
547: pt.content_type="text/html"
548: return pt()
549:
550:
551: def getGraphicCoords(self):
552: """Give list of coordinates"""
553: subColTypes=['ECHO_collection','ECHO_externalLink','ECHO_resource']
554: ids=[]
555: for entry in self.__dict__.keys():
556: object=getattr(self,entry)
557: #print "OB:",object
558:
559: try:
1.3 dwinter 560: #print "MT:",object.meta_type
1.1 casties 561: if object.meta_type in subColTypes:
1.3 dwinter 562: #print "MT:",object.meta_type,object.getId()
1.4 ! dwinter 563: for coordtemp in object.coords:
! 564: if len(coordtemp)>3:
! 565: coord=coordtemp[0:4]
1.3 dwinter 566: if hasattr(object,'title'):
567: if not object.title=="":
568: ids.append([string.join(coord,", "),object.getId(),object.title])
569: else:
570: ids.append([string.join(coord,", "),object.getId(),object.getId()])
571: else:
572: ids.append([string.join(coord,", "),object.getId(),object.getId()])
1.1 casties 573:
574: except:
575: """nothing"""
1.2 dwinter 576: #print "IDS",ids
1.1 casties 577: return ids
578:
579: def getSubCols(self,sortfield="weight"):
580:
581: subColTypes=['ECHO_collection','ECHO_externalLink','ECHO_resource']
582: ids=[]
583: for entry in self.__dict__.keys():
584: object=getattr(self,entry)
585: #print "OB:",object
586:
587: try:
588: #print "MT:",object.meta_type
589: if object.meta_type in subColTypes:
590: ids.append(object)
591:
592: except:
593: """nothing"""
594: try:
595: sortfield=self.sortfield
596: except:
597: """nothing"""
598:
599: tmplist=[]
600: for x in ids:
601: if hasattr(x,sortfield):
602: try:
603: x=int(x)
604: except:
605: """nothing"""
606: tmp=getattr(x,sortfield)
607: else:
608: tmp=10000000
609: tmplist.append((tmp,x))
610: tmplist.sort()
611: return [x for (key,x) in tmplist]
612:
613:
614:
615:
616:
617:
618: def manage_AddECHO_collectionForm(self):
619: """Nothing yet"""
620: pt=PageTemplateFile('Products/ECHO_content/AddECHO_collectionForm.zpt').__of__(self)
621: return pt()
622:
623:
624: def manage_AddECHO_collection(self,context,science,practice,source_type,period,id,title,label,description,content_type,responsible,credits,weight,sortfield,coords,RESPONSE=None):
625:
626: """nothing yet"""
627: scientificClassificationObj=scientificClassification(context,science,practice)
628:
629: scientificInformationObj=scientificInformation(source_type,period)
630:
631:
632: newObj=ECHO_collection(id,title,label,description,content_type,responsible,credits,weight,sortfield,coords)
633:
634: self._setObject(id,newObj)
635: getattr(self,id)._setObject('scientific_Information',scientificInformationObj)
636: getattr(self,id).scientific_Information._setObject('scientific_Classification',scientificClassificationObj)
637: if RESPONSE is not None:
638: RESPONSE.redirect('manage_main')
639:
640: class ECHO_root(Folder,Persistent,Implicit):
641: """ECHO Root Folder"""
642: meta_type="ECHO_root"
643:
644: def __init__(self,id,title):
645: """init"""
646: self.id = id
647: self.title=title
648:
649: def getPartners(self):
650: """Get list of Partners. Presently only from a subfolder partners"""
651: partnerTypes=['ECHO_partner']
652: ids=[]
653: for entry in self.partners.__dict__.keys():
654: object=getattr(self.partners,entry)
655:
656: try:
657:
658: if object.meta_type in partnerTypes:
659: ids.append(object)
660:
661: except:
662: """nothing"""
663: return ids
664:
665: def getCollectionTree(self):
666: """get the collection tree (list of triples (parent,child, depth)"""
667:
668: def getCollection(object,depth=0):
669: depth+=1
670: collections=[]
671: for entry in object.__dict__.keys():
672: element=getattr(object,entry)
673: try:
674: if element.meta_type=="ECHO_collection":
675: collections.append((object,element,depth))
676: collections+=getCollection(element,depth)
677: except:
678: """nothing"""
679: return collections
680:
681:
682: return getCollection(self)
683:
684: def getCollectionTreeIds(self):
685: """Show the IDs of the Tree"""
686: ret=[]
687: for collection in self.getCollectionTree():
688: ret.append((collection[0].getId(),collection[1].getId(),collection[2]))
689: return ret
690:
691:
692:
693: def manage_AddECHO_root(self,id,title,RESPONSE=None):
694: """Add an ECHO_root"""
695: self._setObject(id,ECHO_root(id,title))
696:
697: if RESPONSE is not None:
698: RESPONSE.redirect('manage_main')
699:
700: def manage_AddECHO_rootForm(self):
701: """Nothing yet"""
702: pt=PageTemplateFile('Products/ECHO_content/AddECHO_root.zpt').__of__(self)
703: return pt()
704:
705: class ECHO_partner(Image,Persistent):
706: """ECHO Partner"""
707:
708: meta_type="ECHO_partner"
709:
710: def __init__(self, id, title,url, file, content_type='', precondition=''):
711: self.__name__=id
712: self.title=title
713: self.url=url
714: self.precondition=precondition
715:
716: data, size = self._read_data(file)
717: content_type=self._get_content_type(file, data, id, content_type)
718: self.update_data(data, content_type, size)
719:
720: manage_options = Image.manage_options+(
721: {'label':'Partner Information','action':'ECHO_partner_config'},
722: )
723:
724: def changeECHO_partner(self,url,RESPONSE=None):
725: """Change main information"""
726: self.url=url
727: if RESPONSE is not None:
728: RESPONSE.redirect('manage_main')
729:
730:
731:
732: def ECHO_partner_config(self):
733: """Main configuration"""
734: if not hasattr(self,'url'):
735: self.url=""
736: pt=PageTemplateFile('Products/ECHO_content/ChangeECHO_partner.zpt').__of__(self)
737: return pt()
738:
739:
740: manage_AddECHO_partnerForm=DTMLFile('ECHO_partnerAdd',globals(),
741: Kind='ECHO_partner',kind='ECHO_partner')
742:
743:
744:
745: def manage_AddECHO_partner(self, id, file,url, title='', precondition='', content_type='',
746: REQUEST=None):
747: """
748: Add a new ECHO_partner object.
749:
750: Creates a new ECHO_partner object 'id' with the contents of 'file'.
751: Based on Image.manage_addImage
752: """
753:
754: id=str(id)
755: title=str(title)
756: content_type=str(content_type)
757: precondition=str(precondition)
758:
759: id, title = OFS.Image.cookId(id, title, file)
760:
761: self=self.this()
762:
763: # First, we create the image without data:
764: self._setObject(id, ECHO_partner(id,title,url,'',content_type, precondition))
765:
766: # Now we "upload" the data. By doing this in two steps, we
767: # can use a database trick to make the upload more efficient.
768: if file:
769: self._getOb(id).manage_upload(file)
770: if content_type:
771: self._getOb(id).content_type=content_type
772:
773: if REQUEST is not None:
774: try: url=self.DestinationURL()
775: except: url=REQUEST['URL1']
776: REQUEST.RESPONSE.redirect('%s/manage_main' % url)
777: return id
778:
779:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>