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

Generated on Thu Jan 23 04:46:18 2003 for id3lib by doxygen1.2.18