Main Page   Namespace List   Class Hierarchy   Compound List   File List   Namespace Members   Compound Members   File Members  

src/tag_file.cpp

Go to the documentation of this file.
00001 // $Id: tag_file.cpp,v 1.34 2001/12/16 09:40:56 shadrack Exp $
00002 
00003 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
00004 // Copyright 1999, 2000  Scott Thomas Haug
00005 
00006 // This library is free software; you can redistribute it and/or modify it
00007 // under the terms of the GNU Library General Public License as published by
00008 // the Free Software Foundation; either version 2 of the License, or (at your
00009 // option) any later version.
00010 //
00011 // This library is distributed in the hope that it will be useful, but WITHOUT
00012 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00014 // License for more details.
00015 //
00016 // You should have received a copy of the GNU Library General Public License
00017 // along with this library; if not, write to the Free Software Foundation,
00018 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00019 
00020 // The id3lib authors encourage improvements and optimisations to be sent to
00021 // the id3lib coordinator.  Please see the README file for details on where to
00022 // send such submissions.  See the AUTHORS file for a list of people who have
00023 // contributed to id3lib.  See the ChangeLog file for a list of changes to
00024 // id3lib.  These files are distributed with id3lib at
00025 // http://download.sourceforge.net/id3lib/
00026 
00027 #if defined HAVE_CONFIG_H
00028 #  include <config.h>
00029 #endif
00030 
00031 
00032 
00033 #include <string.h>
00034 #include <fstream.h>
00035 #include <stdlib.h>
00036 #include "utils.h"
00037 #include "writers.h"
00038 #include "io_strings.h"
00039 #include "tag_impl.h"
00040 #include "utils.h"
00041 
00042 using namespace dami;
00043 
00044 #if !defined HAVE_MKSTEMP
00045 #  include <stdio.h>
00046 #endif
00047 
00048 #if defined HAVE_UNISTD_H
00049 #  include <unistd.h>
00050 #endif
00051 
00052 #if defined WIN32 && (!defined(WINCE))
00053 #  include <windows.h>
00054 static int truncate(const char *path, size_t length)
00055 {
00056   int result = -1;
00057   HANDLE fh;
00058   
00059   fh = ::CreateFile(path,
00060                     GENERIC_WRITE | GENERIC_READ,
00061                     0,
00062                     NULL,
00063                     OPEN_EXISTING,
00064                     FILE_ATTRIBUTE_NORMAL,
00065                     NULL);
00066   
00067   if(INVALID_HANDLE_VALUE != fh)
00068   {
00069     SetFilePointer(fh, length, NULL, FILE_BEGIN);
00070     SetEndOfFile(fh);
00071     CloseHandle(fh);
00072     result = 0;
00073   }
00074   
00075   return result;
00076 }
00077 
00078 // prevents a weird error I was getting compiling this under windows
00079 #  if defined CreateFile
00080 #    undef CreateFile
00081 #  endif
00082 
00083 #elif defined(WINCE)
00084 // Createfile is apparently to defined to CreateFileW. (Bad Bad Bad), so we 
00085 // work around it by converting path to Unicode
00086 #  include <windows.h>
00087 static int truncate(const char *path, size_t length)
00088 {
00089   int result = -1;
00090   wchar_t wcTempPath[256];
00091   mbstowcs(wcTempPath,path,255);
00092   HANDLE fh;
00093   fh = ::CreateFile(wcTempPath,
00094                     GENERIC_WRITE | GENERIC_READ,
00095                     0,
00096                     NULL,
00097                     OPEN_EXISTING,
00098                     FILE_ATTRIBUTE_NORMAL,
00099                     NULL);
00100 
00101   if (INVALID_HANDLE_VALUE != fh)
00102   {
00103     SetFilePointer(fh, length, NULL, FILE_BEGIN);
00104     SetEndOfFile(fh);
00105     CloseHandle(fh);
00106     result = 0;
00107   }
00108   
00109   return result;
00110 }
00111 
00112 #elif defined(macintosh)
00113         
00114 static int truncate(const char *path, size_t length)
00115 {
00116    /* not implemented on the Mac */
00117    return -1;
00118 }
00119         
00120 #endif
00121 
00122 size_t ID3_TagImpl::Link(const char *fileInfo, bool parseID3v1, bool parseLyrics3)
00123 {
00124   flags_t tt = ID3TT_NONE;
00125   if (parseID3v1)
00126   {
00127     tt |= ID3TT_ID3V1;
00128   }
00129   if (parseLyrics3)
00130   {
00131     tt |= ID3TT_LYRICS;
00132   }
00133   return this->Link(fileInfo, tt);
00134 }
00135 
00136 size_t ID3_TagImpl::Link(const char *fileInfo, flags_t tag_types)
00137 {
00138   _tags_to_parse.set(tag_types);
00139   
00140   if (NULL == fileInfo)
00141   {
00142     return 0;
00143   }
00144 
00145   _file_name = fileInfo;
00146   _changed = true;
00147 
00148   this->ParseFile();
00149   
00150   return this->GetPrependedBytes();
00151 }
00152 
00153 size_t RenderV1ToFile(ID3_TagImpl& tag, fstream& file)
00154 {
00155   if (!file)
00156   {
00157     return 0;
00158   }
00159 
00160   // Heck no, this is stupid.  If we do not read in an initial V1(.1)
00161   // header then we are constantly appending new V1(.1) headers. Files
00162   // can get very big that way if we never overwrite the old ones.
00163   //  if (ID3_V1_LEN > tag.GetAppendedBytes())   - Daniel Hazelbaker
00164   if (ID3_V1_LEN > tag.GetFileSize())
00165   {
00166     file.seekp(0, ios::end);
00167   }
00168   else
00169   {
00170     // We want to check if there is already an id3v1 tag, so we can write over
00171     // it.  First, seek to the beginning of any possible id3v1 tag
00172     file.seekg(0-ID3_V1_LEN, ios::end);
00173     char sID[ID3_V1_LEN_ID];
00174 
00175     // Read in the TAG characters
00176     file.read(sID, ID3_V1_LEN_ID);
00177 
00178     // If those three characters are TAG, then there's a preexisting id3v1 tag,
00179     // so we should set the file cursor so we can overwrite it with a new tag.
00180     if (memcmp(sID, "TAG", ID3_V1_LEN_ID) == 0)
00181     {
00182       file.seekp(0-ID3_V1_LEN, ios::end);
00183     }
00184     // Otherwise, set the cursor to the end of the file so we can append on 
00185     // the new tag.
00186     else
00187     {
00188       file.seekp(0, ios::end);
00189     }
00190   }
00191   
00192   ID3_IOStreamWriter out(file);
00193   
00194   id3::v1::render(out, tag);
00195 
00196   return ID3_V1_LEN;
00197 }
00198 
00199 size_t RenderV2ToFile(const ID3_TagImpl& tag, fstream& file)
00200 {
00201   ID3D_NOTICE( "RenderV2ToFile: starting" );
00202   if (!file)
00203   {
00204     ID3D_WARNING( "RenderV2ToFile: error in file" );
00205     return 0;
00206   }
00207 
00208   String tagString; 
00209   io::StringWriter writer(tagString);
00210   id3::v2::render(writer, tag);
00211   ID3D_NOTICE( "RenderV2ToFile: rendered v2" );
00212 
00213   const char* tagData = tagString.data();
00214   size_t tagSize = tagString.size();
00215 
00216   // if the new tag fits perfectly within the old and the old one
00217   // actually existed (ie this isn't the first tag this file has had)
00218   if ((!tag.GetPrependedBytes() && !ID3_GetDataSize(tag)) ||
00219       (tagSize == tag.GetPrependedBytes()))
00220   {
00221     file.seekp(0, ios::beg);
00222     file.write(tagData, tagSize);
00223   }
00224   else
00225   {
00226     String filename = tag.GetFileName();
00227 #if !defined HAVE_MKSTEMP
00228     // This section is for Windows folk
00229 
00230     FILE *tempOut = tmpfile();
00231     if (NULL == tempOut)
00232     {
00233       // log this
00234       return 0;
00235       //ID3_THROW(ID3E_ReadOnly);
00236     }
00237     
00238     fwrite(tagData, 1, tagSize, tempOut);
00239     
00240     file.seekg(tag.GetPrependedBytes(), ios::beg);
00241     
00242     uchar tmpBuffer[BUFSIZ];
00243     while (!file.eof())
00244     {
00245       file.read((char *)tmpBuffer, BUFSIZ);
00246       size_t nBytes = file.gcount();
00247       fwrite(tmpBuffer, 1, nBytes, tempOut);
00248     }
00249     
00250     rewind(tempOut);
00251     openWritableFile(filename, file);
00252     
00253     while (!feof(tempOut))
00254     {
00255       size_t nBytes = fread((char *)tmpBuffer, 1, BUFSIZ, tempOut);
00256       file.write((char *)tmpBuffer, nBytes);
00257     }
00258     
00259     fclose(tempOut);
00260     
00261 #else
00262 
00263     // else we gotta make a temp file, copy the tag into it, copy the
00264     // rest of the old file after the tag, delete the old file, rename
00265     // this new file to the old file's name and update the handle
00266     String sTmpSuffix = ".XXXXXX";
00267     if (filename.size() + sTmpSuffix.size() > ID3_PATH_LENGTH)
00268     {
00269       // log this
00270       return 0;
00271       //ID3_THROW_DESC(ID3E_NoFile, "filename too long");
00272     }
00273     char sTempFile[ID3_PATH_LENGTH];
00274     strcpy(sTempFile, filename.c_str());
00275     strcat(sTempFile, sTmpSuffix.c_str());
00276     
00277     int fd = mkstemp(sTempFile);
00278     if (fd < 0)
00279     {
00280       remove(sTempFile);
00281       //ID3_THROW_DESC(ID3E_NoFile, "couldn't open temp file");
00282     }
00283 
00284     ofstream tmpOut(fd);
00285     if (!tmpOut)
00286     {
00287       tmpOut.close();
00288       remove(sTempFile);
00289       return 0;
00290       // log this
00291       //ID3_THROW(ID3E_ReadOnly);
00292     }
00293 
00294     tmpOut.write(tagData, tagSize);
00295     file.seekg(tag.GetPrependedBytes(), ios::beg);
00296     uchar tmpBuffer[BUFSIZ];
00297     while (file)
00298     {
00299       file.read(tmpBuffer, BUFSIZ);
00300       size_t nBytes = file.gcount();
00301       tmpOut.write(tmpBuffer, nBytes);
00302     }
00303       
00304     tmpOut.close();
00305 
00306     file.close();
00307 
00308     remove(filename.c_str());
00309     rename(sTempFile, filename.c_str());
00310 
00311     openWritableFile(filename, file);
00312 #endif
00313   }
00314 
00315   return tagSize;
00316 }
00317 
00318 
00319 flags_t ID3_TagImpl::Update(flags_t ulTagFlag)
00320 {
00321   flags_t tags = ID3TT_NONE;
00322   
00323   fstream file;
00324   String filename = this->GetFileName();
00325   ID3_Err err = openWritableFile(filename, file);
00326   _file_size = getFileSize(file);
00327   
00328   if (err == ID3E_NoFile)
00329   {
00330     err = createFile(filename, file);
00331   }
00332   if (err == ID3E_ReadOnly)
00333   {
00334     return tags;
00335   }
00336 
00337   if ((ulTagFlag & ID3TT_ID3V2) && this->HasChanged())
00338   {
00339     _prepended_bytes = RenderV2ToFile(*this, file);
00340     if (_prepended_bytes)
00341     {
00342       tags |= ID3TT_ID3V2;
00343     }
00344   }
00345   
00346   if ((ulTagFlag & ID3TT_ID3V1) && 
00347       (!this->HasTagType(ID3TT_ID3V1) || this->HasChanged()))
00348   {
00349     size_t tag_bytes = RenderV1ToFile(*this, file);
00350     if (tag_bytes)
00351     {
00352       // only add the tag_bytes if there wasn't an id3v1 tag before
00353       if (! _file_tags.test(ID3TT_ID3V1))
00354       {
00355         _appended_bytes += tag_bytes;
00356       }
00357       tags |= ID3TT_ID3V1;
00358     }
00359   }
00360   _changed = false;
00361   _file_tags.add(tags);
00362   _file_size = getFileSize(file);
00363   file.close();
00364   return tags;
00365 }
00366 
00367 flags_t ID3_TagImpl::Strip(flags_t ulTagFlag)
00368 {
00369   flags_t ulTags = ID3TT_NONE;
00370   const size_t data_size = ID3_GetDataSize(*this);
00371   
00372   // First remove the v2 tag, if requested
00373   if (ulTagFlag & ID3TT_PREPENDED & _file_tags.get())
00374   {
00375     fstream file;
00376     if (ID3E_NoError != openWritableFile(this->GetFileName(), file))
00377     {
00378       return ulTags;
00379     }
00380     _file_size = getFileSize(file);
00381 
00382     // We will remove the id3v2 tag in place: since it comes at the beginning
00383     // of the file, we'll effectively move all the data that comes after the
00384     // tag back n bytes, where n is the size of the id3v2 tag.  Once we've
00385     // copied the data, we'll truncate the file.
00386     file.seekg(this->GetPrependedBytes(), ios::beg);
00387     
00388     uchar aucBuffer[BUFSIZ];
00389     
00390     // The nBytesRemaining variable indicates how many bytes are to be copied
00391     size_t nBytesToCopy = data_size;
00392 
00393     // Here we increase the nBytesToCopy by the size of any tags that appear
00394     // at the end of the file if we don't want to strip them
00395     if (!(ulTagFlag & ID3TT_APPENDED))
00396     {
00397       nBytesToCopy += this->GetAppendedBytes();
00398     }
00399     
00400     // The nBytesRemaining variable indicates how many bytes are left to be 
00401     // moved in the actual file.
00402     // The nBytesCopied variable keeps track of how many actual bytes were
00403     // copied (or moved) so far.
00404     size_t 
00405       nBytesRemaining = nBytesToCopy,
00406       nBytesCopied = 0;
00407     while (!file.eof())
00408     {
00409       size_t nBytesToRead = dami::min<size_t>(nBytesRemaining - nBytesCopied, BUFSIZ);
00410       file.read((char *)aucBuffer, nBytesToRead);
00411       size_t nBytesRead = file.gcount();
00412 
00413       if (nBytesRead != nBytesToRead)
00414       {
00415         // TODO: log this
00416         //cerr << "--- attempted to write " << nBytesRead << " bytes, "
00417         //     << "only wrote " << nBytesWritten << endl;
00418       }
00419       if (nBytesRead > 0)
00420       {
00421         long offset = nBytesRead + this->GetPrependedBytes();
00422         file.seekp(-offset, ios::cur);
00423         file.write((char *)aucBuffer, nBytesRead);
00424         file.seekg(this->GetPrependedBytes(), ios::cur);
00425         nBytesCopied += nBytesRead;
00426       }
00427       
00428       if (nBytesCopied == nBytesToCopy || nBytesToRead < BUFSIZ)
00429       {
00430         break;
00431       }
00432     }
00433     file.close();
00434   }
00435   
00436   size_t nNewFileSize = data_size;
00437 
00438   if ((_file_tags.get() & ID3TT_APPENDED) && (ulTagFlag & ID3TT_APPENDED))
00439   {
00440     ulTags |= _file_tags.get() & ID3TT_APPENDED;
00441   }
00442   else
00443   {
00444     // if we're not stripping the appended tags, be sure to increase the file
00445     // size by those bytes
00446     nNewFileSize += this->GetAppendedBytes();
00447   }
00448   
00449   if ((ulTagFlag & ID3TT_PREPENDED) && (_file_tags.get() & ID3TT_PREPENDED))
00450   {
00451     // If we're stripping the ID3v2 tag, there's no need to adjust the new
00452     // file size, since it doesn't account for the ID3v2 tag size
00453     ulTags |= _file_tags.get() & ID3TT_PREPENDED;
00454   }
00455   else
00456   {
00457     // add the original prepended tag size since we don't want to delete it,
00458     // and the new file size represents the file size _not_ counting the ID3v2
00459     // tag
00460     nNewFileSize += this->GetPrependedBytes();
00461   }
00462 
00463   if (ulTags && (truncate(_file_name.c_str(), nNewFileSize) == -1))
00464   {
00465     // log this
00466     return 0;
00467     //ID3_THROW(ID3E_NoFile);
00468   }
00469 
00470   _prepended_bytes = (ulTags & ID3TT_PREPENDED) ? 0 : _prepended_bytes;
00471   _appended_bytes  = (ulTags & ID3TT_APPENDED)  ? 0 : _appended_bytes;
00472   _file_size = data_size + _prepended_bytes + _appended_bytes;
00473   
00474   _changed = _file_tags.remove(ulTags) || _changed;
00475   
00476   return ulTags;
00477 }

Generated on Thu Jan 3 07:35:56 2002 for id3lib by doxygen1.2.12 written by Dimitri van Heesch, © 1997-2001