001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.job;
020
021 import com.mucommander.file.AbstractFile;
022 import com.mucommander.file.AbstractRWArchiveFile;
023 import com.mucommander.file.util.FileSet;
024 import com.mucommander.text.Translator;
025 import com.mucommander.ui.dialog.file.FileCollisionDialog;
026 import com.mucommander.ui.dialog.file.ProgressDialog;
027 import com.mucommander.ui.dialog.file.RenameDialog;
028 import com.mucommander.ui.main.MainFrame;
029
030 import java.io.IOException;
031
032 /**
033 * This class is the parent class of {@link com.mucommander.job.CopyJob} and {@link com.mucommander.job.MoveJob} and
034 * allows them to share methods and fields.
035 *
036 * @author Maxence Bernard, Mariusz Jakubowski
037 * @see com.mucommander.job.CopyJob
038 * @see com.mucommander.job.MoveJob
039 */
040 public abstract class AbstractCopyJob extends TransferFileJob {
041
042 /** Base destination folder */
043 protected AbstractFile baseDestFolder;
044
045 /** New filename in destination */
046 protected String newName;
047
048 /** Default choice when encountering an existing file */
049 protected int defaultFileExistsAction = FileCollisionDialog.ASK_ACTION;
050
051 /** Title used for error dialogs */
052 protected String errorDialogTitle;
053
054 protected boolean append;
055
056 /** The archive that contains the destination files (may be null) */
057 protected AbstractRWArchiveFile archiveToOptimize;
058
059 /** True when an archive is being optimized */
060 protected boolean isOptimizingArchive;
061
062 /**
063 * Creates a new <code>AbstractCopyJob</code>.
064 *
065 * @param progressDialog dialog which shows this job's progress
066 * @param mainFrame mainFrame this job has been triggered by
067 * @param files files which are going to be copied
068 * @param destFolder destination folder where the files will be copied
069 * @param newName the new filename in the destination folder, can be <code>null</code> in which case the original filename will be used.
070 * @param fileExistsAction default action to be performed when a file already exists in the destination, see {@link com.mucommander.ui.dialog.file.FileCollisionDialog} for allowed values
071 */
072 public AbstractCopyJob(ProgressDialog progressDialog, MainFrame mainFrame,
073 FileSet files, AbstractFile destFolder, String newName, int fileExistsAction) {
074 super(progressDialog, mainFrame, files);
075
076 this.baseDestFolder = destFolder;
077 this.newName = newName;
078 this.defaultFileExistsAction = fileExistsAction;
079 }
080
081 /**
082 * Creates a destination file given a destination folder and a new file name.
083 * @param destFolder a destination folder
084 * @param destFileName a destination file name
085 * @return the destination file or null if it cannot be created
086 */
087 protected AbstractFile createDestinationFile(AbstractFile destFolder,
088 String destFileName) {
089 AbstractFile destFile;
090 do { // Loop for retry
091 try {
092 destFile = destFolder.getDirectChild(destFileName);
093 break;
094 }
095 catch(IOException e) {
096 // Destination file couldn't be instanciated
097
098 int ret = showErrorDialog(errorDialogTitle, Translator.get("cannot_write_file", destFileName));
099 // Retry loops
100 if(ret==RETRY_ACTION)
101 continue;
102 // Cancel or close dialog return false
103 return null;
104 // Skip continues
105 }
106 } while(true);
107 return destFile;
108 }
109
110 /**
111 * Checks if there is a file collision (file exists in the destination).
112 * If there is no collision this method returns destFile.
113 * If there is a collision this method returns: <ul>
114 * <li>null if a user cancelled the transfer
115 * <li>null if a user skipped the file
116 * <li>destFile if a user resumed the transfer (and sets append flag)
117 * <li>destFile if a user has chosen to overwrite the file
118 * <li>new file if a user renamed the file
119 * </ul>
120 * @param file a source file
121 * @param destFolder a destination folder
122 * @param destFile a destination file
123 * @param allowCaseVariation if true,
124 * @return destFile the new destination file
125 */
126 protected AbstractFile checkForCollision(AbstractFile file, AbstractFile destFolder, AbstractFile destFile, boolean allowCaseVariation) {
127 append = false;
128 while (true) {
129 // Check for file collisions (file exists in the destination, destination subfolder of source, ...)
130 // if a default action hasn't been specified
131 int collision = FileCollisionChecker.checkForCollision(file, destFile);
132
133 // If allowCaseVariation is true and both files are equal, test if the destination filename is a variation
134 // of the original filename with a different case. If that is the case, do not warn about the source and
135 // destination being the same.
136 if(allowCaseVariation && collision==FileCollisionChecker.SAME_SOURCE_AND_DESTINATION) {
137 String sourceFileName = file.getName();
138 String destFileName = destFile.getName();
139 if(sourceFileName.equalsIgnoreCase(destFileName) && !sourceFileName.equals(destFileName))
140 break;
141 }
142
143 // Handle collision, asking the user what to do or using a default action to resolve the collision
144 if(collision != FileCollisionChecker.NO_COLLOSION) {
145 int choice;
146 // Use default action if one has been set, if not show up a dialog
147 if(defaultFileExistsAction==FileCollisionDialog.ASK_ACTION) {
148 FileCollisionDialog dialog = new FileCollisionDialog(progressDialog, mainFrame, collision, file, destFile, true, true);
149 choice = waitForUserResponse(dialog);
150 // If 'apply to all' was selected, this choice will be used for any other files (user will not be asked again)
151 if(dialog.applyToAllSelected())
152 defaultFileExistsAction = choice;
153 }
154 else
155 choice = defaultFileExistsAction;
156
157 // Cancel, skip or close dialog
158 if (choice==-1 || choice== FileCollisionDialog.CANCEL_ACTION) {
159 interrupt();
160 return null;
161 }
162 // Skip file
163 else if (choice== FileCollisionDialog.SKIP_ACTION) {
164 return null;
165 }
166 // Append to file (resume file copy)
167 else if (choice== FileCollisionDialog.RESUME_ACTION) {
168 append = true;
169 break;
170 }
171 // Overwrite file
172 else if (choice== FileCollisionDialog.OVERWRITE_ACTION) {
173 // Do nothing, simply continue
174 break;
175 }
176 // Overwrite file if destination is older
177 else if (choice== FileCollisionDialog.OVERWRITE_IF_OLDER_ACTION) {
178 // Overwrite if file is newer (stricly)
179 if(file.getDate()<=destFile.getDate())
180 return null;
181 break;
182 } else if (choice == FileCollisionDialog.RENAME_ACTION) {
183 setPaused(true);
184 RenameDialog dlg = new RenameDialog(mainFrame, destFile);
185 setPaused(false);
186 String destFileName = dlg.getNewName();
187 if (destFileName != null) {
188 destFile = createDestinationFile(destFolder, destFileName);
189 } else {
190 // turn on FileCollisionDialog, so we don't loop indefinitely
191 defaultFileExistsAction = FileCollisionDialog.ASK_ACTION;
192 }
193 // continue with collision checking
194 continue;
195 }
196 }
197 break; // no collision
198 }
199 return destFile;
200 }
201
202 /**
203 * Optimizes the given writable archive file and notifies the user in case of an error.
204 *
205 * @param rwArchiveFile the writable archive file to optimize
206 */
207 protected void optimizeArchive(AbstractRWArchiveFile rwArchiveFile) {
208 isOptimizingArchive = true;
209
210 while(true) {
211 try {
212 archiveToOptimize = rwArchiveFile;
213 archiveToOptimize.optimizeArchive();
214
215 break;
216 }
217 catch(IOException e) {
218 if(showErrorDialog(errorDialogTitle, Translator.get("error_while_optimizing_archive", rwArchiveFile.getName()))==RETRY_ACTION)
219 continue;
220
221 break;
222 }
223 }
224
225 isOptimizingArchive = false;
226 }
227
228 }