source: tags/gsdl-2_70-distribution-branch-merged/gli/src/org/greenstone/gatherer/file/FileQueue.java@ 11818

Last change on this file since 11818 was 11818, checked in by (none), 18 years ago

This commit was manufactured by cvs2svn to create tag
'gsdl-2_70-distribution-branch-merged'.

  • Property svn:keywords set to Author Date Id Revision
File size: 51.8 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.collection.CollectionTreeNode;
39import org.greenstone.gatherer.gui.GProgressBar;
40import org.greenstone.gatherer.gui.tree.DragTree;
41import org.greenstone.gatherer.metadata.MetadataValue;
42import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
43import org.greenstone.gatherer.util.DragComponent;
44import org.greenstone.gatherer.util.StaticStrings;
45import org.greenstone.gatherer.util.SynchronizedTreeModelTools;
46import org.greenstone.gatherer.util.Utility;
47
48/** 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.
49 * @author John Thompson, Greenstone Digital Library, University of Waikato
50 * @version 2.3
51 */
52public class FileQueue
53 extends Thread
54{
55 /** When someone requests the movement queue to be dumped this cancel flag is set to true. */
56 private boolean cancel_action = false;
57 /** The button which controls the stopping of the file queue. */
58 private JButton stop_button = null;
59 /** true if the user has selected yes to all from a file 'clash' dialog. */
60 private boolean yes_to_all = false;
61 /** A label explaining the current moving files status. */
62 private JLabel file_status = null;
63 /** A list containing a queue of waiting movement jobs. */
64 private ArrayList queue = null;
65 /** A progress bar which shows how many bytes, out of the total size of bytes, has been moved. */
66 private GProgressBar progress = null;
67
68
69 /** Constructor.
70 */
71 public FileQueue() {
72 DebugStream.println("FileQueue started.");
73 this.queue = new ArrayList();
74 file_status = new JLabel();
75 Dictionary.setText(file_status, "FileActions.No_Activity");
76 progress = new GProgressBar();
77 progress.setBackground(Configuration.getColor("coloring.collection_tree_background", false));
78 progress.setForeground(Configuration.getColor("coloring.collection_tree_foreground", false));
79 progress.setString(Dictionary.get("FileActions.No_Activity"));
80 progress.setStringPainted(true);
81 }
82
83 /** Requeue an existing job into the queue.
84 * @param job A previously created FileJob.
85 */
86// synchronized private void addJob(FileJob job, int position) {
87// job.done = true; // Ensure that the requeued job is marked as done.
88// queue.add(position, job);
89// notify();
90// }
91
92 /** 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).
93 * @param id A long id unique to all jobs created by a single action.
94 * @param source The DragComponent source of this file, most likely a DragTree.
95 * @param child The FileNode you wish to mode.
96 * @param target The DragComponent to move the file to, again most likely a DragTree.
97 * @param parent The files new FileNode parent within the target.
98 * @param type The type of this movement as an int, either COPY or DELETE.
99 */
100 public void addJob(long id, DragComponent source, FileNode[] children, DragComponent target, FileNode parent, byte type)
101 {
102 // Queue the sub-job(s) (this may fail if we are asked to delete a read only file)
103 for (int i = 0; i < children.length; i++) {
104 addJob(id, source, children[i], target, parent, type, -1);
105 }
106 }
107
108 synchronized private void addJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type, int position) {
109 FileJob job = new FileJob(id, source, child, target, parent, type);
110 DebugStream.println("Adding job: " + job);
111 if(position != -1 && position <= queue.size() + 1) {
112 queue.add(position, job);
113 }
114 else {
115 queue.add(job);
116 }
117 notify();
118 }
119
120 /** Calculates the total deep file size of the selected file nodes.
121 * @param files a FileNode[] of selected files
122 * @return true if a cancel was signalled, false otherwise
123 * @see org.greenstone.gatherer.file.FileManager.Task#run()
124 */
125 public boolean calculateSize(FileNode[] files)
126 {
127 file_status.setText(Dictionary.get("FileActions.Calculating_Size"));
128 progress.setString(Dictionary.get("FileActions.Calculating_Size"));
129
130 // Calculate the total file size of all the selected file nodes
131 Vector remaining = new Vector();
132 for (int i = 0; !cancel_action && i < files.length; i++) {
133 remaining.add(files[i]);
134 }
135 while (!cancel_action && remaining.size() > 0) {
136 FileNode node = (FileNode) remaining.remove(0);
137 if (node.isLeaf()) {
138 progress.addMaximum(node.getFile().length());
139 }
140 else {
141 for (int i = 0; !cancel_action && i < node.getChildCount(); i++) {
142 remaining.add(node.getChildAt(i));
143 }
144 }
145 }
146
147 // Now we return if calculation was cancelled so that the FileManagers Task can skip the addJob phase correctly.
148 if (cancel_action) {
149 cancel_action = false; // reset
150 return true;
151 }
152 else {
153 return false;
154 }
155 }
156
157 /** This method is called to cancel the job queue at the next available moment. */
158 public void cancelAction() {
159 cancel_action = true;
160 clearJobs();
161 }
162
163
164// private int countFolderDepth(File file)
165// {
166// int depth = 0;
167// while (file != null) {
168// depth++;
169// file = file.getParentFile();
170// }
171// return depth;
172// }
173
174
175 /** Format the given filename path string so that it is no longer than the given width. If it is wider replace starting directories with ...
176 * @param key The key <strong>String</Strong> used to retrieve a phrase from the dictionary for this item.
177 * @param raw The raw filename path <strong>String</strong>.
178 * @param width The maximum width as an <i>int</i>.
179 * @return A path <strong>String</strong> no longer than width.
180 */
181 private String formatPath(String key, String raw, int width)
182 {
183 JLabel label = new JLabel(Dictionary.get(key, raw));
184 int position = -1;
185 while(label.getPreferredSize().width > width && (position = raw.indexOf(File.separator)) != -1) {
186 raw = "..." + raw.substring(position + 1);
187 label.setText(Dictionary.get(key, raw));
188 }
189 if(raw.indexOf(File.separator) == -1 && raw.startsWith("...")) {
190 raw = raw.substring(3);
191 }
192 return raw;
193 }
194
195
196 /** Access to the file state label. */
197 public JLabel getFileStatus() {
198 return file_status;
199 }
200
201 /** Access to the progress bar. */
202 public GProgressBar getProgressBar() {
203 return progress;
204 }
205
206
207 synchronized private void addFileJob(long id, DragComponent source, FileNode child, DragComponent target, FileNode parent, byte type)
208 {
209 queue.add(new FileJob(id, source, child, target, parent, type));
210 notify();
211 }
212
213
214 private void doEmptyDirectoryDelete(FileJob file_job)
215 {
216 FileNode source_node = file_job.getOrigin();
217 File source_directory = source_node.getFile();
218
219 // If the directory isn't empty then this will fail
220 if (source_directory.delete() == false) {
221 // The source directory couldn't be deleted, so give the user the option of continuing or cancelling
222 if (showErrorDialog(Dictionary.get("FileActions.Could_Not_Delete", source_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
223 clearJobs(); // Aborting action
224 }
225 return;
226 }
227
228 // Remove the node from the model
229 SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
230 }
231
232
233 private void doDirectoryDelete(FileJob file_job)
234 {
235 FileNode source_node = file_job.getOrigin();
236 File source_directory = source_node.getFile();
237
238 // The last thing we will do is delete this directory (which should be empty by then)
239 addFileJob(file_job.ID(), file_job.source, source_node, null, null, FileJob.DELETE_EMPTY_DIRECTORY);
240
241 // Add a new Delete job for each child of this directory (except metadata.xml files)
242 source_node.refresh();
243 for (int i = 0; i < source_node.size(); i++) {
244 FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
245 if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
246 addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
247 }
248 }
249
250 // Treat metadata.xml files specially: delete them first
251 for (int i = 0; i < source_node.size(); i++) {
252 FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
253 if (child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
254 addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
255 break;
256 }
257 }
258 }
259
260
261 private void doDirectoryCopy(FileJob file_job)
262 {
263 FileNode source_node = file_job.getOrigin();
264 FileNode target_node = file_job.getDestination();
265
266 File source_directory = source_node.getFile();
267 File target_directory = new File(target_node.getFile(), source_directory.getName());
268
269 // Check that the source directory doesn't contain the target directory (will create a cyclic loop)
270 if (target_directory.getAbsolutePath().startsWith(source_directory.getAbsolutePath())) {
271 if (showErrorDialog(Dictionary.get("FileActions.Cyclic_Path", source_directory.getName())) == JOptionPane.CANCEL_OPTION) {
272 clearJobs(); // Aborting action
273 }
274 return;
275 }
276
277 // The target directory shouldn't already exist
278 if (target_directory.exists()) {
279 if (showErrorDialog(Dictionary.get("FileActions.Folder_Already_Exists", target_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
280 clearJobs(); // Aborting action
281 }
282 return;
283 }
284 target_directory.mkdirs();
285
286 // Create a node for the new directory in the collection tree
287 FileSystemModel target_model = (FileSystemModel) file_job.target.getTreeModel();
288 CollectionTreeNode new_target_node = new CollectionTreeNode(target_directory);
289 SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
290 new_target_node.setParent(target_node);
291
292 // Copy the non-folder level metadata assigned to the original directory to the new directory
293 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(source_directory);
294 MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
295
296 // Add a new Copy job for each child of this directory (except metadata.xml files)
297 source_node.refresh();
298 for (int i = 0; i < source_node.size(); i++) {
299 FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
300 if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
301 addFileJob(file_job.ID(), file_job.source, child_file_node, file_job.target, new_target_node, FileJob.COPY);
302 }
303 }
304 }
305
306
307 private void doDirectoryMove(FileJob file_job)
308 {
309 FileNode source_node = file_job.getOrigin();
310 FileNode target_node = file_job.getDestination();
311
312 File source_directory = source_node.getFile();
313 File target_directory = new File(target_node.getFile(), source_directory.getName());
314 if (file_job.type == FileJob.RENAME) {
315 // This is the only difference between moves and renames
316 target_directory = target_node.getFile();
317 target_node = (FileNode) source_node.getParent();
318 }
319
320 // Check the target directory isn't the source directory
321 if (target_directory.equals(source_directory)) {
322 DebugStream.println("Target directory is the source directory!");
323 return;
324 }
325
326 // The target directory shouldn't already exist
327 if (target_directory.exists()) {
328 if (showErrorDialog(Dictionary.get("FileActions.Folder_Already_Exists", target_directory.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
329 clearJobs(); // Aborting action
330 }
331 return;
332 }
333 target_directory.mkdirs();
334
335 // Create a node for the new directory in the collection tree
336 FileSystemModel target_model = (FileSystemModel) file_job.target.getTreeModel();
337 CollectionTreeNode new_target_node = new CollectionTreeNode(target_directory);
338 SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
339 new_target_node.setParent(target_node);
340
341 // Move the folder level metadata assigned to the original directory to the new directory
342 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_directory);
343 MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
344 MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
345
346 // The last thing we will do is delete this directory
347 addFileJob(file_job.ID(), file_job.source, source_node, null, null, FileJob.DELETE);
348
349 // Treat metadata.xml files specially: delete them last
350 source_node.refresh();
351 for (int i = 0; i < source_node.size(); i++) {
352 FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
353 if (child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
354 addFileJob(file_job.ID(), file_job.source, child_file_node, null, null, FileJob.DELETE);
355 break;
356 }
357 }
358
359 // Add a new Move job for each child of this directory (except metadata.xml files)
360 for (int i = 0; i < source_node.size(); i++) {
361 FileNode child_file_node = (FileNode) source_node.getChildAtUnfiltered(i);
362 if (!child_file_node.getFile().getName().equals(StaticStrings.METADATA_XML)) {
363 addFileJob(file_job.ID(), file_job.source, child_file_node, file_job.target, new_target_node, FileJob.MOVE);
364 }
365 }
366 }
367
368
369 private void doFileDelete(FileJob file_job)
370 {
371 FileNode source_node = file_job.getOrigin();
372 File source_file = source_node.getFile();
373
374 // Almost all files will be deleted from the collection tree (exception: files in "Downloaded Files")
375 if (source_node instanceof CollectionTreeNode) {
376 // If we're deleting a metadata.xml file we must unload it
377 boolean metadata_xml_file = source_file.getName().equals(StaticStrings.METADATA_XML);
378 if (metadata_xml_file) {
379 MetadataXMLFileManager.unloadMetadataXMLFile(source_file);
380 }
381 // Otherwise remove any metadata assigned directly to the file
382 else {
383 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
384 MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
385 }
386 }
387
388 // Delete the source file
389 if (!Utility.delete(source_file)) {
390 // The source file couldn't be deleted, so give the user the option of continuing or cancelling
391 if (showErrorDialog(Dictionary.get("FileActions.File_Not_Deleted_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
392 clearJobs(); // Aborting action
393 }
394 return;
395 }
396
397 // Remove the node from the model
398 SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
399 }
400
401
402 private void doFileCopy(FileJob file_job)
403 {
404 FileNode source_node = file_job.getOrigin();
405 FileNode target_node = file_job.getDestination();
406
407 File source_file = source_node.getFile();
408 File target_file = new File(target_node.getFile(), source_file.getName());
409
410 // The target file shouldn't already exist -- if it does ask the user whether they want to overwrite
411 boolean overwrite_file = false;
412 if (target_file.exists()) {
413 int result = showOverwriteDialog(target_file.getName());
414 if (result == JOptionPane.NO_OPTION) {
415 // Don't overwrite
416 return;
417 }
418 if (result == JOptionPane.CANCEL_OPTION) {
419 clearJobs(); // Aborting action
420 return;
421 }
422
423 overwrite_file = true;
424 }
425
426 // Copy the file
427 try {
428 copyFile(source_file, target_file, true);
429 }
430 catch (FileAlreadyExistsException exception) {
431 // This should not ever happen, since we've called copyFile with overwrite set
432 DebugStream.printStackTrace(exception);
433 return;
434 }
435 catch (FileNotFoundException exception) {
436 DebugStream.printStackTrace(exception);
437 if (showErrorDialog(Dictionary.get("FileActions.File_Not_Found_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
438 clearJobs(); // Aborting action
439 }
440 // Refresh the source tree model
441 FileSystemModel source_model = file_job.source.getTreeModel();
442 source_model.refresh(new TreePath(((FileNode) file_job.getOrigin().getParent()).getPath()));
443 return;
444 }
445 catch (InsufficientSpaceException exception) {
446 DebugStream.printStackTrace(exception);
447 if (showErrorDialog(Dictionary.get("FileActions.Insufficient_Space_Message", exception.getMessage())) == JOptionPane.CANCEL_OPTION) {
448 clearJobs(); // Aborting action
449 }
450 return;
451 }
452 catch (IOException exception) {
453 DebugStream.printStackTrace(exception);
454 if (showErrorDialog(exception.getMessage()) == JOptionPane.CANCEL_OPTION) {
455 clearJobs(); // Aborting action
456 }
457 return;
458 }
459 catch (ReadNotPermittedException exception) {
460 DebugStream.printStackTrace(exception);
461 if (showErrorDialog(Dictionary.get("FileActions.Read_Not_Permitted_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
462 clearJobs(); // Aborting action
463 }
464 return;
465 }
466 catch (UnknownFileErrorException exception) {
467 DebugStream.printStackTrace(exception);
468 if (showErrorDialog(Dictionary.get("FileActions.Unknown_File_Error_Message")) == JOptionPane.CANCEL_OPTION) {
469 clearJobs(); // Aborting action
470 }
471 return;
472 }
473 catch (WriteNotPermittedException exception) {
474 DebugStream.printStackTrace(exception);
475 if (showErrorDialog(Dictionary.get("FileActions.Write_Not_Permitted_Message", target_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
476 clearJobs(); // Aborting action
477 }
478 return;
479 }
480
481 CollectionTreeNode new_target_node = new CollectionTreeNode(target_file);
482 if (overwrite_file == false) {
483 // Add the new node into the tree
484 FileSystemModel target_model = file_job.target.getTreeModel();
485 SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
486 }
487 Gatherer.c_man.fireFileAddedToCollection(target_file);
488
489 // Copy the non-folder level metadata assigned to the original file to the new file
490 if (file_job.type == FileJob.COPY) {
491 // do metadata too
492 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(source_file);
493 MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
494 }
495 }
496
497
498 private void doFileMove(FileJob file_job)
499 {
500 FileNode source_node = file_job.getOrigin();
501 FileNode target_node = file_job.getDestination();
502
503 File source_file = source_node.getFile();
504 File target_file = new File(target_node.getFile(), source_file.getName());
505 if (file_job.type == FileJob.RENAME) {
506 // This is the only difference between moves and renames
507 target_file = target_node.getFile();
508 target_node = (FileNode) source_node.getParent();
509 }
510
511 // Check the target file isn't the source file
512 if (target_file.equals(source_file)) {
513 DebugStream.println("Target file is the source file!");
514 return;
515 }
516
517 // The target file shouldn't already exist
518 if (target_file.exists()) {
519 int result = showOverwriteDialog(target_file.getName());
520 if (result == JOptionPane.NO_OPTION) {
521 // Don't overwrite
522 return;
523 }
524 if (result == JOptionPane.CANCEL_OPTION) {
525 clearJobs(); // Aborting action
526 return;
527 }
528 }
529
530 // Move the file by renaming it
531 if (!source_file.renameTo(target_file)) {
532 String args[] = { source_file.getName(), target_file.getAbsolutePath() };
533 if (showErrorDialog(Dictionary.get("FileActions.File_Move_Error_Message", args)) == JOptionPane.CANCEL_OPTION) {
534 clearJobs(); // Aborting action
535 }
536 return;
537 }
538
539 // Remove the node from the source model and add it to the target model
540 SynchronizedTreeModelTools.removeNodeFromParent(file_job.source.getTreeModel(), source_node);
541 CollectionTreeNode new_target_node = new CollectionTreeNode(target_file);
542 FileSystemModel target_model = file_job.target.getTreeModel();
543 SynchronizedTreeModelTools.insertNodeInto(target_model, target_node, new_target_node);
544
545 // Move the non-folder level metadata assigned to the original file to the new file
546 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
547 MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
548 MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
549 }
550
551
552 /** all this does is move the metadata, and delete the source */
553 private void doFileReplace(FileJob file_job)
554 {
555 FileNode source_node = file_job.getOrigin();
556 FileNode target_node = file_job.getDestination();
557
558 File source_file = source_node.getFile();
559 File target_file = target_node.getFile();
560
561 // Move the non-folder level metadata assigned to the original file to the new file
562 CollectionTreeNode new_target_node = new CollectionTreeNode(target_file);
563 ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
564 MetadataXMLFileManager.removeMetadata((CollectionTreeNode) source_node, assigned_metadata);
565 MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_target_node, assigned_metadata);
566
567 // now delete the original
568 doFileDelete(file_job);
569 }
570
571
572 private void processFileJob(FileJob file_job)
573 {
574 DebugStream.println("Processing file job " + file_job + "...");
575
576 // Ensure that the source file exists
577 File source_file = file_job.getOrigin().getFile();
578 if (!source_file.exists()) {
579 // The source file doesn't exist, so give the user the option of continuing or cancelling
580 if (showErrorDialog(Dictionary.get("FileActions.File_Not_Found_Message", source_file.getAbsolutePath())) == JOptionPane.CANCEL_OPTION) {
581 clearJobs(); // Aborting action
582 }
583 // Refresh the source tree model
584 FileSystemModel source_model = file_job.source.getTreeModel();
585 source_model.refresh(new TreePath(((FileNode) file_job.getOrigin().getParent()).getPath()));
586 return;
587 }
588
589 // Enable the "Stop" button
590 stop_button.setEnabled(true);
591
592 // Delete empty directory job
593 if (file_job.type == FileJob.DELETE_EMPTY_DIRECTORY) {
594 file_status.setText(Dictionary.get("FileActions.Deleting", formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width)));
595 doEmptyDirectoryDelete(file_job);
596 return;
597 }
598
599 // Delete job
600 if (file_job.type == FileJob.DELETE) {
601 file_status.setText(Dictionary.get("FileActions.Deleting", formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width)));
602 if (source_file.isFile()) {
603 long source_file_size = source_file.length();
604 doFileDelete(file_job);
605 progress.addValue(source_file_size); // Update progress bar
606 }
607 else {
608 doDirectoryDelete(file_job);
609 }
610 return;
611 }
612
613 // Copy job
614 if (file_job.type == FileJob.COPY || file_job.type == FileJob.COPY_FILE_ONLY) {
615 file_status.setText(Dictionary.get("FileActions.Copying", formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width)));
616 if (source_file.isFile()) {
617 long source_file_size = source_file.length();
618 doFileCopy(file_job);
619 progress.addValue(source_file_size); // Update progress bar
620 }
621 else {
622 doDirectoryCopy(file_job);
623 }
624 return;
625 }
626
627 // Move (or rename) job
628 if (file_job.type == FileJob.MOVE || file_job.type == FileJob.RENAME) {
629 file_status.setText(Dictionary.get("FileActions.Moving", formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width)));
630 if (source_file.isFile()) {
631 long source_file_size = source_file.length();
632 doFileMove(file_job);
633 progress.addValue(source_file_size); // Update progress bar
634 }
635 else {
636 doDirectoryMove(file_job);
637 }
638 return;
639 }
640
641 // Replace job
642 if (file_job.type == FileJob.REPLACE) {
643 file_status.setText(Dictionary.get("FileActions.Replacing", formatPath("FileActions.Replacing", source_file.getAbsolutePath(), file_status.getSize().width)));
644 doFileReplace(file_job);
645 return;
646 }
647 }
648
649
650 private int showErrorDialog(String error_message)
651 {
652 Object[] options = { Dictionary.get("General.OK"), Dictionary.get("General.Cancel") };
653 int result = JOptionPane.showOptionDialog(Gatherer.g_man, error_message, Dictionary.get("General.Error"), JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE, null, options, options[0]);
654 if (result == 0) {
655 return JOptionPane.OK_OPTION;
656 }
657 else {
658 return JOptionPane.CANCEL_OPTION;
659 }
660 }
661
662
663 private int showOverwriteDialog(String target_file_name)
664 {
665 // Has "yes to all" been set?
666 if (yes_to_all) {
667 return JOptionPane.YES_OPTION;
668 }
669
670 Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
671 int result = JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Exists", target_file_name), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
672 if (result == 0) {
673 return JOptionPane.YES_OPTION;
674 }
675 else if (result == 1) {
676 yes_to_all = true;
677 return JOptionPane.YES_OPTION;
678 }
679 else if (result == 2) {
680 return JOptionPane.NO_OPTION;
681 }
682 else {
683 return JOptionPane.CANCEL_OPTION;
684 }
685 }
686
687
688 public void run()
689 {
690 super.setName("FileQueue");
691
692 while (!Gatherer.exit) {
693 // Retrieve the next job
694 int position = queue.size() - 1;
695 if (position >= 0) {
696 // We have a file job, so process it
697 processFileJob((FileJob) queue.remove(position));
698 }
699 else {
700 // No jobs, so reset and wait until we are notified of one
701 synchronized(this) {
702 // Force both workspace and collection trees to refresh
703 if (Gatherer.g_man != null && Gatherer.c_man.ready()) {
704 Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
705 // Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
706 }
707
708 // Reset status area
709 file_status.setText(Dictionary.get("FileActions.No_Activity"));
710 progress.reset();
711 progress.setString(Dictionary.get("FileActions.No_Activity"));
712
713 // Reset "yes to all" and "cancel" flags
714 yes_to_all = false;
715 cancel_action = false;
716
717 // Wait for a new file job
718 try {
719 wait();
720 }
721 catch (InterruptedException exception) {}
722 }
723 }
724 }
725 }
726
727
728 /** 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.
729 * @see org.greenstone.gatherer.Gatherer
730 * @see org.greenstone.gatherer.collection.CollectionManager
731 * @see org.greenstone.gatherer.file.FileJob
732 * @see org.greenstone.gatherer.file.FileNode
733 * @see org.greenstone.gatherer.gui.GProgressBar
734 * @see org.greenstone.gatherer.util.Utility
735 */
736// public void run()
737// {
738// super.setName("FileQueue");
739
740// while (!Gatherer.exit) {
741// try {
742// // Retrieve the next job
743// int position = queue.size() - 1;
744// FileJob job = null;
745// if (position >= 0) {
746// job = (FileJob) queue.remove(position);
747// }
748
749// if (job != null) {
750// ///ystem.err.println("Found job: " + job);
751// // Enabled stop button
752// stop_button.setEnabled(true);
753// // The user can cancel this individual action at several places, so keep track if the state is 'ready' for the next step.
754// boolean ready = true;
755// FileNode origin_node = job.getOrigin();
756// FileNode destination_node = job.getDestination();
757// FileSystemModel source_model = (FileSystemModel)job.source.getTreeModel();
758// FileSystemModel target_model = (FileSystemModel)job.target.getTreeModel();
759// if(destination_node == null) {
760// // 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.
761// destination_node = (FileNode) target_model.getRoot();
762// }
763
764// // Extract common job details.
765// File source_file = origin_node.getFile();
766// File target_file = null;
767// // Determine the target file for a copy or move.
768// if (job.type == FileJob.COPY || job.type == FileJob.MOVE) {
769// // 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
770// target_file = new File(destination_node.getFile(), origin_node.toString());
771// }
772// // 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.
773// if((job.type == FileJob.COPY || job.type == FileJob.MOVE) && !job.done) {
774// ///ystem.err.println("Copy/Move: " + origin_node);
775
776// // 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
777// int max_folder_depth = Configuration.getInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC);
778// boolean continue_over_depth = false;
779// if (countFolderDepth(source_file) > max_folder_depth) {
780// Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("General.No"), Dictionary.get("FileActions.Increase_Depth") };
781// String args[] = { String.valueOf(max_folder_depth), source_file.getAbsolutePath() };
782// 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]);
783// args = null;
784// options = null;
785// switch(result) {
786// case 0: // Yes
787// continue_over_depth = true;
788// break;
789// case 2: // Continue and increase depth
790// continue_over_depth = true;
791// Configuration.setInt("general.max_folder_depth", Configuration.COLLECTION_SPECIFIC, (max_folder_depth + 1));
792// break;
793// }
794// }
795// else {
796// continue_over_depth = true;
797// }
798
799// if(continue_over_depth) {
800// FileNode new_node = null;
801// // 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).
802// if(target_file.exists()) {
803// // We've previously been told
804// if(yes_to_all) {
805// // Remove the old file and tree entry.
806// target_file.delete();
807// ready = true;
808// }
809// else {
810// ///atherer.println("Opps! This filename already exists. Give the user some options.");
811// Object[] options = { Dictionary.get("General.Yes"), Dictionary.get("FileActions.Yes_To_All"), Dictionary.get("General.No"), Dictionary.get("General.Cancel") };
812// 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]);
813// switch(result) {
814// case 1: // Yes To All
815// yes_to_all = true;
816// case 0: // Yes
817// // Remove the old file and tree entry.
818// if(destination_node != null) {
819// TreePath destination_path = new TreePath(destination_node.getPath());
820// CollectionTreeNode temp_target_node = new CollectionTreeNode(target_file); // !!! , target_model, true);
821// TreePath target_path = destination_path.pathByAddingChild(temp_target_node);
822// SynchronizedTreeModelTools.removeNodeFromParent(target_model, target_model.getNode(target_path));
823// target_path = null;
824// temp_target_node = null;
825// destination_path = null;
826// }
827// target_file.delete();
828// ready = true;
829// break;
830// case 3: // No To All
831// cancel_action = true;
832// case 2: // No
833// default:
834// ready = false;
835// // Increment progress by size of potentially copied file
836// progress.addValue(origin_node.getFile().length());
837// }
838// }
839// }
840// // 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.
841// if(ready) {
842// // update status area
843// String args[] = new String[1];
844// args[0] = "" + (queue.size() + 1) + "";
845// if(job.type == FileJob.COPY) {
846// args[0] = formatPath("FileActions.Copying", source_file.getAbsolutePath(), file_status.getSize().width);
847// file_status.setText(Dictionary.get("FileActions.Copying", args));
848// }
849// else {
850// args[0] = formatPath("FileActions.Moving", source_file.getAbsolutePath(), file_status.getSize().width);
851// file_status.setText(Dictionary.get("FileActions.Moving", args));
852// }
853// args = null;
854
855// // If source is a file
856// if(source_file.isFile()) {
857// // 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?
858// try {
859// copyFile(source_file, target_file, false);
860// progress.addValue(source_file.length());
861// }
862// // 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.
863// catch(FileNotFoundException fnf_exception) {
864// DebugStream.printStackTrace(fnf_exception);
865// cancel_action = true;
866// // Show warning.
867// 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);
868// // Force refresh of source folder.
869// source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
870// }
871// catch(FileAlreadyExistsException fae_exception) {
872// DebugStream.printStackTrace(fae_exception);
873// cancel_action = true;
874// // Show warning.
875// 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);
876// // Nothing else can be done by the Gatherer.
877// }
878// catch(InsufficientSpaceException is_exception) {
879// DebugStream.printStackTrace(is_exception);
880// cancel_action = true;
881// // Show warning. The message body of the expection explains how much more space is required for this file copy.
882// JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Insufficient_Space_Message", is_exception.getMessage()), Dictionary.get("FileActions.Insufficient_Space_Title"), JOptionPane.ERROR_MESSAGE);
883// // 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.
884// }
885// catch (ReadNotPermittedException rnp_exception) {
886// if (DebugStream.isDebuggingEnabled()) {
887// DebugStream.printStackTrace(rnp_exception);
888// }
889// cancel_action = true;
890// // Show warning
891// JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Read_Not_Permitted_Message", source_file.getAbsolutePath()), Dictionary.get("FileActions.Write_Not_Permitted_Title"), JOptionPane.ERROR_MESSAGE);
892// // Nothing else we can do.
893// }
894// catch(UnknownFileErrorException ufe_exception) {
895// DebugStream.printStackTrace(ufe_exception);
896// cancel_action = true;
897// // Show warning
898// JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.Unknown_File_Error_Message"), Dictionary.get("FileActions.Unknown_File_Error_Title"), JOptionPane.ERROR_MESSAGE);
899// // Nothing else we can do.
900// }
901// catch(WriteNotPermittedException wnp_exception) {
902// if (DebugStream.isDebuggingEnabled()) {
903// DebugStream.printStackTrace(wnp_exception);
904// }
905// cancel_action = true;
906// // Show warning
907// 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);
908// // Nothing else we can do.
909// }
910// catch(IOException exception) {
911// // Can't really do much about this.
912// DebugStream.printStackTrace(exception);
913// }
914// // If not cancelled
915// if (!cancel_action) {
916// // Create a dummy FileNode with the correct structure (so getPath works)
917// new_node = new CollectionTreeNode(target_file);
918// SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, new_node);
919// }
920// }
921// // Else
922// else if(source_file.isDirectory()) {
923// // create new record
924// CollectionTreeNode directory_record = new CollectionTreeNode(target_file);
925// SynchronizedTreeModelTools.insertNodeInto(target_model, destination_node, directory_record);
926// // Why is this not happening eh?
927// directory_record.setParent(destination_node);
928// if(!target_file.exists()) {
929// // make the directory
930// target_file.mkdirs();
931// new_node = directory_record;
932// }
933
934// // 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.
935// FileNode child_record = null;
936// // 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.
937// // -- Starting queue ...[a]
938// // remove(position) = 'a' ...
939// // add(position, 'b') ...[b]
940// // add(position, 'c') ...[c][b]
941// // add(position, 'd') ...[d][c][b]
942// // Next loop
943// // remove(position) = 'b' ...[d][c]
944// //for(int i = 0; i < origin_node.getChildCount(); i++) {
945// for (int i=origin_node.getChildCount()-1; i>=0; i--) {
946// child_record = (FileNode) origin_node.getChildAt(i);
947// addJob(job.ID(), job.source, child_record, job.target, directory_record, job.type, false, position);
948// }
949// child_record = null;
950// directory_record = null;
951// }
952// // The file wasn't found!
953// else {
954// cancel_action = true;
955// // Show warning.
956// 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);
957// // Force refresh of source folder.
958// source_model.refresh(new TreePath(((FileNode)origin_node.getParent()).getPath()));
959// }
960
961// // If we haven't been cancelled and we created a new FileNode during the above phase, now is the time to deal with metadata
962// if (!cancel_action && new_node != null) {
963// // If the file came from inside our collection...
964// if (job.source.toString().equals("Collection")) {
965// // Get the non-folder level metadata assigned to the origin node...
966// ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(source_file);
967// // ...and remove it from the original node and assign it to the new folder
968// MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
969// MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
970// }
971// // If it came from the workspace search for metadata assigned to the file
972// else if (job.source.toString().equals("Workspace")) {
973// ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToExternalFile(origin_node.getFile());
974// MetadataXMLFileManager.addMetadata((CollectionTreeNode) new_node, assigned_metadata);
975// }
976
977// if (job.type == FileJob.COPY && new_node.getFile().isFile()) {
978// Gatherer.c_man.fireFileAddedToCollection(new_node.getFile());
979// }
980// }
981// new_node = null;
982// }
983// }
984// }
985// // 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.
986// if(!cancel_action && ready && (job.type == FileJob.DELETE || job.type == FileJob.MOVE)) {
987// // Update the progress bar for this job
988// if (source_file.isFile()) {
989// progress.addValue(source_file.length());
990// }
991
992// // If the source is a file or an empty directory (but not the root node of a tree)
993// File[] child_list = source_file.listFiles();
994// if (source_file.isFile() || (child_list != null && child_list.length == 0 && origin_node.getParent() != null)) {
995// // Update status area
996// String args[] = new String[1];
997// args[0] = formatPath("FileActions.Deleting", source_file.getAbsolutePath(), file_status.getSize().width);
998// file_status.setText(Dictionary.get("FileActions.Deleting", args));
999
1000// // If it is a metadata.xml file, we must unload it
1001// if (source_file.getName().equals(StaticStrings.METADATA_XML)) {
1002// MetadataXMLFileManager.unloadMetadataXMLFile(source_file);
1003// }
1004
1005// // Remove the metadata assigned directly to the file
1006// ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedDirectlyToFile(origin_node.getFile());
1007// MetadataXMLFileManager.removeMetadata((CollectionTreeNode) origin_node, assigned_metadata);
1008
1009// // Remove from model
1010// FileNode parent_record = (FileNode) origin_node.getParent();
1011// if (parent_record != null) {
1012// SynchronizedTreeModelTools.removeNodeFromParent(source_model, origin_node);
1013// }
1014
1015// // Delete the source file
1016// if (!Utility.delete(source_file)) {
1017// // Show message that we couldn't delete
1018// JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.File_Not_Deleted_Message", source_file.getName()), Dictionary.get("FileActions.File_Not_Deleted_Title"), JOptionPane.ERROR_MESSAGE);
1019// }
1020// }
1021// // Else the source is a directory and it has children remaining
1022// else if(child_list != null && child_list.length > 0) {
1023// // Don't worry about all this for true file move actions.
1024// if(job.type == FileJob.DELETE) {
1025// // 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.
1026// origin_node.refresh();
1027// for(int i = 0; i < origin_node.size(); i++) {
1028// FileNode child_record = (FileNode) origin_node.getChildAtUnfiltered(i);
1029// ///atherer.println("Queuing: " + child_record);
1030// addJob(job.ID(), job.source, child_record, job.target, destination_node, FileJob.DELETE, false, position);
1031// }
1032// }
1033// // 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.
1034// // One special case. Do not requeue root nodes. Don't requeue jobs marked as done.
1035// if(origin_node.getParent() != null && !job.done) {
1036// ///atherer.println("Requeuing: " + origin_node.getFile().getAbsolutePath());
1037// job.type = FileJob.DELETE; // You only requeue jobs that are deletes, as directories must be inspected before children, but deleted after.
1038// addJob(job, position);
1039// }
1040// else {
1041// DebugStream.println("I've already done this job twice. I refuse to requeue it again!");
1042// }
1043// }
1044// }
1045// job = null;
1046// source_file = null;
1047// target_file = null;
1048// origin_node = null;
1049
1050// // We only break out of the while loop if we are out of files or the action was cancelled
1051// if (cancel_action) {
1052// // Empty queue
1053// clearJobs();
1054// cancel_action = false;
1055// }
1056// }
1057// else { // job == null
1058// // Disable stop button
1059// if (stop_button != null) {
1060// stop_button.setEnabled(false);
1061// }
1062// synchronized(this) {
1063// // Force both workspace and collection trees to refresh
1064// if (Gatherer.g_man != null) {
1065// Gatherer.g_man.refreshWorkspaceTree(DragTree.COLLECTION_CONTENTS_CHANGED);
1066// Gatherer.g_man.refreshCollectionTree(DragTree.COLLECTION_CONTENTS_CHANGED);
1067// }
1068
1069// // Reset status area
1070// file_status.setText(Dictionary.get("FileActions.No_Activity"));
1071// progress.reset();
1072// progress.setString(Dictionary.get("FileActions.No_Activity"));
1073// yes_to_all = false;
1074// try {
1075// wait();
1076// }
1077// catch (InterruptedException exception) {}
1078// }
1079// }
1080// }
1081// catch (Exception error) {
1082// DebugStream.printStackTrace(error);
1083// }
1084// }
1085// }
1086
1087
1088 /** Register the button that will be responsible for stopping executing file actions.
1089 * @param stop_button a JButton
1090 */
1091 public void registerStopButton(JButton stop_button) {
1092 this.stop_button = stop_button;
1093 }
1094
1095
1096 synchronized private void clearJobs() {
1097 queue.clear();
1098 }
1099
1100 /** Copy the contents from the source directory to the destination
1101 * directory.
1102 * @param source The source directory
1103 * @param destination The destination directory
1104 * @see org.greenstone.gatherer.Gatherer
1105 */
1106 public void copyDirectoryContents(File source, File destination)
1107 throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, ReadNotPermittedException, UnknownFileErrorException, WriteNotPermittedException
1108 {
1109 if (!source.isDirectory()) return;
1110 // check that dest dirs exist
1111 destination.mkdirs();
1112
1113 File [] src_files = source.listFiles();
1114 if (src_files.length == 0) return; // nothing to copy
1115 for (int i=0; i<src_files.length; i++) {
1116 File f = src_files[i];
1117 String f_name = f.getName();
1118 File new_file = new File(destination, f_name);
1119 if (f.isDirectory()) {
1120 copyDirectoryContents(f, new_file);
1121 } else if (f.isFile()) {
1122 copyFile(f, new_file, false);
1123 }
1124 }
1125 }
1126
1127
1128 /** Copy a file from the source location to the destination location.
1129 * @param source The source File.
1130 * @param destination The destination File.
1131 * @see org.greenstone.gatherer.Gatherer
1132 */
1133 public void copyFile(File source, File destination, boolean overwrite)
1134 throws FileAlreadyExistsException, FileNotFoundException, InsufficientSpaceException, IOException, ReadNotPermittedException, UnknownFileErrorException, WriteNotPermittedException
1135 {
1136 if (source.isDirectory()) {
1137 destination.mkdirs();
1138 return;
1139 }
1140
1141 // Check if the origin file exists.
1142 if (!source.exists()) {
1143 DebugStream.println("Couldn't find the source file.");
1144 throw new FileNotFoundException();
1145 }
1146
1147 // Make sure the destination file does not exist.
1148 if (destination.exists() && !overwrite) {
1149 throw new FileAlreadyExistsException();
1150 }
1151
1152 // Open an input stream to the source file
1153 FileInputStream f_in = null;
1154 try {
1155 f_in = new FileInputStream(source);
1156 }
1157 catch (FileNotFoundException exception) {
1158 // A FileNotFoundException translates into a ReadNotPermittedException in this case
1159 throw new ReadNotPermittedException(exception.toString());
1160 }
1161
1162 // Create an necessary directories for the target file
1163 File dirs = destination.getParentFile();
1164 dirs.mkdirs();
1165
1166 // Open an output stream to the target file
1167 FileOutputStream f_out = null;
1168 try {
1169 f_out = new FileOutputStream(destination);
1170 }
1171 catch (FileNotFoundException exception) {
1172 // A FileNotFoundException translates into a WriteNotPermittedException in this case
1173 throw new WriteNotPermittedException(exception.toString());
1174 }
1175
1176 // Copy the file
1177 byte data[] = new byte[Utility.BUFFER_SIZE];
1178 int data_size = 0;
1179 while ((data_size = f_in.read(data, 0, Utility.BUFFER_SIZE)) != -1 && !cancel_action) {
1180 long destination_size = destination.length();
1181 try {
1182 f_out.write(data, 0, data_size);
1183 }
1184 // 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.
1185 catch (IOException io_exception) {
1186 if (destination_size + (long) data_size > destination.length()) {
1187 // Determine the difference (which I guess is in bytes).
1188 long difference = (destination_size + (long) data_size) - destination.length();
1189 // Transform that into a human readable string.
1190 String message = Utility.formatFileLength(difference);
1191 throw new InsufficientSpaceException(message);
1192 }
1193 else {
1194 throw(io_exception);
1195 }
1196 }
1197 }
1198
1199 // Flush and close the streams to ensure all bytes are written.
1200 f_in.close();
1201 f_out.close();
1202
1203 // We have now, in theory, produced an exact copy of the source file. Check this by comparing sizes.
1204 if(!destination.exists() || (!cancel_action && source.length() != destination.length())) {
1205 throw new UnknownFileErrorException();
1206 }
1207
1208 // If we were cancelled, ensure that none of the destination file exists.
1209 if (cancel_action) {
1210 destination.delete();
1211 }
1212 }
1213}
Note: See TracBrowser for help on using the repository browser.