source: trunk/gli/src/org/greenstone/gatherer/file/FileQueue.java@ 8243

Last change on this file since 8243 was 8243, checked in by mdewsnip, 20 years ago

Removed all occurrences of classes explicitly importing other classes in the same package.

  • Property svn:keywords set to Author Date Id Revision
File size: 35.7 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.file;
28
29import java.io.*;
30import java.util.*;
31import javax.swing.*;
32import javax.swing.event.*;
33import javax.swing.tree.*;
34import org.greenstone.gatherer.Configuration;
35import org.greenstone.gatherer.DebugStream;
36import org.greenstone.gatherer.Dictionary;
37import org.greenstone.gatherer.Gatherer;
38import org.greenstone.gatherer.gui.LongProgressBar;
39import org.greenstone.gatherer.gui.tree.DragTree;
40import org.greenstone.gatherer.util.ArrayTools;
41import org.greenstone.gatherer.util.DragComponent;
42import org.greenstone.gatherer.util.SynchronizedTreeModelTools;
43import org.greenstone.gatherer.util.Utility;
44
45/** A threaded object which processes a queue of file actions such as copying and movement. It also handles updating the various trees involved so they are an accurate representation of the file system they are meant to match.
46 * @author John Thompson, Greenstone Digital Library, University of Waikato
47 * @version 2.3
48 */
49public class FileQueue
50 extends Thread
51 implements TreeSelectionListener {
52 /** When someone requests the movement queue to be dumped this cancel flag is set to true. */
53 private boolean cancel_action = false;
54 /** A temporary mapping from currently existing FileNode folder to their equivelent FileNode folder within the undo managers tree. */
55 private HashMap completed_folder_mappings = new HashMap();
56
57 /** The button which controls the stopping of the file queue. */
58 private JButton stop_button = null;
59
60 /** 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. */
61 private boolean return_immediately = false;
62 /** We are only allowed to wait under specific circumstances. */
63 /* private boolean wait_allowed = true; */
64 /** true if the user has selected yes to all from a file 'clash' dialog. */
65 private boolean yes_to_all = false;
66 /** A temporary mapping from currently existing FileNodes to the potential FileNode folder within the undo managers tree. */
67 private HashMap recycle_folder_mappings = new HashMap();
68 /** A label explaining the current moving files status. */
69 private JLabel file_status = null;
70 /** A list containing a queue of waiting movement jobs. */
71 private ArrayList queue;
72 /** A progress bar which shows how many bytes, out of the total size of bytes, has been moved. */
73 private LongProgressBar progress = null;
74 /** The last piece of text shown on the file status label, just incase we are displaying a very temporary message. */
75 private String previous = null;
76 /** Constructor.
77 * @param return_immediately true to cause this file queue to return from run() as soon as there are no jobs left on the queue.
78 * @see org.greenstone.gatherer.Configuration
79 * @see org.greenstone.gatherer.gui.Coloring
80 * @see org.greenstone.gatherer.gui.LongProgressBar
81 */
82 public FileQueue(boolean return_immediately) {
83 this.return_immediately = return_immediately;
84 this.queue = new ArrayList();
85 String args[] = new String[2];
86 args[0] = "0";
87 args[1] = "0";
88 file_status = new JLabel();
89 Dictionary.setText(file_status, "FileActions.Selected", args);
90 progress = new LongProgressBar();
91 progress.setBackground(Configuration.getColor("coloring.collection_tree_background", false));
92 progress.setForeground(Configuration.getColor("coloring.collection_tree_foreground", false));
93 progress.setString(Dictionary.get("FileActions.No_Activity"));
94 progress.setStringPainted(true);
95 args = null;
96 }
97
98 /** Requeue an existing job into the queue.
99 * @param job A previously created FileJob.
100 */
101 synchronized public void addJob(FileJob job, int position) {
102 job.done = true; // Ensure that the requeued job is marked as done.
103 queue.add(position, job);
104 notify();
105 }
106
107 /** 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).
108 * @param id A long id unique to all jobs created by a single action.
109 * @param source The DragComponent source of this file, most likely a DragTree.
110 * @param child The FileNode you wish to mode.
111 * @param target The DragComponent to move the file to, again most likely a DragTree.
112 * @param parent The files new FileNode parent within the target.
113 * @param type The type of this movement as an int, either COPY or DELETE.
114 * @param undo true if this job should generate undo jobs, false for redo ones.
115 * @param undoable true if this job can generate undo or redo jobs at all, false otherwise.
116 */
117 public void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, boolean undo, boolean undoable, boolean folder_level) {
118 addJob(id, source, child, target, parent, type, undo, undoable, folder_level, -1);
119 }
120
121 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) {
122 FileJob job = new FileJob(id, source, child, target, parent, type, undo, undoable);
123 job.folder_level = folder_level;
124 DebugStream.println("Adding job: " + job);
125 if(position != -1 && position <= queue.size() + 1) {
126 queue.add(position, job);
127 }
128 else {
129 queue.add(job);
130 }
131 notify();
132 }
133
134 /** Calculates the total deep file size of the selected file nodes.
135 * @param files a FileNode[] of selected files
136 * @return true if a cancel was signalled, false otherwise
137 * @see org.greenstone.gatherer.file.FileManager.Task#run()
138 */
139 public boolean calculateSize(FileNode[] files)
140 {
141 file_status.setText(Dictionary.get("FileActions.Calculating_Size"));
142 progress.setString(Dictionary.get("FileActions.Calculating_Size"));
143
144 // Calculate the total file size of all the selected file nodes
145 Vector remaining = new Vector();
146 for (int i = 0; !cancel_action && i < files.length; i++) {
147 remaining.add(files[i]);
148 }
149 while (!cancel_action && remaining.size() > 0) {
150 FileNode node = (FileNode) remaining.remove(0);
151 if (node.isLeaf()) {
152 progress.addMaximum(node.getFile().length());
153 }
154 else {
155 for (int i = 0; !cancel_action && i < node.getChildCount(); i++) {
156 remaining.add(node.getChildAt(i));
157 }
158 }
159 }
160
161 // Now we return if calculation was cancelled so that the FileManagers Task can skip the addJob phase correctly.
162 if (cancel_action) {
163 cancel_action = false; // reset
164 return true;
165 }
166 else {
167 return false;
168 }
169 }
170
171 /** This method is called to cancel the job queue at the next available moment. */
172 public void cancelAction() {
173 cancel_action = true;
174 }
175 /** Access to the file state label. */
176 public JLabel getFileStatus() {
177 return file_status;
178 }
179
180 /** Access to the progress bar. */
181 public LongProgressBar getProgressBar() {
182 return progress;
183 }
184 /** Prevent the progress bar updating momentarily, while its size is re-adjusted. */
185 public void pause() {
186 progress.setIndeterminate(true);
187 }
188
189
190 /** 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.
191 * @see org.greenstone.gatherer.Gatherer
192 * @see org.greenstone.gatherer.collection.CollectionManager
193 * @see org.greenstone.gatherer.file.FileJob
194 * @see org.greenstone.gatherer.file.FileNode
195 * @see org.greenstone.gatherer.gui.LongProgressBar
196 * @see org.greenstone.gatherer.util.Utility
197 */
198 public void run()
199 {
200 super.setName("FileQueue");
201
202 while (!Gatherer.self.exit) {
203 try {
204 // Retrieve the next job
205 int position = queue.size() - 1;
206 FileJob job = removeJob(position);
207 if (job != null) {
208 ///ystem.err.println("Found job: " + job);
209 // Enabled stop button
210 stop_button.setEnabled(true);
211 // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
212 boolean ready = true;
213 FileNode origin_node = job.getOrigin();
214 FileNode destination_node = job.getDestination();
215 FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
216 FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
217 if(destination_node == null) {
218 // 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.
219 destination_node = (FileNode) target_model.getRoot();
220 }
221 // Extract common job details.
222 File source_file = origin_node.getFile();
223 File target_file = null;
224 // Determine the target file for a copy or move.
225 if(job.type == FileJob.COPY || job.type == FileJob.MOVE) {
226 //target_file = new File(destination_node.getFile(), source_file.getName());
227 // use the name of the filenode instead of the name of the file - these should be the same except for the collection directories where we want the collection name to be used, not 'import' which is the underlying name
228 target_file = new File(destination_node.getFile(), origin_node.toString());
229 }
230 // 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.
231 if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
232 ///ystem.err.println("Copy/Move: " + origin_node);
233
234 // The number one thing to check is whether we are in a cyclic loop. The easiest way is just to check how deep we are
235 int max_folder_depth = Configuration.getInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC);
236 boolean continue_over_depth = false;
237 if(FileManager.countFolderDepth(source_file) > max_folder_depth) {
238 Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("General.No"), Dictionary.get("FileActions.Increase_Depth") };
239 String args[] = { String.valueOf(max_folder_depth), source_file.getAbsolutePath() };
240 int result = JOptionPane.showOptionDialog(Gatherer.g_man, Utility.formatHTMLWidth(Dictionary.get("FileActions.Possible_Cyclic_Path", args), 80), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[1]);
241 args = null;
242 options = null;
243 switch(result) {
244 case 0: // Yes
245 continue_over_depth = true;
246 break;
247 case 2: // Continue and increase depth
248 continue_over_depth = true;
249 Configuration.setInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC, (max_folder_depth + 1));
250 break;
251 }
252 }
253 else {
254 continue_over_depth = true;
255 }
256
257 if(continue_over_depth) {
258 FileNode new_node = null;
259 // 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).
260 if(target_file.exists()) {
261 // We've previously been told
262 if(yes_to_all) {
263 // Remove the old file and tree entry.
264 target_file.delete();
265 ready = true;
266 }
267 else {
268 ///atherer.println("Opps! This filename already exists. Give the user some options.");
269 Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
270 int result = JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Exists", target_file.getName()), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
271 switch(result) {
272 case 1: // Yes To All
273 yes_to_all = true;
274 case 0: // Yes
275 // Remove the old file and tree entry.
276 if(destination_node != null) {
277 TreePath destination_path = new TreePath(destination_node.getPath());
278 FileNode temp_target_node = new FileNode(target_file, target_model, true);
279 TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
280 SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
281 target_path = null;
282 temp_target_node = null;
283 destination_path = null;
284 }
285 target_file.delete();
286 ready = true;
287 break;
288 case 3: // No To All
289 cancel_action = true;
290 case 2: // No
291 default:
292 ready = false;
293 // Increment progress by size of potentially copied file
294 progress.addValue(origin_node.getFile().length());
295 }
296 }
297 }
298 // 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.
299 if(ready) {
300 // update status area
301 String args[] = new String[1];
302 args[0] = "" + (queue.size() + 1) + "";
303 if(job.type == FileJob.COPY) {
304 args[0] = Utility.formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
305 file_status.setText(Dictionary.get("FileActions.Copying", args));
306 }
307 else {
308 args[0] = Utility.formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
309 file_status.setText(Dictionary.get("FileActions.Moving", args));
310 }
311 args = null;
312
313 // If source is a file
314 if(source_file.isFile()) {
315 // 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?
316 try {
317 copyFile(source_file, target_file, progress);
318 }
319 // 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.
320 catch(FileNotFoundException fnf_exception) {
321 DebugStream.printStackTrace(fnf_exception);
322 cancel_action = true;
323 // Show warning.
324 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
325 // Force refresh of source folder.
326 source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
327 }
328 catch(FileAlreadyExistsException fae_exception) {
329 DebugStream.printStackTrace(fae_exception);
330 cancel_action = true;
331 // Show warning.
332 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Already_Exists_Message", target_file.getName()), Dictionary.get("FileActions.File_Already_Exists_Title"), JOptionPane.ERROR_MESSAGE);
333 // Nothing else can be done by the Gatherer.
334 }
335 catch(InsufficientSpaceException is_exception) {
336 DebugStream.printStackTrace(is_exception);
337 cancel_action = true;
338 // Show warning. The message body of the expection explains how much more space is required for this file copy.
339 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Insufficient_Space_Message", is_exception.getMessage()), Dictionary.get("FileActions.Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
340 // 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.
341 }
342 catch(UnknownFileErrorException ufe_exception) {
343 DebugStream.printStackTrace(ufe_exception);
344 cancel_action = true;
345 // Show warning
346 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Unknown_File_Error_Message"), Dictionary.get("FileActions.Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
347 // Nothing else we can do.
348 }
349 catch(WriteNotPermittedException wnp_exception) {
350 DebugStream.printStackTrace(wnp_exception);
351 cancel_action = true;
352 // Show warning
353 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Write_Not_Permitted_Message", target_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
354 // Nothing else we can do.
355 }
356 catch(IOException exception) {
357 // Can't really do much about this.
358 DebugStream.printStackTrace(exception);
359 }
360 // If not cancelled
361 if(!cancel_action) {
362 // Step one is to create a dummy FileNode. Its important it has the correct structure so getPath works.
363 FileNode new_record = new FileNode(target_file);
364 SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_record);
365 new_node = new_record;
366
367 // create undo job
368 if(job.undoable) {
369 job.undoable = false;
370 if(job.type == FileJob.COPY) {
371 // 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!
372 // Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, new_record, job.undo);
373 }
374 else {
375 // Movements however do need a source and source parent so the file can be moved back to the correct place.
376 // Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, new_record, job.undo);
377 }
378 }
379 new_record = null;
380 }
381 }
382 // Else
383 else if(source_file.isDirectory()) {
384 // create new record
385 FileNode directory_record = new FileNode(target_file);
386 ///ystem.err.println("Directory record = " + directory_record + " (" + target_file.getAbsolutePath() + ")");
387 SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
388 // Why is this not happening eh?
389 directory_record.setParent(destination_node);
390 if(!target_file.exists()) {
391 // make the directory
392 target_file.mkdirs();
393 new_node = directory_record;
394 // create undo job
395 if(job.undoable) {
396 job.undoable = false;
397 if(job.type == FileJob.COPY) {
398 // 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!
399 // Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_COPY, null, null, job.target, directory_record, job.undo);
400 }
401 else {
402 // Movements however do need a source and source parent so the file can be moved back to the correct place.
403 // Gatherer.c_man.undo.addUndo(job.ID(), UndoManager.FILE_MOVE, job.source, (FileNode)origin_node.getParent(), job.target, directory_record, job.undo);
404 }
405 }
406 }
407 // Else inform the users that a directory already exists and files will be copied into it
408 //else {
409 // JOptionPane.showMessageDialog(null, Dictionary.get("FileActions.Directory_Exists", target_file.toString()), Dictionary.get("General.Warning"), JOptionPane.WARNING_MESSAGE);
410 //}
411 // 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.
412 FileNode child_record = null;
413 // 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.
414 // -- Starting queue ...[a]
415 // remove(position) = 'a' ...
416 // add(position, 'b') ...[b]
417 // add(position, 'c') ...[c][b]
418 // add(position, 'd') ...[d][c][b]
419 // Next loop
420 // remove(position) = 'b' ...[d][c]
421 for(int i = 0; i < origin_node.getChildCount(); i++) {
422 child_record = (FileNode) origin_node.getChildAt(i);
423 addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, job.undo, false, false, position);
424 }
425 child_record = null;
426 directory_record = null;
427 }
428 // The file wasn't found!
429 else {
430 cancel_action = true;
431 // Show warning.
432 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Found_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Found_Title"), JOptionPane.ERROR_MESSAGE);
433 // Force refresh of source folder.
434 source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
435 }
436
437 // We can't have been cancelled, and we must have created a new FileNode during the above phase, before we can handle metadata.
438 if(!cancel_action && new_node != null) {
439 /* Time to handle any existing metadata. */
440 // If the directory came from inside our collection...
441 if (job.source.toString().equals("Collection")) {
442 ///ystem.err.println("Move within collection...");
443 // we just retrieve the metadata attached to the origin node...
444 ArrayList existing_metadata = Gatherer.c_man.getCollection().gdm.getMetadataOnly(source_file);
445 ///atherer.println("Existing metadata for " + origin_node + ": " + gdm.toString(existing_metadata));
446 // then assign this remainder to the new folder.
447 ///ystem.err.println("New metadata: " + gdm.toString(existing_metadata));
448 Gatherer.c_man.getCollection().gdm.addMetadata(new_node, existing_metadata);
449 existing_metadata = null;
450 }
451 // If it came from the recycle bin retrieve the metadata from there, once again remembering to account for inherited metadata
452// else if (job.source.toString().equals("Undo")) {
453// // Retrieve metadata from the recycle bin
454// ArrayList existing_metadata = Gatherer.c_man.undo.getMetadata(source_file);
455// // then assign this remainder to the new folder.
456// Gatherer.c_man.getCollection().gdm.addMetadata(new_node, existing_metadata);
457// existing_metadata = null;
458// }
459 // Otherwise if it came from the workspace use the MSMs parsers to search for folder level metadata (such as metadata.xml or marc records).
460 else if (job.source.toString().equals("Workspace")) {
461 cancel_action = !Gatherer.c_man.getCollection().msm.searchForMetadata(new_node, origin_node, job.folder_level);
462 }
463 }
464 new_node = null;
465 }
466 }
467 }
468 // 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.
469 if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
470 ///atherer.println("Delete/Move: " + origin_node);
471 ///atherer.println(queue.size() + " jobs remain in queue");
472
473 if (source_file.isFile()) {
474 progress.addValue(source_file.length());
475 }
476
477 // If the source is an empty directory or a file. Don't do anything to the root node of a tree.
478 File[] child_list = source_file.listFiles();
479 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)) {
480 ///atherer.println("File or empty directory.");
481 // Delete any metadata.xml still in the directory.
482 if(child_list != null && child_list.length == 1) {
483 child_list[0].delete();
484 }
485
486 ///atherer.println("Origin is file or is directory and is empty.");
487 // update status area
488 String args[] = new String[1];
489 // args[0] = "" + (queue.size() + 1) + "";
490 args[0] = Utility.formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
491 file_status.setText(Dictionary.get("FileActions.Deleting", args));
492 args = null;
493
494 // Remove its metadata
495 ArrayList metadatum = null;
496// if(job.source == Gatherer.c_man.undo) {
497// Gatherer.c_man.undo.addMetadata(target_file, metadatum);
498// }
499// else {
500 metadatum = Gatherer.c_man.getCollection().gdm.removeMetadata(origin_node.getFile());
501 // }
502 // determine its parent node
503 FileNode parent_record = (FileNode)origin_node.getParent();
504 // Remove from model
505 if(parent_record != null) {
506 SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
507 }
508 // If we are deleting
509 File recycled_file = null;
510 FileNode recycled_parent = null;
511 // delete the source file
512 Utility.delete(source_file);
513 }
514 // Else the source is a directory and it has children remaining
515 else if(child_list != null && child_list.length > 0) {
516 ///ystem.err.print("Nonempty directory -> ");
517 ///atherer.println("Directory is non-empty. Remove children first.");
518 FileNode recycle_folder_record = null;
519 // Don't worry about all this for true file move actions.
520 if(job.type == FileJob.DELETE) {
521 // 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.
522 ///ystem.err.println("Directory has " + origin_node.getChildCount() + " children.");
523 ///ystem.err.println("Directory actually has " + child_list.length + " children.");
524 origin_node.refresh();
525 ///atherer.println("Directory has " + origin_node.getChildCount() + " children.");
526 ///atherer.println("Directory actually has " + child_list.length + " children.");
527 for(int i = 0; i < origin_node.size(); i++) {
528 FileNode child_record = (FileNode) origin_node.get(i);
529 ///atherer.println("Queuing: " + child_record);
530 addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, job.undo, false, false, position);
531 //if(recycle_folder_record != null) {
532 // recycle_folder_mappings.put(child_record, recycle_folder_record);
533 //}
534 }
535 }
536 // 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.
537 // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
538 if(origin_node.getParent() != null && !job.done) {
539 ///atherer.println("Requeuing: " + origin_node.getFile().getAbsolutePath());
540 job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
541 addJob(job, position);
542 }
543 else {
544 DebugStream.println("I've already done this job twice. I refuse to requeue it again!!");
545 }
546 }
547 }
548 job = null;
549 source_file = null;
550 target_file = null;
551 origin_node = null;
552 // We can only break out of the while loop if we are out of files, or if the action was cancelled.
553 if(cancel_action) {
554 // Empty queue
555 clearJobs();
556 cancel_action = false;
557 }
558 // Debugging pause.
559 ///ystem.err.println("Job complete.");
560 }
561 else {
562 // Disable stop button
563 if(stop_button != null) {
564 stop_button.setEnabled(false);
565 }
566 synchronized(this) {
567 // Force both workspace and collection trees to refresh
568 if (Gatherer.g_man != null) {
569 Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
570 Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
571 }
572
573 // Reset status area
574 file_status.setText(Dictionary.get("FileActions.No_Activity"));
575 progress.reset();
576 progress.setString(Dictionary.get("FileActions.No_Activity"));
577 yes_to_all = false;
578 completed_folder_mappings.clear();
579 recycle_folder_mappings.clear();
580
581 // Reset whether we complain about no sets.
582 if(Gatherer.f_man != null) {
583 Gatherer.f_man.complain_if_no_sets = true;
584 }
585
586 // Now wait if applicable.
587 if(return_immediately) {
588 return;
589 }
590 ///ystem.err.println("Waiting");
591 wait();
592 }
593 }
594 }
595 catch (Exception error) {
596 DebugStream.printStackTrace(error);
597 }
598 }
599 }
600
601 /** Register the button that will be responsible for stopping executing file actions.
602 * @param stop_button a JButton
603 */
604 public void registerStopButton(JButton stop_button) {
605 this.stop_button = stop_button;
606 }
607
608 /** Called when the user makes some selection in one of the trees we are listening to. From this we update the status details. */
609 public void valueChanged(TreeSelectionEvent event) {
610 JTree tree = (JTree) event.getSource();
611 if(tree.getSelectionCount() > 0) {
612 TreePath selection[] = tree.getSelectionPaths();
613 int file_count = 0;
614 int dir_count = 0;
615 for(int i = 0; i < selection.length; i++) {
616 TreeNode record = (TreeNode) selection[i].getLastPathComponent();
617 if(record.isLeaf()) {
618 file_count++;
619 }
620 else {
621 dir_count++;
622 }
623 record = null;
624 }
625 selection = null;
626 }
627 tree = null;
628 }
629
630 synchronized private void clearJobs() {
631 queue.clear();
632 }
633
634 /** Copy the contents from the source directory to the destination
635 * directory.
636 * @param source The source directory
637 * @param destination The destination directory
638 * @param progress A progress bar to monitor copying progress
639 * @see org.greenstone.gatherer.Gatherer
640 */
641 public void copyDirectoryContents(File source, File destination, LongProgressBar progress)
642 throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, UnknownFileErrorException, WriteNotPermittedException
643 {
644 if (!source.isDirectory()) return;
645 // check that dest dirs exist
646 destination.mkdirs();
647
648 File [] src_files = source.listFiles();
649 if (src_files.length == 0) return; // nothing to copy
650 for (int i=0; i<src_files.length; i++) {
651 File f = src_files[i];
652 String f_name = f.getName();
653 File new_file = new File(destination, f_name);
654 if (f.isDirectory()) {
655 copyDirectoryContents(f, new_file, progress);
656 } else if (f.isFile()) {
657 copyFile(f, new_file, progress);
658 }
659 }
660
661 }
662
663 /** Copy a file from the source location to the destination location.
664 * @param source The source File.
665 * @param destination The destination File.
666 * @see org.greenstone.gatherer.Gatherer
667 */
668 public void copyFile(File source, File destination, LongProgressBar progress)
669 throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, UnknownFileErrorException, WriteNotPermittedException {
670 if(source.isDirectory()) {
671 destination.mkdirs();
672 }
673 else {
674 // Check if the origin file exists.
675 if(!source.exists()) {
676 DebugStream.println("Couldn't find the source file.");
677 throw(new FileNotFoundException());
678 }
679 // Check if the destination file does not exist.
680 if(destination.exists()) {
681 throw(new FileAlreadyExistsException());
682 }
683 File dirs = destination.getParentFile();
684 dirs.mkdirs();
685 // Copy the file.
686 FileInputStream f_in = new FileInputStream(source);
687 FileOutputStream f_out = null;
688 // This may throw a file not found exception, but this translates to a WriteNotPermittedException, in this case
689 try {
690 f_out = new FileOutputStream(destination);
691 }
692 catch (FileNotFoundException exception) {
693 throw new WriteNotPermittedException(exception.toString());
694 }
695 byte data[] = new byte[Utility.BUFFER_SIZE];
696 int data_size = 0;
697 while((data_size = f_in.read(data, 0, Utility.BUFFER_SIZE)) != -1 && !cancel_action) {
698 long destination_size = destination.length();
699 try {
700 f_out.write(data, 0, data_size);
701 }
702 // 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.
703 catch (IOException io_exception) {
704 if(destination_size + (long) data_size > destination.length()) {
705 // Determine the difference (which I guess is in bytes).
706 long difference = (destination_size + (long) data_size) - destination.length();
707 // Transform that into a human readable string.
708 String message = Utility.formatFileLength(difference);
709 throw(new InsufficientSpaceException(message));
710 }
711 else {
712 throw(io_exception);
713 }
714 }
715 if(progress != null) {
716 progress.addValue(data_size);
717 }
718 }
719 // Flush and close the streams to ensure all bytes are written.
720 f_in.close();
721 f_out.close();
722 // We have now, in theory, produced an exact copy of the source file. Check this by comparing sizes.
723 if(!destination.exists() || (!cancel_action && source.length() != destination.length())) {
724 throw(new UnknownFileErrorException());
725 }
726 // If we were cancelled, ensure that none of the destination file exists.
727 if(cancel_action) {
728 destination.delete();
729 }
730 }
731 }
732
733
734 private FileJob removeJob(int position) {
735 FileJob job = null;
736 if(queue.size() > 0) {
737 job = (FileJob) queue.remove(position);
738 }
739 return job;
740 }
741}
Note: See TracBrowser for help on using the repository browser.