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