mirror of
https://gitlab.xiph.org/xiph/ezstream.git
synced 2024-11-03 04:17:18 -05:00
512 lines
13 KiB
C++
512 lines
13 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"
|
||
|
|
||
|
#ifdef WIN32
|
||
|
# include <io.h>
|
||
|
# define ftruncate _chsize
|
||
|
#endif /* WIN32 */
|
||
|
#include <stdio.h>
|
||
|
#include <sys/stat.h>
|
||
|
#ifdef HAVE_UNISTD_H
|
||
|
# include <unistd.h>
|
||
|
#endif /* HAVE_UNISTD_H */
|
||
|
|
||
|
#if !defined(R_OK) || !defined(W_OK)
|
||
|
# undef R_OK
|
||
|
# undef W_OK
|
||
|
# define R_OK 4
|
||
|
# define W_OK 2
|
||
|
#endif /* !R_OK || !W_OK */
|
||
|
|
||
|
using namespace TagLib;
|
||
|
|
||
|
class File::FilePrivate
|
||
|
{
|
||
|
public:
|
||
|
FilePrivate(const char *fileName) :
|
||
|
file(0),
|
||
|
name(fileName),
|
||
|
readOnly(true),
|
||
|
valid(true),
|
||
|
size(0)
|
||
|
{}
|
||
|
|
||
|
~FilePrivate()
|
||
|
{
|
||
|
free((void *)name);
|
||
|
}
|
||
|
|
||
|
FILE *file;
|
||
|
const char *name;
|
||
|
bool readOnly;
|
||
|
bool valid;
|
||
|
ulong size;
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
if(length > FilePrivate::bufferSize &&
|
||
|
length > ulong(File::length()))
|
||
|
{
|
||
|
length = File::length();
|
||
|
}
|
||
|
|
||
|
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()
|
||
|
{
|
||
|
// Do some caching in case we do multiple calls.
|
||
|
|
||
|
if(d->size > 0)
|
||
|
return d->size;
|
||
|
|
||
|
if(!d->file)
|
||
|
return 0;
|
||
|
|
||
|
long curpos = tell();
|
||
|
|
||
|
seek(0, End);
|
||
|
long endpos = tell();
|
||
|
|
||
|
seek(curpos, Beginning);
|
||
|
|
||
|
d->size = endpos;
|
||
|
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;
|
||
|
}
|