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
33using 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>
49static 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>
82static 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
109static int truncate(const char *path, size_t length)
110{
111 /* not implemented on the Mac */
112 return -1;
113}
114
115#endif
116
117size_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
131size_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:
149size_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
161size_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
207size_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 // Also restore the original owner and group on the freshly created
311 // replacement file (Debian bug #630957). Writing the tag via a temp
312 // file plus rename(2) otherwise leaves the new file owned by the current
313 // process, e.g. silently changing the file's group. chown(2) is
314 // best-effort: an unprivileged caller may not be permitted to restore
315 // the owner (or a group it does not belong to); in that case keep the
316 // previous behaviour instead of failing the whole update. Restore the
317 // owner/group before the mode, as chown(2) may clear the set-user-ID and
318 // set-group-ID bits.
319# if defined(HAVE_UNISTD_H)
320 if (chown(filename.c_str(), fileStat.st_uid, fileStat.st_gid) != 0)
321 {
322 ID3D_WARNING( "RenderV2ToFile: could not restore owner/group of " <<
323 filename );
324 }
325# endif //defined(HAVE_UNISTD_H)
326 chmod(filename.c_str(), fileStat.st_mode);
327 }
328#endif //defined(HAVE_SYS_STAT_H)
329
330// file = tmpOut;
331 file.clear();//to clear the eof mark
332 openWritableFile(filename, file);
333 }
334
335 return tagSize;
336}
337
338
340{
341 flags_t tags = ID3TT_NONE;
342
343 fstream file;
344 String filename = this->GetFileName();
345 ID3_Err err = openWritableFile(filename, file);
346 _file_size = getFileSize(file);
347
348 if (err == ID3E_NoFile)
349 {
350 err = createFile(filename, file);
351 }
352 if (err == ID3E_ReadOnly)
353 {
354 return tags;
355 }
356
357 if ((ulTagFlag & ID3TT_ID3V2) && this->HasChanged())
358 {
359 _prepended_bytes = RenderV2ToFile(*this, file);
360 if (_prepended_bytes)
361 {
362 tags |= ID3TT_ID3V2;
363 }
364 }
365
366 if ((ulTagFlag & ID3TT_ID3V1) &&
367 (!this->HasTagType(ID3TT_ID3V1) || this->HasChanged()))
368 {
369 size_t tag_bytes = RenderV1ToFile(*this, file);
370 if (tag_bytes)
371 {
372 // only add the tag_bytes if there wasn't an id3v1 tag before
373 if (! _file_tags.test(ID3TT_ID3V1))
374 {
375 _appended_bytes += tag_bytes;
376 }
377 tags |= ID3TT_ID3V1;
378 }
379 }
380 _changed = false;
381 _file_tags.add(tags);
382 _file_size = getFileSize(file);
383 file.close();
384 return tags;
385}
386
388{
389 flags_t ulTags = ID3TT_NONE;
390 const size_t data_size = ID3_GetDataSize(*this);
391
392 // First remove the v2 tag, if requested
393 if (ulTagFlag & ID3TT_PREPENDED & _file_tags.get())
394 {
395 fstream file;
396 if (ID3E_NoError != openWritableFile(this->GetFileName(), file))
397 {
398 return ulTags;
399 }
400 _file_size = getFileSize(file);
401
402 // We will remove the id3v2 tag in place: since it comes at the beginning
403 // of the file, we'll effectively move all the data that comes after the
404 // tag back n bytes, where n is the size of the id3v2 tag. Once we've
405 // copied the data, we'll truncate the file.
406 file.seekg(this->GetPrependedBytes(), ios::beg);
407
408 uchar aucBuffer[BUFSIZ];
409
410 // The nBytesRemaining variable indicates how many bytes are to be copied
411 size_t nBytesToCopy = data_size;
412
413 // Here we increase the nBytesToCopy by the size of any tags that appear
414 // at the end of the file if we don't want to strip them
415 if (!(ulTagFlag & ID3TT_APPENDED))
416 {
417 nBytesToCopy += this->GetAppendedBytes();
418 }
419
420 // The nBytesRemaining variable indicates how many bytes are left to be
421 // moved in the actual file.
422 // The nBytesCopied variable keeps track of how many actual bytes were
423 // copied (or moved) so far.
424 size_t nBytesRemaining = nBytesToCopy,
425 nBytesCopied = 0;
426 while (!file.eof())
427 {
428#if (defined(__GNUC__) && __GNUC__ == 2)
429 size_t nBytesToRead = (size_t)dami::min((unsigned int)(nBytesRemaining - nBytesCopied), (unsigned int)BUFSIZ);
430#else
431 size_t nBytesToRead = min((unsigned int)(nBytesRemaining - nBytesCopied), (unsigned int)BUFSIZ);
432#endif
433 file.read((char *)aucBuffer, nBytesToRead);
434 size_t nBytesRead = file.gcount();
435
436 if (nBytesRead != nBytesToRead)
437 {
438 // TODO: log this
439 //cerr << "--- attempted to write " << nBytesRead << " bytes, "
440 // << "only wrote " << nBytesWritten << endl;
441 }
442 if (nBytesRead > 0)
443 {
444 long offset = nBytesRead + this->GetPrependedBytes();
445 file.seekp(-offset, ios::cur);
446 file.write((char *)aucBuffer, nBytesRead);
447 file.seekg(this->GetPrependedBytes(), ios::cur);
448 nBytesCopied += nBytesRead;
449 }
450
451 if (nBytesCopied == nBytesToCopy || nBytesToRead < BUFSIZ)
452 {
453 break;
454 }
455 }
456 file.close();
457 }
458
459 size_t nNewFileSize = data_size;
460
461 if ((_file_tags.get() & ID3TT_APPENDED) && (ulTagFlag & ID3TT_APPENDED))
462 {
463 ulTags |= _file_tags.get() & ID3TT_APPENDED;
464 }
465 else
466 {
467 // if we're not stripping the appended tags, be sure to increase the file
468 // size by those bytes
469 nNewFileSize += this->GetAppendedBytes();
470 }
471
472 if ((ulTagFlag & ID3TT_PREPENDED) && (_file_tags.get() & ID3TT_PREPENDED))
473 {
474 // If we're stripping the ID3v2 tag, there's no need to adjust the new
475 // file size, since it doesn't account for the ID3v2 tag size
476 ulTags |= _file_tags.get() & ID3TT_PREPENDED;
477 }
478 else
479 {
480 // add the original prepended tag size since we don't want to delete it,
481 // and the new file size represents the file size _not_ counting the ID3v2
482 // tag
483 nNewFileSize += this->GetPrependedBytes();
484 }
485
486 if (ulTags && (truncate(_file_name.c_str(), nNewFileSize) == -1))
487 {
488 // log this
489 return 0;
490 //ID3_THROW(ID3E_NoFile);
491 }
492
493 _prepended_bytes = (ulTags & ID3TT_PREPENDED) ? 0 : _prepended_bytes;
494 _appended_bytes = (ulTags & ID3TT_APPENDED) ? 0 : _appended_bytes;
495 _file_size = data_size + _prepended_bytes + _appended_bytes;
496
497 _changed = _file_tags.remove(ulTags) || _changed;
498
499 return ulTags;
500}
501
flags_t Strip(flags_t=(flags_t) ID3TT_ALL)
Definition tag_file.cpp:387
size_t GetFileSize() const
Definition tag_impl.h:113
size_t GetAppendedBytes() const
Definition tag_impl.h:112
bool HasTagType(ID3_TagType tt) const
Definition tag_impl.h:124
dami::String GetFileName() const
Definition tag_impl.h:114
void ParseReader(ID3_Reader &reader)
size_t GetPrependedBytes() const
Definition tag_impl.h:111
flags_t Update(flags_t=(flags_t) ID3TT_ALL)
Definition tag_file.cpp:339
size_t Link(const char *fileInfo, flags_t=(flags_t) ID3TT_ALL)
Definition tag_file.cpp:131
void ParseFile()
bool HasChanged() const
Definition tag_impl.cpp:202
#define NULL
Definition globals.h:751
ID3_Err
Predefined id3lib error types.
Definition globals.h:372
@ ID3E_ReadOnly
Attempting to write to a read-only file.
Definition globals.h:385
@ ID3E_NoError
No error reported.
Definition globals.h:373
@ ID3E_NoFile
No file to parse.
Definition globals.h:384
@ ID3TT_APPENDED
Represents all tag types that can be appended to a file.
Definition globals.h:199
@ ID3TT_ID3V2
Represents an id3v2 tag.
Definition globals.h:186
@ ID3TT_LYRICS
Definition globals.h:191
@ ID3TT_PREPENDED
Represents all tag types that can be prepended to a file.
Definition globals.h:197
@ ID3TT_ID3V1
Represents an id3v1 or id3v1.1 tag.
Definition globals.h:185
@ ID3TT_NONE
Represents an empty or non-existant tag.
Definition globals.h:184
unsigned char uchar
Definition globals.h:122
uint16 flags_t
Definition globals.h:126
@ ID3_V1_LEN
Definition globals.h:339
@ ID3_V1_LEN_ID
Definition globals.h:340
void render(ID3_Writer &, const ID3_TagImpl &)
void render(ID3_Writer &writer, const ID3_TagImpl &tag)
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
size_t ID3_GetDataSize(const ID3_TagImpl &)
Definition tag_impl.cpp:323