
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