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

Last change on this file since 11085 was 11085, checked in by mdewsnip, 18 years ago

Slaughtered the behemoth FileQueue.run() function. This was 500 lines of mess, with blocks of code nested 10 deep in some places, and was impossible to understand or maintain. Completely rewritten -- likely to be a few bugs for a little while, but these should soon be ironed out.

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