viernes, 27 de agosto de 2010

Error 404 Joomla - "Component Not Found"

Realmente me di una mala primera impresión al comenzar a explorar las funciones de Joomla. No me quejo de la instalación la cual fue muy clara en los pasos a seguir. Pero ya cuando comenzé a editar los menús de repenté tuve un error 404:


El problema es bastante común al parecer, ya que en un par de idas a Google encontré la solución. Lo que hay que hacer es seleccionar un item de menú para que sea el por defecto ("default").



Voy a continuar usando Joomla esperando que este tipo de errores poco amigables no sean la tónica de este administrador de contenido.

miércoles, 25 de agosto de 2010

Gimp: Crear un Gif Animado

Aquí va una pequeña guía para crear un gif animado usando Gimp:

1. Primero creamos una imagen con el tamaño que necesitamos:


2. Editamos la imagen. No me vayan a robar el diseño de esta imagen (<sarcasmo/>).


3. Repetimos el proceso agregando otra imagen.

4. En la última imagen que agregamos usamos el comando de copiar (ctrl + c) y lo pegamos en la primera imagen (ctrl + v). Esto hace que la imagen quede como una capa de nuestra primera imagen.


5. Podemos renombrar las capas solo para tener más orden. Doble clic en la capa.


6. Repetimos el proceso para cada imagen que aparecerá en el gif animado y le damos salvar como ".gif".



7. Luego aparece la pantalla de opciones de exportación de la cual elejimos que lo guarde como animación.



8. Ya en la última pantalla configuramos las últimas opciones. Las más relevantes son las del periodo de tiempo para mostrar cada cuadro y la opción de si repetir la animación infinitamente.



Resultado:


Archivos bat : generar una carpeta con la fecha actual

Si quisieramos tener un bat que corre recurrentemente como en una tarea programada del cual se genera un archivo, lo conveniente es guardarlos en carpetas con la fecha en que se ejecutó. Por ejemplo el siguiente script corre un listado de archivos y los guarda en una carpete con la fecha actual:

@echo off
set folder=%date:~10,4%%date:~7,2%%date:~4,2%
mkdir %folder%
dir *.* > dir.txt
move dir.txt %folder%\dir.txt
pause

viernes, 20 de agosto de 2010

Uso del patrón Observador para llevar el progreso durante la carga de la página

¿Alguna te ha tocado entrar a una página que corre por detrás un proceso pesado y que por tanto dura bastante en terminar? Si la página no está bien diseñada en términos de experiencia de usuario, terminamos con una sensación de que va tomar toda la eternidad en cargar.

Si queremos evitar este efecto tortuga en nuestros sitios, debemos considerar siempre si podemos agregar un indicador en la página que muestre el progreso avanzado de la tarea que está corriendo el servidor. Para lograr esto podemos tomar ventaja del patrón Observador.

Lo que necesitamos primariamente es correr el proceso de manera asincrónica, en otras palabras, en un hilo diferente. El siguiente diagrama muestra como el proceso pesados en contenido en una clase que extiende de "Thread".




La clase de abajo va a simular un proceso que toma mucho tiempo al tomar varias siestas (Thread.sleep()).

package com.gsolano.longprocess

import java.util.Observable;

/**
* Class with an observable mock long progress.
* @author gsolano
*
*/
public class LongProcess extends Observable {

/**
* Keeps the progress of the process.
*/
protected Float progress;

/**
* Simulates a long process.
*/
public void start() {
int n =10;
for (int i=0;i <= n; i++) {
progress = (float)i/(float)n * 100; // Calculates progress.
try {
Thread.currentThread();
Thread.sleep(2000);
this.setChanged();
this.notifyObservers(progress);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


El progreso de esta clase es calculado en cada iteración notificando al mismo tiempo del cambio que se ha efectuado en el progreso. La siguiente clase es la que observa a "LongProcess".

package com.gsolano.longprocess;

import java.util.Observable;
import java.util.Observer;

/**
* Observer class
*
* @author gsolano
*/
public class LongProcessObserver implements Observer{

protected Float progress;
/**
* Tracks the progress of the long process.
* @return
*/
public Float getProgress() {
return progress;
}

public void update(Observable o, Object arg) {
progress = (Float) arg;
}
}


Para completar el diagrama anterior, necesitamos crear una clase que extienda de Thread para envolver el proceso largo y lanzarlo en un hilo diferente.

/**
*
* Class to run a LongProcess in a separate thread.
*
* @author gsolano
*
*/
public class LongProcessThread extends Thread {

private LongProcess longProcess;

public LongProcess getLongProcess() {
return longProcess;
}

public void setLongProcess(LongProcess longProcess) {
this.longProcess = longProcess;
}

@Override
public void run() {
if(longProcess != null) {
longProcess.start();
}
}
}


Ahora brinquemos a al lado de la parte web. En el siguiente action de struts se maneja dos tipos de eventos:

1. Arranque el proceso pesado:

  • El proceso pesado es creado.
  • Un observador es agregado al proceso.
  • Lance el proceso en un hilo distinto.
  • Se guarda el observador en sesión.

2. Enviar una actualización del progreso del proceso

  • Se saca el observador de la sesión.
  • El valor de progreso se toma del observador y se escribe en la respuesta.

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class FooProgressAction extends Action{

@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {

String action = request.getParameter("action");

if(action != null) {
if(action.equalsIgnoreCase("progress")) { // If action is ajax request to get progress.
// Get the observer.
LongProcessObserver longProcessObserver = (LongProcessObserver)
request.getSession().getAttribute("observer");
if(longProcessObserver != null) {
// Get the progress from the observer.
Float progress = longProcessObserver.getProgress();
if(progress != null) {
// Send the progress to the page.
response.getWriter().write(progress.toString());
}
return null;
}
} else if(action.equalsIgnoreCase("start")) { // Did someone click the start button?
launchLongProcess(request);
}
}
return mapping.findForward("success");
}

private void launchLongProcess(HttpServletRequest request) {
LongProcess longProcess = new LongProcess();
LongProcessObserver observer = new LongProcessObserver();
// Add the observer to the long process.
longProcess.addObserver(observer);
// Launch long process in a thread.
LongProcessThread longProcessThread = new LongProcessThread();
longProcessThread.setLongProcess(longProcess);
longProcessThread.start();
// Keep the observer in session.
request.getSession().setAttribute("observer", observer);
// Send a flag indicating that party just started!
request.setAttribute("processStarted", true);
}
}
Del lado del cleinte se necesita solamente lógica para comenzar un ciclo de invocaciones Ajax para solicitar el progreso hasta que se alcance el 100%.


<%@ taglib uri="/WEB-INF/tld/c.tld" prefix="c" %>

<html>
<head>
<script language="Javascript">
var seconds = 1;
var run = false;
var ajaxURL;

function checkProgress(url) {
if(typeof url != 'undefined') {
ajaxURL = url;
}

var xmlHttp;
try {
xmlHttp = new XMLHttpRequest(); // Firefox, Opera 8.0+, Safari
} catch (e) {
try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); // Internet Explorer
} catch (e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
alert("Ajax not supported");
return false;
}
}
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 ) {
var progress = xmlHttp.responseText;
if(progress == 100.0) {
document.getElementById('progress').innerHTML = "Finished!!";
return;
}else {
if(progress) {
document.getElementById('progress').innerHTML = progress + "%";
}
setTimeout('checkProgress()', seconds * 1000);
}
}
};
xmlHttp.open("GET", ajaxURL, true);
xmlHttp.send(null);
}
</script>
</head>
<body>
<div style="position: absolute; left:40%; text-align:center; border: 1px solid; margin: 20px; padding:20px; width: 150px;">
<form action="${pageContext.request.contextPath}/longProcess.do">
<input type="hidden" name="action" value="start" />
<input type="submit" value="Start!" />
</form>

<div id="progress"></div>

<c:if test="${not empty processStarted}">
<script language="Javascript">
setTimeout('checkProgress(\'${pageContext.request.contextPath}/longProcess.do?action=progress\')', 1000);
</script>
</c:if>
</div>
</body>
</html


Resultado:




video

lunes, 16 de agosto de 2010

Documentum: seleccionar documentos (todas las versiones) con la ruta

Esta es una modificación a una consulta anterior de DQL para seleccionar archivos, sus identificadores de objeto, etiqueta de versión y rutas completas.

Solamente tiene una modificación para seleccionar todas las versiones de un mismo documento, no solamente la versión actual. Esto me ha sido útil para automatizar unos borrados en masa, borrando no solamente la versión actual, sino todas las versiones asociadas del documento.


select d.r_object_id, d.object_name, d.r_version_label, f.r_folder_path,
d.r_modify_date
from dm_document d, dm_folder f
where d.i_folder_id = f.r_object_id and d.i_position = -1
and f.i_position = -1
and i_chronicle_id in (
select i_chronicle_id from dm_document where (
(
Folder('/') and ( object_name in ('a', 'b', 'c')))
or (Folder('/') and ( object_name in ('d', 'e', 'f')))
)
) enable(row_based)

viernes, 6 de agosto de 2010

Ejecutando comandos WIN/DOS en Java

Aquí va un código sencillo para ejecutar comandos de DOS en Java:


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class DosCommandExecutor {

public static String exec(String command) {
String cmdOutput = "";
try {
Process p = Runtime.getRuntime().exec( "cmd /c " + command);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
String s = null;

while ((s = stdInput.readLine()) != null) {
cmdOutput += s + "\n";
}
} catch (IOException e) {
e.printStackTrace();
}
return cmdOutput;
}

public static void main(String[] args) {
System.out.println( DosCommandExecutor.exec("dir") );
}
}

Salida:


Volume in drive C is OSDisk
Volume Serial Number is 901D-8590

Directory of C:\code

08/03/2010 01:57 PM <DIR> .
08/03/2010 01:57 PM <DIR> ..
07/30/2010 12:14 PM 301 .classpath
07/30/2010 12:14 PM 388 .project
07/30/2010 12:14 PM <DIR> .settings
08/03/2010 02:00 PM <DIR> back_up
08/06/2010 01:57 PM <DIR> bin
08/06/2010 01:57 PM <DIR> src
2 File(s) 689 bytes
6 Dir(s) 5,739,667,456 bytes free