From 69ea2a0b3450d6e619bfce2aba58eac2d072b10d Mon Sep 17 00:00:00 2001 From: moritz Date: Sat, 1 Aug 2009 10:54:00 +0000 Subject: [PATCH] Add and redistribute tfile.cpp, which I needed to modify in order to build Taglib 1.4 on Windows. This should also satisfy the LGPL. git-svn-id: https://svn.xiph.org/trunk/ezstream@16372 0101bb08-14d6-0310-b084-bc0e0c8e3800 --- win32/Makefile.am | 3 +- win32/tfile.cpp | 511 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 513 insertions(+), 1 deletion(-) create mode 100644 win32/tfile.cpp diff --git a/win32/Makefile.am b/win32/Makefile.am index 8cb3504..2fec610 100644 --- a/win32/Makefile.am +++ b/win32/Makefile.am @@ -7,7 +7,8 @@ EXTRA_DIST = \ ezstream.sln ezstream.vcproj \ libflac.vcproj libogg.vcproj libshout.vcproj libspeex.vcproj \ libtheora.vcproj libvorbis.vcproj libvorbisfile.vcproj libz.vcproj \ - taglib.vcproj taglib_c.vcproj + taglib.vcproj taglib_c.vcproj \ + tfile.cpp CLEANFILES = core *.core *~ .*~ ezstream.1.pdf diff --git a/win32/tfile.cpp b/win32/tfile.cpp new file mode 100644 index 0000000..9add593 --- /dev/null +++ b/win32/tfile.cpp @@ -0,0 +1,511 @@ +/*************************************************************************** + 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 +# define ftruncate _chsize +#endif /* WIN32 */ +#include +#include +#ifdef HAVE_UNISTD_H +# include +#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(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(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(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; +}