Agosto 2009
L M X J V S D
« Jul   Sep »
 12
3456789
10111213141516
17181920212223
24252627282930
31  

Un microgestor de descargas casero

Llevo tiempo dándole vueltas a adentrarme en el estudio de una nueva carrera (sí, soy un poco masoquista), aunque la aparición de Bolonia con sus pros y sus contras (más de estos últimos, según parece) me daba que pensar en cuanto a mi decisión. Obviamente, con el trabajo únicamente me puedo plantear estudios a distancia, por lo que acceder a mi más deseada titulación (Biología) queda, de momento, un poco lejos. Ya el año pasado estuve dándole vueltas a los másteres homologados para el EEES (Espacio Europeo de Educación Superior), en concreto a uno de redes y seguridad informática que me llamaba poderosamente la atención. Sin embargo, y mientras nuestra titulación no obtenga por parte de quienes gobiernan la deferencia que merecen, y se nos otorguen las atribuciones (es decir, tanto obligaciones como derechos) que deberíamos ostentar creo que dejaré correr lo del máster y, a lo sumo, me adentraré en algún estudio por el mero placer de aprender.

En fin, volviendo a lo que iba, este fin de semana he estado mirando alguna información sobre los Grados que empiezan a impartirse este curso académico, y algunos de los que se iniciarán en el próximo, siempre en la UNED. Además de las guías de la carrera y las de cada asignatura, resulta interesante echar un vistazo a los exámenes de años anteriores, para hacerse a la idea del nivel que alcanzan los conocimientos esperados y los contenidos que entran en juego a la hora de evaluar nuestro trabajo.

Uno de los centros asociados más interesantes a este respecto es el de Calatayud, que cuenta en su página web con un depósito de exámenes realmente apabullante. Accedemos a la carrera deseada, marcamos las asignaturas de las que queremos consultar los exámenes y tenemos ante nosotros los correspondientes a los últimos años (desde 2005 en adelante). Según sea la carrera, pueden rondar cerca de los mil exámenes en total para cada una de ellas. Los exámenes aparecen en PDF, y se pueden descargar o visualizar simplemente con pulsar sobre el enlace correspondiente. Así que nada, me dispuse a descargar unos pocos y aunque sólo quería los de los últimos años, el proceso era harto engorroso: “Botón derecho->Guardar enlace cómo…->Aceptar” y con Google Chrome, en el mejor de los casos (ya que descarga los PDF directamente) ir pulsando en cada enlace. Cuando llevaba unos pocos, como habréis imaginado, me he dicho: ¿por qué no hago un programita para automatizar esto? Dicho y hecho, se tarda menos en ello que en descargar los exámenes de una asignatura. El programa no es nada del otro mundo, y lo cierto es que tiene poco interés, pero ya que está hecho, me he dicho: ¿por qué no divago un poco en el blog y dejo el código fuente?

C#:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Net;
  7. using System.IO;
  8.  
  9. namespace Lobosoft.Utilidades
  10. {
  11. public class Downloader
  12. {
  13. WebClient webClient = new WebClient();
  14. Regex urlRegEx = new Regex(@"(((f|ht){1}tp://)[-a-zA-Z0-9@:%_\+.~#?&//=]+)");
  15.  
  16. ///
  17. /// Descarga los archivos apuntados por los enlaces al path indicado.
  18. ///
  19. ///
  20. La lista de enlaces.   ///
  21. La ruta donde descargarlo.   public void GetLinksContent(string html, string extension, string outputPath)
  22. {
  23. string filename;
  24.  
  25. foreach (string url in GetLinksUrl(html, extension))
  26. {
  27. filename = url.Substring((url.LastIndexOf('/') + 1), url.Length - url.LastIndexOf('/') - 1);
  28. webClient.DownloadFile(url, Path.Combine(outputPath , filename));
  29. }
  30. }
  31.  
  32. // Obtiene todas las URL dentro de los enlaces existentes en la web cuyo código HTML examinamos
  33. List GetLinksUrl(string htmlSource, string extension)
  34. {
  35. List links = new List();
  36.  
  37. // Obtiene todas las URL de la web
  38. foreach (Match match in urlRegEx.Matches((htmlSource)))
  39. {
  40. //Pero únicamente agrega aquellas que terminen con la extensión dada.
  41. // Podría sustituirse la expresión regular en tiempo de ejecución para devolver únicamente
  42. // las que nos interesan, pero así puede quedar algo más genérico.
  43. if (match.Value.EndsWith(extension))
  44. {
  45. links.Add(match.Value);
  46. }
  47. }
  48.  
  49. return links;
  50. }
  51. }
  52. }

Como veis, no tiene demasiado misterio. Un método, GetLinksUrl() se encarga de buscar en el código HTML de la página las URLs que contenga, y lo devuelve como una lista de enlaces a GetLinksContent(), que se encarga de ir recorriéndola y descargando los archivos a una determinada carpeta que habremos creado previamente. La llamada a este método se llevaría a cabo con el código fuente de la página en cuestión. Podemos haberlo leído de un archivo local, o bien recuperarlo de Internet mediante un WebClient (con su método estático DownloadString(url)), o si lo usamos desde una aplicación Windows, utilizando un objeto WebBrowser y accediendo a su propiedad DocumentText en un momento determinado). El misterio (o no tanto) estará en saber de qué carrera ando buscando información ;)

Bueno, y ahora, a hacer lo mismo en Python. Estoy mirando una librería, PycURL, que pinta bastante bién, y vamos a ver si consigo lograrlo. Si me pongo a ello os contaré algo por aquí, claro está. ¡Vamos a ello!


Entradas relacionadas:
  • La gacela encontradiza
  • Error 404… ¡no!
  • Protección ante XSRF
  • Control de acceso de un usuario en ASP.NET
  • ¿Cómo saber si Google ha pasado a indexar nuestra web?
  • Etiquetas: , , ,

    Evitando fallos de carga dinámica de ensamblados

    En ocasiones necesitamos cargar un tipo contenido en una DLL de forma dinámica, sin haber establecido una referencia cuando generamos nuestra aplicación. Es el caso de un sistema de plugins en el que una aplicación debe cargar en tiempo de ejecución una serie de DLLs que le ofrecen una funcionalidad adicional. En .NET disponemos de la poderosa herramienta de la reflexión (reflection) para cargar una DLL dinámicamente e instanciar un objeto de una determinada clase para hacer uso del mismo. En principio su uso es bastante sencillo y no suele darnos demasiados problemas pero ¿qué ocurre cuando una DLL que cargamos dinámicamente hace referencias a otras? Si esas librerías están ubicadas en la ruta de nuestro ejecutable no tendremos mayor problema. Recordemos que el comportamiento establecido por diseño en .NET para localizar una DLL es el siguiente:

    1. Busca en el GAC.
    2. Intenta localizarla en el directorio que contiene el ejecutable.
    3. La busca en un subdirectorio dentro de la ruta del ejecutable, cuando se ha establecido a través de un archivo de configuración.

    En el caso en que nuestras DLLs no estén en ninguno de estos lugares, se producirá una excepción al intentar cargar el ensamblado correspondiente.

    Para la primera situación, la solución es fácil: basta con instalar en el Global Assembly Cache la DLL en cuestión. Sin embargo, no es algo que convenga hacer con todas nuestras DLLs: el GAC debe mantenerse tan limpio como sea posible.

    En el segundo caso, por supuesto, bastaría con tener todas las DLLs en la ruta del ejecutable en cuestión, pero no es algo recomendable ni siempre debe ser así. Comenzaríamos a tener librerías duplicadas en el sistema y las aplicaciones terminarían por ser difíciles de mantener.

    Para el tercer caso, la solución pasa por incluir unas etiquetas que especifiquen dónde buscará el CLR nuestro ensamblado. Hay que llevar a cabo una firma del ensamblado para obtener su strong name, usar los atributos publicKeyToken y la versión del ensamblado para terminar incluyendo en nuestro archivo de configuración algo como lo siguiente (se trata de un XML, claro está, aunque sustituyo un carácter para que no se interprete como tal y sea visible):

    [configuration>
      [runtime>
        [assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
          [dependentAssembly>
            [assemblyIdentity name="Dummy"  culture="neutral"
             publicKeyToken="589048994a485948"/>
            [codeBase version="1.0.1423.13588" 
             href="FILE://C:/Lobosoft/Dummy.dll"/>
          [/dependentAssembly>
        [/assemblyBinding>
      [/runtime>
    [/configuration>

    Pero, ¿qué ocurre si las DLLs pueden estar en distintas ubicaciones, no tenemos posibilidad de firmar digitalmente las mismas, o se da algún otro impedimento para optar por esta solución? En ese caso, tal vez debamos optar por controlar el evento AssembyResolve, asociado al dominio de aplicación actual. Su uso es bastante simple:

    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyResolveEventHandler);

    Al incluir este par de líneas, en el momento en que deba resolverse si un determinado ensamblado ha sido cargado. Esto ocurre, por ejemplo, al hacer una llamada al método CreateInstance() de un determinado Assembly, para instanciar un objeto de un tipo determinado. En el caso de que alguna de las DLLs referenciadas por el ensamblado no esté disponible, nuestro manejador de evento procederá a llevar a cabo la carga correspondiente:

    C#:
    1. Assembly AssemblyResolveEventHandler(object sender, ResolveEventArgs args)
    2. {
    3.  //Este manejador es llamado únicamente cuando el CLR intenta cargar un ensamblado y se produce un error.
    4.  //Recupera la lista de ensamblados referenciados
    5.   Assembly needAssembly, objExecutingAssemblies;
    6.   string assemblyTemporalPath = "";
    7.   /* myAssembly, en este caso, es el objeto que intentó realizar la carga fallida. Es el Assembly que hemos cargado
    8.   * mediante reflection, y que ha lanzado el evento al hacer un CreateInstance() sobre él. Si quisiéramos llevar a
    9.   * cabo el control sobre nuestra aplicación podríamos sustituir myAssembly por Assembly.GetExecutingAssembly();
    10.   */
    11.   objExecutingAssemblies = myAssembly;           
    12.   AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();           
    13.   foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
    14.   {
    15.     if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
    16.     {
    17.       assemblyTemporalPath = DllBasePath + @"\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
    18.       break;
    19.      }
    20.    }
    21.    needAssembly = Assembly.LoadFrom(assemblyTemporalPath);
    22.    return needAssembly;
    23.  }

    Es el caso para un Assembly (myAssembly) que pudimos cargar con un Assembly.LoadFile(DllBasePath + “\DllName.dll”), y que al recuperar sus tipos e intentar instanciar uno de ellos con Assembly.CreateInstance() ha intentado acceder a otra DLL de la que haga uso o herede. Si la ubicación de esa DLL no coincide con los tres casos que especificábamos antes, la carga dará un error. Será entonces cuando entre en acción el manejador del evento AssemblyResolve y llevará a cabo la carga de las DLLs necesarias para continuar con la creación de la instancia de nuestro objeto.


    Entradas relacionadas:
  • Un microgestor de descargas casero
  • Ironías de la vida
  • Funciones Variadic
  • ALM, la gestión del ciclo de vida del software
  • KeyLed
  • Etiquetas: ,