Add FileStream as a copy of File's methods

This commit is contained in:
Lukáš Lalinský 2011-04-11 23:12:58 +02:00
parent 0b0cbc2e34
commit b53a577e38
6 changed files with 611 additions and 252 deletions

View File

@ -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
)

View File

@ -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<uint>(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<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;
// 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<uint>(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.
return d->stream->length();
}
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::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)
{
ftruncate(fileno(d->file), length);
}
TagLib::uint File::bufferSize()
{
return FilePrivate::bufferSize;
}
void File::setValid(bool valid)
{
d->valid = valid;
}

View File

@ -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 <stdio.h>
#include <string.h>
#include <sys/stat.h>
#ifdef _WIN32
# include <wchar.h>
# include <windows.h>
# include <io.h>
# define ftruncate _chsize
#else
# include <unistd.h>
#endif
#include <stdlib.h>
#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<uint>(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<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;
// 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<uint>(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;
}

View File

@ -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

View File

@ -31,7 +31,10 @@ using namespace TagLib;
// public members
////////////////////////////////////////////////////////////////////////////////
IOStream::IOStream()
{
}
IOStream::~IOStream()
{
delete d;
}

View File

@ -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 &);