[cpif] r279 - in trunk: . backend frontend-web

svn at argo.es svn at argo.es
Sun Jul 15 21:52:20 CEST 2007


Author: jcea
Date: Sun Jul 15 21:52:18 2007
New Revision: 279

Log:
Ya podemos modificar la configuracion global de CPIF, no solo visualizarla.

Podemos enviar mensajes por correo electronico, con control de errores,
reintentos, etc.



Added:
   trunk/backend/smtp.py   (contents, props changed)
   trunk/frontend-web/url_edit_config_POST.py
      - copied, changed from r278, /trunk/frontend-web/url_perfil_POST.py
Modified:
   trunk/TODO
   trunk/backend/database.py
   trunk/backend/upgrade.py
   trunk/frontend-web/init.py
   trunk/frontend-web/url_edit_config.py

Modified: trunk/TODO
==============================================================================
--- trunk/TODO	(original)
+++ trunk/TODO	Sun Jul 15 21:52:18 2007
@@ -258,15 +258,6 @@
 - 20070712: Ya que estamos conservando la base de datos entre versiones,
   eliminar el backup de usuarios, que se ha quedado anticuado.
 
-- 20070712: Ya podemos visualizar la configuracion CPIF via web, pero
-  todavia no podemos cambiarla.
-
-  Ojo en esta opcion, ya que si la transaccion DURUS se aborta, no debemos
-  modificar el estado global. La forma mas simple de lograrlo es hacer
-  un "commit" al final del cuerpo de la funcion, luego actualizar el estado
-  global y, por ultimo, regresar. No debe hacerse nada mas, so pena de que
-  ocurra una excepcion y nos deje el estado global a medio cambiar.
-
 - 20070712: Poder grabar en un fichero toda la configuracion de una
   instalacion CPIF, tipo Pickle o XML.
 
@@ -283,4 +274,17 @@
   - http://www.darkreading.com/document.asp?doc_id=127731
 
   - http://www.darkreading.com/document.asp?doc_id=107651
+
+- 20070715: Si hay algun problema en el envio de mensajes por correo
+  electronico, lo volvemos a intentar de nuevo al cabo de un minuto.
+  Pero hay que tener en cuenta el caso de envios con problemas persistentes, por
+  ejemplo, porque el usuario pertenece al servidor de correo local,
+  pero no existe.
+
+- 20070715: Si el servidor SMTP nos desconecta en medio de un envio,
+  todos los mensajes siguientes se encolaran. Si es algo persistente,
+  no saldra correo.
+
+- 20070715: Cuando se encola un mensaje de correo electronico, puede
+  tardar hasta un minuto en intentar enviarse.
 

Modified: trunk/backend/database.py
==============================================================================
--- trunk/backend/database.py	(original)
+++ trunk/backend/database.py	Sun Jul 15 21:52:18 2007
@@ -1,7 +1,7 @@
 # $Id$
 
 
-VERSION_DB="2007071202"
+VERSION_DB="2007071503"
 
 import globales
 
@@ -51,6 +51,8 @@
     mensajes["num_mensajes"]=0
     root["mensajes"]=mensajes
 
+    root["outgoing emails"]=BTree()
+
     seguimiento_no_leidos=PersistentDict()
     seguimiento_no_leidos["hilo2last_msg"]=BTree()
     seguimiento_no_leidos["last_msg2hilo"]=BTree()
@@ -73,6 +75,12 @@
     conf["allow_bbcode"]=True
     conf["allow_html"]=True
     conf["allow_avatar_change"]=True
+    conf["smtp_server"]=""
+    conf["smtp_server_port"]=25
+    conf["smtp_auth_user"]=""
+    conf["smtp_auth_passwd"]=""
+    conf["smtp_use_tls"]=False
+    conf["smtp_email_sender"]=""
 
   @monitor
   def load_initial_config(conn) :

Added: trunk/backend/smtp.py
==============================================================================
--- (empty file)
+++ trunk/backend/smtp.py	Sun Jul 15 21:52:18 2007
@@ -0,0 +1,76 @@
+# $Id$
+
+from globales import monitor
+
+from globales import smtp_server,smtp_server_port,smtp_email_sender,smtp_use_tls,smtp_auth_user,smtp_auth_passwd
+
+def process_queue() :
+  @monitor
+  def outgoing_pending(conn) :
+    emails=conn.get_root()["outgoing emails"]
+    if not emails : return None,None  # Nothing to do
+    return emails,emails.keys()   # Load all keys in RAM, so we can delete element in the BTree safely while iterating
+
+  emails,pending=outgoing_pending()
+  if not pending : return None
+
+  import smtplib
+  m=smtplib.SMTP(smtp_server,smtp_server_port)
+  m.ehlo()
+  if smtp_use_tls :
+    m.starttls()
+    m.ehlo()   # We need a new "ehlo" because capabilities could have changed
+  if smtp_auth_user :
+    m.login(smtp_auth_user,smtp_auth_passwd)
+
+# We don't keep the monitor locked because mail sending can be
+# fairly slow.
+  for k in pending :
+    @monitor
+    def get_msg(conn,emails,k) :
+      destinations,message_obj,error=emails[k]
+      text=message_obj[0]
+      return destinations,message_obj,text
+
+    @monitor
+    def requeue(conn,emails,k,destinations,message,error) :
+      emails[k]=(destinations,message,error)
+
+    @monitor
+    def del_message(conn,emails,k) :
+      del emails[k]
+
+    destinations,message_obj,text=get_msg(emails,k)
+    try :
+      ret=m.sendmail(smtp_email_sender,destinations,text)
+    except :
+      import sys
+      requeue(emails,k,destinations,message_obj,sys.exc_value)
+      continue
+    if ret=={} :
+      del_message(emails,k)
+      continue
+    destinations=ret.keys() # The destinations that were not processed
+    error=ret.popitem()
+    requeue(emails,k,destinations,message,error)
+
+def smtp() :
+  import time
+  while True :
+    try :
+      process_queue()
+    except :
+      raise
+    time.sleep(60)
+
+def enqueue(conn,destinations,message) :
+  from durus.persistent_list import PersistentList
+  emails=conn.get_root()["outgoing emails"]
+  if not emails :
+    k=0
+  else :
+    k=emails.get_max_item()[0]+1
+  message_obj=PersistentList()
+  message_obj.append(message)
+  emails[k]=(destinations,message_obj,None)
+

Modified: trunk/backend/upgrade.py
==============================================================================
--- trunk/backend/upgrade.py	(original)
+++ trunk/backend/upgrade.py	Sun Jul 15 21:52:18 2007
@@ -199,3 +199,31 @@
     root["config"]=conf
     conn.commit()
 
+  if root["version del foro"]=="2007071202" :
+    print "Actualizando la base de datos: 2007071202 -> 2007071501"
+    root["version del foro"]="2007071501"
+    conf=root["config"]
+    conf["smtp_server"]=""
+    conf["smtp_server_port"]=25
+    conf["smtp_auth_user"]=""
+    conf["smtp_auth_passwd"]=""
+    conf["smtp_use_tls"]=False
+    conf["smtp_email_sender"]=""
+    conn.commit()
+
+  if root["version del foro"]=="2007071501" :
+    print "Actualizando la base de datos: 2007071501 -> 2007071502"
+    root["version del foro"]="2007071502"
+    root["outgoing emails"]=BTree()
+    conn.commit()
+
+  if root["version del foro"]=="2007071502" :
+    print "Actualizando la base de datos: 2007071502 -> 2007071503"
+    root["version del foro"]="2007071503"
+    conf=root["config"]
+    conf2=PersistentDict()
+    conf2.update(conf)
+    root["config"]=conf2
+    conn.commit()
+
+

Modified: trunk/frontend-web/init.py
==============================================================================
--- trunk/frontend-web/init.py	(original)
+++ trunk/frontend-web/init.py	Sun Jul 15 21:52:18 2007
@@ -96,7 +96,19 @@
         database.mensaje_add(conn,"Cuerpo del hilo %d" %i,r.sample(usuarios,1)[0],titulo="Hilo %d" %i,metatag=0)
       time.time=t
 
+    if globales.smtp_server=="mail2.argo.es" :
+      import smtp
+      smtp.enqueue(conn,["jcea at argo.es"],"Subject: Arranque CPIF\r\n\r\nArrancamos CPIF")
+
   inicializa(masterkey=sys.argv[1])
+
+# Lanzamos el servicio de envios de mensajes
+# electronicos en un hilo separado
+  from smtp import smtp
+  import threading
+  t=threading.Thread(target=smtp)
+  t.setDaemon(True)
+  t.start()
 
 # Lanzamos el servidor web en un hilo separado
   from servidor_web import servidor_web

Modified: trunk/frontend-web/url_edit_config.py
==============================================================================
--- trunk/frontend-web/url_edit_config.py	(original)
+++ trunk/frontend-web/url_edit_config.py	Sun Jul 15 21:52:18 2007
@@ -16,14 +16,20 @@
     return pagina.web()
 
   texto=['<tr><td>Number of messages per page on a thread</td><td><input type="text" name="thread_len" size="5" value="%d" /></td></tr>' %globales.thread_len]
-  texto.append('<tr><td>Allow anonymous access?</td><td><input type="checkbox" name="allow_anonymous" %s /></td></tr>' % "CHECKED" if globales.allow_anonymous else "")
+  texto.append('<tr><td>Allow anonymous access?</td><td><input type="checkbox" name="allow_anonymous" %s /></td></tr>' % ("CHECKED" if globales.allow_anonymous else ""))
   texto.append('<tr><td>Simultaneous HTTP connections</td><td><input type="text" name="http_max_clients" size="5" value="%d" /></td></tr>' %globales.http_max_clients)
   texto.append('<tr><td>Specify initial HTTP timeout</td><td><input type="text" name="http_initial_timeout" size="5" value="%d" /></td></tr>' %globales.http_initial_timeout)
   texto.append('<tr><td>Keep-alive timeout<br/>(if zero or False, no keep-alive support)</td><td><input type="text" name="http_keep_alive_timeout" size="5" value="%d" /></td></tr>' %globales.http_keep_alive_timeout)
-  texto.append('<tr><td>OpenID Support<br/>(Requires library. See docs)</td><td><input type="checkbox" name="openid_support" %s /></td></tr>' % "CHECKED" if globales.openid_support else "")
-  texto.append('<tr><td>Allow BBcode?</td><td><input type="checkbox" name="allow_bbcode" %s /></td></tr>' % "CHECKED" if globales.allow_bbcode else "")
-  texto.append('<tr border=1><td>Allow HTML?</td><td><input type="checkbox" name="allow_html" %s /></td></tr>' % "CHECKED" if globales.allow_html else "")
-  texto.append('<tr><td>Allow avatar change<br/>(Requires library. See docs)</td><td><input type="checkbox" name="allow_avatar_change" %s /></td></tr>' % "CHECKED" if globales.allow_avatar_change else "")
+  texto.append('<tr><td>OpenID Support<br/>(Requires library. See docs)</td><td><input type="checkbox" name="openid_support" %s /></td></tr>' % ("CHECKED" if globales.openid_support else ""))
+  texto.append('<tr><td>Allow BBcode?</td><td><input type="checkbox" name="allow_bbcode" %s /></td></tr>' % ("CHECKED" if globales.allow_bbcode else ""))
+  texto.append('<tr><td>Allow HTML?</td><td><input type="checkbox" name="allow_html" %s /></td></tr>' % ("CHECKED" if globales.allow_html else ""))
+  texto.append('<tr><td>Allow avatar change<br/>(Requires library. See docs)</td><td><input type="checkbox" name="allow_avatar_change" %s /></td></tr>' % ("CHECKED" if globales.allow_avatar_change else ""))
+  texto.append('<tr><td>Outgoing SMTP server</td><td><input type="text" name="smtp_server" size="30" value="%s" /></td><tr>' %globales.smtp_server)
+  texto.append('<tr><td>Outgoing SMTP server port</td><td><input type="text" name="smtp_server_port" size="5" value="%d" /></td></tr>' %globales.smtp_server_port)
+  texto.append('<tr><td>SMTP sender address</td><td><input type="text" name="smtp_email_sender" size="30" value="%s" /></td><tr>' %globales.smtp_email_sender)
+  texto.append('<tr><td>Use TLS for SMTP</td><td><input type="checkbox" name="smtp_use_tls" %s /></td></tr>' % ("CHECKED" if globales.smtp_use_tls else ""))
+  texto.append('<tr><td>Outgoing SMTP user<br/>(if server requires authentication)</td><td><input type="text" name="smtp_auth_user" size="25" value="%s" /></td><tr>' %globales.smtp_auth_user)
+  texto.append('<tr><td>Outgoing SMTP password<br/>(if server requires authentication)</td><td><input type="password" name="smtp_auth_passwd" size="20" value="%s" /></td><tr>' %globales.smtp_auth_passwd)
 
   return (200, {"Content-Type":"text/html; charset=utf-8"},
 """

Copied: trunk/frontend-web/url_edit_config_POST.py (from r278, /trunk/frontend-web/url_perfil_POST.py)
==============================================================================
--- /trunk/frontend-web/url_perfil_POST.py	(original)
+++ trunk/frontend-web/url_edit_config_POST.py	Sun Jul 15 21:52:18 2007
@@ -4,65 +4,45 @@
 
 @monitor
 def gestiona_url(conn,handler,path,usuario) :
-  if not usuario : # Acceso anonimo
+  if usuario!="master" : # Acceso anonimo
     return None
 
-  if len(path)!=2 : return None
+  if len(path)!=1 : return None
 
-  import database
-  usuario_pedido=path[1]
-  usuario_pedido_normalizado=database.normaliza_nick(usuario_pedido)
-
-  if (usuario!="master") and (usuario_pedido_normalizado!=database.normaliza_nick(usuario)) :
-    return None
-
-  import skins
-  pagina = skins.Skin(path,usuario)
-  pagina.load_dict({"page_title": "cpif - Cambio de perfil exitoso"})
-  
   import cgi
   ctype,pdict=cgi.parse_header(handler.headers.getheader('content-type'))
   cuerpo=cgi.FieldStorage(fp=handler.rfile,headers=handler.headers,environ={'REQUEST_METHOD':'POST'},keep_blank_values=1)
-  clave1=cuerpo.getfirst("clave1")
-  clave2=cuerpo.getfirst("clave2")
-  avatar=cuerpo.getfirst("avatar")
-  if clave1==None or clave2==None or avatar==None: # Comprobamos el caso de campos inexistentes
-    return None
-
-  clave1=clave1.strip()
-  clave2=clave2.strip()
-  OpenIDs=set()
-  for i in xrange(1,6) :
-    OpenID=cuerpo.getfirst("OpenID%d" %i)
-    if OpenID : # Comprobamos el caso de campos inexistentes
-      OpenID=OpenID.strip()
-      if OpenID :
-        if '"' in OpenID : # XSS
-          pagina.load_url(["error"])
-          pagina.load_dict({"generic_message": "<h1>Caracteres extra&ntilde;os en los servidores OpenID</h1>"})
-          return pagina.web()
-        OpenIDs.add(OpenID)
-
-  if clave1!=clave2 :
-    pagina.load_url(["error"])
-    pagina.load_dict({"generic_message": "<h1>Las claves introducidas no coinciden</h1>"})
-    return (pagina.web())
 
   import database
-  if not database.usuario_verifica(conn,nick=usuario_pedido) :
-    pagina.load_url(["error"])
-    pagina.load_dict({"generic_message": "<h1>El usuario NO existe</h1>"})
-    return (pagina.web())
-
-  if avatar :
-    error=database.avatar_update(conn,usuario_pedido,avatar)
-    if error :
-      pagina.load_url(["error"])
-      pagina.load_dict({"generic_message": "<h1>%s</h1>" %error})
-      return (pagina.web())
-
-  database.usuario_update(conn,usuario_pedido,clave=clave1,OpenIDs=OpenIDs)
+  root=conn.get_root()
+  conf=root["config"]
+  new_conf={}
+  for k,v in conf.items() :
+    t=type(v)
+    if type(True)==t :
+      v=cuerpo.getfirst(k,"*")   # If we don't have a value, then false
+      new_conf[k]=(v!="*")
+    elif type(0)==t :
+      v=cuerpo.getfirst(k)
+      try :
+        v=int(v)   # Be sure it is present and a number
+      except :
+        return None
+      new_conf[k]=v
+    elif type("")==t :
+      v=cuerpo.getfirst(k)
+      if v==None : return None   # Be sure it is present
+      new_conf[k]=v
+    else :
+      return None # Error!!
+  # conf.clear() # Don't need "clear", since keys are overwritten
+  conf.update(new_conf) # Be careful, since the dictionary is persistent
+
+# We do a commit here to be sure we store the
+# new configuration before activating it.
+# This code should be the very last code in this function.
+  conn.commit()
+  database.load_new_config(new_conf)
 
-  pagina.load_dict({"generic_message": "<h1>Cambio de perfil exitoso</h1>"})
-  return pagina.web()
+  return (200,{"Content-Type":"text/plain; charset=utf-8"},"Cambio de configuracion CPIF efectuado")
 



More information about the cpif mailing list