id3lib  3.8.3
tag_file.cpp
Go to the documentation of this file.
1 // $Id: tag_file.cpp,v 1.43 2003/03/02 14:14:08 t1mpy Exp $
2 
3 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
4 // Copyright 1999, 2000 Scott Thomas Haug
5 // Copyright 2002 Thijmen Klok (thijmen@id3lib.org)
6 
7 // This library is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU Library General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or (at your
10 // option) any later version.
11 //
12 // This library is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU Library General Public License
18 // along with this library; if not, write to the Free Software Foundation,
19 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 
21 // The id3lib authors encourage improvements and optimisations to be sent to
22 // the id3lib coordinator. Please see the README file for details on where to
23 // send such submissions. See the AUTHORS file for a list of people who have
24 // contributed to id3lib. See the ChangeLog file for a list of changes to
25 // id3lib. These files are distributed with id3lib at
26 // http://download.sourceforge.net/id3lib/
27 
28 #include <stdio.h> //for BUFSIZ and functions remove & rename
29 #include "writers.h"
30 #include "io_strings.h"
31 #include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
32 
33 using namespace dami;
34 
35 #if !defined HAVE_MKSTEMP
36 # include <stdio.h>
37 #endif
38 
39 #if defined HAVE_UNISTD_H
40 # include <unistd.h>
41 #endif
42 
43 #if defined HAVE_SYS_STAT_H
44 # include <sys/stat.h>
45 #endif
46 
47 #if defined WIN32 && (!defined(WINCE))
48 # include <windows.h>
49 static int truncate(const char *path, size_t length)
50 {
51  int result = -1;
52  HANDLE fh;
53 
54  fh = ::CreateFile(path,
55  GENERIC_WRITE | GENERIC_READ,
56  0,
57  NULL,
58  OPEN_EXISTING,
59  FILE_ATTRIBUTE_NORMAL,
60  NULL);
61 
62  if(INVALID_HANDLE_VALUE != fh)
63  {
64  SetFilePointer(fh, length, NULL, FILE_BEGIN);
65  SetEndOfFile(fh);
66  CloseHandle(fh);
67  result = 0;
68  }
69 
70  return result;
71 }
72 
73 // prevents a weird error I was getting compiling this under windows
74 # if defined CreateFile
75 # undef CreateFile
76 # endif
77 
78 #elif defined(WINCE)
79 // Createfile is apparently to defined to CreateFileW. (Bad Bad Bad), so we
80 // work around it by converting path to Unicode
81 # include <windows.h>
82 static int truncate(const char *path, size_t length)
83 {
84  int result = -1;
85  wchar_t wcTempPath[256];
86  mbstowcs(wcTempPath,path,255);
87  HANDLE fh;
88  fh = ::CreateFile(wcTempPath,
89  GENERIC_WRITE | GENERIC_READ,
90  0,
91  NULL,
92  OPEN_EXISTING,
93  FILE_ATTRIBUTE_NORMAL,
94  NULL);
95 
96  if (INVALID_HANDLE_VALUE != fh)
97  {
98  SetFilePointer(fh, length, NULL, FILE_BEGIN);
99  SetEndOfFile(fh);
100  CloseHandle(fh);
101  result = 0;
102  }
103 
104  return result;
105 }
106 
107 #elif defined(macintosh)
108 
109 static int truncate(const char *path, size_t length)
110 {
111  /* not implemented on the Mac */
112  return -1;
113 }
114 
115 #endif
116 
117 size_t ID3_TagImpl::Link(const char *fileInfo, bool parseID3v1, bool parseLyrics3)
118 {
119  flags_t tt = ID3TT_NONE;
120  if (parseID3v1)
121  {
122  tt |= ID3TT_ID3V1;
123  }
124  if (parseLyrics3)
125  {
126  tt |= ID3TT_LYRICS;
127  }
128  return this->Link(fileInfo, tt);
129 }
130 
131 size_t ID3_TagImpl::Link(const char *fileInfo, flags_t tag_types)
132 {
133  _tags_to_parse.set(tag_types);
134 
135  if (NULL == fileInfo)
136  {
137  return 0;
138  }
139 
140  _file_name = fileInfo;
141  _changed = true;
142 
143  this->ParseFile();
144 
145  return this->GetPrependedBytes();
146 }
147 
148 // used for streaming:
149 size_t ID3_TagImpl::Link(ID3_Reader &reader, flags_t tag_types)
150 {
151  _tags_to_parse.set(tag_types);
152 
153  _file_name = "";
154  _changed = true;
155 
156  this->ParseReader(reader);
157 
158  return this->GetPrependedBytes();
159 }
160 
161 size_t RenderV1ToFile(ID3_TagImpl& tag, fstream& file)
162 {
163  if (!file)
164  {
165  return 0;
166  }
167 
168  // Heck no, this is stupid. If we do not read in an initial V1(.1)
169  // header then we are constantly appending new V1(.1) headers. Files
170  // can get very big that way if we never overwrite the old ones.
171  // if (ID3_V1_LEN > tag.GetAppendedBytes()) - Daniel Hazelbaker
172  if (ID3_V1_LEN > tag.GetFileSize())
173  {
174  file.seekp(0, ios::end);
175  }
176  else
177  {
178  // We want to check if there is already an id3v1 tag, so we can write over
179  // it. First, seek to the beginning of any possible id3v1 tag
180  file.seekg(0-ID3_V1_LEN, ios::end);
181  char sID[ID3_V1_LEN_ID];
182 
183  // Read in the TAG characters
184  file.read(sID, ID3_V1_LEN_ID);
185 
186  // If those three characters are TAG, then there's a preexisting id3v1 tag,
187  // so we should set the file cursor so we can overwrite it with a new tag.
188  if (memcmp(sID, "TAG", ID3_V1_LEN_ID) == 0)
189  {
190  file.seekp(0-ID3_V1_LEN, ios::end);
191  }
192  // Otherwise, set the cursor to the end of the file so we can append on
193  // the new tag.
194  else
195  {
196  file.seekp(0, ios::end);
197  }
198  }
199 
200  ID3_IOStreamWriter out(file);
201 
202  id3::v1::render(out, tag);
203 
204  return ID3_V1_LEN;
205 }
206 
207 size_t RenderV2ToFile(const ID3_TagImpl& tag, fstream& file)
208 {
209  ID3D_NOTICE( "RenderV2ToFile: starting" );
210  if (!file)
211  {
212  ID3D_WARNING( "RenderV2ToFile: error in file" );
213  return 0;
214  }
215 
216  String tagString;
217  io::StringWriter writer(tagString);
218  id3::v2::render(writer, tag);
219  ID3D_NOTICE( "RenderV2ToFile: rendered v2" );
220 
221  const char* tagData = tagString.data();
222  size_t tagSize = tagString.size();
223  // if the new tag fits perfectly within the old and the old one
224  // actually existed (ie this isn't the first tag this file has had)
225  if ((!tag.GetPrependedBytes() && !ID3_GetDataSize(tag)) ||
226  (tagSize == tag.GetPrependedBytes()))
227  {
228  file.seekp(0, ios::beg);
229  file.write(tagData, tagSize);
230  }
231  else
232  {
233  String filename = tag.GetFileName();
234  String sTmpSuffix = ".XXXXXX";
235  if (filename.size() + sTmpSuffix.size() > ID3_PATH_LENGTH)
236  {
237  // log this
238  return 0;
239  //ID3_THROW_DESC(ID3E_NoFile, "filename too long");
240  }
241  char sTempFile[ID3_PATH_LENGTH];
242  strcpy(sTempFile, filename.c_str());
243  strcat(sTempFile, sTmpSuffix.c_str());
244 
245 #if !defined(HAVE_MKSTEMP)
246  // This section is for Windows folk
247  fstream tmpOut;
248  createFile(sTempFile, tmpOut);
249 
250  tmpOut.write(tagData, tagSize);
251  file.seekg(tag.GetPrependedBytes(), ios::beg);
252  char *tmpBuffer[BUFSIZ];
253  while (!file.eof())
254  {
255  file.read((char *)tmpBuffer, BUFSIZ);
256  size_t nBytes = file.gcount();
257  tmpOut.write((char *)tmpBuffer, nBytes);
258  }
259 
260 #else
261 
262  // else we gotta make a temp file, copy the tag into it, copy the
263  // rest of the old file after the tag, delete the old file, rename
264  // this new file to the old file's name and update the handle
265 
266  int fd = mkstemp(sTempFile);
267  if (fd < 0)
268  {
269  remove(sTempFile);
270  //ID3_THROW_DESC(ID3E_NoFile, "couldn't open temp file");
271  }
272 
273  ofstream tmpOut(sTempFile);
274  if (!tmpOut)
275  {
276  tmpOut.close();
277  remove(sTempFile);
278  return 0;
279  // log this
280  //ID3_THROW(ID3E_ReadOnly);
281  }
282 
283  tmpOut.write(tagData, tagSize);
284  file.seekg(tag.GetPrependedBytes(), ios::beg);
285  uchar tmpBuffer[BUFSIZ];
286  while (file)
287  {
288  file.read((char *)tmpBuffer, BUFSIZ);
289  size_t nBytes = file.gcount();
290  tmpOut.write((char *)tmpBuffer, nBytes);
291  }
292 
293  close(fd); //closes the file
294 
295 #endif
296 
297  tmpOut.close();
298  file.close();
299 
300  // the following sets the permissions of the new file
301  // to be the same as the original
302 #if defined(HAVE_SYS_STAT_H)
303  struct stat fileStat;
304  if(stat(filename.c_str(), &fileStat) == 0)
305  {
306 #endif //defined(HAVE_SYS_STAT_H)
307  remove(filename.c_str());
308  rename(sTempFile, filename.c_str());
309 #if defined(HAVE_SYS_STAT_H)
310  chmod(filename.c_str(), fileStat.st_mode);
311  }
312 #endif //defined(HAVE_SYS_STAT_H)
313 
314 // file = tmpOut;
315  file.clear();//to clear the eof mark
316  openWritableFile(filename, file);
317  }
318 
319  return tagSize;
320 }
321 
322 
324 {
325  flags_t tags = ID3TT_NONE;
326 
327  fstream file;
328  String filename = this->GetFileName();
329  ID3_Err err = openWritableFile(filename, file);
330  _file_size = getFileSize(file);
331 
332  if (err == ID3E_NoFile)
333  {
334  err = createFile(filename, file);
335  }
336  if (err == ID3E_ReadOnly)
337  {
338  return tags;
339  }
340 
341  if ((ulTagFlag & ID3TT_ID3V2) && this->HasChanged())
342  {
343  _prepended_bytes = RenderV2ToFile(*this, file);
344  if (_prepended_bytes)
345  {
346  tags |= ID3TT_ID3V2;
347  }
348  }
349 
350  if ((ulTagFlag & ID3TT_ID3V1) &&
351  (!this->HasTagType(ID3TT_ID3V1) || this->HasChanged()))
352  {
353  size_t tag_bytes = RenderV1ToFile(*this, file);
354  if (tag_bytes)
355  {
356  // only add the tag_bytes if there wasn't an id3v1 tag before
357  if (! _file_tags.test(ID3TT_ID3V1))
358  {
359  _appended_bytes += tag_bytes;
360  }
361  tags |= ID3TT_ID3V1;
362  }
363  }
364  _changed = false;
365  _file_tags.add(tags);
366  _file_size = getFileSize(file);
367  file.close();
368  return tags;
369 }
370 
372 {
373  flags_t ulTags = ID3TT_NONE;
374  const size_t data_size = ID3_GetDataSize(*this);
375 
376  // First remove the v2 tag, if requested
377  if (ulTagFlag & ID3TT_PREPENDED & _file_tags.get())
378  {
379  fstream file;
380  if (ID3E_NoError != openWritableFile(this->GetFileName(), file))
381  {
382  return ulTags;
383  }
384  _file_size = getFileSize(file);
385 
386  // We will remove the id3v2 tag in place: since it comes at the beginning
387  // of the file, we'll effectively move all the data that comes after the
388  // tag back n bytes, where n is the size of the id3v2 tag. Once we've
389  // copied the data, we'll truncate the file.
390  file.seekg(this->GetPrependedBytes(), ios::beg);
391 
392  uchar aucBuffer[BUFSIZ];
393 
394  // The nBytesRemaining variable indicates how many bytes are to be copied
395  size_t nBytesToCopy = data_size;
396 
397  // Here we increase the nBytesToCopy by the size of any tags that appear
398  // at the end of the file if we don't want to strip them
399  if (!(ulTagFlag & ID3TT_APPENDED))
400  {
401  nBytesToCopy += this->GetAppendedBytes();
402  }
403 
404  // The nBytesRemaining variable indicates how many bytes are left to be
405  // moved in the actual file.
406  // The nBytesCopied variable keeps track of how many actual bytes were
407  // copied (or moved) so far.
408  size_t nBytesRemaining = nBytesToCopy,
409  nBytesCopied = 0;
410  while (!file.eof())
411  {
412 #if (defined(__GNUC__) && __GNUC__ == 2)
413  size_t nBytesToRead = (size_t)dami::min((unsigned int)(nBytesRemaining - nBytesCopied), (unsigned int)BUFSIZ);
414 #else
415  size_t nBytesToRead = min((unsigned int)(nBytesRemaining - nBytesCopied), (unsigned int)BUFSIZ);
416 #endif
417  file.read((char *)aucBuffer, nBytesToRead);
418  size_t nBytesRead = file.gcount();
419 
420  if (nBytesRead != nBytesToRead)
421  {
422  // TODO: log this
423  //cerr << "--- attempted to write " << nBytesRead << " bytes, "
424  // << "only wrote " << nBytesWritten << endl;
425  }
426  if (nBytesRead > 0)
427  {
428  long offset = nBytesRead + this->GetPrependedBytes();
429  file.seekp(-offset, ios::cur);
430  file.write((char *)aucBuffer, nBytesRead);
431  file.seekg(this->GetPrependedBytes(), ios::cur);
432  nBytesCopied += nBytesRead;
433  }
434 
435  if (nBytesCopied == nBytesToCopy || nBytesToRead < BUFSIZ)
436  {
437  break;
438  }
439  }
440  file.close();
441  }
442 
443  size_t nNewFileSize = data_size;
444 
445  if ((_file_tags.get() & ID3TT_APPENDED) && (ulTagFlag & ID3TT_APPENDED))
446  {
447  ulTags |= _file_tags.get() & ID3TT_APPENDED;
448  }
449  else
450  {
451  // if we're not stripping the appended tags, be sure to increase the file
452  // size by those bytes
453  nNewFileSize += this->GetAppendedBytes();
454  }
455 
456  if ((ulTagFlag & ID3TT_PREPENDED) && (_file_tags.get() & ID3TT_PREPENDED))
457  {
458  // If we're stripping the ID3v2 tag, there's no need to adjust the new
459  // file size, since it doesn't account for the ID3v2 tag size
460  ulTags |= _file_tags.get() & ID3TT_PREPENDED;
461  }
462  else
463  {
464  // add the original prepended tag size since we don't want to delete it,
465  // and the new file size represents the file size _not_ counting the ID3v2
466  // tag
467  nNewFileSize += this->GetPrependedBytes();
468  }
469 
470  if (ulTags && (truncate(_file_name.c_str(), nNewFileSize) == -1))
471  {
472  // log this
473  return 0;
474  //ID3_THROW(ID3E_NoFile);
475  }
476 
477  _prepended_bytes = (ulTags & ID3TT_PREPENDED) ? 0 : _prepended_bytes;
478  _appended_bytes = (ulTags & ID3TT_APPENDED) ? 0 : _appended_bytes;
479  _file_size = data_size + _prepended_bytes + _appended_bytes;
480 
481  _changed = _file_tags.remove(ulTags) || _changed;
482 
483  return ulTags;
484 }
485 
flags_t Strip(flags_t=(flags_t) ID3TT_ALL)
Definition: tag_file.cpp:371
size_t GetFileSize() const
Definition: tag_impl.h:113
dami::String GetFileName() const
Definition: tag_impl.h:114
size_t GetPrependedBytes() const
Definition: tag_impl.h:111
flags_t Update(flags_t=(flags_t) ID3TT_ALL)
Definition: tag_file.cpp:323
size_t Link(const char *fileInfo, flags_t=(flags_t) ID3TT_ALL)
Definition: tag_file.cpp:131
#define NULL
Definition: globals.h:743
ID3_Err
Predefined id3lib error types.
Definition: globals.h:364
@ ID3E_ReadOnly
Attempting to write to a read-only file.
Definition: globals.h:377
@ ID3E_NoError
No error reported.
Definition: globals.h:365
@ ID3E_NoFile
No file to parse.
Definition: globals.h:376
@ ID3TT_APPENDED
Represents all tag types that can be appended to a file.
Definition: globals.h:191
@ ID3TT_ID3V2
Represents an id3v2 tag.
Definition: globals.h:178
@ ID3TT_LYRICS
Definition: globals.h:183
@ ID3TT_PREPENDED
Represents all tag types that can be prepended to a file.
Definition: globals.h:189
@ ID3TT_ID3V1
Represents an id3v1 or id3v1.1 tag.
Definition: globals.h:177
@ ID3TT_NONE
Represents an empty or non-existant tag.
Definition: globals.h:176
unsigned char uchar
Definition: globals.h:114
uint16 flags_t
Definition: globals.h:118
@ ID3_V1_LEN
Definition: globals.h:331
@ ID3_V1_LEN_ID
Definition: globals.h:332
void render(ID3_Writer &, const ID3_TagImpl &)
Definition: tag_render.cpp:42
void render(ID3_Writer &writer, const ID3_TagImpl &tag)
Definition: tag_render.cpp:78
Definition: tag_impl.h:42
size_t ID3_C_EXPORT getFileSize(fstream &)
Definition: utils.cpp:256
const T & min(const T &a, const T &b)
Definition: utils.h:51
ID3_Err ID3_C_EXPORT openWritableFile(String, fstream &)
Definition: utils.cpp:295
ID3_Err ID3_C_EXPORT createFile(String, fstream &)
Definition: utils.cpp:240
size_t RenderV1ToFile(ID3_TagImpl &tag, fstream &file)
Definition: tag_file.cpp:161
size_t RenderV2ToFile(const ID3_TagImpl &tag, fstream &file)
Definition: tag_file.cpp:207
size_t ID3_GetDataSize(const ID3_TagImpl &tag)
Definition: tag_impl.cpp:323
#define ID3_PATH_LENGTH
Definition: utils.h:46