BruteForceParanoidDirectoryCopy: Some hacky code I wrote a while ago

Sometimes I write a small program to deal with a minor inconvenience. This is one of those programs.

Oh wow, that's the same intro I used on my last Java article. Is this site growing stale, or have I discovered a new form of contentment in posting small miscellaneous pieces of content?

Quite a while ago, before the existence of Java 1.7 (this is an important note), a friend came to me with a problem. He had a USB drive with some important files that was dying. It would disappear from Windows Explorer (and other places one might see a USB drive) before all the files could be copied. Although maybe it was a Linux PC, I don't recall at this point. Regardless, I thought I could probably write a small program that handled this so I did. It even worked on the first try.

The actual code is very hacky and would be a tad simpler under Java 1.7+ which upgraded file operations.

Let's start with the initial setup...


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;

public class BruteForceParanoidDirectoryCopy{
 private final static int DEFAULT_WAIT=100;
 //arg[0]=source directory -- arg[1]=optional, destination directory -- arg[2]=optional wait time
 public static void main(String[] args) throws InterruptedException{
  int wait=DEFAULT_WAIT;
  String destinationDir=System.getProperty("user.dir");
  ArrayList<String> fileList=new ArrayList<String>();
  ArrayList<String> dirList=new ArrayList<String>();

OK, simple enough so far, some imports & lists to hold files. I forgot I made wait time configurable, neat idea I guess.

Next is reading command line arguments...


if(args.length>=1){
  dirList.add(args[0]);
  if(args.length>=2){
    destinationDir=args[1];
    if(args.length>=3){
      try{
        wait=Integer.parseInt(args[2]);
      }catch(NumberFormatException nfx){
        System.out.println("Optional wait argument wasn't a number, using default value: "+DEFAULT_WAIT);
      }
    }else{
      System.out.println("Optional wait argument not passed, using default value: "+DEFAULT_WAIT);
    }
  }else{
    System.out.println("Optional destination directory not passed, using default value: "+destinationDir);
  }
}else{
  System.out.println("Dude? Next time try passing the source directory.");
  System.exit(-1);
}

I think I got a little too fancy there. I can follow it at least.

Now we need to build the list of files, at this point dirList only has the root directory...


while(dirList.size()>0){
  String canonicalPath=dirList.remove(0);
  File dir=new File(canonicalPath);
  boolean listComplete=false;
  System.out.println("Attempting to list files in directory: "+canonicalPath);
  while(!listComplete){
    //wait for the directory to become available
    while(!dir.exists()){
      System.out.println("Waiting for directory to be available: "+canonicalPath);
      Thread.sleep(wait);
    }
    //the directory is back, list the files
    String[] allFiles=dir.list();
    int length=allFiles.length;
    System.out.println("Number of things in the directory: "+length);
    //sort out which are files and which are directories
    for(int fileIndex=0;fileIndex<length;fileIndex++){
      String fileCanonicalPath=canonicalPath+File.separator+allFiles[fileIndex];
      File testFile=new File(fileCanonicalPath);
      System.out.println("Attempting to read attributes for: "+fileCanonicalPath);
      //wait for the file to become available
      while(!testFile.exists()){
        System.out.println("Waiting for file to be available: "+fileCanonicalPath);
        Thread.sleep(wait);
      }
      //the file is back, check if it's a file or directory
      if(testFile.isDirectory()){
        dirList.add(fileCanonicalPath);
      }else{
        fileList.add(fileCanonicalPath);
      }
    }
    listComplete=true;
  }
}

I'm kind of surprised this worked. It could for sure get stuck in an infinite loop. I have some newer code to recursively list files here now.

I had to read this next section a few times to figure out what I was doing. This is where things get really hacky. Rather than recreate the original folder structure I dump all the files in one directory and replace the file paths with underscores.

So sourceFolder/subFolder/File1.blah becomes sourceFolder_subFolder_File1.blah. If for some reason there was a file actually named sourceFolder_subFolder_File1.blah in the source root directory this would fail horribly.

Here's the embarrassing code...


while(fileList.size()>0){
  String fileCanonicalPath=fileList.get(0);
  boolean success=false;
  //build the output file name
  String destinationFileName=fileCanonicalPath.replace(":","_").replace(File.separator,"_");
  String destinationCanonicalPath=destinationDir;
  if(destinationCanonicalPath.endsWith(File.separator)){
    destinationCanonicalPath=destinationCanonicalPath+destinationFileName;
  }else{
    destinationCanonicalPath=destinationCanonicalPath+File.separator+destinationFileName;
  }
  System.out.println("Attempting to copy: "+fileCanonicalPath);
  System.out.println("Output file will be: "+destinationCanonicalPath);
  File sourceFile=new File(fileCanonicalPath);
  File destinationFile=new File(destinationCanonicalPath);
[loop continues below]

Now there's a loop to account for a condition where the drive disconnects, this could also lead to an endless loop...


[still in the while loop]
//wait for the file to become available
while(!sourceFile.exists()){
  System.out.println("Waiting for file to be available: "+fileCanonicalPath);
  Thread.sleep(wait);
}
[loop continues below]

Now the file is copied. This code validates based on file length which is imperfect...


[still in the while loop]
try{
  copyFile(sourceFile,destinationFile);
  if(sourceFile.length()==destinationFile.length()){
    success=true;
  }else{
    System.out.println("Something bad happened because file lengths don't match.");
    System.out.println("sourceFile.length()="+sourceFile.length());
    System.out.println("destinationFile.length()="+destinationFile.length());
  }
}catch(IOException iox){
  System.out.println("Error copying file: "+iox.getMessage());
}
//force a retry if the copy failed
if(success){
 fileList.remove(0);
}
}[end of while loop]

This file copy code is outdated now. It could be replaced with Files.copy(). That's how old this code is...


//based on this example - http://stackoverflow.com/questions/300559/move-copy-file-operations-in-java
public static void copyFile(File sourceFile, File destFile) throws IOException{
  if(!destFile.exists()){
    destFile.createNewFile();
  }else{
    destFile.delete(); //added for this application because we'll be dealing with a lot of partially copied files
  }
  FileChannel source=null;
  FileChannel destination=null;
  try{
    source=new FileInputStream(sourceFile).getChannel();
    destination=new FileOutputStream(destFile).getChannel();
    long count=0;
    long size=source.size();     
    while((count+=destination.transferFrom(source, count, size-count))<size);
  }finally{
    if(source!=null){
      source.close();
    }
    if(destination!=null){
      destination.close();
    }
  }
}

And now after the while loop, it's time to exit the program...


System.out.println("Done - have a nice day.");
System.exit(0);

That's it. I can now delete this file and feel no regrets. You have a nice day too.



Related