/** * Saves the tags in this dataType to the file argument. It will be saved as * TagConstants.MP3_FILE_SAVE_WRITE * * @param fileToSave file to save the this dataTypes tags to * @throws FileNotFoundException if unable to find file * @throws IOException on any I/O error */ public void save(File fileToSave) throws IOException { // Ensure we are dealing with absolute filepaths not relative ones File file = fileToSave.getAbsoluteFile(); logger.config("Saving : " + file.getPath()); // Checks before starting write precheck(file); RandomAccessFile rfile = null; try { // ID3v2 Tag if (TagOptionSingleton.getInstance().isId3v2Save()) { if (id3v2tag == null) { rfile = new RandomAccessFile(file, "rw"); (new ID3v24Tag()).delete(rfile); (new ID3v23Tag()).delete(rfile); (new ID3v22Tag()).delete(rfile); logger.config("Deleting ID3v2 tag:" + file.getName()); rfile.close(); } else { logger.config("Writing ID3v2 tag:" + file.getName()); final MP3AudioHeader mp3AudioHeader = (MP3AudioHeader) this.getAudioHeader(); final long mp3StartByte = mp3AudioHeader.getMp3StartByte(); final long newMp3StartByte = id3v2tag.write(file, mp3StartByte); if (mp3StartByte != newMp3StartByte) { logger.config("New mp3 start byte: " + newMp3StartByte); mp3AudioHeader.setMp3StartByte(newMp3StartByte); } } } rfile = new RandomAccessFile(file, "rw"); // Lyrics 3 Tag if (TagOptionSingleton.getInstance().isLyrics3Save()) { if (lyrics3tag != null) { lyrics3tag.write(rfile); } } // ID3v1 tag if (TagOptionSingleton.getInstance().isId3v1Save()) { logger.config("Processing ID3v1"); if (id3v1tag == null) { logger.config("Deleting ID3v1"); (new ID3v1Tag()).delete(rfile); } else { logger.config("Saving ID3v1"); id3v1tag.write(rfile); } } } catch (FileNotFoundException ex) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()), ex); throw ex; } catch (IOException iex) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), iex.getMessage()), iex); throw iex; } catch (RuntimeException re) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), re.getMessage()), re); throw re; } finally { if (rfile != null) { rfile.close(); } } }
// TODO Creates temp file in same folder as the original file, this is safe but would impose a // performance // overhead if the original file is on a networked drive public synchronized void write(AudioFile af) throws CannotWriteException { logger.info("Started writing tag data for file:" + af.getFile().getName()); // Prechecks precheckWrite(af); RandomAccessFile raf = null; RandomAccessFile rafTemp = null; File newFile = null; File result = null; // Create temporary File try { newFile = File.createTempFile( af.getFile().getName().replace('.', '_'), TEMP_FILENAME_SUFFIX, af.getFile().getParentFile()); } // Unable to create temporary file, can happen in Vista if have Create Files/Write Data set to // Deny catch (IOException ioe) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg( af.getFile().getName(), af.getFile().getParentFile().getAbsolutePath()), ioe); throw new CannotWriteException( ErrorMessage.GENERAL_WRITE_FAILED_TO_CREATE_TEMPORARY_FILE_IN_FOLDER.getMsg( af.getFile().getName(), af.getFile().getParentFile().getAbsolutePath())); } // Open temporary file and actual file for Editing try { rafTemp = new RandomAccessFile(newFile, WRITE_MODE); raf = new RandomAccessFile(af.getFile(), WRITE_MODE); } // Unable to write to writable file, can happen in Vista if have Create Folders/Append Data set // to Deny catch (IOException ioe) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg( af.getFile().getAbsolutePath()), ioe); // If we managed to open either file, delete it. try { if (raf != null) { raf.close(); } if (rafTemp != null) { rafTemp.close(); } } catch (IOException ioe2) { // Warn but assume has worked okay logger.log( Level.WARNING, ErrorMessage.GENERAL_WRITE_PROBLEM_CLOSING_FILE_HANDLE.getMsg( af.getFile(), ioe.getMessage()), ioe2); } // Delete the temp file ( we cannot delet until closed correpsonding rafTemp) if (!newFile.delete()) { // Non critical failed deletion logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_DELETE_TEMPORARY_FILE.getMsg( newFile.getAbsolutePath())); } throw new CannotWriteException( ErrorMessage.GENERAL_WRITE_FAILED_TO_OPEN_FILE_FOR_EDITING.getMsg( af.getFile().getAbsolutePath())); } // Write data to File try { raf.seek(0); rafTemp.seek(0); try { if (this.modificationListener != null) { this.modificationListener.fileWillBeModified(af, false); } writeTag(af.getTag(), raf, rafTemp); if (this.modificationListener != null) { this.modificationListener.fileModified(af, newFile); } } catch (ModifyVetoException veto) { throw new CannotWriteException(veto); } } catch (Exception e) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(af.getFile(), e.getMessage()), e); try { if (raf != null) { raf.close(); } if (rafTemp != null) { rafTemp.close(); } } catch (IOException ioe) { // Warn but assume has worked okay logger.log( Level.WARNING, ErrorMessage.GENERAL_WRITE_PROBLEM_CLOSING_FILE_HANDLE.getMsg( af.getFile().getAbsolutePath(), ioe.getMessage()), ioe); } // Delete the temporary file because either it was never used so lets just tidy up or we did // start writing to it but // the write failed and we havent renamed it back to the original file so we can just delete // it. if (!newFile.delete()) { // Non critical failed deletion logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_DELETE_TEMPORARY_FILE.getMsg( newFile.getAbsolutePath())); } throw new CannotWriteException( ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(af.getFile(), e.getMessage())); } finally { try { if (raf != null) { raf.close(); } if (rafTemp != null) { rafTemp.close(); } } catch (IOException ioe) { // Warn but assume has worked okay logger.log( Level.WARNING, ErrorMessage.GENERAL_WRITE_PROBLEM_CLOSING_FILE_HANDLE.getMsg( af.getFile().getAbsolutePath(), ioe.getMessage()), ioe); } } // Result held in this file result = af.getFile(); // If the temporary file was used if (newFile.length() > 0) { // Rename Original File // Can fail on Vista if have Special Permission 'Delete' set Deny File originalFileBackup = new File( af.getFile().getParentFile().getPath(), AudioFile.getBaseFilename(af.getFile()) + ".old"); boolean renameResult = af.getFile().renameTo(originalFileBackup); if (renameResult == false) { logger.log( Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg( af.getFile().getPath(), originalFileBackup.getName())); throw new CannotWriteException( ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_FILE_TO_BACKUP.getMsg( af.getFile().getPath(), originalFileBackup.getName())); } // Rename Temp File to Original File renameResult = newFile.renameTo(af.getFile()); if (!renameResult) { // Renamed failed so lets do some checks rename the backup back to the original file // New File doesnt exist if (!newFile.exists()) { logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_NEW_FILE_DOESNT_EXIST.getMsg( newFile.getAbsolutePath())); } // Rename the backup back to the original if (!originalFileBackup.renameTo(af.getFile())) { // TODO now if this happens we are left with testfile.old instead of testfile.mp4 logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_ORIGINAL_BACKUP_TO_ORIGINAL.getMsg( originalFileBackup.getAbsolutePath(), af.getFile().getName())); } logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg( af.getFile().getAbsolutePath(), newFile.getName())); throw new CannotWriteException( ErrorMessage.GENERAL_WRITE_FAILED_TO_RENAME_TO_ORIGINAL_FILE.getMsg( af.getFile().getAbsolutePath(), newFile.getName())); } else { // Rename was okay so we can now delete the backup of the original boolean deleteResult = originalFileBackup.delete(); if (!deleteResult) { // Not a disaster but can't delete the backup so make a warning logger.warning( ErrorMessage.GENERAL_WRITE_WARNING_UNABLE_TO_DELETE_BACKUP_FILE.getMsg( originalFileBackup.getAbsolutePath())); } } // Delete the temporary file if still exists if (newFile.exists()) { if (!newFile.delete()) { // Non critical failed deletion logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_DELETE_TEMPORARY_FILE.getMsg(newFile.getPath())); } } } else { // Delete the temporary file that wasn't ever used if (!newFile.delete()) { // Non critical failed deletion logger.warning( ErrorMessage.GENERAL_WRITE_FAILED_TO_DELETE_TEMPORARY_FILE.getMsg(newFile.getPath())); } } if (this.modificationListener != null) { this.modificationListener.fileOperationFinished(result); } }