- Timestamp:
- 2003-05-27T15:57:37+12:00 (21 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java
r4293 r4366 58 58 */ 59 59 public class FileQueue 60 60 extends Thread 61 61 implements TreeSelectionListener { 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 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. 86 86 * @param return_immediately true to cause this file queue to return from run() as soon as there are no jobs left on the queue. 87 87 * @see org.greenstone.gatherer.Configuration … … 89 89 * @see org.greenstone.gatherer.gui.LongProgressBar 90 90 */ 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 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. 108 108 * @param job A previously created FileJob. 109 109 */ 110 111 112 113 114 115 116 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). 117 117 * @param id A long id unique to all jobs created by a single action. 118 118 * @param source The DragComponent source of this file, most likely a DragTree. … … 124 124 * @param undoable true if this job can generate undo or redo jobs at all, false otherwise. 125 125 */ 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 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. 186 186 * @see org.greenstone.gatherer.Gatherer 187 187 * @see org.greenstone.gatherer.collection.CollectionManager … … 193 193 * @see org.greenstone.gatherer.util.Utility 194 194 */ 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? 198 281 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 } 394 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 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. 604 604 * @param wait_allowed The new state of the wait_allowed flag, as a boolean. 605 605 */ 606 607 608 609 606 public void setWaitAllowed(boolean wait_allowed) { 607 this.wait_allowed = wait_allowed; 608 } 609 /** Restore the progress bar so that it updates normally. 610 610 * @see org.greenstone.gatherer.gui.LongProgressBar 611 611 */ 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 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. 651 651 * @param source The source File. 652 652 * @param destination The destination File. 653 653 * @see org.greenstone.gatherer.Gatherer 654 654 */ 655 656 657 658 659 660 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 { 661 661 // Check if the origin file exists. 662 663 664 662 if(!source.exists()) { 663 throw(new FileNotFoundException()); 664 } 665 665 // Check if the destination file does not exist. 666 667 668 669 670 666 if(destination.exists()) { 667 throw(new FileAlreadyExistsException()); 668 } 669 File dirs = destination.getParentFile(); 670 dirs.mkdirs(); 671 671 // Copy the file. 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 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 } 698 698 // Flush and close the streams to ensure all bytes are written. 699 700 699 f_in.close(); 700 f_out.close(); 701 701 // We have now, in theory, produced an exact copy of the source file. Check this by comparing sizes. 702 703 704 702 if(!cancel_action && source.length() != destination.length()) { 703 throw(new UnknownFileErrorException()); 704 } 705 705 // If we were cancelled, ensure that none of the destination file exists. 706 707 708 709 710 711 706 if(cancel_action) { 707 destination.delete(); 708 } 709 } 710 } 711 /** Retrieve a phrase from the dictionary based on the key. 712 712 * @param key The key index as a String. 713 713 * @return The desired phrase also as a String. 714 714 */ 715 716 717 718 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. 719 719 * @param key The key index as a String. 720 720 * @param args A String which is the argument to be added to the phrase. 721 721 * @return The desired phrase also as a String. 722 722 */ 723 724 725 726 727 728 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. 729 729 * @param key The key index as a String. 730 730 * @param args A String[] of arguments to be added to the phrase. … … 733 733 * @see org.greenstone.gatherer.Gatherer 734 734 */ 735 736 737 738 739 740 741 742 743 744 745 746 747 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 } 749 749 }
Note:
See TracChangeset
for help on using the changeset viewer.