1 /*
2  * aTunes
3  * Copyright (C) Alex Aranda, Sylvain Gaudard and contributors
4  *
5  * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors
6  *
7  * http://www.atunes.org
8  * http://sourceforge.net/projects/atunes
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  */
20 
21 package net.sourceforge.atunes.kernel.modules.process;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.regex.Pattern;
29 
30 import net.sourceforge.atunes.model.ILocalAudioObject;
31 import net.sourceforge.atunes.model.ITag;
32 import net.sourceforge.atunes.model.ITagAttributesReviewed;
33 import net.sourceforge.atunes.model.ITagHandler;
34 import net.sourceforge.atunes.model.IWebServicesHandler;
35 import net.sourceforge.atunes.utils.FileNameUtils;
36 import net.sourceforge.atunes.utils.FileUtils;
37 import net.sourceforge.atunes.utils.I18nUtils;
38 import net.sourceforge.atunes.utils.StringUtils;
39 
40 /**
41  * Imports (song) files to repository
42  */
43 public class ImportFilesProcess extends AbstractLocalAudioObjectTransferProcess {
44 
45 	private static final Pattern NUMBER_SEPARATOR_PATTERN = Pattern
46 			.compile("[^0-9]+");
47 
48 	/**
49 	 * Folders to import
50 	 */
51 	private List<File> folders;
52 
53 	/** Set of audio files whose tag must be written */
54 	private Set<ILocalAudioObject> filesToChangeTag;
55 
56 	private ITagHandler tagHandler;
57 
58 	private IWebServicesHandler webServicesHandler;
59 
60 	/**
61 	 * @param webServicesHandler
62 	 */
setWebServicesHandler( final IWebServicesHandler webServicesHandler)63 	public void setWebServicesHandler(
64 			final IWebServicesHandler webServicesHandler) {
65 		this.webServicesHandler = webServicesHandler;
66 	}
67 
68 	/**
69 	 * @param tagHandler
70 	 */
setTagHandler(final ITagHandler tagHandler)71 	public void setTagHandler(final ITagHandler tagHandler) {
72 		this.tagHandler = tagHandler;
73 	}
74 
75 	/**
76 	 * Replaces tags before import audio objects
77 	 *
78 	 * @param tagAttributesReviewed
79 	 */
initialize(final ITagAttributesReviewed tagAttributesReviewed)80 	public void initialize(final ITagAttributesReviewed tagAttributesReviewed) {
81 		this.filesToChangeTag = new HashSet<ILocalAudioObject>();
82 		for (ILocalAudioObject fileToImport : getFilesToTransfer()) {
83 			// Replace tags (in memory) before import audio files if necessary
84 			replaceTag(fileToImport, tagAttributesReviewed);
85 
86 			// Set track number if necessary
87 			setTrackNumber(fileToImport);
88 		}
89 	}
90 
91 	/**
92 	 * @param folders
93 	 */
setFolders(final List<File> folders)94 	public void setFolders(final List<File> folders) {
95 		this.folders = folders;
96 	}
97 
98 	@Override
getProgressDialogTitle()99 	public String getProgressDialogTitle() {
100 		return StringUtils.getString(I18nUtils.getString("IMPORTING"), "...");
101 	}
102 
103 	/**
104 	 * Prepares the directory structure in which the song will be written.
105 	 *
106 	 * @param song
107 	 *            Song to be written
108 	 * @param destinationBaseFolder
109 	 *            Destination path
110 	 * @return Returns the directory structure with full path where the file
111 	 *         will be written
112 	 */
getDirectory(final ILocalAudioObject song, final File destinationBaseFolder)113 	private File getDirectory(final ILocalAudioObject song,
114 			final File destinationBaseFolder) {
115 		// Get base folder or the first folder if there is any error
116 		File baseFolder = null;
117 		for (File folder : this.folders) {
118 			String filePath = getFileManager().getFolderPath(song);
119 			String folderParentPath = folder.getParentFile() != null ? FileUtils
120 					.getPath(folder.getParentFile()) : null;
121 			if (filePath != null && folderParentPath != null
122 					&& filePath.startsWith(folderParentPath)) {
123 				baseFolder = folder.getParentFile();
124 				break;
125 			}
126 		}
127 		if (baseFolder == null) {
128 			baseFolder = this.folders.get(0);
129 		}
130 
131 		String songPath = this.getFileManager().getFolderPath(song);
132 		String songRelativePath = songPath.replace(FileUtils
133 				.getPath(baseFolder).replace("\\", "\\\\").replace("$", "\\$"),
134 				"");
135 		if (getStateRepository().getImportFolderPathPattern() != null) {
136 			songRelativePath = FileNameUtils
137 					.getValidFolderName(
138 							getNewFolderPath(getStateRepository()
139 									.getImportFolderPathPattern(), song,
140 									getOsManager()), getOsManager());
141 		}
142 		return new File(StringUtils.getString(FileUtils
143 				.getPath(destinationBaseFolder), getOsManager()
144 				.getFileSeparator(), songRelativePath));
145 	}
146 
147 	@Override
transferAudioFile(final File destination, final ILocalAudioObject file, final List<Exception> thrownExceptions)148 	protected ILocalAudioObject transferAudioFile(final File destination,
149 			final ILocalAudioObject file, final List<Exception> thrownExceptions) {
150 		// Change title. As this can be a long-time task we get titles during
151 		// transfer process instead of before to avoid not showing any progress
152 		// dialog
153 		// while performing this task
154 		setTitle(file);
155 
156 		// If necessary, apply changes to original files before copy
157 		if (getStateRepository().isApplyChangesToSourceFilesBeforeImport()) {
158 			changeTag(file, file);
159 		}
160 
161 		// Import file
162 		ILocalAudioObject destFile = importFile(destination, file,
163 				thrownExceptions);
164 
165 		if (destFile != null) {
166 			// Change tag if necessary after import
167 			if (!getStateRepository().isApplyChangesToSourceFilesBeforeImport()) {
168 				changeTag(file, destFile);
169 			}
170 		}
171 
172 		return destFile;
173 	}
174 
175 	/**
176 	 * Imports a single file to a destination
177 	 *
178 	 * @param destination
179 	 * @param file
180 	 * @param list
181 	 *            to add exceptions when thrown
182 	 * @return A reference to the created file or null if error
183 	 * @throws IOException
184 	 */
importFile(final File destination, final ILocalAudioObject file, final List<Exception> thrownExceptions)185 	private ILocalAudioObject importFile(final File destination,
186 			final ILocalAudioObject file, final List<Exception> thrownExceptions) {
187 		File destDir = getDirectory(file, destination);
188 		String newName;
189 		if (getStateRepository().getImportFileNamePattern() != null) {
190 			newName = getNewFileName(getStateRepository()
191 					.getImportFileNamePattern(), file, getOsManager());
192 		} else {
193 			newName = FileNameUtils.getValidFileName(this.getFileManager()
194 					.getFileName(file).replace("\\", "\\\\")
195 					.replace("$", "\\$"), false, getOsManager());
196 		}
197 
198 		try {
199 			return this.getFileManager().copyFile(file,
200 					FileUtils.getPath(destDir), newName);
201 		} catch (IOException e) {
202 			thrownExceptions.add(e);
203 			return null;
204 		}
205 	}
206 
207 	/**
208 	 * Changes tag if necessary in disk
209 	 *
210 	 * @param sourceFile
211 	 *            original AudioFile
212 	 * @param destFile
213 	 *            destination file
214 	 */
changeTag(final ILocalAudioObject sourceFile, final ILocalAudioObject destFile)215 	private void changeTag(final ILocalAudioObject sourceFile,
216 			final ILocalAudioObject destFile) {
217 		if (this.filesToChangeTag.contains(sourceFile)) {
218 			this.tagHandler.setTag(destFile, sourceFile.getTag());
219 		}
220 	}
221 
222 	/**
223 	 * Changes tag of a file if it is defined in a TagAttributesReviewed object
224 	 * LocalAudioObject is added to list of files to change tag physically on
225 	 * disk
226 	 *
227 	 * @param fileToImport
228 	 * @param tagAttributesReviewed
229 	 */
replaceTag(final ILocalAudioObject fileToImport, final ITagAttributesReviewed tagAttributesReviewed)230 	private void replaceTag(final ILocalAudioObject fileToImport,
231 			final ITagAttributesReviewed tagAttributesReviewed) {
232 		if (tagAttributesReviewed != null) {
233 			ITag modifiedTag = tagAttributesReviewed
234 					.getTagForAudioFile(fileToImport);
235 			// This file must be changed
236 			if (modifiedTag != null) {
237 				fileToImport.setTag(modifiedTag);
238 				this.filesToChangeTag.add(fileToImport);
239 			}
240 		}
241 	}
242 
243 	/**
244 	 * Changes track number of a file. LocalAudioObject is added to list of
245 	 * files to change tag physically on disk
246 	 *
247 	 * @param fileToImport
248 	 */
setTrackNumber(final ILocalAudioObject fileToImport)249 	private void setTrackNumber(final ILocalAudioObject fileToImport) {
250 		if (getStateRepository().isSetTrackNumbersWhenImporting()
251 				&& fileToImport.getTrackNumber() < 1) {
252 			int newTrackNumber = getTrackNumber(fileToImport);
253 			if (newTrackNumber > 0) {
254 				if (fileToImport.getTag() == null) {
255 					fileToImport.setTag(this.tagHandler.getNewTag());
256 				}
257 				fileToImport.getTag().setTrackNumber(newTrackNumber);
258 				if (!this.filesToChangeTag.contains(fileToImport)) {
259 					this.filesToChangeTag.add(fileToImport);
260 				}
261 			}
262 		}
263 	}
264 
265 	/**
266 	 * Returns track number for a given audio file
267 	 *
268 	 * @param audioFile
269 	 * @return
270 	 */
getTrackNumber(final ILocalAudioObject audioFile)271 	private int getTrackNumber(final ILocalAudioObject audioFile) {
272 		// Try to get a number from file name
273 		String fileName = audioFile.getNameWithoutExtension();
274 		String[] aux = NUMBER_SEPARATOR_PATTERN.split(fileName);
275 		int trackNumber = 0;
276 		int i = 0;
277 		while (trackNumber == 0 && i < aux.length) {
278 			String token = aux[i];
279 			try {
280 				trackNumber = Integer.parseInt(token);
281 				// If trackNumber >= 1000 maybe it's not a track number (year?)
282 				if (trackNumber >= 1000) {
283 					trackNumber = 0;
284 				}
285 			} catch (NumberFormatException e) {
286 				// Ok, it's not a valid number, skip it
287 			}
288 			i++;
289 		}
290 
291 		// If trackNumber could not be retrieved from file name, try to get from
292 		// last.fm
293 		// To get this, titles must match
294 		if (trackNumber == 0) {
295 			trackNumber = this.webServicesHandler.getTrackNumber(audioFile);
296 		}
297 
298 		return trackNumber;
299 	}
300 
301 	/**
302 	 * Changes title of a file. LocalAudioObject is added to list of files to
303 	 * change tag physically on disk
304 	 *
305 	 * @param fileToImport
306 	 */
setTitle(final ILocalAudioObject fileToImport)307 	private void setTitle(final ILocalAudioObject fileToImport) {
308 		if (getStateRepository().isSetTitlesWhenImporting()) {
309 			String newTitle = this.webServicesHandler
310 					.getTitleForAudioObject(fileToImport);
311 			if (newTitle != null) {
312 				if (fileToImport.getTag() == null) {
313 					fileToImport.setTag(this.tagHandler.getNewTag());
314 				}
315 				fileToImport.getTag().setTitle(newTitle);
316 				if (!this.filesToChangeTag.contains(fileToImport)) {
317 					this.filesToChangeTag.add(fileToImport);
318 				}
319 			}
320 		}
321 	}
322 
323 	@Override
getFileNamePattern()324 	protected String getFileNamePattern() {
325 		return getStateRepository().getImportFileNamePattern();
326 	}
327 
328 	@Override
getFolderPathPattern()329 	protected String getFolderPathPattern() {
330 		return getStateRepository().getImportFolderPathPattern();
331 	}
332 }
333