Ignore:
Timestamp:
2003-05-27T15:57:37+12:00 (21 years ago)
Author:
kjdon
Message:

re-tabbed the code for java

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java

    r4293 r4366  
    5858 */
    5959public class FileQueue
    60     extends Thread
     60    extends Thread
    6161    implements TreeSelectionListener {
    62     /** When someone requests the movement queue to be dumped this cancel flag is set to true. */
    63     private boolean cancel_action = false;
    64     /** A temporary mapping from currently existing FileNode folder to their equivelent FileNode folder within the undo managers tree. */
    65     private HashMap completed_folder_mappings = new HashMap();
    66     /** true to cause this file queue to return from run() as soon as there are no jobs left on the queue. Useful for undo jobs which must occur before a specific action. */
    67     private boolean return_immediately = false;
    68     /** We are only allowed to wait under specific circumstances. */
    69     private boolean wait_allowed = true;
    70     /** true if the user has selected yes to all from a file 'clash' dialog. */
    71     private boolean yes_to_all = false;
    72     /** A temporary mapping from currently existing FileNodes to the potential FileNode folder within the undo managers tree. */
    73     private HashMap recycle_folder_mappings = new HashMap();
    74     /** A label explaining the current moving files status. */
    75     private SmudgyLabel file_status = null;
    76     /** A label explaining the status of this job. */
    77     private JLabel job_status = null;
    78     /** A list containing a queue of waiting movement jobs. */
    79     //private LinkedList queue;
    80     private ArrayList queue;
    81     /** A progress bar which shows how many bytes, out of the total size of bytes, has been moved. */
    82     private LongProgressBar progress = null;
    83     /** The last piece of text shown on the file status label, just incase we are displaying a very temporary message. */
    84     private String previous = null;
    85     /** Constructor.
     62    /** When someone requests the movement queue to be dumped this cancel flag is set to true. */
     63    private boolean cancel_action = false;
     64    /** A temporary mapping from currently existing FileNode folder to their equivelent FileNode folder within the undo managers tree. */
     65    private HashMap completed_folder_mappings = new HashMap();
     66    /** true to cause this file queue to return from run() as soon as there are no jobs left on the queue. Useful for undo jobs which must occur before a specific action. */
     67    private boolean return_immediately = false;
     68    /** We are only allowed to wait under specific circumstances. */
     69    private boolean wait_allowed = true;
     70    /** true if the user has selected yes to all from a file 'clash' dialog. */
     71    private boolean yes_to_all = false;
     72    /** A temporary mapping from currently existing FileNodes to the potential FileNode folder within the undo managers tree. */
     73    private HashMap recycle_folder_mappings = new HashMap();
     74    /** A label explaining the current moving files status. */
     75    private SmudgyLabel file_status = null;
     76    /** A label explaining the status of this job. */
     77    private JLabel job_status = null;
     78    /** A list containing a queue of waiting movement jobs. */
     79    //private LinkedList queue;
     80    private ArrayList queue;
     81    /** A progress bar which shows how many bytes, out of the total size of bytes, has been moved. */
     82    private LongProgressBar progress = null;
     83    /** The last piece of text shown on the file status label, just incase we are displaying a very temporary message. */
     84    private String previous = null;
     85    /** Constructor.
    8686      * @param return_immediately true to cause this file queue to return from run() as soon as there are no jobs left on the queue.
    8787      * @see org.greenstone.gatherer.Configuration
     
    8989      * @see org.greenstone.gatherer.gui.LongProgressBar
    9090      */
    91     public FileQueue(boolean return_immediately) {
    92           this.return_immediately = return_immediately;
    93           this.queue = new ArrayList();//LinkedList();
    94           String args[] = new String[2];
    95           args[0] = "0";
    96           args[1] = "0";
    97           file_status = new SmudgyLabel(get("Selected", args));
    98           job_status = new JLabel(get("No_Activity"));
    99           progress = new LongProgressBar();
    100           progress.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
    101           progress.setForeground(Gatherer.config.getColor("coloring.collection_tree_foreground", false));
    102           progress.setString(get("No_Activity"));
    103           progress.setStringPainted(true);
    104           args = null;
    105     }
    106 
    107     /** Requeue an existing job into the queue.
     91    public FileQueue(boolean return_immediately) {
     92    this.return_immediately = return_immediately;
     93    this.queue = new ArrayList();//LinkedList();
     94    String args[] = new String[2];
     95    args[0] = "0";
     96    args[1] = "0";
     97    file_status = new SmudgyLabel(get("Selected", args));
     98    job_status = new JLabel(get("No_Activity"));
     99    progress = new LongProgressBar();
     100    progress.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
     101    progress.setForeground(Gatherer.config.getColor("coloring.collection_tree_foreground", false));
     102    progress.setString(get("No_Activity"));
     103    progress.setStringPainted(true);
     104    args = null;
     105    }
     106
     107    /** Requeue an existing job into the queue.
    108108      * @param job A previously created FileJob.
    109109      */
    110     synchronized public void addJob(FileJob job, int position) {
    111           job.done = true; // Ensure that the requeued job is marked as done.
    112           queue.add(position, job);
    113           notify();
    114     }
    115 
    116     /** Add a new job to the queue, specifiying as many arguments as is necessary to complete this type of job (ie delete needs no target information).
     110    synchronized public void addJob(FileJob job, int position) {
     111    job.done = true; // Ensure that the requeued job is marked as done.
     112    queue.add(position, job);
     113    notify();
     114    }
     115
     116    /** Add a new job to the queue, specifiying as many arguments as is necessary to complete this type of job (ie delete needs no target information).
    117117      * @param id A long id unique to all jobs created by a single action.
    118118      * @param source The DragComponent source of this file, most likely a DragTree.
     
    124124      * @param undoable true if this job can generate undo or redo jobs at all, false otherwise.
    125125      */
    126     public void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean undo, boolean undoable, boolean folder_level) {
    127           addJob(id, source, child, target, parent, type, undo, undoable, folder_level, -1);
    128     }
    129     synchronized public void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean undo, boolean undoable, boolean folder_level, int position) {
    130           FileJob job = new FileJob(id, source, child, target, parent, type, undo, undoable);
    131           job.folder_level = folder_level;
    132           ///ystem.err.println("Adding job: " + job);
    133           if(position != -1 && position <= queue.size() + 1) {
    134                 queue.add(position, job);
    135           }
    136           else {
    137                 queue.add(job);
    138           }
    139           notify();
    140     }
    141 
    142     public void calculateSize(FileNode[] files) {
    143           progress.reset();
    144           progress.setString(get("FileActions.Calculating_Size"));
    145           progress.setIndeterminate(true);
    146           Vector remaining = new Vector();
    147           for(int i = 0; i < files.length; i++) {
    148                 remaining.add(files[i]);
    149           }
    150           while(remaining.size() > 0) {
    151                 FileNode node = (FileNode)remaining.remove(0);
    152                 if(node.isLeaf()) {
    153                      progress.addMaximum(node.getFile().length());
    154                 }
    155                 else {
    156                      for(int i = 0; i < node.getChildCount(); i++) {
    157                           remaining.add(node.getChildAt(i));
    158                      }
    159                 }
    160           }
    161           progress.setString(get("No_Activity"));
    162           progress.setIndeterminate(false);
    163     }
    164 
    165     /** This method is called to cancel the job queue at the next available moment. */
    166     public void cancelAction() {
    167           cancel_action = true;
    168     }
    169     /** Access to the file state label. */
    170     public JLabel getFileStatus() {
    171           return file_status;
    172     }
    173     /** Access to the job state label. */   
    174     public JLabel getJobStatus() {
    175           return job_status;
    176     }
    177     /** Access to the progress bar. */
    178     public LongProgressBar getProgress() {
    179           return progress;
    180     }
    181     /** Prevent the progress bar updating momentarily, while the progress bar size is re-adjusted. */
    182     public void pause() {
    183           progress.setIndeterminate(true);
    184     }
    185     /** The run method exists in every thread, and here it is used to work its way through the queue of Jobs. If no jobs are waiting and it cans, it waits until a job arrives. If a job is present then it is either COPIED or DELETED, with the records being copied or removed as necessary, and directories being recursed through. Finally the user can press cancel to cause the loop to prematurely dump the job queue then wait.
     126    public void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean undo, boolean undoable, boolean folder_level) {
     127    addJob(id, source, child, target, parent, type, undo, undoable, folder_level, -1);
     128    }
     129    synchronized public void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean undo, boolean undoable, boolean folder_level, int position) {
     130    FileJob job = new FileJob(id, source, child, target, parent, type, undo, undoable);
     131    job.folder_level = folder_level;
     132    ///ystem.err.println("Adding job: " + job);
     133    if(position != -1 && position <= queue.size() + 1) {
     134        queue.add(position, job);
     135    }
     136    else {
     137        queue.add(job);
     138    }
     139    notify();
     140    }
     141
     142    public void calculateSize(FileNode[] files) {
     143    progress.reset();
     144    progress.setString(get("FileActions.Calculating_Size"));
     145    progress.setIndeterminate(true);
     146    Vector remaining = new Vector();
     147    for(int i = 0; i < files.length; i++) {
     148        remaining.add(files[i]);
     149    }
     150    while(remaining.size() > 0) {
     151        FileNode node = (FileNode)remaining.remove(0);
     152        if(node.isLeaf()) {
     153        progress.addMaximum(node.getFile().length());
     154        }
     155        else {
     156        for(int i = 0; i < node.getChildCount(); i++) {
     157            remaining.add(node.getChildAt(i));
     158        }
     159        }
     160    }
     161    progress.setString(get("No_Activity"));
     162    progress.setIndeterminate(false);
     163    }
     164
     165    /** This method is called to cancel the job queue at the next available moment. */
     166    public void cancelAction() {
     167    cancel_action = true;
     168    }
     169    /** Access to the file state label. */
     170    public JLabel getFileStatus() {
     171    return file_status;
     172    }
     173    /** Access to the job state label. */   
     174    public JLabel getJobStatus() {
     175    return job_status;
     176    }
     177    /** Access to the progress bar. */
     178    public LongProgressBar getProgress() {
     179    return progress;
     180    }
     181    /** Prevent the progress bar updating momentarily, while the progress bar size is re-adjusted. */
     182    public void pause() {
     183    progress.setIndeterminate(true);
     184    }
     185    /** The run method exists in every thread, and here it is used to work its way through the queue of Jobs. If no jobs are waiting and it cans, it waits until a job arrives. If a job is present then it is either COPIED or DELETED, with the records being copied or removed as necessary, and directories being recursed through. Finally the user can press cancel to cause the loop to prematurely dump the job queue then wait.
    186186      * @see org.greenstone.gatherer.Gatherer
    187187      * @see org.greenstone.gatherer.collection.CollectionManager
     
    193193      * @see org.greenstone.gatherer.util.Utility
    194194      */
    195      public void run() {
    196           super.setName("FileQueue");
    197           while(!Gatherer.self.exit) {
     195    public void run() {
     196    super.setName("FileQueue");
     197    while(!Gatherer.self.exit) {
     198        try {
     199        // Retrieve the next job
     200        int position = queue.size() - 1;
     201        FileJob job = removeJob(position);
     202        if(job != null) {
     203            ///ystem.err.println("Found job: " + job);
     204            // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
     205            boolean ready = true;
     206            FileNode origin_node = job.getOrigin();
     207            FileNode destination_node = job.getDestination();
     208            FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
     209            FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
     210            if(destination_node == null) {
     211            // Retrieve the root node of the target model instead. A delete, or course, has no target file so all deleted files are added to the root of the Recycle Bin model.
     212            destination_node = (FileNode) target_model.getRoot();
     213            }
     214            // Extract common job details.
     215            File source_file = origin_node.getFile();
     216            File target_file = null;
     217            // Determine the target file for a copy or move.
     218            if(job.type == FileJob.COPY || job.type == FileJob.MOVE) {
     219            target_file = new File(destination_node.getFile(), source_file.getName());
     220            }
     221            // To copy a file, copy it then add any metadata found at the source. If this file was already in our collection then we must ensure the lastest version of its metadata.xml has been saved to disk. To copy a directory simply create the directory at the destination, then add all of its children files as new jobs.
     222            if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
     223            ///ystem.err.println("Copy/Move: " + origin_node);
     224            FileNode new_node = null;
     225            // Check if file exists, and action as necessary. Be aware the user can choose to cancel the action all together (where upon ready becomes false).
     226            if(target_file.exists()) {
     227                // We've previously been told
     228                if(yes_to_all) {
     229                // Remove the old file and tree entry.
     230                target_file.delete();
     231                ready = true;
     232                }
     233                else {
     234                ///atherer.println("Opps! This filename already exists. Give the user some options.");
     235                Object[] options = { get("General.Yes"), get("Yes_To_All"), get("General.No"), get("General.Cancel") };
     236                int result = JOptionPane.showOptionDialog(Gatherer.g_man, get("File_Exists", target_file.getName()), get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
     237                switch(result) {
     238                case 1: // Yes To All
     239                    yes_to_all = true;
     240                case 0: // Yes
     241                    // Remove the old file and tree entry.
     242                    if(destination_node != null) {
     243                    TreePath destination_path = new TreePath(destination_node.getPath());
     244                    FileNode temp_target_node = new FileNode(target_file, target_model, true);
     245                    TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
     246                    SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
     247                    target_path = null;
     248                    temp_target_node = null;
     249                    destination_path = null;
     250                    }
     251                    target_file.delete();
     252                    ready = true;
     253                    break;
     254                case 3: // No To All
     255                    cancel_action = true;
     256                case 2: // No
     257                default:
     258                    ready = false;
     259                }
     260                }
     261            }
     262            // We proceed with the copy/move if the ready flag is still set. If it is that means there is no longer any existing file of the same name.
     263            if(ready) {
     264                // update status area
     265                String args[] = new String[1];
     266                args[0] = "" + (queue.size() + 1) + "";
     267                job_status.setText(get("Jobs", args));
     268                if(job.type == FileJob.COPY) {
     269                args[0] = Utility.formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
     270                file_status.setText(get("Copying", args));
     271                }
     272                else {
     273                args[0] = Utility.formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
     274                file_status.setText(get("Moving", args));
     275                }
     276                args = null;
     277                file_status.setToolTipText(Utility.formatHTMLWidth(file_status.getText(), 80));
     278                // If source is a file
     279                if(source_file.isFile()) {
     280                // copy the file. If anything goes wrong the copy file should throw the appropriate exception. No matter what exception is thrown (bar an IOException) we display some message, perhaps take some action, then cancel the remainder of the pending file jobs. No point in being told your out of hard drive space for each one of six thousand files eh?
    198281                try {
    199                      // Retrieve the next job
    200                      int position = queue.size() - 1;
    201                      FileJob job = removeJob(position);
    202                      if(job != null) {
    203                           ///ystem.err.println("Found job: " + job);
    204                           // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
    205                           boolean ready = true;
    206                           FileNode origin_node = job.getOrigin();
    207                           FileNode destination_node = job.getDestination();
    208                           FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
    209                           FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
    210                           if(destination_node == null) {
    211                                 // Retrieve the root node of the target model instead. A delete, or course, has no target file so all deleted files are added to the root of the Recycle Bin model.
    212                                 destination_node = (FileNode) target_model.getRoot();
    213                           }
    214                           // Extract common job details.
    215                           File source_file = origin_node.getFile();
    216                           File target_file = null;
    217                           // Determine the target file for a copy or move.
    218                           if(job.type == FileJob.COPY || job.type == FileJob.MOVE) {
    219                                 target_file = new File(destination_node.getFile(), source_file.getName());
    220                           }
    221                           // To copy a file, copy it then add any metadata found at the source. If this file was already in our collection then we must ensure the lastest version of its metadata.xml has been saved to disk. To copy a directory simply create the directory at the destination, then add all of its children files as new jobs.
    222                           if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
    223                                 ///ystem.err.println("Copy/Move: " + origin_node);
    224                                 FileNode new_node = null;
    225                                 // Check if file exists, and action as necessary. Be aware the user can choose to cancel the action all together (where upon ready becomes false).
    226                                 if(target_file.exists()) {
    227                                      // We've previously been told
    228                                      if(yes_to_all) {
    229                                           // Remove the old file and tree entry.
    230                                           target_file.delete();
    231                                           ready = true;
    232                                      }
    233                                      else {
    234                                           ///atherer.println("Opps! This filename already exists. Give the user some options.");
    235                                           Object[] options = { get("General.Yes"), get("Yes_To_All"), get("General.No"), get("General.Cancel") };
    236                                           int result = JOptionPane.showOptionDialog(Gatherer.g_man, get("File_Exists", target_file.getName()), get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
    237                                           switch(result) {
    238                                           case 1: // Yes To All
    239                                                 yes_to_all = true;
    240                                           case 0: // Yes
    241                                                 // Remove the old file and tree entry.
    242                                                 if(destination_node != null) {
    243                                                      TreePath destination_path = new TreePath(destination_node.getPath());
    244                                                      FileNode temp_target_node = new FileNode(target_file, target_model, true);
    245                                                      TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
    246                                                      SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
    247                                                      target_path = null;
    248                                                      temp_target_node = null;
    249                                                      destination_path = null;
    250                                                 }
    251                                                 target_file.delete();
    252                                                 ready = true;
    253                                                 break;
    254                                           case 3: // No To All
    255                                                 cancel_action = true;
    256                                           case 2: // No
    257                                           default:
    258                                                 ready = false;
    259                                           }
    260                                      }
    261                                 }
    262                                 // We proceed with the copy/move if the ready flag is still set. If it is that means there is no longer any existing file of the same name.
    263                                 if(ready) {
    264                                      // update status area
    265                                      String args[] = new String[1];
    266                                      args[0] = "" + (queue.size() + 1) + "";
    267                                      job_status.setText(get("Jobs", args));
    268                                      if(job.type == FileJob.COPY) {
    269                                           args[0] = Utility.formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
    270                                           file_status.setText(get("Copying", args));
    271                                      }
    272                                      else {
    273                                           args[0] = Utility.formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
    274                                           file_status.setText(get("Moving", args));
    275                                      }
    276                                      args = null;
    277                                      file_status.setToolTipText(Utility.formatHTMLWidth(file_status.getText(), 80));
    278                                      // If source is a file
    279                                      if(source_file.isFile()) {
    280                                           // copy the file. If anything goes wrong the copy file should throw the appropriate exception. No matter what exception is thrown (bar an IOException) we display some message, perhaps take some action, then cancel the remainder of the pending file jobs. No point in being told your out of hard drive space for each one of six thousand files eh?
    281                                           try {
    282                                                 copyFile(source_file, target_file, progress);
    283                                           }
    284                                           // If we can't find the source file, then the most likely reason is that the file system has changed since the last time it was mapped. Warn the user that the requested file can't be found, then force a refresh of the source folder involved.
    285                                           catch(FileNotFoundException fnf_exception) {
    286                                                 Gatherer.printStackTrace(fnf_exception);
    287                                                 cancel_action = true;
    288                                                 // Show warning.
    289                                                 JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Not_Found_Message", source_file.getName()), get("File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
    290                                                 // Force refresh of source folder.
    291                                                 source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
    292                                           }
    293                                           catch(FileAlreadyExistsException fae_exception) {
    294                                                 Gatherer.printStackTrace(fae_exception);
    295                                                 cancel_action = true;
    296                                                 // Show warning.
    297                                                 JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Already_Exists_Message", target_file.getName()), get("File_Already_Exists_Title"), JOptionPane.ERROR_MESSAGE);
    298                                                 // Nothing else can be done by the Gatherer.
    299                                           }
    300                                           catch(InsufficientSpaceException is_exception) {
    301                                                 Gatherer.printStackTrace(is_exception);
    302                                                 cancel_action = true;
    303                                                 // Show warning. The message body of the expection explains how much more space is required for this file copy.
    304                                                 JOptionPane.showMessageDialog(Gatherer.g_man, get("Insufficient_Space_Message", is_exception.getMessage()), get("Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
    305                                                 // Nothing else can be done by the Gatherer. In fact if we are really out of space I'm not even sure we can quit safely.
    306                                           }
    307                                           catch(UnknownFileErrorException ufe_exception) {
    308                                                 Gatherer.printStackTrace(ufe_exception);
    309                                                 cancel_action = true;
    310                                                 // Show warning
    311                                                 JOptionPane.showMessageDialog(Gatherer.g_man, get("Unknown_File_Error_Message"), get("Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
    312                                                 // Nothing else we can do.
    313                                           }
    314                                           catch(IOException exception) {
    315                                                 // Can't really do much about this.
    316                                                 Gatherer.printStackTrace(exception);
    317                                           }
    318                                           // If not cancelled
    319                                           if(!cancel_action) {
    320                                                 // Step one is to create a dummy FileNode. Its important it has the correct structure so getPath works.
    321                                                 FileNode new_record = new FileNode(target_file);
    322                                                 SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_record);
    323                                               new_node = new_record;
    324 
    325                                               // create undo job
    326                                                 if(job.undoable) {
    327                                                      job.undoable = false;
    328                                                      if(job.type == FileJob.COPY) {
    329                                                           // A copy is undone with a delete, so it doesn't really matter where the file originally came from (we're not moving it back there, but into the recycle bin). You may also notice we don't make use of the target parent record. This is because no undo action needs this information, and even if it did it could simply ask for records parent!
    330                                                           Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, new_record, job.undo);
    331                                                      }
    332                                                      else {
    333                                                           // Movements however do need a source and source parent so the file can be moved back to the correct place.
    334                                                           Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, new_record, job.undo);
    335                                                      }
    336                                                 }
    337                                                 new_record = null;
    338                                           }
    339                                      }
    340                                      // Else
    341                                      else if(source_file.isDirectory()) {
    342                                           // create new record
    343                                           FileNode directory_record = new FileNode(target_file);
    344                                           ///ystem.err.println("Directory record = " + directory_record + " (" + target_file.getAbsolutePath() + ")");
    345                                           SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
    346                                           // Why is this not happening eh?
    347                                           directory_record.setParent(destination_node);
    348                                           if(!target_file.exists()) {
    349                                              // make the directory
    350                                                 target_file.mkdirs();
    351                                                 new_node = directory_record;
    352                                               // create undo job
    353                                                 if(job.undoable) {
    354                                                      job.undoable = false;
    355                                                      if(job.type == FileJob.COPY) {
    356                                                           // A copy is undone with a delete, so it doesn't really matter where the file originally came from (we're not moving it back there, but into the recycle bin). You may also notice we don't make use of the target parent record. This is because no undo action needs this information, and even if it did it could simply ask for records parent!
    357                                                           Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, directory_record, job.undo);
    358                                                      }
    359                                                      else {
    360                                                           // Movements however do need a source and source parent so the file can be moved back to the correct place.
    361                                                           Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, directory_record, job.undo);
    362                                                      }
    363                                                 }
    364                                           }
    365                                           // Else inform the users that a directory already exists and files will be copied into it
    366                                           else {
    367                                                 JOptionPane.showMessageDialog(null, get("Directory_Exists", target_file.toString()), get("General.Warning"), JOptionPane.WARNING_MESSAGE);
    368                                           }
    369                                           // Queue non-filtered child files for copying. If this directory already existed, the child records will have to generate the undo jobs, as we don't want to entirely delete this directory if it already existed.
    370                                           FileNode child_record = null;
    371                                           // In order to have a sane copy proceedure (rather than always copying last file first as it used to) we always add the child node at the position the parent was removed from. Consider the file job 'a' at the end of the queue which generates three new jobs 'b', 'c' and 'd'. The resulting flow should look like this.
    372                                           // -- Starting queue                   ...[a]
    373                                           // remove(position) = 'a'              ...
    374                                           // add(position, 'b')                  ...[b]
    375                                           // add(position, 'c')                  ...[c][b]
    376                                           // add(position, 'd')                  ...[d][c][b]
    377                                           // Next loop
    378                                           // remove(position) = 'b'              ...[d][c]
    379                                           for(int i = 0; i < origin_node.getChildCount(); i++) {
    380                                                 child_record = (FileNode) origin_node.getChildAt(i);
    381                                                 addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, job.undo, false, false, position);
    382                                           }
    383                                           child_record = null;
    384                                           directory_record = null;
    385                                      }
    386                                      // The file wasn't found!
    387                                      else {
    388                                           cancel_action = true;
    389                                           // Show warning.
    390                                           JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Not_Found_Message", source_file.getName()), get("File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
    391                                           // Force refresh of source folder.
    392                                           source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
    393                                      }
     282                    copyFile(source_file, target_file, progress);
     283                }
     284                // If we can't find the source file, then the most likely reason is that the file system has changed since the last time it was mapped. Warn the user that the requested file can't be found, then force a refresh of the source folder involved.
     285                catch(FileNotFoundException fnf_exception) {
     286                    Gatherer.printStackTrace(fnf_exception);
     287                    cancel_action = true;
     288                    // Show warning.
     289                    JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Not_Found_Message", source_file.getName()), get("File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
     290                    // Force refresh of source folder.
     291                    source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
     292                }
     293                catch(FileAlreadyExistsException fae_exception) {
     294                    Gatherer.printStackTrace(fae_exception);
     295                    cancel_action = true;
     296                    // Show warning.
     297                    JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Already_Exists_Message", target_file.getName()), get("File_Already_Exists_Title"), JOptionPane.ERROR_MESSAGE);
     298                    // Nothing else can be done by the Gatherer.
     299                }
     300                catch(InsufficientSpaceException is_exception) {
     301                    Gatherer.printStackTrace(is_exception);
     302                    cancel_action = true;
     303                    // Show warning. The message body of the expection explains how much more space is required for this file copy.
     304                    JOptionPane.showMessageDialog(Gatherer.g_man, get("Insufficient_Space_Message", is_exception.getMessage()), get("Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
     305                    // Nothing else can be done by the Gatherer. In fact if we are really out of space I'm not even sure we can quit safely.
     306                }
     307                catch(UnknownFileErrorException ufe_exception) {
     308                    Gatherer.printStackTrace(ufe_exception);
     309                    cancel_action = true;
     310                    // Show warning
     311                    JOptionPane.showMessageDialog(Gatherer.g_man, get("Unknown_File_Error_Message"), get("Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
     312                    // Nothing else we can do.
     313                }
     314                catch(IOException exception) {
     315                    // Can't really do much about this.
     316                    Gatherer.printStackTrace(exception);
     317                }
     318                // If not cancelled
     319                if(!cancel_action) {
     320                    // Step one is to create a dummy FileNode. Its important it has the correct structure so getPath works.
     321                    FileNode new_record = new FileNode(target_file);
     322                    SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_record);
     323                    new_node = new_record;
     324
     325                    // create undo job
     326                    if(job.undoable) {
     327                    job.undoable = false;
     328                    if(job.type == FileJob.COPY) {
     329                        // A copy is undone with a delete, so it doesn't really matter where the file originally came from (we're not moving it back there, but into the recycle bin). You may also notice we don't make use of the target parent record. This is because no undo action needs this information, and even if it did it could simply ask for records parent!
     330                        Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, new_record, job.undo);
     331                    }
     332                    else {
     333                        // Movements however do need a source and source parent so the file can be moved back to the correct place.
     334                        Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, new_record, job.undo);
     335                    }
     336                    }
     337                    new_record = null;
     338                }
     339                }
     340                // Else
     341                else if(source_file.isDirectory()) {
     342                // create new record
     343                FileNode directory_record = new FileNode(target_file);
     344                ///ystem.err.println("Directory record = " + directory_record + " (" + target_file.getAbsolutePath() + ")");
     345                SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
     346                // Why is this not happening eh?
     347                directory_record.setParent(destination_node);
     348                if(!target_file.exists()) {
     349                    // make the directory
     350                    target_file.mkdirs();
     351                    new_node = directory_record;
     352                    // create undo job
     353                    if(job.undoable) {
     354                    job.undoable = false;
     355                    if(job.type == FileJob.COPY) {
     356                        // A copy is undone with a delete, so it doesn't really matter where the file originally came from (we're not moving it back there, but into the recycle bin). You may also notice we don't make use of the target parent record. This is because no undo action needs this information, and even if it did it could simply ask for records parent!
     357                        Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, directory_record, job.undo);
     358                    }
     359                    else {
     360                        // Movements however do need a source and source parent so the file can be moved back to the correct place.
     361                        Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, directory_record, job.undo);
     362                    }
     363                    }
     364                }
     365                // Else inform the users that a directory already exists and files will be copied into it
     366                else {
     367                    JOptionPane.showMessageDialog(null, get("Directory_Exists", target_file.toString()), get("General.Warning"), JOptionPane.WARNING_MESSAGE);
     368                }
     369                // Queue non-filtered child files for copying. If this directory already existed, the child records will have to generate the undo jobs, as we don't want to entirely delete this directory if it already existed.
     370                FileNode child_record = null;
     371                // In order to have a sane copy proceedure (rather than always copying last file first as it used to) we always add the child node at the position the parent was removed from. Consider the file job 'a' at the end of the queue which generates three new jobs 'b', 'c' and 'd'. The resulting flow should look like this.
     372                // -- Starting queue                   ...[a]
     373                // remove(position) = 'a'              ...
     374                // add(position, 'b')                  ...[b]
     375                // add(position, 'c')                  ...[c][b]
     376                // add(position, 'd')                  ...[d][c][b]
     377                // Next loop
     378                // remove(position) = 'b'              ...[d][c]
     379                for(int i = 0; i < origin_node.getChildCount(); i++) {
     380                    child_record = (FileNode) origin_node.getChildAt(i);
     381                    addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, job.undo, false, false, position);
     382                }
     383                child_record = null;
     384                directory_record = null;
     385                }
     386                // The file wasn't found!
     387                else {
     388                cancel_action = true;
     389                // Show warning.
     390                JOptionPane.showMessageDialog(Gatherer.g_man, get("File_Not_Found_Message", source_file.getName()), get("File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
     391                // Force refresh of source folder.
     392                source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
     393                }
    394394                                     
    395                                     // We can't have been cancelled, and we must have created a new FileNode during the above phase, before we can handle metadata.
    396                                     if(!cancel_action && new_node != null) {
    397                                           /* Time to handle any existing metadata. */
    398                                           // If the directory came from inside our collection...
    399                                           if (job.source.toString().equals("Collection")) {
    400                                                 ///ystem.err.println("Move within collection...");
    401                                                 GDMManager gdm = Gatherer.c_man.getCollection().gdm;
    402                                               // we just retrieve the metadata attached to the origin node...
    403                                                 ArrayList existing_metadata = gdm.getMetadata(source_file);
    404                                                 ///ystem.err.println("Existing metadata for " + origin_node + ": " + gdm.toString(existing_metadata));
    405                                               // then assign this remainder to the new folder.
    406                                                 ///ystem.err.println("New metadata: " + gdm.toString(existing_metadata));
    407                                                 gdm.addMetadata(new_node, existing_metadata);
    408                                                 existing_metadata = null;
    409                                                 gdm = null;
    410                                           }
    411                                           // If it came from the recycle bin retrieve the metadata from there, once again remembering to account for inherited metadata
    412                                           else if (job.source.toString().equals("Undo")) {
    413                                                 GDMManager gdm = Gatherer.c_man.getCollection().gdm;
    414                                                 // Retrieve metadata from the recycle bin
    415                                                 ArrayList existing_metadata = Gatherer.c_man.undo.getMetadata(source_file);
    416                                                 // then assign this remainder to the new folder.
    417                                                 gdm.addMetadata(new_node, existing_metadata);
    418                                                 existing_metadata = null;
    419                                                 gdm = null;
    420                                           }
    421                                           // Otherwise if it came from the workspace use the MSMs parsers to search for folder level metadata (such as metadata.xml or marc records).
    422                                           else if (job.source.toString().equals("Workspace")) {
    423                                                 cancel_action = Gatherer.c_man.getCollection().msm.searchForMetadata(new_node, origin_node, source_file.isDirectory());
    424                                           }
    425                                     }
    426                                     new_node = null;
    427 
    428                                 }
    429                           }
    430                           // If we haven't been cancelled, and we've been asked to delete a directory/file, or perhaps as part of a move, we delete the file. This involves removing any existing metadata and then copying the file to the recycled bin (for a delete only), then deleting the file. When deleting a directory record from the tree (or from the filesystem for that matter) we must ensure that all of the descendant records have already been removed. If we fail to do this the delete will fail, or you will be bombarded with hundreds of 'Parent node of null not allowed' error messages. Also be aware that if the user has cancelled just this action, because of say a name clash, then we shouldn't do any deleting of any sort dammit.
    431                           if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
    432                                 ///ystem.err.print("Delete/Move: " + origin_node + " -> ");
    433                                 // If the source is an empty directory or a file. Don't do anything to the root node of a tree.
    434                                 File[] child_list = source_file.listFiles();
    435                                 if(source_file.isFile() || (child_list != null && (child_list.length == 0 || (child_list.length == 1 && child_list[0].getName().equals(Utility.METADATA_XML))) && origin_node.getParent() != null)) {
    436                                     ///ystem.err.println("File or empty directory.");
    437                                     // Delete any metadata.xml still in the directory.
    438                                     if(child_list != null && child_list.length == 1) {
    439                                           child_list[0].delete();
    440                                     }
    441 
    442                                     ///ystem.err.println("Origin is file or is directory and is empty.");
    443                                     // update status area
    444                                     String args[] = new String[1];
    445                                     args[0] = "" + (queue.size() + 1) + "";
    446                                     job_status.setText(get("Jobs", args));
    447                                     args[0] = Utility.formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
    448                                     file_status.setText(get("Deleting", args));
    449                                     args = null;
    450                                     file_status.setToolTipText(Utility.formatHTMLWidth(file_status.getText(), 80));
    451                                     // Remove its metadata
    452                                     ArrayList metadatum = null;
    453                                     if(job.source == Gatherer.c_man.undo) {
    454                                           Gatherer.c_man.undo.addMetadata(target_file, metadatum);
    455                                     }
    456                                     else {
    457                                           metadatum= Gatherer.c_man.getCollection().gdm.removeMetadata(origin_node.getFile());
    458                                     }
    459                                     // determine it parent node
    460                                     FileNode parent_record = (FileNode)origin_node.getParent();
    461                                     // Remove from model
    462                                     SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
    463                                     // If we are deleting
    464                                     File recycled_file = null;
    465                                     FileNode recycled_parent = null;
    466                                     if(job.type == FileJob.DELETE) {
    467                                           // See if this record already has a previous recycle folder noted in the recycle folder mappings.
    468                                           recycled_parent = (FileNode) recycle_folder_mappings.get(origin_node);
    469                                           if(recycled_parent != null) {
    470                                                 recycle_folder_mappings.remove(origin_node);
    471                                           }
    472                                           else {
    473                                                 recycled_parent = (FileNode) Gatherer.c_man.undo.getTreeModel().getRoot();
    474                                           }
    475                                           // This may be a directory which we have already added previously.
    476                                           if(completed_folder_mappings.containsKey(origin_node)) {
    477                                                 FileNode recycled_record = (FileNode) completed_folder_mappings.get(origin_node);
    478                                               // Replace the temporary directory record in the undo tree with this one.
    479                                               SynchronizedTreeModelTools.replaceNode(Gatherer.c_man.undo.getTreeModel(), recycled_record, origin_node);
    480                                                 origin_node.setFile(recycled_record.getFile());
    481                                           }
    482                                           else {
    483                                               // copy the file to the recycle bin
    484                                                 recycled_file = new File(recycled_parent.getFile(), origin_node.toString());
    485                                                 // If the file already exists, delete it.
    486                                                 if(recycled_file.exists()) {
    487                                                      recycled_file.delete();
    488                                                 }
    489                                                 recycled_file.deleteOnExit();
    490                                                 copyFile(source_file, recycled_file, progress);
    491                                                 origin_node.setFile(recycled_file);
    492                                               // Add the node to the appropriate place in the UndoManagers tree model. Unfortunately this one does have the possibility of altering the GUI (if the removeNodeFromParent above hasn't occured yet), so we must put it on the queue.
    493                                               SynchronizedTreeModelTools.insertNodeInto(Gatherer.c_man.undo.getTreeModel(), recycled_parent, origin_node);
    494                                           }
    495                                           Gatherer.c_man.undo.addMetadata(source_file, metadatum);
    496                                     }
    497                                     // delete the source file
    498                                     Utility.delete(source_file);
    499                                     // create undo job as necessary. File move would have been handled above.
    500                                     if(job.undoable) {
    501                                           job.undoable = false;
    502                                           // The target is null, indicating that we moved this file to the recycle bin. The UndoManager will replace null with 'this'.
    503                                           Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_DELETE, job.source, parent_record, null, origin_node, job.undo);
    504                                     }
    505                                     recycled_parent = null;
    506                                     recycled_file = null;
    507                                     //metadatum = null;
    508                                 }
    509                                 // Else the source is a directory and it has children remaining
    510                                 else if(child_list != null && child_list.length > 0) {
    511                                     ///ystem.err.print("Nonempty directory -> ");
    512                                     ///ystem.err.println("Directory is non-empty. Remove children first.");
    513                                     FileNode recycle_folder_record = null;
    514                                     // Don't worry about all this for true file move actions.
    515                                     if(job.type == FileJob.DELETE) {
    516                                           // See if this record already has a previous recycle folder noted in the recycle folder mappings.
    517                                           FileNode parent = (FileNode) recycle_folder_mappings.get(origin_node);
    518                                           if(parent != null) {
    519                                                 recycle_folder_mappings.remove(origin_node);
    520                                           }
    521                                           else {
    522                                                 parent = (FileNode) Gatherer.c_man.undo.getTreeModel().getRoot();
    523                                           }
    524                                           // We must add the folder node to our undo manager tree now, so we'll have something to add children to. We use a copy of the directory file record.
    525                                           File recycle_folder = new File(parent.getFile(), origin_node.toString());
    526                                           recycle_folder.deleteOnExit();
    527                                           recycle_folder.mkdirs();
    528                                           recycle_folder_record = new FileNode(recycle_folder);
    529                                           // Add this node to the undo tree model.
    530                                           SynchronizedTreeModelTools.insertNodeInto(Gatherer.c_man.undo.getTreeModel(), parent, recycle_folder_record);
    531                                           // We add an entry to the complete mappings to ensure this directory isn't added again
    532                                           completed_folder_mappings.put(origin_node, recycle_folder_record);
    533                                           ///ystem.err.println("Added completed directories mapping " + origin_node);
    534                                           // queue all of its children, (both filtered and non-filtered), but for deleting only. Don't queue jobs for a current move event, as they would be queued as part of copying. I have no idea way, per sec, however the children within the origin node are always invalid during deletion (there are several copies of some nodes?!?). I'll check that each child is only added once.
    535                                           ///ystem.err.println("Directory has " + origin_node.getChildCount() + " children.");
    536                                           ///ystem.err.println("Directory actually has " + child_list.length + " children.");
    537                                           origin_node.unmap();
    538                                           origin_node.map();
    539                                           ///ystem.err.println("Directory has " + origin_node.getChildCount() + " children.");
    540                                           ///ystem.err.println("Directory actually has " + child_list.length + " children.");
    541                                           for(int i = 0; i < origin_node.getChildCount(); i++) {
    542                                                 FileNode child_record = (FileNode) origin_node.getChildAt(i);
    543                                                 addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, job.undo, false, false, position);
    544                                                 if(recycle_folder_record != null) {
    545                                                      recycle_folder_mappings.put(child_record, recycle_folder_record);
    546                                                 }
    547                                           }
    548                                     }
    549                                     // Requeue a delete job -after- the children have been dealt with. Remember I've reversed the direction of the queue so sooner is later. Te-he. Also have to remember that we have have followed this path to get here for a move job: Copy Directory -> Queue Child Files -> Delete Directory (must occur after child files) -> Queue Directory.
    550                                     // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
    551                                     if(origin_node.getParent() != null && !job.done) {
    552                                           ///ystem.err.println("Requeue");
    553                                           job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
    554                                           addJob(job, position);
    555                                     }
    556                                     else {
    557                                           ///ystem.err.println("I've already done this job twice. I refuse to requeue it again!!!");
    558                                     }
    559                                 }
    560                           }
    561                           job = null;
    562                           source_file = null;
    563                           target_file = null;
    564                           origin_node = null;
    565                           // We can only break out of the while loop if we are out of files, or if the action was cancelled.
    566                           if(cancel_action) {
    567                                 // Empty queue
    568                                 clearJobs();
    569                                 cancel_action = false;
    570                           }
    571                           // Debugging pause.
    572                           ///ystem.err.println("Job complete.");
    573                      }
    574                      else {
    575                           synchronized(this) {
    576                                 ///ystem.err.println("Queue size = " + queue.size());
    577                                 // Force the trees to refresh (but only if there is something on screen!)
    578                                 if(Gatherer.g_man != null) {
    579                                     Gatherer.g_man.refreshTrees();
    580                                 }
    581                                 // Reset status area
    582                                 job_status.setText(get("No_Selection"));
    583                                 file_status.setText(get("No_Activity"));
    584                                 progress.reset();
    585                                 progress.setString(get("No_Activity"));
    586                                 yes_to_all = false;         
    587                                 completed_folder_mappings.clear();
    588                                 recycle_folder_mappings.clear();
    589                                 // Now wait if applicable.
    590                                 if(return_immediately) {
    591                                     return;
    592                                 }
    593                                 ///ystem.err.println("Waiting");
    594                                 wait();
    595                           }
    596                      }
    597                 }
    598                 catch (Exception error) {
    599                      Gatherer.printStackTrace(error);
    600                 }
    601           }
    602     }
    603     /** A second lock is necessary to prevent the thread waiting, because its notify occured momentarily before it waited. If the flag is set then the thread can't wait, but loops around. This may chew processor time so should only be used if you are certain files are about to be placed on the queue.
     395                // We can't have been cancelled, and we must have created a new FileNode during the above phase, before we can handle metadata.
     396                if(!cancel_action && new_node != null) {
     397                /* Time to handle any existing metadata. */
     398                // If the directory came from inside our collection...
     399                if (job.source.toString().equals("Collection")) {
     400                    ///ystem.err.println("Move within collection...");
     401                    GDMManager gdm = Gatherer.c_man.getCollection().gdm;
     402                    // we just retrieve the metadata attached to the origin node...
     403                    ArrayList existing_metadata = gdm.getMetadata(source_file);
     404                    ///ystem.err.println("Existing metadata for " + origin_node + ": " + gdm.toString(existing_metadata));
     405                    // then assign this remainder to the new folder.
     406                    ///ystem.err.println("New metadata: " + gdm.toString(existing_metadata));
     407                    gdm.addMetadata(new_node, existing_metadata);
     408                    existing_metadata = null;
     409                    gdm = null;
     410                }
     411                // If it came from the recycle bin retrieve the metadata from there, once again remembering to account for inherited metadata
     412                else if (job.source.toString().equals("Undo")) {
     413                    GDMManager gdm = Gatherer.c_man.getCollection().gdm;
     414                    // Retrieve metadata from the recycle bin
     415                    ArrayList existing_metadata = Gatherer.c_man.undo.getMetadata(source_file);
     416                    // then assign this remainder to the new folder.
     417                    gdm.addMetadata(new_node, existing_metadata);
     418                    existing_metadata = null;
     419                    gdm = null;
     420                }
     421                // Otherwise if it came from the workspace use the MSMs parsers to search for folder level metadata (such as metadata.xml or marc records).
     422                else if (job.source.toString().equals("Workspace")) {
     423                    cancel_action = Gatherer.c_man.getCollection().msm.searchForMetadata(new_node, origin_node, source_file.isDirectory());
     424                }
     425                }
     426                new_node = null;
     427
     428            }
     429            }
     430            // If we haven't been cancelled, and we've been asked to delete a directory/file, or perhaps as part of a move, we delete the file. This involves removing any existing metadata and then copying the file to the recycled bin (for a delete only), then deleting the file. When deleting a directory record from the tree (or from the filesystem for that matter) we must ensure that all of the descendant records have already been removed. If we fail to do this the delete will fail, or you will be bombarded with hundreds of 'Parent node of null not allowed' error messages. Also be aware that if the user has cancelled just this action, because of say a name clash, then we shouldn't do any deleting of any sort dammit.
     431            if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
     432            ///ystem.err.print("Delete/Move: " + origin_node + " -> ");
     433            // If the source is an empty directory or a file. Don't do anything to the root node of a tree.
     434            File[] child_list = source_file.listFiles();
     435            if(source_file.isFile() || (child_list != null && (child_list.length == 0 || (child_list.length == 1 && child_list[0].getName().equals(Utility.METADATA_XML))) && origin_node.getParent() != null)) {
     436                ///ystem.err.println("File or empty directory.");
     437                // Delete any metadata.xml still in the directory.
     438                if(child_list != null && child_list.length == 1) {
     439                child_list[0].delete();
     440                }
     441
     442                ///ystem.err.println("Origin is file or is directory and is empty.");
     443                // update status area
     444                String args[] = new String[1];
     445                args[0] = "" + (queue.size() + 1) + "";
     446                job_status.setText(get("Jobs", args));
     447                args[0] = Utility.formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
     448                file_status.setText(get("Deleting", args));
     449                args = null;
     450                file_status.setToolTipText(Utility.formatHTMLWidth(file_status.getText(), 80));
     451                // Remove its metadata
     452                ArrayList metadatum = null;
     453                if(job.source == Gatherer.c_man.undo) {
     454                Gatherer.c_man.undo.addMetadata(target_file, metadatum);
     455                }
     456                else {
     457                metadatum= Gatherer.c_man.getCollection().gdm.removeMetadata(origin_node.getFile());
     458                }
     459                // determine it parent node
     460                FileNode parent_record = (FileNode)origin_node.getParent();
     461                // Remove from model
     462                SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
     463                // If we are deleting
     464                File recycled_file = null;
     465                FileNode recycled_parent = null;
     466                if(job.type == FileJob.DELETE) {
     467                // See if this record already has a previous recycle folder noted in the recycle folder mappings.
     468                recycled_parent = (FileNode) recycle_folder_mappings.get(origin_node);
     469                if(recycled_parent != null) {
     470                    recycle_folder_mappings.remove(origin_node);
     471                }
     472                else {
     473                    recycled_parent = (FileNode) Gatherer.c_man.undo.getTreeModel().getRoot();
     474                }
     475                // This may be a directory which we have already added previously.
     476                if(completed_folder_mappings.containsKey(origin_node)) {
     477                    FileNode recycled_record = (FileNode) completed_folder_mappings.get(origin_node);
     478                    // Replace the temporary directory record in the undo tree with this one.
     479                    SynchronizedTreeModelTools.replaceNode(Gatherer.c_man.undo.getTreeModel(), recycled_record, origin_node);
     480                    origin_node.setFile(recycled_record.getFile());
     481                }
     482                else {
     483                    // copy the file to the recycle bin
     484                    recycled_file = new File(recycled_parent.getFile(), origin_node.toString());
     485                    // If the file already exists, delete it.
     486                    if(recycled_file.exists()) {
     487                    recycled_file.delete();
     488                    }
     489                    recycled_file.deleteOnExit();
     490                    copyFile(source_file, recycled_file, progress);
     491                    origin_node.setFile(recycled_file);
     492                    // Add the node to the appropriate place in the UndoManagers tree model. Unfortunately this one does have the possibility of altering the GUI (if the removeNodeFromParent above hasn't occured yet), so we must put it on the queue.
     493                    SynchronizedTreeModelTools.insertNodeInto(Gatherer.c_man.undo.getTreeModel(), recycled_parent, origin_node);
     494                }
     495                Gatherer.c_man.undo.addMetadata(source_file, metadatum);
     496                }
     497                // delete the source file
     498                Utility.delete(source_file);
     499                // create undo job as necessary. File move would have been handled above.
     500                if(job.undoable) {
     501                job.undoable = false;
     502                // The target is null, indicating that we moved this file to the recycle bin. The UndoManager will replace null with 'this'.
     503                Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_DELETE, job.source, parent_record, null, origin_node, job.undo);
     504                }
     505                recycled_parent = null;
     506                recycled_file = null;
     507                //metadatum = null;
     508            }
     509            // Else the source is a directory and it has children remaining
     510            else if(child_list != null && child_list.length > 0) {
     511                ///ystem.err.print("Nonempty directory -> ");
     512                ///ystem.err.println("Directory is non-empty. Remove children first.");
     513                FileNode recycle_folder_record = null;
     514                // Don't worry about all this for true file move actions.
     515                if(job.type == FileJob.DELETE) {
     516                // See if this record already has a previous recycle folder noted in the recycle folder mappings.
     517                FileNode parent = (FileNode) recycle_folder_mappings.get(origin_node);
     518                if(parent != null) {
     519                    recycle_folder_mappings.remove(origin_node);
     520                }
     521                else {
     522                    parent = (FileNode) Gatherer.c_man.undo.getTreeModel().getRoot();
     523                }
     524                // We must add the folder node to our undo manager tree now, so we'll have something to add children to. We use a copy of the directory file record.
     525                File recycle_folder = new File(parent.getFile(), origin_node.toString());
     526                recycle_folder.deleteOnExit();
     527                recycle_folder.mkdirs();
     528                recycle_folder_record = new FileNode(recycle_folder);
     529                // Add this node to the undo tree model.
     530                SynchronizedTreeModelTools.insertNodeInto(Gatherer.c_man.undo.getTreeModel(), parent, recycle_folder_record);
     531                // We add an entry to the complete mappings to ensure this directory isn't added again
     532                completed_folder_mappings.put(origin_node, recycle_folder_record);
     533                ///ystem.err.println("Added completed directories mapping " + origin_node);
     534                // queue all of its children, (both filtered and non-filtered), but for deleting only. Don't queue jobs for a current move event, as they would be queued as part of copying. I have no idea way, per sec, however the children within the origin node are always invalid during deletion (there are several copies of some nodes?!?). I'll check that each child is only added once.
     535                ///ystem.err.println("Directory has " + origin_node.getChildCount() + " children.");
     536                ///ystem.err.println("Directory actually has " + child_list.length + " children.");
     537                origin_node.unmap();
     538                origin_node.map();
     539                ///ystem.err.println("Directory has " + origin_node.getChildCount() + " children.");
     540                ///ystem.err.println("Directory actually has " + child_list.length + " children.");
     541                for(int i = 0; i < origin_node.getChildCount(); i++) {
     542                    FileNode child_record = (FileNode) origin_node.getChildAt(i);
     543                    addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, job.undo, false, false, position);
     544                    if(recycle_folder_record != null) {
     545                    recycle_folder_mappings.put(child_record, recycle_folder_record);
     546                    }
     547                }
     548                }
     549                // Requeue a delete job -after- the children have been dealt with. Remember I've reversed the direction of the queue so sooner is later. Te-he. Also have to remember that we have have followed this path to get here for a move job: Copy Directory -> Queue Child Files -> Delete Directory (must occur after child files) -> Queue Directory.
     550                // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
     551                if(origin_node.getParent() != null && !job.done) {
     552                ///ystem.err.println("Requeue");
     553                job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
     554                addJob(job, position);
     555                }
     556                else {
     557                ///ystem.err.println("I've already done this job twice. I refuse to requeue it again!!!");
     558                }
     559            }
     560            }
     561            job = null;
     562            source_file = null;
     563            target_file = null;
     564            origin_node = null;
     565            // We can only break out of the while loop if we are out of files, or if the action was cancelled.
     566            if(cancel_action) {
     567            // Empty queue
     568            clearJobs();
     569            cancel_action = false;
     570            }
     571            // Debugging pause.
     572            ///ystem.err.println("Job complete.");
     573        }
     574        else {
     575            synchronized(this) {
     576            ///ystem.err.println("Queue size = " + queue.size());
     577            // Force the trees to refresh (but only if there is something on screen!)
     578            if(Gatherer.g_man != null) {
     579                Gatherer.g_man.refreshTrees();
     580            }
     581            // Reset status area
     582            job_status.setText(get("No_Selection"));
     583            file_status.setText(get("No_Activity"));
     584            progress.reset();
     585            progress.setString(get("No_Activity"));
     586            yes_to_all = false;         
     587            completed_folder_mappings.clear();
     588            recycle_folder_mappings.clear();
     589            // Now wait if applicable.
     590            if(return_immediately) {
     591                return;
     592            }
     593            ///ystem.err.println("Waiting");
     594            wait();
     595            }
     596        }
     597        }
     598        catch (Exception error) {
     599        Gatherer.printStackTrace(error);
     600        }
     601    }
     602    }
     603    /** A second lock is necessary to prevent the thread waiting, because its notify occured momentarily before it waited. If the flag is set then the thread can't wait, but loops around. This may chew processor time so should only be used if you are certain files are about to be placed on the queue.
    604604      * @param wait_allowed The new state of the wait_allowed flag, as a boolean.
    605605      */
    606     public void setWaitAllowed(boolean wait_allowed) {
    607           this.wait_allowed = wait_allowed;
    608     }
    609     /** Restore the progress bar so that it updates normally.
     606    public void setWaitAllowed(boolean wait_allowed) {
     607    this.wait_allowed = wait_allowed;
     608    }
     609    /** Restore the progress bar so that it updates normally.
    610610      * @see org.greenstone.gatherer.gui.LongProgressBar
    611611      */
    612     synchronized public void unpause() {
    613           progress.setIndeterminate(false);
    614           if(previous != null) {
    615                 progress.setString(previous);
    616                 previous = null;
    617           }
    618     }
    619     /** Called when the user makes some selection in one of the trees we are listening to. From this we update the status details. */
    620     public void valueChanged(TreeSelectionEvent event) {
    621           JTree tree = (JTree) event.getSource();
    622           if(tree.getSelectionCount() > 0) {
    623                 TreePath selection[] = tree.getSelectionPaths();
    624                 int file_count = 0;
    625                 int dir_count = 0;
    626                 for(int i = 0; i < selection.length; i++) {
    627                      TreeNode record = (TreeNode) selection[i].getLastPathComponent();
    628                      if(record.isLeaf()) {
    629                           file_count++;
    630                      }
    631                      else {
    632                           dir_count++;
    633                      }
    634                      record = null;
    635                 }
    636                 selection = null;
    637                 String args[] = new String[2];
    638                 args[0] = "" + file_count + "";
    639                 args[1] = "" + dir_count + "";
    640                 job_status.setText(get("Selected", args));
    641                 args = null;
    642           }
    643           tree = null;
    644     }
    645 
    646     synchronized private void clearJobs() {
    647           queue.clear();
    648     }
    649 
    650     /** Copy a file from the source location to the destination location.
     612    synchronized public void unpause() {
     613    progress.setIndeterminate(false);
     614    if(previous != null) {
     615        progress.setString(previous);
     616        previous = null;
     617    }
     618    }
     619    /** Called when the user makes some selection in one of the trees we are listening to. From this we update the status details. */
     620    public void valueChanged(TreeSelectionEvent event) {
     621    JTree tree = (JTree) event.getSource();
     622    if(tree.getSelectionCount() > 0) {
     623        TreePath selection[] = tree.getSelectionPaths();
     624        int file_count = 0;
     625        int dir_count = 0;
     626        for(int i = 0; i < selection.length; i++) {
     627        TreeNode record = (TreeNode) selection[i].getLastPathComponent();
     628        if(record.isLeaf()) {
     629            file_count++;
     630        }
     631        else {
     632            dir_count++;
     633        }
     634        record = null;
     635        }
     636        selection = null;
     637        String args[] = new String[2];
     638        args[0] = "" + file_count + "";
     639        args[1] = "" + dir_count + "";
     640        job_status.setText(get("Selected", args));
     641        args = null;
     642    }
     643    tree = null;
     644    }
     645
     646    synchronized private void clearJobs() {
     647    queue.clear();
     648    }
     649
     650    /** Copy a file from the source location to the destination location.
    651651      * @param source The source File.
    652652      * @param destination The destination File.
    653653      * @see org.greenstone.gatherer.Gatherer
    654654      */
    655     public void copyFile(File source, File destination, LongProgressBar progress)
    656           throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, UnknownFileErrorException {
    657           if(source.isDirectory()) {
    658                 destination.mkdirs();
    659           }
    660           else {
     655    public void copyFile(File source, File destination, LongProgressBar progress)
     656    throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, UnknownFileErrorException {
     657    if(source.isDirectory()) {
     658        destination.mkdirs();
     659    }
     660    else {
    661661                // Check if the origin file exists.
    662                 if(!source.exists()) {
    663                      throw(new FileNotFoundException());
    664                 }
     662        if(!source.exists()) {
     663        throw(new FileNotFoundException());
     664        }
    665665                // Check if the destination file does not exist.
    666                 if(destination.exists()) {
    667                      throw(new FileAlreadyExistsException());
    668                 }
    669                 File dirs = destination.getParentFile();
    670                 dirs.mkdirs();
     666        if(destination.exists()) {
     667        throw(new FileAlreadyExistsException());
     668        }
     669        File dirs = destination.getParentFile();
     670        dirs.mkdirs();
    671671                // Copy the file.
    672                 FileInputStream f_in = new FileInputStream(source);
    673                 FileOutputStream f_out = new FileOutputStream(destination);
    674                 byte data[] = new byte[Utility.BUFFER_SIZE];
    675                 int data_size = 0;
    676                 while((data_size = f_in.read(data, 0, Utility.BUFFER_SIZE)) != -1 && !cancel_action) {
    677                      long destination_size = destination.length();
    678                      try {
    679                           f_out.write(data, 0, data_size);
    680                      }
    681                      // If an IO exception occurs, we can do some maths to determine if the number of bytes written to the file was less than expected. If so we assume a InsufficientSpace exception. If not we just throw the exception again.
    682                      catch (IOException io_exception) {
    683                           if(destination_size + (long) data_size > destination.length()) {
    684                                 // Determine the difference (which I guess is in bytes).
    685                                 long difference = (destination_size + (long) data_size) - destination.length();
    686                                 // Transform that into a human readable string.
    687                                 String message = Utility.formatFileLength(difference);
    688                                 throw(new InsufficientSpaceException(message));
    689                           }
    690                           else {
    691                                 throw(io_exception);
    692                           }
    693                      }
    694                      if(progress != null) {
    695                           progress.addValue(data_size);
    696                      }
    697                 }
     672        FileInputStream f_in = new FileInputStream(source);
     673        FileOutputStream f_out = new FileOutputStream(destination);
     674        byte data[] = new byte[Utility.BUFFER_SIZE];
     675        int data_size = 0;
     676        while((data_size = f_in.read(data, 0, Utility.BUFFER_SIZE)) != -1 && !cancel_action) {
     677        long destination_size = destination.length();
     678        try {
     679            f_out.write(data, 0, data_size);
     680        }
     681        // If an IO exception occurs, we can do some maths to determine if the number of bytes written to the file was less than expected. If so we assume a InsufficientSpace exception. If not we just throw the exception again.
     682        catch (IOException io_exception) {
     683            if(destination_size + (long) data_size > destination.length()) {
     684            // Determine the difference (which I guess is in bytes).
     685            long difference = (destination_size + (long) data_size) - destination.length();
     686            // Transform that into a human readable string.
     687            String message = Utility.formatFileLength(difference);
     688            throw(new InsufficientSpaceException(message));
     689            }
     690            else {
     691            throw(io_exception);
     692            }
     693        }
     694        if(progress != null) {
     695            progress.addValue(data_size);
     696        }
     697        }
    698698                // Flush and close the streams to ensure all bytes are written.
    699                 f_in.close();
    700                 f_out.close();
     699        f_in.close();
     700        f_out.close();
    701701                // We have now, in theory, produced an exact copy of the source file. Check this by comparing sizes.
    702                 if(!cancel_action && source.length() != destination.length()) {
    703                      throw(new UnknownFileErrorException());
    704                 }
     702        if(!cancel_action && source.length() != destination.length()) {
     703        throw(new UnknownFileErrorException());
     704        }
    705705                // If we were cancelled, ensure that none of the destination file exists.
    706                 if(cancel_action) {
    707                      destination.delete();
    708                 }
    709           }
    710     }
    711     /** Retrieve a phrase from the dictionary based on the key.
     706        if(cancel_action) {
     707        destination.delete();
     708        }
     709    }
     710    }
     711    /** Retrieve a phrase from the dictionary based on the key.
    712712      * @param key The key index as a String.
    713713      * @return The desired phrase also as a String.
    714714      */
    715     private String get(String key) {
    716           return get(key, (String[])null);
    717     }
    718     /** Retrieve a phrase from the dictionary based on the key, and taking into account the given argument.
     715    private String get(String key) {
     716    return get(key, (String[])null);
     717    }
     718    /** Retrieve a phrase from the dictionary based on the key, and taking into account the given argument.
    719719      * @param key The key index as a String.
    720720      * @param args A String which is the argument to be added to the phrase.
    721721      * @return The desired phrase also as a String.
    722722      */
    723     private String get(String key, String arg) {
    724           String args[] = new String[1];
    725           args[0] = arg;
    726           return get(key, args);
    727     }
    728     /** Retrieve a phrase from the dictionary based on the key, and taking into account the given arguments.
     723    private String get(String key, String arg) {
     724    String args[] = new String[1];
     725    args[0] = arg;
     726    return get(key, args);
     727    }
     728    /** Retrieve a phrase from the dictionary based on the key, and taking into account the given arguments.
    729729      * @param key The key index as a String.
    730730      * @param args A String[] of arguments to be added to the phrase.
     
    733733      * @see org.greenstone.gatherer.Gatherer
    734734      */
    735     private String get(String key, String args[]) {
    736           if(key.indexOf('.') == -1) {
    737                 key = "FileActions." + key;
    738           }
    739           return Gatherer.dictionary.get(key, args);
    740     }
    741 
    742     private FileJob removeJob(int position) {
    743           FileJob job = null;
    744           if(queue.size() > 0) {
    745                 job = (FileJob) queue.remove(position);
    746           }
    747           return job;
    748     }
     735    private String get(String key, String args[]) {
     736    if(key.indexOf('.') == -1) {
     737        key = "FileActions." + key;
     738    }
     739    return Gatherer.dictionary.get(key, args);
     740    }
     741
     742    private FileJob removeJob(int position) {
     743    FileJob job = null;
     744    if(queue.size() > 0) {
     745        job = (FileJob) queue.remove(position);
     746    }
     747    return job;
     748    }
    749749}
Note: See TracChangeset for help on using the changeset viewer.