
/* 0123456789012345678901234567890123456789012345678901234567890123456789789 */

/*
 * CD.java
 *
 * This file is part of cd_create.
 *
 * cd_create is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * cd_create is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with cd_create; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Copyright 2004 Abi Arroyo
 */
 
import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;

public class CD {

   public static String mkisofs_path;
   public static String cdrecord_path;
   										  
   public static String mkisofs_opts;      
   public static String cdrecord_opts;
   public static String cdrecord_scanbus;
   
   public String cdName;                  // name of CD
   public long length;                    // number of files in the CD
   public long cdSize;                    // current size of CD

   private LinkedList files;              // list of files
   private LinkedList fileSizes;          // parallel list of file sizes

   private BufferedWriter outBuf;
   final int SUCCESS = 0;

   private static Logger logger = Logger.getLogger ("CD.class");

   // Create new CD file
   public CD () {
      init ();
   }
   
   // This is an internal initialization function.
   private boolean init () {
      cdName = "";
      cdSize = 0;
      files = new LinkedList();
      fileSizes = new LinkedList();
      outBuf = null;
      
	   logger.setParent (CDCase.logger); 
	  
	   return true;
   }

   // This function loads CD data from a file to a CD object.
   // This function is called from the parameratized CD constructor.
   
   // Save the CD to a file
   public boolean saveCD () {
	  if (this.createCDFile()) {
		 return (this.writeFiles());
	  }
	  return (false);
   }

   // Create an empty CD file
   private boolean createCDFile () {
      String newFile;
      int i = -1;
	  File f;

      do {
         cdName = "CD" + String.valueOf(++i) + ".txt";
         newFile = CDCase.tempDir + File.separator + cdName;
         f = new File (newFile);
      } while (f.exists());

      try {
         f.createNewFile();
         outBuf = new BufferedWriter (new FileWriter(f));
         logger.log (Level.INFO, "CD.createCDFile(): CD file created: " + newFile);
         return true;
      }
      catch (IOException e) {
         logger.log (Level.WARNING, "CD.createCDFile(): unable to open file: " + newFile);
         return false;
      }
   }

   // save the CD to cdName
   private boolean writeFiles () {
      try {
      	
      	 // TODO may want to consider sorting file now...
      	 // But there is a problem since the sizes and filenames are in seperate lists
    
         String str;
         for (int i = 0; i < files.size(); i++) {
         	str = (String) files.get(i);
         	// str = ((edc_file) files.get(i)).toString();
            outBuf.write(str, 0, str.length());
            outBuf.newLine();
         }
         outBuf.close();
      }
      catch (IOException e) {
         logger.log (Level.WARNING, "CD.writeFiles(): Error: " + e.toString());
         return false;
      }

      return true;
   }
   
   // This function will attempt to add a file onto the local CD
   public boolean addFile (String nameOfFile) {
   	  File fileToAdd = new File(nameOfFile);

      // TODO: what happens when you add a directory and a bunch of files cannot be added?
      // should the files be automatically excluded? (Say the files are read protected)
      // In other words: should execution stop on one error or should it ignore errors?
      // If nothing should happen, simply remove the IF and edit sizeOfFile()
      // Perhaps we should make this a configurable option?
      
      if ((length = this.sizeOf(fileToAdd)) < 0) {
         return false;
      }
      
      if ((cdSize + length) < CDCase.MAX_CD_SIZE) {
         if (files.add (nameOfFile) && fileSizes.add (new Long(length))) {
            cdSize += length;
            // logger.log (Level.INFO, "CD.addFile(): file added successfully: " + nameOfFile + '\n');
            return true;
         }
      }
      else {
         logger.log (Level.WARNING, "CD.addFile(): file does fit on CD: " + nameOfFile + '\n');
      }
      
      logger.log (Level.WARNING, "CD.addFile(): error adding file: " + nameOfFile + '\n');

	  return false;
   }
   
   
   // Same as function above but size is passed in as a value
   public boolean addFile (String nameOfFile, long size) {
   	length = size;
      if ((cdSize + length) < CDCase.MAX_CD_SIZE) {
         if (files.add (nameOfFile) && fileSizes.add (new Long(size))) {
         	cdSize += size;
         	logger.log (Level.INFO, "CD.addFile(): file added successfully: " + nameOfFile + '\n');
            return true;
         }
      }
      
      logger.log (Level.WARNING, "CD.addFile(): unable to add file: " + nameOfFile + '\n');
      return false;
   }

   // This function computes the size of a file including the size of its contents if it is
   // a directory.
   // Returns:
   // -1 - If the given file is a directory and does not fit on a CD
   // -2 - If the given file cannot be read
   // N - The size of the file
   private long sizeOf (File someFile) {

      long sum = 0;                              // the size of a given path
      
      if (someFile.canRead()) {
         if (!someFile.isDirectory())
            return someFile.length();
         else {
         	
            // At this point we now know we are dealing with a directory
            
            String[] filesInDir = someFile.list();     // all of the files in a given path     
            File sizeCheck;                            // object used to check the size of files

            if (filesInDir == null) {
               logger.log (Level.WARNING, "CD.sizeOf(): Unable to determine size of file: " + someFile.toString() + '\n');
               return -2;
            }
       
            for (int i = 0; i < filesInDir.length; i++) {
               sizeCheck = new File (someFile.toString() + File.separator + filesInDir[i]);
               
               sum += this.sizeOf (sizeCheck);
               // Performance enhancement: don't keep computing if you already know its too big
			      if (sum > CDCase.MAX_CD_SIZE) {
				      logger.log(Level.INFO, "CD.sizeOf: performance ehancement here: " + someFile.toString() + File.separator + filesInDir[i] + '\n');
				      return -1;
			      }
            }
         }
      }
      else { 
         logger.log (Level.WARNING, "CD.sizeOf(): Unable to determine size of file: " + someFile.toString());
         return -2;
      }
      
      return sum;
   }
 
   // This function is a performance enhancement.
   // It allows a class to add a file without checking to see if it fits on the CD.
   // This function assumes that the other class has been keeping track and knows that it fits.
   public boolean addFileNoCheck (String name, long length) {
      if ((files.add (name)) && (fileSizes.add (new Long(length)))) {
         cdSize += length;
		   logger.log (Level.INFO, "CD.addFileNoCheck(): file added successfully: " + name + '\n'); 
         return true;
      }
      return false;
   }

   // must contain the full path and exact filename
   public boolean removeFile (String name) {
      int pos = files.indexOf (name);

      if (pos < 0) {
         logger.log (Level.INFO, "CD.removeFile(): requested file not on CD: " + name);
      }
      else {
         try {
            files.remove (pos);
            Long size = (Long) fileSizes.remove (pos);
            cdSize -= size.longValue();
            return true;
         }
         catch (IndexOutOfBoundsException e) {
            logger.log (Level.WARNING, "CD.removeFile(): error removing file: " + name);
         }
      }
      return false;
   }

   public long size () {
      return cdSize;
   }

   // This is a temporary function used for debugging purposes.
   public void printCD(boolean printall) {
      if (printall) {
         for (int i = 0; i < files.size(); i++) {
            System.out.println(fileSizes.get(i) + " " + files.get(i));
            logger.log (Level.WARNING, fileSizes.get(i) + " " + files.get(i) + '\n');
         }
      }
   }
   
   // Searches the CD for the given regular expression.
   public LinkedList search (String expr) {
      LinkedList matches = new LinkedList();
      
      // parse list of filenames for regular expression...
	  try {
		 String line;
		 Pattern p = Pattern.compile(expr);
		 
		 for (int i = 0; i < files.size(); i++) {
		 	line = (String) files.get(i);
		 	Matcher m = p.matcher(line);
		 	if (m.find()) {
		 	   matches.add(line);
		 	}
		 }
	  } catch (PatternSyntaxException x) {
	     logger.log(Level.WARNING, "CDCase.search(): invalied regular expression: " + expr + '\n');
	  }
	
      return matches;
   }
   
   // TODO: extensively test mkisofs command
   public boolean createISO () {
      String cmd[];                      // This array serves two purposes:
   	                                     // 1. Pass to createBatFile
   	                                     // 2. Pass to TestExec
   	                                     // They are expected in diff formats
   	                                     // for each function
   	                                     
		String iso = CDCase.tempDir + File.separator + "test.iso";
   	String isofile = "-o " + iso;
   	String line;
   	String listOfFiles = "";
      String batfile = "createISO.bat";
		String shFile = "createISO.sh";
   	boolean rtn = false;
   	
   	int pos = 0; String filetest; String item;
   	int i;

   	// transfer files (LinkedList) to a single string for mkisofs
      // Quotations ensure spaces are handled properly
   	// TODO: Add quotations... remove after graft points
      for (i = 0; i < files.size(); i++) {
         item = (String) files.get(i);
         if (CDCase.isWindows) {
            pos = item.lastIndexOf (File.separator);
         }
      	   
      	filetest = item.substring (pos+1, item.length());
         
         // listOfFiles += "\"" + filetest +  "\"=\"" + item + "\" ";
         listOfFiles += "\"" + filetest +  "\"=\"" + item + "\" ";
         // listOfFiles += "\"" +  item + "\" ";
      }

		// write md5 data
      String md5file;
      i = 0;
      File test;
         
      do {  
         md5file = (++i) + "-" + verify.origSum;
         test = new File(CDCase.cdPath + File.separator + md5file);
         // logger.log (Level.SEVERE, "TESTING EXISTANCE: " + test.toString() + "\n");
      } while (test.canRead());
      
      listOfFiles += "\"" + md5file + "\"=\"" + CDCase.tempDir + File.separator + verify.origSum + "\" ";
      
      // step 1 create ISO
      cmd = new String[3];
      
      // iso directory creation issue... top level directory not included
      line = "\"" + mkisofs_path + "\" " + mkisofs_opts + this.isMulti() + isofile + " -graft-points " + listOfFiles;
      logger.log (Level.WARNING, "CD.createISO(): " + line + '\n');
	   
      if (CDCase.isWindows) {
         cmd[0] = "@ECHO OFF";
			cmd[1] = "del " + iso;
         cmd[2] = line;
      
         utility.createFile (batfile, cmd);
         return utility.exec(batfile);
	   }
      else {
         cmd[0] = "#!/bin/bash";
			cmd[1] = "rm -f " + iso;
         cmd[2] = line;
									   
			utility.createFile (shFile, cmd);
												   
			cmd = new String[1];
			cmd[0] = "chmod a+x " + CDCase.tempDir + File.separator + shFile;
			utility.exec(cmd);

		   return utility.exec(shFile);
      }
   }
   
   // this function will check to see if the cd that is in the drive is a 
   // multisession cd
   private String isMulti () {
      String line;
      String cmd[];
      String batfile = "isMulti.bat";
		String shFile = "isMulti.sh";
      
      // compose and execute command to check for multisession CD
      logger.log (Level.INFO, "CD.isMulti(): start\n");
      cmd = new String[2];
      
      line = "\"" + cdrecord_path + "\"" + " -msinfo " + cdrecord_scanbus + " > " + CDCase.tempDir + File.separator + "multi.txt";
      logger.log (Level.WARNING, "CD.isMulti(): " + line + '\n');

		if (CDCase.isWindows) {
      
         cmd[0] = "@ECHO OFF";
         cmd[1] = line;
      
         utility.createFile (batfile, cmd);
         utility.exec(batfile);
	   }
		else {
			   
         cmd[0] = "#!/bin/bash";
         cmd[1] = line;
									   
			utility.createFile (shFile, cmd);
												   
			cmd = new String[1];
			cmd[0] = "chmod a+x " + CDCase.tempDir + File.separator + shFile;
			utility.exec(cmd);

		   utility.exec(shFile);

		}
      
      // retrieve results from file
      try {
       
         BufferedReader someFile = new BufferedReader (new FileReader(new File(CDCase.tempDir + File.separator + "multi.txt")));
         line = someFile.readLine();
          
         if (line != null && line.length() > 0) {
            logger.log (Level.SEVERE, "CD.isMulti(): multisession CD detected\n");
            
            return (" -C " + line + " -M " + cdrecord_scanbus.substring(cdrecord_scanbus.lastIndexOf("=")+1, cdrecord_scanbus.length()));
         }
			else {
				logger.log (Level.SEVERE, "CD.isMulti(): multinession CD  not  found\n");
			}
      }
      catch (FileNotFoundException e) {
         logger.log(Level.WARNING, "CD.isMulti(): open file output file\n");
      }
      catch (IOException e) {
         logger.log(Level.WARNING, "CD.isMulti(): error reading file\n");
      }
      
      logger.log (Level.INFO, "CD.isMulti() multisession CD is not already in drive\n");  
      
      return "";
   }
      
   public boolean burnISO () {
      String cmd[];
      String line;
   	String listOfFiles = "";
   	final int SUCCESS = 0;
      
   	String burnfile = CDCase.tempDir + File.separator + "test.iso ";
      String batfile = "burnISO.bat";
   	String shFile = "burnISO.sh";
   	  
   	cmd = new String[2];

      line = "\"" + cdrecord_path + "\"  " + cdrecord_scanbus + cdrecord_opts + burnfile;
      logger.log (Level.WARNING, "CD.burnISO(): " + line + '\n');
	   
	   if (CDCase.isWindows) {
         cmd[0] = "@ECHO OFF";
         cmd[1] = line;
      
         utility.createFile (batfile, cmd);
         return utility.exec(batfile);
      }
      else {
         cmd[0] = "#!/bin/bash";
         cmd[1] = line;
                              
         utility.createFile (shFile, cmd);
                                       
         cmd = new String[1];
         cmd[0] = "chmod a+x " + CDCase.tempDir + File.separator + shFile;
         utility.exec(cmd);

         return utility.exec(shFile);
      }
   }
}
