Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / toolkit / tfile.cpp
1 /***************************************************************************
2     copyright            : (C) 2002 - 2008 by Scott Wheeler
3     email                : wheeler@kde.org
4  ***************************************************************************/
5
6 /***************************************************************************
7  *   This library is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU Lesser General Public License version   *
9  *   2.1 as published by the Free Software Foundation.                     *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful, but   *
12  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the Free Software   *
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
19  *   USA                                                                   *
20  *                                                                         *
21  *   Alternatively, this file is available under the Mozilla Public        *
22  *   License Version 1.1.  You may obtain a copy of the License at         *
23  *   http://www.mozilla.org/MPL/                                           *
24  ***************************************************************************/
25
26 #include "tfile.h"
27 #include "tstring.h"
28 #include "tdebug.h"
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/stat.h>
33
34 #ifdef _WIN32
35 # include <wchar.h>
36 # include <windows.h>
37 # include <io.h>
38 # define ftruncate _chsize
39 #else
40 # include <unistd.h>
41 #endif
42
43 #include <stdlib.h>
44
45 #ifndef R_OK
46 # define R_OK 4
47 #endif
48 #ifndef W_OK
49 # define W_OK 2
50 #endif
51
52 using namespace TagLib;
53
54 #ifdef _WIN32
55
56 typedef FileName FileNameHandle;
57
58 #else
59
60 struct FileNameHandle : public std::string
61 {
62   FileNameHandle(FileName name) : std::string(name) {}
63   operator FileName () const { return c_str(); }
64 };
65
66 #endif
67
68 class File::FilePrivate
69 {
70 public:
71   FilePrivate(FileName fileName);
72
73   FILE *file;
74
75   FileNameHandle name;
76
77   bool readOnly;
78   bool valid;
79   ulong size;
80   static const uint bufferSize = 1024;
81 };
82
83 File::FilePrivate::FilePrivate(FileName fileName) :
84   file(0),
85   name(fileName),
86   readOnly(true),
87   valid(true),
88   size(0)
89 {
90   // First try with read / write mode, if that fails, fall back to read only.
91
92 #ifdef _WIN32
93
94   if(wcslen((const wchar_t *) fileName) > 0) {
95
96     file = _wfopen(name, L"rb+");
97
98     if(file)
99       readOnly = false;
100     else
101       file = _wfopen(name, L"rb");
102
103     if(file)
104       return;
105
106   }
107
108 #endif
109
110   file = fopen(name, "rb+");
111
112   if(file)
113     readOnly = false;
114   else
115     file = fopen(name, "rb");
116
117   if(!file)
118     debug("Could not open file " + String((const char *) name));
119 }
120
121 ////////////////////////////////////////////////////////////////////////////////
122 // public members
123 ////////////////////////////////////////////////////////////////////////////////
124
125 File::File(FileName file)
126 {
127   d = new FilePrivate(file);
128 }
129
130 File::~File()
131 {
132   if(d->file)
133     fclose(d->file);
134   delete d;
135 }
136
137 FileName File::name() const
138 {
139   return d->name;
140 }
141
142 ByteVector File::readBlock(ulong length)
143 {
144   if(!d->file) {
145     debug("File::readBlock() -- Invalid File");
146     return ByteVector::null;
147   }
148
149   if(length == 0)
150     return ByteVector::null;
151
152   if(length > FilePrivate::bufferSize &&
153      length > ulong(File::length()))
154   {
155     length = File::length();
156   }
157
158   ByteVector v(static_cast<uint>(length));
159   const int count = fread(v.data(), sizeof(char), length, d->file);
160   v.resize(count);
161   return v;
162 }
163
164 void File::writeBlock(const ByteVector &data)
165 {
166   if(!d->file)
167     return;
168
169   if(d->readOnly) {
170     debug("File::writeBlock() -- attempted to write to a file that is not writable");
171     return;
172   }
173
174   fwrite(data.data(), sizeof(char), data.size(), d->file);
175 }
176
177 long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
178 {
179   if(!d->file || pattern.size() > d->bufferSize)
180       return -1;
181
182   // The position in the file that the current buffer starts at.
183
184   long bufferOffset = fromOffset;
185   ByteVector buffer;
186
187   // These variables are used to keep track of a partial match that happens at
188   // the end of a buffer.
189
190   int previousPartialMatch = -1;
191   int beforePreviousPartialMatch = -1;
192
193   // Save the location of the current read pointer.  We will restore the
194   // position using seek() before all returns.
195
196   long originalPosition = tell();
197
198   // Start the search at the offset.
199
200   seek(fromOffset);
201
202   // This loop is the crux of the find method.  There are three cases that we
203   // want to account for:
204   //
205   // (1) The previously searched buffer contained a partial match of the search
206   // pattern and we want to see if the next one starts with the remainder of
207   // that pattern.
208   //
209   // (2) The search pattern is wholly contained within the current buffer.
210   //
211   // (3) The current buffer ends with a partial match of the pattern.  We will
212   // note this for use in the next itteration, where we will check for the rest
213   // of the pattern.
214   //
215   // All three of these are done in two steps.  First we check for the pattern
216   // and do things appropriately if a match (or partial match) is found.  We
217   // then check for "before".  The order is important because it gives priority
218   // to "real" matches.
219
220   for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
221
222     // (1) previous partial match
223
224     if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
225       const int patternOffset = (d->bufferSize - previousPartialMatch);
226       if(buffer.containsAt(pattern, 0, patternOffset)) {
227         seek(originalPosition);
228         return bufferOffset - d->bufferSize + previousPartialMatch;
229       }
230     }
231
232     if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
233       const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
234       if(buffer.containsAt(before, 0, beforeOffset)) {
235         seek(originalPosition);
236         return -1;
237       }
238     }
239
240     // (2) pattern contained in current buffer
241
242     long location = buffer.find(pattern);
243     if(location >= 0) {
244       seek(originalPosition);
245       return bufferOffset + location;
246     }
247
248     if(!before.isNull() && buffer.find(before) >= 0) {
249       seek(originalPosition);
250       return -1;
251     }
252
253     // (3) partial match
254
255     previousPartialMatch = buffer.endsWithPartialMatch(pattern);
256
257     if(!before.isNull())
258       beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
259
260     bufferOffset += d->bufferSize;
261   }
262
263   // Since we hit the end of the file, reset the status before continuing.
264
265   clear();
266
267   seek(originalPosition);
268
269   return -1;
270 }
271
272
273 long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
274 {
275   if(!d->file || pattern.size() > d->bufferSize)
276       return -1;
277
278   // The position in the file that the current buffer starts at.
279
280   ByteVector buffer;
281
282   // These variables are used to keep track of a partial match that happens at
283   // the end of a buffer.
284
285   /*
286   int previousPartialMatch = -1;
287   int beforePreviousPartialMatch = -1;
288   */
289
290   // Save the location of the current read pointer.  We will restore the
291   // position using seek() before all returns.
292
293   long originalPosition = tell();
294
295   // Start the search at the offset.
296
297   long bufferOffset;
298   if(fromOffset == 0) {
299     seek(-1 * int(d->bufferSize), End);
300     bufferOffset = tell();
301   }
302   else {
303     seek(fromOffset + -1 * int(d->bufferSize), Beginning);
304     bufferOffset = tell();
305   }
306
307   // See the notes in find() for an explanation of this algorithm.
308
309   for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
310
311     // TODO: (1) previous partial match
312
313     // (2) pattern contained in current buffer
314
315     long location = buffer.rfind(pattern);
316     if(location >= 0) {
317       seek(originalPosition);
318       return bufferOffset + location;
319     }
320
321     if(!before.isNull() && buffer.find(before) >= 0) {
322       seek(originalPosition);
323       return -1;
324     }
325
326     // TODO: (3) partial match
327
328     bufferOffset -= d->bufferSize;
329     seek(bufferOffset);
330   }
331
332   // Since we hit the end of the file, reset the status before continuing.
333
334   clear();
335
336   seek(originalPosition);
337
338   return -1;
339 }
340
341 void File::insert(const ByteVector &data, ulong start, ulong replace)
342 {
343   if(!d->file)
344     return;
345
346   if(data.size() == replace) {
347     seek(start);
348     writeBlock(data);
349     return;
350   }
351   else if(data.size() < replace) {
352       seek(start);
353       writeBlock(data);
354       removeBlock(start + data.size(), replace - data.size());
355       return;
356   }
357
358   // Woohoo!  Faster (about 20%) than id3lib at last.  I had to get hardcore
359   // and avoid TagLib's high level API for rendering just copying parts of
360   // the file that don't contain tag data.
361   //
362   // Now I'll explain the steps in this ugliness:
363
364   // First, make sure that we're working with a buffer that is longer than
365   // the *differnce* in the tag sizes.  We want to avoid overwriting parts
366   // that aren't yet in memory, so this is necessary.
367
368   ulong bufferLength = bufferSize();
369
370   while(data.size() - replace > bufferLength)
371     bufferLength += bufferSize();
372
373   // Set where to start the reading and writing.
374
375   long readPosition = start + replace;
376   long writePosition = start;
377
378   ByteVector buffer;
379   ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
380
381   // This is basically a special case of the loop below.  Here we're just
382   // doing the same steps as below, but since we aren't using the same buffer
383   // size -- instead we're using the tag size -- this has to be handled as a
384   // special case.  We're also using File::writeBlock() just for the tag.
385   // That's a bit slower than using char *'s so, we're only doing it here.
386
387   seek(readPosition);
388   int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
389   readPosition += bufferLength;
390
391   seek(writePosition);
392   writeBlock(data);
393   writePosition += data.size();
394
395   buffer = aboutToOverwrite;
396
397   // In case we've already reached the end of file...
398
399   buffer.resize(bytesRead);
400
401   // Ok, here's the main loop.  We want to loop until the read fails, which
402   // means that we hit the end of the file.
403
404   while(!buffer.isEmpty()) {
405
406     // Seek to the current read position and read the data that we're about
407     // to overwrite.  Appropriately increment the readPosition.
408
409     seek(readPosition);
410     bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
411     aboutToOverwrite.resize(bytesRead);
412     readPosition += bufferLength;
413
414     // Check to see if we just read the last block.  We need to call clear()
415     // if we did so that the last write succeeds.
416
417     if(ulong(bytesRead) < bufferLength)
418       clear();
419
420     // Seek to the write position and write our buffer.  Increment the
421     // writePosition.
422
423     seek(writePosition);
424     fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);
425     writePosition += buffer.size();
426
427     // Make the current buffer the data that we read in the beginning.
428
429     buffer = aboutToOverwrite;
430
431     // Again, we need this for the last write.  We don't want to write garbage
432     // at the end of our file, so we need to set the buffer size to the amount
433     // that we actually read.
434
435     bufferLength = bytesRead;
436   }
437 }
438
439 void File::removeBlock(ulong start, ulong length)
440 {
441   if(!d->file)
442     return;
443
444   ulong bufferLength = bufferSize();
445
446   long readPosition = start + length;
447   long writePosition = start;
448
449   ByteVector buffer(static_cast<uint>(bufferLength));
450
451   ulong bytesRead = 1;
452
453   while(bytesRead != 0) {
454     seek(readPosition);
455     bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
456     readPosition += bytesRead;
457
458     // Check to see if we just read the last block.  We need to call clear()
459     // if we did so that the last write succeeds.
460
461     if(bytesRead < bufferLength)
462       clear();
463
464     seek(writePosition);
465     fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
466     writePosition += bytesRead;
467   }
468   truncate(writePosition);
469 }
470
471 bool File::readOnly() const
472 {
473   return d->readOnly;
474 }
475
476 bool File::isReadable(const char *file)
477 {
478   return access(file, R_OK) == 0;
479 }
480
481 bool File::isOpen() const
482 {
483   return (d->file != NULL);
484 }
485
486 bool File::isValid() const
487 {
488   return isOpen() && d->valid;
489 }
490
491 void File::seek(long offset, Position p)
492 {
493   if(!d->file) {
494     debug("File::seek() -- trying to seek in a file that isn't opened.");
495     return;
496   }
497
498   switch(p) {
499   case Beginning:
500     fseek(d->file, offset, SEEK_SET);
501     break;
502   case Current:
503     fseek(d->file, offset, SEEK_CUR);
504     break;
505   case End:
506     fseek(d->file, offset, SEEK_END);
507     break;
508   }
509 }
510
511 void File::clear()
512 {
513   clearerr(d->file);
514 }
515
516 long File::tell() const
517 {
518   return ftell(d->file);
519 }
520
521 long File::length()
522 {
523   // Do some caching in case we do multiple calls.
524
525   if(d->size > 0)
526     return d->size;
527
528   if(!d->file)
529     return 0;
530
531   long curpos = tell();
532
533   seek(0, End);
534   long endpos = tell();
535
536   seek(curpos, Beginning);
537
538   d->size = endpos;
539   return endpos;
540 }
541
542 bool File::isWritable(const char *file)
543 {
544   return access(file, W_OK) == 0;
545 }
546
547 ////////////////////////////////////////////////////////////////////////////////
548 // protected members
549 ////////////////////////////////////////////////////////////////////////////////
550
551 void File::setValid(bool valid)
552 {
553   d->valid = valid;
554 }
555
556 void File::truncate(long length)
557 {
558   ftruncate(fileno(d->file), length);
559 }
560
561 TagLib::uint File::bufferSize()
562 {
563   return FilePrivate::bufferSize;
564 }