1
/***************************************************************************
2
copyright : (C) 2002 - 2008 by Scott Wheeler
3
email : wheeler@kde.org
4
***************************************************************************/
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. *
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. *
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 *
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
***************************************************************************/
38
# define ftruncate _chsize
52
using namespace TagLib;
56
typedef FileName FileNameHandle;
60
struct FileNameHandle : public std::string
62
FileNameHandle(FileName name) : std::string(name) {}
63
operator FileName () const { return c_str(); }
68
class File::FilePrivate
71
FilePrivate(FileName fileName);
80
static const uint bufferSize = 1024;
83
File::FilePrivate::FilePrivate(FileName fileName) :
90
// First try with read / write mode, if that fails, fall back to read only.
94
if(wcslen((const wchar_t *) fileName) > 0) {
96
file = _wfopen(name, L"rb+");
101
file = _wfopen(name, L"rb");
110
file = fopen(name, "rb+");
115
file = fopen(name, "rb");
118
debug("Could not open file " + String((const char *) name));
122
////////////////////////////////////////////////////////////////////////////////
124
////////////////////////////////////////////////////////////////////////////////
126
File::File(FileName file)
128
d = new FilePrivate(file);
138
FileName File::name() const
143
ByteVector File::readBlock(ulong length)
146
debug("File::readBlock() -- Invalid File");
147
return ByteVector::null;
151
return ByteVector::null;
153
if(length > FilePrivate::bufferSize &&
154
length > ulong(File::length()))
156
length = File::length();
159
ByteVector v(static_cast<uint>(length));
160
const int count = fread(v.data(), sizeof(char), length, d->file);
165
void File::writeBlock(const ByteVector &data)
171
debug("File::writeBlock() -- attempted to write to a file that is not writable");
175
fwrite(data.data(), sizeof(char), data.size(), d->file);
178
long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
180
if(!d->file || pattern.size() > d->bufferSize)
183
// The position in the file that the current buffer starts at.
185
long bufferOffset = fromOffset;
188
// These variables are used to keep track of a partial match that happens at
189
// the end of a buffer.
191
int previousPartialMatch = -1;
192
int beforePreviousPartialMatch = -1;
194
// Save the location of the current read pointer. We will restore the
195
// position using seek() before all returns.
197
long originalPosition = tell();
199
// Start the search at the offset.
203
// This loop is the crux of the find method. There are three cases that we
204
// want to account for:
206
// (1) The previously searched buffer contained a partial match of the search
207
// pattern and we want to see if the next one starts with the remainder of
210
// (2) The search pattern is wholly contained within the current buffer.
212
// (3) The current buffer ends with a partial match of the pattern. We will
213
// note this for use in the next itteration, where we will check for the rest
216
// All three of these are done in two steps. First we check for the pattern
217
// and do things appropriately if a match (or partial match) is found. We
218
// then check for "before". The order is important because it gives priority
219
// to "real" matches.
221
for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
223
// (1) previous partial match
225
if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
226
const int patternOffset = (d->bufferSize - previousPartialMatch);
227
if(buffer.containsAt(pattern, 0, patternOffset)) {
228
seek(originalPosition);
229
return bufferOffset - d->bufferSize + previousPartialMatch;
233
if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
234
const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
235
if(buffer.containsAt(before, 0, beforeOffset)) {
236
seek(originalPosition);
241
// (2) pattern contained in current buffer
243
long location = buffer.find(pattern);
245
seek(originalPosition);
246
return bufferOffset + location;
249
if(!before.isNull() && buffer.find(before) >= 0) {
250
seek(originalPosition);
256
previousPartialMatch = buffer.endsWithPartialMatch(pattern);
259
beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
261
bufferOffset += d->bufferSize;
264
// Since we hit the end of the file, reset the status before continuing.
268
seek(originalPosition);
274
long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
276
if(!d->file || pattern.size() > d->bufferSize)
279
// The position in the file that the current buffer starts at.
283
// These variables are used to keep track of a partial match that happens at
284
// the end of a buffer.
287
int previousPartialMatch = -1;
288
int beforePreviousPartialMatch = -1;
291
// Save the location of the current read pointer. We will restore the
292
// position using seek() before all returns.
294
long originalPosition = tell();
296
// Start the search at the offset.
299
if(fromOffset == 0) {
300
seek(-1 * int(d->bufferSize), End);
301
bufferOffset = tell();
304
seek(fromOffset + -1 * int(d->bufferSize), Beginning);
305
bufferOffset = tell();
308
// See the notes in find() for an explanation of this algorithm.
310
for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
312
// TODO: (1) previous partial match
314
// (2) pattern contained in current buffer
316
long location = buffer.rfind(pattern);
318
seek(originalPosition);
319
return bufferOffset + location;
322
if(!before.isNull() && buffer.find(before) >= 0) {
323
seek(originalPosition);
327
// TODO: (3) partial match
329
bufferOffset -= d->bufferSize;
333
// Since we hit the end of the file, reset the status before continuing.
337
seek(originalPosition);
342
void File::insert(const ByteVector &data, ulong start, ulong replace)
347
if(data.size() == replace) {
352
else if(data.size() < replace) {
355
removeBlock(start + data.size(), replace - data.size());
359
// Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
360
// and avoid TagLib's high level API for rendering just copying parts of
361
// the file that don't contain tag data.
363
// Now I'll explain the steps in this ugliness:
365
// First, make sure that we're working with a buffer that is longer than
366
// the *differnce* in the tag sizes. We want to avoid overwriting parts
367
// that aren't yet in memory, so this is necessary.
369
ulong bufferLength = bufferSize();
371
while(data.size() - replace > bufferLength)
372
bufferLength += bufferSize();
374
// Set where to start the reading and writing.
376
long readPosition = start + replace;
377
long writePosition = start;
380
ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
382
// This is basically a special case of the loop below. Here we're just
383
// doing the same steps as below, but since we aren't using the same buffer
384
// size -- instead we're using the tag size -- this has to be handled as a
385
// special case. We're also using File::writeBlock() just for the tag.
386
// That's a bit slower than using char *'s so, we're only doing it here.
389
int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
390
readPosition += bufferLength;
394
writePosition += data.size();
396
buffer = aboutToOverwrite;
398
// In case we've already reached the end of file...
400
buffer.resize(bytesRead);
402
// Ok, here's the main loop. We want to loop until the read fails, which
403
// means that we hit the end of the file.
405
while(!buffer.isEmpty()) {
407
// Seek to the current read position and read the data that we're about
408
// to overwrite. Appropriately increment the readPosition.
411
bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
412
aboutToOverwrite.resize(bytesRead);
413
readPosition += bufferLength;
415
// Check to see if we just read the last block. We need to call clear()
416
// if we did so that the last write succeeds.
418
if(ulong(bytesRead) < bufferLength)
421
// Seek to the write position and write our buffer. Increment the
425
fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);
426
writePosition += buffer.size();
428
// Make the current buffer the data that we read in the beginning.
430
buffer = aboutToOverwrite;
432
// Again, we need this for the last write. We don't want to write garbage
433
// at the end of our file, so we need to set the buffer size to the amount
434
// that we actually read.
436
bufferLength = bytesRead;
440
void File::removeBlock(ulong start, ulong length)
445
ulong bufferLength = bufferSize();
447
long readPosition = start + length;
448
long writePosition = start;
450
ByteVector buffer(static_cast<uint>(bufferLength));
454
while(bytesRead != 0) {
456
bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
457
readPosition += bytesRead;
459
// Check to see if we just read the last block. We need to call clear()
460
// if we did so that the last write succeeds.
462
if(bytesRead < bufferLength)
466
fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
467
writePosition += bytesRead;
469
truncate(writePosition);
472
bool File::readOnly() const
477
bool File::isReadable(const char *file)
479
return access(file, R_OK) == 0;
482
bool File::isOpen() const
484
return (d->file != NULL);
487
bool File::isValid() const
489
return isOpen() && d->valid;
492
void File::seek(long offset, Position p)
495
debug("File::seek() -- trying to seek in a file that isn't opened.");
501
fseek(d->file, offset, SEEK_SET);
504
fseek(d->file, offset, SEEK_CUR);
507
fseek(d->file, offset, SEEK_END);
517
long File::tell() const
519
return ftell(d->file);
524
// Do some caching in case we do multiple calls.
532
long curpos = tell();
535
long endpos = tell();
537
seek(curpos, Beginning);
543
bool File::isWritable(const char *file)
545
return access(file, W_OK) == 0;
548
////////////////////////////////////////////////////////////////////////////////
550
////////////////////////////////////////////////////////////////////////////////
552
void File::setValid(bool valid)
557
void File::truncate(long length)
559
ftruncate(fileno(d->file), length);
562
TagLib::uint File::bufferSize()
564
return FilePrivate::bufferSize;