public virtual void WriteToFile(string fileName, ID3v2MajorVersion version, EncodingScheme encoding)
{
FileStream targetFileStream=null;
try
{
// First write all frame data to a memory buffer
// This way we catch all fatal exceptions before
// touching the file, so there's not a even a slight
// chance of corrupting the file.
MemoryStream memoryBuffer=new MemoryStream();
WriteFrames(memoryBuffer, version, encoding);
int newSize=(int)memoryBuffer.Length;
if(newSize==0)
{
throw new FatalException("No data to write in the tag.");
}
targetFileStream=File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
TagHeader oldHeader=TagHeader.FromStream(targetFileStream);
bool useIntermediate=false;
if(oldHeader==null)
{
// There is no attached tag in the file. We need to use an intermediate temporary file.
useIntermediate=true;
// Rewind the place in stream to the very beginning, so that when
// we copy the data from the original file to the intermediate file,
// we won't lose any data.
targetFileStream.Seek(0, SeekOrigin.Begin);
}
else
{
// File already has an ID3 v2 tag attached.
if(oldHeader.TagSize<newSize)
{
useIntermediate=true;
// Allow for 4KB of padding.
newSize+=4*1024;
// Move to the correct place in stream so when we copy data to
// the intermediate file, we won't lose any data.
// The +10 is to go skip the tag header, since it's not included
// in TagHeader.TagSize
targetFileStream.Seek(oldHeader.TagSize+10, SeekOrigin.Begin);
}
else
{
// We should write exactly the same number of bytes back to the file.
// When writing the padding, compare memoryBuffer.Length to newSize to
// calculate the number of padding bytes required to write.
newSize=oldHeader.TagSize;
// Seek the beginning of the file. The tag header and frame information
// will be overwritten.
targetFileStream.Seek(0, SeekOrigin.Begin);
}
}
TagHeader newHeader=new TagHeader((byte)version, 0, TagHeaderFlags.None, newSize);
if(useIntermediate)
{
string intermediateFileName=GetTempIntermediateFileName(fileName);
FileStream intermediateStream=null;
try
{
intermediateStream=File.Create(intermediateFileName);
newHeader.WriteToStream(intermediateStream);
// Write the frame data residing in memory buffer.
intermediateStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);
//Write any required paddings.
for(int i=(int)memoryBuffer.Length;i<newSize;i++)
{
intermediateStream.WriteByte(0);
}
// Copy the data from the original file to the intermediate file.
CopyFromStreamToStream(targetFileStream, intermediateStream);
// Close the stream of original and intermediate file streams.
targetFileStream.Close();
intermediateStream.Close();
// If control reaches this point, then everything went file, so
// should normally delete the original file and rename the intermediate file.
// But as a safety measure, for pre-release, alpha, and beta version,
// instead of removing the file, I decided to rename the old file to
// fileName+".old."+revisionNumber instead. The user can manually delete
// the these files after ensuring the integrity of the new files.
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
File.Move(fileName, GetBackupFileName(fileName));
#else
File.Delete(fileName);
#endif
File.Move(intermediateFileName, fileName);
}
finally
{
if(intermediateStream!=null)
{
intermediateStream.Close();
}
}
}
else // If using an intermediate file is not necessary.
{
// Similarly, we would normally just start writing to @stream here,
// but instead, we close it, make a backup copy of it, and then
// open it again, and write the tag information just to ensure nothing
// will be lost.
targetFileStream.Close();
#if ACHAMENES_ID3_BACKUP_FILES_BEFORE_MODIFICATION
File.Copy(fileName, GetBackupFileName(fileName));
#endif
targetFileStream=File.Open(fileName, FileMode.Open, FileAccess.Write, FileShare.Write);
// Write the header.
newHeader.WriteToStream(targetFileStream);
// Write frame data from memory buffer.
targetFileStream.Write(memoryBuffer.GetBuffer(), 0, (int)memoryBuffer.Length);
// Write any required padding.
for(int i=(int)memoryBuffer.Length;i<newSize;i++)
{
targetFileStream.WriteByte(0);
}
// And finally close the stream.
targetFileStream.Close();
}
}
finally
{
if(targetFileStream!=null)
{
targetFileStream.Close();
}
}
}