From: Lukáš Lalinský Date: Mon, 11 Apr 2011 21:12:58 +0000 (+0200) Subject: Add FileStream as a copy of File's methods X-Git-Tag: v1.8beta~109^2~5 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b53a577e38c2fd33850d2f3f54c61d826b2d81bd;p=taglib Add FileStream as a copy of File's methods --- diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 53022c0a..3647caed 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -161,7 +161,9 @@ set(toolkit_SRCS toolkit/tstringlist.cpp toolkit/tbytevector.cpp toolkit/tbytevectorlist.cpp + toolkit/tiostream.cpp toolkit/tfile.cpp + toolkit/tfilestream.cpp toolkit/tdebug.cpp toolkit/unicode.cpp ) diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 63c28e5a..c4789221 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -24,6 +24,7 @@ ***************************************************************************/ #include "tfile.h" +#include "tfilestream.h" #include "tstring.h" #include "tdebug.h" @@ -70,55 +71,16 @@ class File::FilePrivate public: FilePrivate(FileName fileName); - FILE *file; IOStream *stream; - - FileNameHandle name; - - bool readOnly; bool valid; - ulong size; static const uint bufferSize = 1024; }; File::FilePrivate::FilePrivate(FileName fileName) : - file(0), - name(fileName), - readOnly(true), - valid(true), - size(0) + stream(0), + valid(true) { - // First try with read / write mode, if that fails, fall back to read only. - -#ifdef _WIN32 - - if(wcslen((const wchar_t *) fileName) > 0) { - - file = _wfopen(name, L"rb+"); - - if(file) - readOnly = false; - else - file = _wfopen(name, L"rb"); - - if(file) - return; - - } - -#endif - - file = fopen(name, "rb+"); - - if(file) - readOnly = false; - else - file = fopen(name, "rb"); - - if(!file) - { - debug("Could not open file " + String((const char *) name)); - } + stream = new FileStream(fileName); } //////////////////////////////////////////////////////////////////////////////// @@ -138,54 +100,29 @@ File::File(IOStream *stream) File::~File() { - if(d->file) - fclose(d->file); + if(d->stream) + delete d->stream; delete d; } FileName File::name() const { - return d->name; + return d->stream->name(); } ByteVector File::readBlock(ulong length) { - if(!d->file) { - debug("File::readBlock() -- Invalid File"); - return ByteVector::null; - } - - if(length == 0) - 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; + return d->stream->readBlock(length); } 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); + d->stream->writeBlock(data); } long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before) { - if(!d->file || pattern.size() > d->bufferSize) + if(!d->stream || pattern.size() > d->bufferSize) return -1; // The position in the file that the current buffer starts at. @@ -281,7 +218,7 @@ long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &be long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before) { - if(!d->file || pattern.size() > d->bufferSize) + if(!d->stream || pattern.size() > d->bufferSize) return -1; // The position in the file that the current buffer starts at. @@ -349,147 +286,22 @@ long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &b 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; - - // In case we've already reached the end of file... - - buffer.resize(bytesRead); - - // 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(!buffer.isEmpty()) { - - // 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), buffer.size(), d->file); - writePosition += buffer.size(); - - // 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; - } + d->stream->insert(data, start, replace); } 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 = 1; - - while(bytesRead != 0) { - seek(readPosition); - bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file); - 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); + d->stream->removeBlock(start, length); } bool File::readOnly() const { - return d->readOnly; -} - -bool File::isReadable(const char *file) -{ - return access(file, R_OK) == 0; + return d->stream->readOnly(); } bool File::isOpen() const { - return (d->file != NULL); + return d->stream->isOpen(); } bool File::isValid() const @@ -499,53 +311,32 @@ bool File::isValid() const void File::seek(long offset, Position p) { - if(!d->file) { - debug("File::seek() -- trying to seek in a file that isn't opened."); - return; - } + d->stream->seek(offset, IOStream::Position(p)); +} - 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::truncate(long length) +{ + d->stream->truncate(length); } void File::clear() { - clearerr(d->file); + d->stream->clear(); } long File::tell() const { - return ftell(d->file); + return d->stream->tell(); } 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); + return d->stream->length(); +} - d->size = endpos; - return endpos; +bool File::isReadable(const char *file) +{ + return access(file, R_OK) == 0; } bool File::isWritable(const char *file) @@ -557,17 +348,12 @@ bool File::isWritable(const char *file) // protected members //////////////////////////////////////////////////////////////////////////////// -void File::setValid(bool valid) -{ - d->valid = valid; -} - -void File::truncate(long length) +TagLib::uint File::bufferSize() { - ftruncate(fileno(d->file), length); + return FilePrivate::bufferSize; } -TagLib::uint File::bufferSize() +void File::setValid(bool valid) { - return FilePrivate::bufferSize; + d->valid = valid; } diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp new file mode 100644 index 00000000..ce2bf11f --- /dev/null +++ b/taglib/toolkit/tfilestream.cpp @@ -0,0 +1,380 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "tfilestream.h" +#include "tstring.h" +#include "tdebug.h" + +#include +#include +#include + +#ifdef _WIN32 +# include +# include +# include +# define ftruncate _chsize +#else +# include +#endif + +#include + +#ifndef R_OK +# define R_OK 4 +#endif +#ifndef W_OK +# define W_OK 2 +#endif + +using namespace TagLib; + +#ifdef _WIN32 + +typedef FileName FileNameHandle; + +#else + +struct FileNameHandle : public std::string +{ + FileNameHandle(FileName name) : std::string(name) {} + operator FileName () const { return c_str(); } +}; + +#endif + +class FileStream::FileStreamPrivate +{ +public: + FileStreamPrivate(FileName fileName); + + FILE *file; + + FileNameHandle name; + + bool readOnly; + ulong size; + static const uint bufferSize = 1024; +}; + +FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName) : + file(0), + name(fileName), + readOnly(true), + size(0) +{ + // First try with read / write mode, if that fails, fall back to read only. + +#ifdef _WIN32 + + if(wcslen((const wchar_t *) fileName) > 0) { + + file = _wfopen(name, L"rb+"); + + if(file) + readOnly = false; + else + file = _wfopen(name, L"rb"); + + if(file) + return; + + } + +#endif + + file = fopen(name, "rb+"); + + if(file) + readOnly = false; + else + file = fopen(name, "rb"); + + if(!file) + { + debug("Could not open file " + String((const char *) name)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +FileStream::FileStream(FileName file) +{ + d = new FileStreamPrivate(file); +} + +FileStream::~FileStream() +{ + if(d->file) + fclose(d->file); + delete d; +} + +FileName FileStream::name() const +{ + return d->name; +} + +ByteVector FileStream::readBlock(ulong length) +{ + if(!d->file) { + debug("FileStream::readBlock() -- Invalid File"); + return ByteVector::null; + } + + if(length == 0) + return ByteVector::null; + + if(length > FileStreamPrivate::bufferSize && + length > ulong(FileStream::length())) + { + length = FileStream::length(); + } + + ByteVector v(static_cast(length)); + const int count = fread(v.data(), sizeof(char), length, d->file); + v.resize(count); + return v; +} + +void FileStream::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); +} + +void FileStream::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; + + // In case we've already reached the end of file... + + buffer.resize(bytesRead); + + // 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(!buffer.isEmpty()) { + + // 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), buffer.size(), d->file); + writePosition += buffer.size(); + + // 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 FileStream::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 = 1; + + while(bytesRead != 0) { + seek(readPosition); + bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file); + 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 FileStream::readOnly() const +{ + return d->readOnly; +} + +bool FileStream::isOpen() const +{ + return (d->file != NULL); +} + +void FileStream::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 FileStream::clear() +{ + clearerr(d->file); +} + +long FileStream::tell() const +{ + return ftell(d->file); +} + +long FileStream::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; +} + +//////////////////////////////////////////////////////////////////////////////// +// protected members +//////////////////////////////////////////////////////////////////////////////// + +void FileStream::truncate(long length) +{ + ftruncate(fileno(d->file), length); +} + +TagLib::uint FileStream::bufferSize() +{ + return FileStreamPrivate::bufferSize; +} diff --git a/taglib/toolkit/tfilestream.h b/taglib/toolkit/tfilestream.h new file mode 100644 index 00000000..ac17ef06 --- /dev/null +++ b/taglib/toolkit/tfilestream.h @@ -0,0 +1,186 @@ +/*************************************************************************** + copyright : (C) 2002 - 2008 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., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_FILESTREAM_H +#define TAGLIB_FILESTREAM_H + +#include "taglib_export.h" +#include "taglib.h" +#include "tbytevector.h" +#include "tiostream.h" + +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 TAGLIB_EXPORT FileStream : public IOStream + { + public: + /*! + * Construct a File object and opens the \a file. \a file should be a + * be a C-string in the local file system encoding. + */ + FileStream(FileName file); + + /*! + * Destroys this FileStream instance. + */ + virtual ~FileStream(); + + /*! + * Returns the file name in the local file system encoding. + */ + FileName name() const; + + /*! + * 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; + + /*! + * 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 within the file. + */ + long tell() const; + + /*! + * Returns the length of the file. + */ + long length(); + + /*! + * Truncates the file to a \a length. + */ + void truncate(long length); + + protected: + + /*! + * Returns the buffer size that is used for internal buffering. + */ + static uint bufferSize(); + + private: + class FileStreamPrivate; + FileStreamPrivate *d; + }; + +} + +#endif diff --git a/taglib/toolkit/tiostream.cpp b/taglib/toolkit/tiostream.cpp index 12c4b6fe..f76fe095 100644 --- a/taglib/toolkit/tiostream.cpp +++ b/taglib/toolkit/tiostream.cpp @@ -31,7 +31,10 @@ using namespace TagLib; // public members //////////////////////////////////////////////////////////////////////////////// +IOStream::IOStream() +{ +} + IOStream::~IOStream() { - delete d; } diff --git a/taglib/toolkit/tiostream.h b/taglib/toolkit/tiostream.h index fee02e8f..8d7ba105 100644 --- a/taglib/toolkit/tiostream.h +++ b/taglib/toolkit/tiostream.h @@ -65,6 +65,8 @@ namespace TagLib { End }; + IOStream(); + /*! * Destroys this IOStream instance. */ @@ -120,11 +122,6 @@ namespace TagLib { */ virtual bool isOpen() const = 0; - /*! - * Returns true if the file is open and readble. - */ - virtual bool isValid() const = 0; - /*! * Move the I/O pointer to \a offset in the stream from position \a p. This * defaults to seeking from the beginning of the stream. @@ -148,6 +145,11 @@ namespace TagLib { */ virtual long length() = 0; + /*! + * Truncates the stream to a \a length. + */ + virtual void truncate(long length) = 0; + private: IOStream(const IOStream &); IOStream &operator=(const IOStream &);