Mercurial > audlegacy
changeset 2064:230e36118438 trunk
[svn] - add work in progress TagLib::TagVFSFile class.
author | nenolod |
---|---|
date | Thu, 07 Dec 2006 00:12:31 -0800 |
parents | a3ad15fb0f34 |
children | 598564ddc4e9 |
files | ChangeLog taglib-vfs/Makefile taglib-vfs/tvfsfile.cxx taglib-vfs/tvfsfile.h |
diffstat | 4 files changed, 790 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Wed Dec 06 03:41:04 2006 -0800 +++ b/ChangeLog Thu Dec 07 00:12:31 2006 -0800 @@ -1,3 +1,13 @@ +2006-12-06 11:41:04 +0000 William Pitcock <nenolod@nenolod.net> + revision [3129] + - playlist_scan_thread_is_going should not be TRUE if the playlist is + not visible, otherwise spinlocking may occur if on demand metadata + reading is enabled. + + trunk/audacious/playlist.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + + 2006-12-05 08:31:38 +0000 William Pitcock <nenolod@nenolod.net> revision [3127] - rewrite input_check_file()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/taglib-vfs/Makefile Thu Dec 07 00:12:31 2006 -0800 @@ -0,0 +1,32 @@ +include ../mk/rules.mk +include ../mk/init.mk + +beepincludedir = $(includedir)/audacious + +OBJECTIVE_LIBS = libtagvfs.so + +LDFLAGS += $(AUDLDFLAGS) + +CXXFLAGS += $(LIBLDFLAGS) \ + $(GTK_CFLAGS) \ + $(LIBGLADE_CFLAGS) \ + $(BEEP_DEFINES) \ + $(ARCH_DEFINES) \ + -D_AUDACIOUS_CORE \ + -I.. \ + -I../intl \ + -I/usr/include/taglib + +HEADERS = \ + tvfsfile.h + +SOURCES = tvfsfile.cxx + +OBJECTS = ${SOURCES:.cxx=.o} + +beepinclude_HEADERS = plugin.h output.h input.h + +desktop_DATA = audacious.desktop +desktopdir = $(datadir)/applications + +include ../mk/objective.mk
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/taglib-vfs/tvfsfile.cxx Thu Dec 07 00:12:31 2006 -0800 @@ -0,0 +1,501 @@ +/*************************************************************************** + 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 "tvfsfile.h" +#include "tstring.h" + +#include <stdio.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libaudacious/vfs.h" + +using namespace TagLib; + +class TagVFSFile::TagVFSFilePrivate +{ +public: + TagVFSFilePrivate(const char *fileName) : + file(0), + name(fileName), + readOnly(true), + valid(true), + size(0) + {} + + ~TagVFSFilePrivate() + { + free((void *)name); + } + + VFSFile *file; + const char *name; + bool readOnly; + bool valid; + ulong size; + static const uint bufferSize = 1024; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +TagVFSFile::TagVFSFile(const char *file) +{ + d = new TagVFSFilePrivate(::strdup(file)); + + d->readOnly = !isWritable(file); + d->file = vfs_fopen(file, d->readOnly ? "r" : "r+"); + +#if 0 + if(!d->file) + puts("Could not open file " + String(file)); +#endif +} + +TagVFSFile::~TagVFSFile() +{ + if(d->file) + vfs_fclose(d->file); + delete d; +} + +const char *TagVFSFile::name() const +{ + return d->name; +} + +ByteVector TagVFSFile::readBlock(ulong length) +{ + if(!d->file) { + puts("File::readBlock() -- Invalid File"); + return ByteVector::null; + } + + if(length > TagVFSFilePrivate::bufferSize && + length > ulong(TagVFSFile::length())) + { + length = TagVFSFile::length(); + } + + ByteVector v(static_cast<uint>(length)); + const int count = vfs_fread(v.data(), sizeof(char), length, d->file); + v.resize(count); + return v; +} + +void TagVFSFile::writeBlock(const ByteVector &data) +{ + if(!d->file) + return; + + if(d->readOnly) { + puts("File::writeBlock() -- attempted to write to a file that is not writable"); + return; + } + + vfs_fwrite(data.data(), sizeof(char), data.size(), d->file); +} + +long TagVFSFile::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 TagVFSFile::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 TagVFSFile::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 = vfs_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 = vfs_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); + vfs_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 TagVFSFile::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 = vfs_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); + vfs_fwrite(buffer.data(), sizeof(char), bytesRead, d->file); + writePosition += bytesRead; + } + truncate(writePosition); +} + +bool TagVFSFile::readOnly() const +{ + return d->readOnly; +} + +bool TagVFSFile::isReadable(const char *file) +{ + return access(file, R_OK) == 0; +} + +bool TagVFSFile::isOpen() const +{ + return d->file; +} + +bool TagVFSFile::isValid() const +{ + return d->file && d->valid; +} + +void TagVFSFile::seek(long offset, Position p) +{ + if(!d->file) { + puts("File::seek() -- trying to seek in a file that isn't opened."); + return; + } + + switch(p) { + case Beginning: + vfs_fseek(d->file, offset, SEEK_SET); + break; + case Current: + vfs_fseek(d->file, offset, SEEK_CUR); + break; + case End: + vfs_fseek(d->file, offset, SEEK_END); + break; + } +} + +void TagVFSFile::clear() +{ + // don't do anything here at the moment +} + +long TagVFSFile::tell() const +{ + return vfs_ftell(d->file); +} + +long TagVFSFile::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 TagVFSFile::isWritable(const char *file) +{ + return access(file, W_OK) == 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void TagVFSFile::setValid(bool valid) +{ + d->valid = valid; +} + +void TagVFSFile::truncate(long length) +{ + vfs_truncate(d->file, length); +} + +TagLib::uint TagVFSFile::bufferSize() +{ + return TagVFSFilePrivate::bufferSize; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/taglib-vfs/tvfsfile.h Thu Dec 07 00:12:31 2006 -0800 @@ -0,0 +1,247 @@ +/*************************************************************************** + 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 * + ***************************************************************************/ + +#ifndef TAGLIB_VFSFILE_H +#define TAGLIB_VFSFILE_H + +#include "taglib.h" +#include "tbytevector.h" +#include "tfile.h" + +#ifndef _AUDACIOUS_CORE +# include <audacious/vfs.h> +#else +# include "libaudacious/vfs.h" +#endif + +namespace TagLib { + + class String; + class Tag; + class AudioProperties; + + //! A file class with some useful methods for tag manipulation + + /*! + * This class is a basic file class with some methods that are particularly + * useful for tag editors. It has methods to take advantage of + * ByteVector and a binary search method for finding patterns in a file. + */ + + class TagVFSFile + { + public: + /*! + * Position in the file used for seeking. + */ + enum Position { + //! Seek from the beginning of the file. + Beginning, + //! Seek from the current position in the file. + Current, + //! Seek from the end of the file. + End + }; + + /*! + * Destroys this File instance. + */ + virtual ~TagVFSFile(); + + /*! + * Returns the file name in the local file system encoding. + */ + const char *name() const; + + /*! + * Returns a pointer to this file's tag. This should be reimplemented in + * the concrete subclasses. + */ + virtual Tag *tag() const = 0; + + /*! + * Returns a pointer to this file's audio properties. This should be + * reimplemented in the concrete subclasses. If no audio properties were + * read then this will return a null pointer. + */ + virtual AudioProperties *audioProperties() const = 0; + + /*! + * Save the file and its associated tags. This should be reimplemented in + * the concrete subclasses. Returns true if the save succeeds. + */ + virtual bool save() = 0; + + /*! + * Reads a block of size \a length at the current get pointer. + */ + ByteVector readBlock(ulong length); + + /*! + * Attempts to write the block \a data at the current get pointer. If the + * file is currently only opened read only -- i.e. readOnly() returns true -- + * this attempts to reopen the file in read/write mode. + * + * \note This should be used instead of using the streaming output operator + * for a ByteVector. And even this function is significantly slower than + * doing output with a char[]. + */ + void writeBlock(const ByteVector &data); + + /*! + * Returns the offset in the file that \a pattern occurs at or -1 if it can + * not be found. If \a before is set, the search will only continue until the + * pattern \a before is found. This is useful for tagging purposes to search + * for a tag before the synch frame. + * + * Searching starts at \a fromOffset, which defaults to the beginning of the + * file. + * + * \note This has the practial limitation that \a pattern can not be longer + * than the buffer size used by readBlock(). Currently this is 1024 bytes. + */ + long find(const ByteVector &pattern, + long fromOffset = 0, + const ByteVector &before = ByteVector::null); + + /*! + * Returns the offset in the file that \a pattern occurs at or -1 if it can + * not be found. If \a before is set, the search will only continue until the + * pattern \a before is found. This is useful for tagging purposes to search + * for a tag before the synch frame. + * + * Searching starts at \a fromOffset and proceeds from the that point to the + * beginning of the file and defaults to the end of the file. + * + * \note This has the practial limitation that \a pattern can not be longer + * than the buffer size used by readBlock(). Currently this is 1024 bytes. + */ + long rfind(const ByteVector &pattern, + long fromOffset = 0, + const ByteVector &before = ByteVector::null); + + /*! + * Insert \a data at position \a start in the file overwriting \a replace + * bytes of the original content. + * + * \note This method is slow since it requires rewriting all of the file + * after the insertion point. + */ + void insert(const ByteVector &data, ulong start = 0, ulong replace = 0); + + /*! + * Removes a block of the file starting a \a start and continuing for + * \a length bytes. + * + * \note This method is slow since it involves rewriting all of the file + * after the removed portion. + */ + void removeBlock(ulong start = 0, ulong length = 0); + + /*! + * Returns true if the file is read only (or if the file can not be opened). + */ + bool readOnly() const; + + /*! + * Since the file can currently only be opened as an argument to the + * constructor (sort-of by design), this returns if that open succeeded. + */ + bool isOpen() const; + + /*! + * Returns true if the file is open and readble and valid information for + * the Tag and / or AudioProperties was found. + */ + bool isValid() const; + + /*! + * Move the I/O pointer to \a offset in the file from position \a p. This + * defaults to seeking from the beginning of the file. + * + * \see Position + */ + void seek(long offset, Position p = Beginning); + + /*! + * Reset the end-of-file and error flags on the file. + */ + void clear(); + + /*! + * Returns the current offset withing the file. + */ + long tell() const; + + /*! + * Returns the length of the file. + */ + long length(); + + /*! + * Returns true if \a file can be opened for reading. If the file does not + * exist, this will return false. + */ + static bool isReadable(const char *file); + + /*! + * Returns true if \a file can be opened for writing. + */ + static bool isWritable(const char *name); + + protected: + /*! + * Construct a File object and opens the \a file. \a file should be a + * be a C-string in the local file system encoding. + * + * \note Constructor is protected since this class should only be + * instantiated through subclasses. + */ + TagVFSFile(const char *file); + + /*! + * Marks the file as valid or invalid. + * + * \see isValid() + */ + void setValid(bool valid); + + /*! + * Truncates the file to a \a length. + */ + void truncate(long length); + + /*! + * Returns the buffer size that is used for internal buffering. + */ + static uint bufferSize(); + + private: + TagVFSFile(const TagVFSFile &); + TagVFSFile &operator=(TagVFSFile &); + + class TagVFSFilePrivate; + TagVFSFilePrivate *d; + }; + +} + +#endif