418 lines
10 KiB
C++
418 lines
10 KiB
C++
|
/***************************************************************************
|
||
|
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
|
||
|
|
||
|
namespace {
|
||
|
FILE *openFile(const FileName &path, bool readOnly)
|
||
|
{
|
||
|
// Calls a proper variation of fopen() depending on the compiling environment.
|
||
|
|
||
|
#if defined(_WIN32)
|
||
|
|
||
|
# if defined(_MSC_VER) && (_MSC_VER >= 1400)
|
||
|
|
||
|
// Visual C++ 2005 or later.
|
||
|
|
||
|
FILE *file;
|
||
|
errno_t err;
|
||
|
|
||
|
if(wcslen(path) > 0)
|
||
|
err = _wfopen_s(&file, path, readOnly ? L"rb" : L"rb+");
|
||
|
else
|
||
|
err = fopen_s(&file, path, readOnly ? "rb" : "rb+");
|
||
|
|
||
|
if(err == 0)
|
||
|
return file;
|
||
|
else
|
||
|
return NULL;
|
||
|
|
||
|
# else // defined(_MSC_VER) && (_MSC_VER >= 1400)
|
||
|
|
||
|
// Visual C++.NET 2003 or earlier.
|
||
|
|
||
|
if(wcslen(path) > 0)
|
||
|
return _wfopen(path, readOnly ? L"rb" : L"rb+");
|
||
|
else
|
||
|
return fopen(path, readOnly ? "rb" : "rb+");
|
||
|
|
||
|
# endif // defined(_MSC_VER) && (_MSC_VER >= 1400)
|
||
|
|
||
|
#else // defined(_WIN32)
|
||
|
|
||
|
// Non-Win32
|
||
|
|
||
|
return fopen(path, readOnly ? "rb" : "rb+");
|
||
|
|
||
|
#endif // defined(_WIN32)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class FileStream::FileStreamPrivate
|
||
|
{
|
||
|
public:
|
||
|
FileStreamPrivate(FileName fileName, bool openReadOnly);
|
||
|
|
||
|
FILE *file;
|
||
|
|
||
|
FileNameHandle name;
|
||
|
|
||
|
bool readOnly;
|
||
|
ulong size;
|
||
|
static const uint bufferSize = 1024;
|
||
|
};
|
||
|
|
||
|
FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openReadOnly) :
|
||
|
file(0),
|
||
|
name(fileName),
|
||
|
readOnly(true),
|
||
|
size(0)
|
||
|
{
|
||
|
// First try with read / write mode, if that fails, fall back to read only.
|
||
|
|
||
|
if(!openReadOnly)
|
||
|
file = openFile(name, false);
|
||
|
|
||
|
if(file)
|
||
|
readOnly = false;
|
||
|
else
|
||
|
file = openFile(name, true);
|
||
|
|
||
|
if(!file) {
|
||
|
debug("Could not open file " + String((const char *) name));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
// public members
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
FileStream::FileStream(FileName file, bool openReadOnly)
|
||
|
{
|
||
|
d = new FileStreamPrivate(file, openReadOnly);
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
|
||
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
||
|
|
||
|
ftruncate(_fileno(d->file), length);
|
||
|
|
||
|
#else
|
||
|
|
||
|
ftruncate(fileno(d->file), length);
|
||
|
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
|
||
|
TagLib::uint FileStream::bufferSize()
|
||
|
{
|
||
|
return FileStreamPrivate::bufferSize;
|
||
|
}
|