domingo, 27 de febrero de 2011

Más que un juguete de geek...


En el tiempo que llevo de desenvolverme en el ámbito de las tecnologías de información, tomando en cuenta mi formación universitaria y experiencia laboral, siempre he considerado que no calzo por completo con el perfil de un geek. Bueno, habría que definir a que me refiero con geek en este contexto, ya que este es un calificativo que hoy en día se usa de manera muy libre. Me refiero a que no he sido tan geek desde el punto de vista de mantenerme muy actualizado con las novedades en gadgets. No soy el tipo de persona que quiero cambiar de PC cada 2 o 3 años. De hecho, mi computadora de escritorio lleva conmigo casi 10 años. Ya está pidiendo jubilación pero aún no he querido dejarla ir, porque a la verdad, no he sentido necesidad de hacer un cambio. Y es que siendo que en la universidad siempre tenía acceso a las computadoras de los laboratorios, y ahora en el trabajo he tenido mi computador personal y en ocasiones una laptop que puedo llevarme a casa, no veo una gran urgencia de buscar refrescarme tecnológicamente. Pero aún si lo hubiese requerido, siento que no soy el típico geek que por ejemplo ahorra parte de su salario para comprarse lo último en aparatos sofisticados como un Ipad .

También todos mis celulares hasta hace poco eran una versión bonita de un ladrillo. No es que sea mal agradecido con ellos, solo quiero decir que comparados con sus contemporáneos más modernos, realmente sí eran ladrillos en aspectos de tecnología. Sin embargo recientemente creo que estoy modificando un poco ese patrón conservador a la modernidad, no por un aspecto de sed consumidora, sino que creo de estrategia profesional. Doy gracias a Dios por mi nuevo aparatito, acabo de adquirir un celular HTC Legend el cual lo tengo actualizado con la más reciente versión 2.2 de Android. En verdad me deje seducir el oído con los comentarios y revisiones acerca de los celulares con sistema operativo Android. Debo confesar que mi impulso consumista se reparte en varios porcentajes: 40% en sentirme relegado con los compañeros de trabajo con sus Iphones y demás celulares inteligentes (lo que mi esposa llama el síndrome Quico), otro 40% en sentir la necesidad de contar con un aparato más sofisticado (“necesidad creada”, pero necesidad a fin de cuentas) y otro 20% en conocer la plataforma de la cual todos hablan hoy en día. ¿Solo un 20%? Pues sí, casi, casi compro un llamativo Iphone. En todo caso, y ya aterrizando esta corta disertación, creo que adquirir este celular ha sido más que un juguete nuevo, una verdadera inversión.

Ya hace un tiempo tenía la inquietud de comenzar a reservar neuronas para el desarrollo de móviles, pero no ha sido hasta que he tenido la experiencia de usar la enorme cantidad de aplicaciones que hay para el Android, que he comprendido el salto tecnológico que estamos experimentando en la interacción humano-computador. Aún teniendo una laptop que puedo llevarme a la cama, casi nunca siento ganas de prenderla para hacer cualquier cosa con ella. Casi siento el mismo nivel de esfuerzo de ir a sentarme al escritorio donde tengo la PC. Pero en cambio la experiencia de un celular con aplicaciones está a varias pulsaciones de distancia. Es más fácil comprender por qué tanto escándalo con este tipo de celulares cuando se tiene uno a mano. Sin duda el desarrollo para este tipo de aparatos va ir cobrando cada vez mayor relevancia, y tenemos que estar preparados para ello.

Así que a codificar se ha dicho…

lunes, 21 de febrero de 2011

Código para acomodar imagenes por fecha de modificación

Quize crear un programa pequeño para organizar un montón de fotos que tengo desordenadas en distintas carpetas. En lo particular me gusta tener las fotos almacenadas en carpetas que indiquen la fecha en que se tomaron. De esa manera puedo hacerme una idea cronólogica de cuándo se fueron tomando. Claro está que este código funciona bajo la premisa de que la fecha de modificación de las fotos es la misma de cuándo se tomaron, que es la misma de cuando se descargaron. Puede ser que no sea el caso de muchos archivos, pero al menos ayuda agregando cierto nivel de orden.


Clase Principal:


package gsolano.photoorganizer;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* This class is used to copy files from a specific folder (recursive scan in that folder)
* into a new folder structure using modification date of the files to map them correctly.
*
* @author gsolano
*
*/
public class FileOrganizerByModificationDate {

/**
* Extensions to that will be processed.
*/
protected final String [] extensionsToProcess = {"jpg","avi"};
/**
* Year(key): 2010
* ++ Date(key): 20100125
* ++++ File: 010223.jpg
* ++++ File: 232455.avi
*/
protected Map<Integer, Map<String,List<String>>> newFolderStructure;

/**
* Directory path that will be scanned.
*/
protected String pathToProcess;

/**
* Counter of total files found.
*/
protected int totalFilesToOrganize;

/**
* Default constructor.
*/
private FileOrganizerByModificationDate() {
newFolderStructure = new HashMap<Integer,Map<String,List<String>>>();
totalFilesToOrganize = 0;
}

/**
* Constructor receiving path that will be processed.
* @param pathToProcess
*/
public FileOrganizerByModificationDate(String pathToProcess){
this();
this.pathToProcess = pathToProcess;
}

/**
* Scans the path provided in the constructor.
*/
public void scan() {
if(pathToProcess != null && pathToProcess != "") {
scan(pathToProcess);
}
}

/**
* Scans a specific path.
* @param rootDirPath
*/
public void scan(String rootDirPath) {
System.out.println("Scanning: " + rootDirPath);
this.pathToProcess=rootDirPath;
File sourceDir = new File(rootDirPath);

if(sourceDir.exists() && sourceDir.isDirectory()) {
scanRecursively(sourceDir);
System.out.println("Files to organize: " + totalFilesToOrganize);

} else {
System.out.println("Directory does not exist!");
}

if(newFolderStructure.size() > 0) { // Files were added to the new structure?
try {
System.out.println("Copying files...");
copyFiles(); // Copy the files in the new directory structure.
} catch (IOException e) {
e.printStackTrace();
}
// Clean the class..
newFolderStructure.clear();
totalFilesToOrganize = 0;
}
}

/**
* Once scan is finished, this method is in charge of copying files to new structure.
* @throws IOException
*/
private void copyFiles() throws IOException{
Set<Integer> years = newFolderStructure.keySet();
int totalFilesCopied = 0;

for(Integer year : years) {
String newYearDirectoryPath = (pathToProcess.endsWith(File.separator) ? pathToProcess + year.toString() :
pathToProcess+ File.separator + year.toString());
// Let's create the year directory.
File newYearDirectory = new File(newYearDirectoryPath);
newYearDirectory.mkdir();
String newYearAbsoluteDirectoryPath = newYearDirectory.getAbsolutePath();
Set<String> newDateFolders = newFolderStructure.get(year).keySet();

// Diving into the years map.
for(String newDateFolder : newDateFolders) {
// Let's create the date folder.
String newDateFolderPath = newYearAbsoluteDirectoryPath + File.separator + newDateFolder;
File newDateDirectory = new File(newDateFolderPath);
newDateDirectory.mkdir();

List<String> files = newFolderStructure.get(year).get(newDateFolder);

for(String file : files) {
// Finally, copy the file in the respective folder.
FileCopy.copy(file, newDateFolderPath, false);
totalFilesCopied++;
System.out.print("\r"+progress(totalFilesCopied, totalFilesToOrganize));
}
}
}

}

/**
* Recursive method to find empty folders.
* @param sourceDir
*/
private void scanRecursively(File sourceDir) {
// List files and directories in the first level.
File[] listOfFiles = sourceDir.listFiles();

if (listOfFiles != null) {
for(int i=0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
String extension = getExtension(listOfFiles[i].getName());
if (isValidExtension(extension)) {
processFile(listOfFiles[i]);
totalFilesToOrganize++;
}
}
else if(listOfFiles[i].isDirectory()) { // Recursive call.
scanRecursively(listOfFiles[i]);
}
}
}
}

/**
* Process the file found. The file path is added to the new map structure used later to create
* the new directory structure and copy the file.
* @param file
*/
private void processFile(File file) {

DateFormat dfYearOnly = new SimpleDateFormat("yyyy");

Date lastModified = new Date(file.lastModified());
Integer yearOfFile = Integer.valueOf(dfYearOnly.format(lastModified));
DateFormat df = new SimpleDateFormat("yyyyMMdd");
String dateFolder = df.format(lastModified);

if(!newFolderStructure.containsKey(yearOfFile)) {
newFolderStructure.put(yearOfFile, new HashMap<String, List<String>>());
}
if(!newFolderStructure.get(yearOfFile).containsKey(dateFolder)) {
newFolderStructure.get(yearOfFile).put(dateFolder, new LinkedList<String>());
}
newFolderStructure.get(yearOfFile).get(dateFolder).add(file.getAbsolutePath());
}

/**
* Get file's extension *
* @param fileName
* @return file extension
*/
private String getExtension(final String fileName) {
final int x = fileName.lastIndexOf(".");
return (x > 0 && x < fileName.length()) ? fileName.substring(fileName.lastIndexOf(".")+1, fileName.length()) : "" ;
}

/**
* Validates if the extension is valid to be processed.
* @param extension
* @return
*/
private boolean isValidExtension(String extension) {
for(String extensionToProcess : extensionsToProcess) {
if(extensionToProcess.equalsIgnoreCase(extension)){
return true;
}
}
return false;
}

/**
* Simple function to calculate progress of the copy process.
* @param numerator
* @param denominator
* @return
*/
private String progress(int numerator, int denominator) {
float progress = 0;
progress = ((float)numerator / denominator)*100;
DecimalFormat df1 = new DecimalFormat("##.00");
return df1.format(progress)+"%";
}

public static void main(String[] args) {
FileOrganizerByModificationDate organizer =
new FileOrganizerByModificationDate(args[0]);
organizer.scan();
}
}



Clase para hacer el copiado:


package gsolano.photoorganizer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* Utility class to copy a file from a source path to a target path.
*
* @author gsolano
*
*/
public class FileCopy {

public static void copy(String fromFileName, String toFileName,
boolean overrideFiles) throws IOException {
File fromFile = new File(fromFileName);
File toFile = new File(toFileName);

if (!fromFile.exists())
throw new IOException("FileCopy: " + "no such source file: "
+ fromFileName);
if (!fromFile.isFile())
throw new IOException("FileCopy: " + "can't copy directory: "
+ fromFileName);
if (!fromFile.canRead())
throw new IOException("FileCopy: " + "source file is unreadable: "
+ fromFileName);

if (toFile.isDirectory())
toFile = new File(toFile, fromFile.getName());
if (toFile.exists()) {
if (!toFile.canWrite()) {
throw new IOException("FileCopy: "
+ "destination file is unwriteable: " + toFileName);
}
if (!overrideFiles) {
// File exist, do not override it.
return;
}

String parent = toFile.getParent();
if (parent == null)
parent = System.getProperty("user.dir");
File dir = new File(parent);
if (!dir.exists())
throw new IOException("FileCopy: "
+ "destination directory doesn't exist: " + parent);
if (dir.isFile())
throw new IOException("FileCopy: "
+ "destination is not a directory: " + parent);
if (!dir.canWrite())
throw new IOException("FileCopy: "
+ "destination directory is unwriteable: " + parent);
} else {
// Create directory.
new File(toFile.getParent()).mkdirs();
}

FileInputStream from = null;
FileOutputStream to = null;
try {
from = new FileInputStream(fromFile);
to = new FileOutputStream(toFile);
byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = from.read(buffer)) != -1)
to.write(buffer, 0, bytesRead); // write
} finally {
if (from != null)
try {
from.close();
} catch (IOException e) {
;
}
if (to != null)
try {
to.close();
toFile.setLastModified(fromFile.lastModified());

} catch (IOException e) {
;
}
}
}
}


Salida:

miércoles, 9 de febrero de 2011

Joomla: Agregando Un Chat

Ya poco a poco le estoy tomando cariño a este reconocido administrador de contenido. Puse a prueba qué tan fácil es instalar un chat en el sitio Joomla que tengo, y la verdad pasó con nota 100.

Primero descargué el plugin (o extensión) jPFChat y lo instalé usando la opción de instalación dentro del menú de extensiones.


Lo único que tenemos que hacer es subir el zip que descargamos.


Una vez instalado es solo cuestión de agregar un nuevo item de menú y elegir la opción de jPFChat nuevecita de paquete.



No requirió la verdad un gran esfuerzo técnico agrgar una opción de chat en el sitio. En todas!



jueves, 3 de febrero de 2011

XCopy: "specify a file name or directory name on the target"

En mi trabajo requerimos respaldar una serie de archivos que como parte del proceso estándar que tiene el cliente, se hace con un archivo .BAT que corre un conjunto de comandos XCOPY. Uno por cada archivo que se respalda. Este comando es bastante útil por el conjunto de opciones que tiene, entre las cuales la que destaca para nuestros propósitos es la que crea las carpetas en el path destino si estas no existen (/s).

XCOPY c:\a\very\long\path\to\myfile.txt d:\back-up\to\another\very\long\path\to\myfile.txt /s /y

El único inconveniente que estabamos teniendo, es que por cada archivo el comando nos pedía una confirmación de sí lo que estabamos copiando era un archivo o una carpeta.

specify a file name or directory name on the target
(F = file, D = directory)?


Algo bastante tedioso, y bastante improbable de que no hubiera manera de indicar que todo lo que copiabamos eran archivos. Mis primeras inmersiones en Google no me arrojaron nada que me ayudara a resolver este asunto. Inclusive intenté atacar el problema usando programación con DOS BATCH para arrojar la tecla 'F' pero tampocó fue de ayuda.

En estos días intenté otra vez buscar algo en Google, y ahora sí encontré algo que me ayudó a entender como hay que usar el comando. En el path del destino no hay que dejar el nombre del archivo, sino solamente el path:

XCOPY c:\a\very\long\path\to\myfile.txt d:\back-up\to\another\very\long\path\to\ /s /y

Parece que cuando el destino termina con alguna extensión, el comando no sabe determinar si es un directorio o un archivo. Así de simple era la solución.

Ordenamiento de colecciones con interface Comparable

Una de las interfaces más útiles en aspectos de ordenar colecciones es la interface Comparable. Al usar esta interface en una clase podemos disponer de la clase de utilidades "Collections" para ordenar de alguna forma deseada, cualquier colección que contenga objetos de ese tipo. En el siguiente ejemplo se utiliza una lista de personas que se ordenan por edad. Para ello solo se implementa el método "compareTo" (preferiblemente usando "generics") en el cual solo se resta la edad de los dos objetos comparados. Después es solo cuestión de invocar el método Collections.sort(Collection<object>).


import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class Person implements Comparable<Person>{

protected String name;
protected int age;

public Person(String name, int age) {
this.name=name;
this.age=age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

/**
* Implementing comparison by age.
*/
public int compareTo(Person o) {
return this.age - o.getAge();
}

@Override
public String toString() {
return this.name + ":" + this.age;
}

public static void main(String[] args) {
List<Person> myFamily = new LinkedList<Person>();
myFamily.add(new Person("Gabriel", 28));
myFamily.add(new Person("Yamai", 51));
myFamily.add(new Person("Blanca", 25));
myFamily.add(new Person("Daniel", 12));
myFamily.add(new Person("Daniela", 27));
myFamily.add(new Person("Lydia", 17));

/*Let's sort the collection.*/
Collections.sort(myFamily);

for(Person familiyMember : myFamily) {
System.out.println(familiyMember);
}
}
}


Salida:

Daniel:12
Lydia:17
Blanca:25
Daniela:27
Gabriel:28
Yamai:51

Así que olvidemonos de hacer ordenamientos de burbuja ;).


miércoles, 2 de febrero de 2011

Código para encontrar carpetas vacías

Dejo a la disposición un código corto para encontrar carpetas vacías usando como entrada un path específico. El programa se adentra en él de manera recursiva y guarda al final los resultados en un archivo TXT.


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.LinkedList;
import java.util.List;

/**
* Class used to scan a given folder recursively to look for all empty folders. *
* @author gsolano
*
*/
public class DirEmptyFolder {

protected List<String> emptyFoldersList; // It will store all the empty folder paths.

public DirEmptyFolder() {
// Initializes the list.
emptyFoldersList = new LinkedList<String>();
}

public void scan(String rootDirPath) {
File sourceDir = new File(rootDirPath);

if(sourceDir.exists() && sourceDir.isDirectory()) {
emptyFoldersList.clear();
/*The real action: scan recursively.*/
scanRecursively(sourceDir);
System.out.println("Found " + emptyFoldersList.size() + " empty folders");

if(emptyFoldersList.size() > 0) {
System.out.println("Saving list...");
writeFilesToDisk("empty_folders_list.txt", emptyFoldersList);
System.out.println("Finished!");
}
} else {
System.out.println("Directory does not exist!");
}
}

/**
* Recursive method to find empty folders.
* @param sourceDir
*/
private void scanRecursively(File sourceDir) {
// List files and directories in the first level.
File[] listOfFiles = sourceDir.listFiles();

if (listOfFiles != null) {
for(int i=0; i < listOfFiles.length; i++) {
if(listOfFiles[i].isDirectory()) { // Only cares about directories.
if(listOfFiles[i].listFiles().length == 0) { // The directory has anything inside?
// Let's store the path and put it on screen.
emptyFoldersList.add(listOfFiles[i].getAbsolutePath());
System.out.println(listOfFiles[i].getAbsolutePath());
}
else {
// It's a directory but is not empty. Let's do the recursive call!
scanRecursively(listOfFiles[i]);
}
}
}
}
}

/**
* Saves the list in disk.
* @param filePath
* @param lines
*/
private void writeFilesToDisk(String filePath, List<String> lines) {
try{
FileWriter fstream = new FileWriter(filePath);
BufferedWriter out = new BufferedWriter(fstream);

for (String line : lines) {
out.write(line + "\n");
}
out.close();
}catch (Exception e){
e.printStackTrace();
}
}

public static void main(String[] args) {
DirEmptyFolder dirEmptyFolder = new DirEmptyFolder();
dirEmptyFolder.scan(args[0]);
}
}



Salida (usando como entrada: "c:\documents and settings\gsolano\My Documents"):
...
c:\documents and settings\gsolano\My Documents\My Meetings
c:\documents and settings\gsolano\My Documents\New Folder
c:\documents and settings\gsolano\My Documents\videos
Found 43 empty folders
Saving list...
Finished!