taglib/toolkit/tfile.cpp
Scott Wheeler 90772906db Make sure that the type conversion happens so that this number ends up being
the appropriate seek location.  I'm pretty sure that this is what was causing
Ogg length information to not work on x86_64.  Can you confirm Hamish?

BUG:86806


git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@358653 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
2004-10-28 23:27:29 +00:00

485 lines
12 KiB
C++

/***************************************************************************
copyright : (C) 2002, 2003 by Scott Wheeler
email : wheeler@kde.org
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
* USA *
***************************************************************************/
#include "tfile.h"
#include "tstring.h"
#include "tdebug.h"
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace TagLib;
class File::FilePrivate
{
public:
FilePrivate(const char *fileName) :
file(0),
name(fileName),
readOnly(true),
valid(true)
{}
~FilePrivate()
{
free((void *)name);
}
FILE *file;
const char *name;
bool readOnly;
bool valid;
static const uint bufferSize = 1024;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
File::File(const char *file)
{
d = new FilePrivate(::strdup(file));
d->readOnly = !isWritable(file);
d->file = fopen(file, d->readOnly ? "r" : "r+");
if(!d->file)
debug("Could not open file " + String(file));
}
File::~File()
{
if(d->file)
fclose(d->file);
delete d;
}
const char *File::name() const
{
return d->name;
}
ByteVector File::readBlock(ulong length)
{
if(!d->file) {
debug("File::readBlock() -- Invalid File");
return ByteVector::null;
}
ByteVector v(static_cast<uint>(length));
const int count = fread(v.data(), sizeof(char), length, d->file);
v.resize(count);
return v;
}
void File::writeBlock(const ByteVector &data)
{
if(!d->file)
return;
if(d->readOnly) {
debug("File::writeBlock() -- attempted to write to a file that is not writable");
return;
}
fwrite(data.data(), sizeof(char), data.size(), d->file);
}
long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
{
if(!d->file || pattern.size() > d->bufferSize)
return -1;
// The position in the file that the current buffer starts at.
long bufferOffset = fromOffset;
ByteVector buffer;
// These variables are used to keep track of a partial match that happens at
// the end of a buffer.
int previousPartialMatch = -1;
int beforePreviousPartialMatch = -1;
// Save the location of the current read pointer. We will restore the
// position using seek() before all returns.
long originalPosition = tell();
// Start the search at the offset.
seek(fromOffset);
// This loop is the crux of the find method. There are three cases that we
// want to account for:
//
// (1) The previously searched buffer contained a partial match of the search
// pattern and we want to see if the next one starts with the remainder of
// that pattern.
//
// (2) The search pattern is wholly contained within the current buffer.
//
// (3) The current buffer ends with a partial match of the pattern. We will
// note this for use in the next itteration, where we will check for the rest
// of the pattern.
//
// All three of these are done in two steps. First we check for the pattern
// and do things appropriately if a match (or partial match) is found. We
// then check for "before". The order is important because it gives priority
// to "real" matches.
for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
// (1) previous partial match
if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
const int patternOffset = (d->bufferSize - previousPartialMatch);
if(buffer.containsAt(pattern, 0, patternOffset)) {
seek(originalPosition);
return bufferOffset - d->bufferSize + previousPartialMatch;
}
}
if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
if(buffer.containsAt(before, 0, beforeOffset)) {
seek(originalPosition);
return -1;
}
}
// (2) pattern contained in current buffer
long location = buffer.find(pattern);
if(location >= 0) {
seek(originalPosition);
return bufferOffset + location;
}
if(!before.isNull() && buffer.find(before) >= 0) {
seek(originalPosition);
return -1;
}
// (3) partial match
previousPartialMatch = buffer.endsWithPartialMatch(pattern);
if(!before.isNull())
beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
bufferOffset += d->bufferSize;
}
// Since we hit the end of the file, reset the status before continuing.
clear();
seek(originalPosition);
return -1;
}
long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
{
if(!d->file || pattern.size() > d->bufferSize)
return -1;
// The position in the file that the current buffer starts at.
ByteVector buffer;
// These variables are used to keep track of a partial match that happens at
// the end of a buffer.
/*
int previousPartialMatch = -1;
int beforePreviousPartialMatch = -1;
*/
// Save the location of the current read pointer. We will restore the
// position using seek() before all returns.
long originalPosition = tell();
// Start the search at the offset.
long bufferOffset;
if(fromOffset == 0) {
seek(-1 * int(d->bufferSize), End);
bufferOffset = tell();
}
else {
seek(fromOffset + -1 * int(d->bufferSize), Beginning);
bufferOffset = tell();
}
// See the notes in find() for an explanation of this algorithm.
for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
// TODO: (1) previous partial match
// (2) pattern contained in current buffer
long location = buffer.rfind(pattern);
if(location >= 0) {
seek(originalPosition);
return bufferOffset + location;
}
if(!before.isNull() && buffer.find(before) >= 0) {
seek(originalPosition);
return -1;
}
// TODO: (3) partial match
bufferOffset -= d->bufferSize;
seek(bufferOffset);
}
// Since we hit the end of the file, reset the status before continuing.
clear();
seek(originalPosition);
return -1;
}
void File::insert(const ByteVector &data, ulong start, ulong replace)
{
if(!d->file)
return;
if(data.size() == replace) {
seek(start);
writeBlock(data);
return;
}
else if(data.size() < replace) {
seek(start);
writeBlock(data);
removeBlock(start + data.size(), replace - data.size());
return;
}
// Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
// and avoid TagLib's high level API for rendering just copying parts of
// the file that don't contain tag data.
//
// Now I'll explain the steps in this ugliness:
// First, make sure that we're working with a buffer that is longer than
// the *differnce* in the tag sizes. We want to avoid overwriting parts
// that aren't yet in memory, so this is necessary.
ulong bufferLength = bufferSize();
while(data.size() - replace > bufferLength)
bufferLength += bufferSize();
// Set where to start the reading and writing.
long readPosition = start + replace;
long writePosition = start;
ByteVector buffer;
ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
// This is basically a special case of the loop below. Here we're just
// doing the same steps as below, but since we aren't using the same buffer
// size -- instead we're using the tag size -- this has to be handled as a
// special case. We're also using File::writeBlock() just for the tag.
// That's a bit slower than using char *'s so, we're only doing it here.
seek(readPosition);
int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
readPosition += bufferLength;
seek(writePosition);
writeBlock(data);
writePosition += data.size();
buffer = aboutToOverwrite;
// Ok, here's the main loop. We want to loop until the read fails, which
// means that we hit the end of the file.
while(bytesRead != 0) {
// Seek to the current read position and read the data that we're about
// to overwrite. Appropriately increment the readPosition.
seek(readPosition);
bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
aboutToOverwrite.resize(bytesRead);
readPosition += bufferLength;
// Check to see if we just read the last block. We need to call clear()
// if we did so that the last write succeeds.
if(ulong(bytesRead) < bufferLength)
clear();
// Seek to the write position and write our buffer. Increment the
// writePosition.
seek(writePosition);
fwrite(buffer.data(), sizeof(char), bufferLength, d->file);
writePosition += bufferLength;
// Make the current buffer the data that we read in the beginning.
buffer = aboutToOverwrite;
// Again, we need this for the last write. We don't want to write garbage
// at the end of our file, so we need to set the buffer size to the amount
// that we actually read.
bufferLength = bytesRead;
}
}
void File::removeBlock(ulong start, ulong length)
{
if(!d->file)
return;
ulong bufferLength = bufferSize();
long readPosition = start + length;
long writePosition = start;
ByteVector buffer(static_cast<uint>(bufferLength));
ulong bytesRead = true;
while(bytesRead != 0) {
seek(readPosition);
bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
buffer.resize(bytesRead);
readPosition += bytesRead;
// Check to see if we just read the last block. We need to call clear()
// if we did so that the last write succeeds.
if(bytesRead < bufferLength)
clear();
seek(writePosition);
fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
writePosition += bytesRead;
}
truncate(writePosition);
}
bool File::readOnly() const
{
return d->readOnly;
}
bool File::isReadable(const char *file)
{
return access(file, R_OK) == 0;
}
bool File::isOpen() const
{
return d->file;
}
bool File::isValid() const
{
return d->file && d->valid;
}
void File::seek(long offset, Position p)
{
if(!d->file) {
debug("File::seek() -- trying to seek in a file that isn't opened.");
return;
}
switch(p) {
case Beginning:
fseek(d->file, offset, SEEK_SET);
break;
case Current:
fseek(d->file, offset, SEEK_CUR);
break;
case End:
fseek(d->file, offset, SEEK_END);
break;
}
}
void File::clear()
{
clearerr(d->file);
}
long File::tell() const
{
return ftell(d->file);
}
long File::length()
{
if(!d->file)
return 0;
long curpos = tell();
seek(0, End);
long endpos = tell();
seek(curpos, Beginning);
return endpos;
}
bool File::isWritable(const char *file)
{
return access(file, W_OK) == 0;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
void File::setValid(bool valid)
{
d->valid = valid;
}
void File::truncate(long length)
{
ftruncate(fileno(d->file), length);
}
TagLib::uint File::bufferSize()
{
return FilePrivate::bufferSize;
}