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 ;).