514 lines
16 KiB
C++
514 lines
16 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 "tfile.h"
|
|
#include "tfilestream.h"
|
|
#include "tstring.h"
|
|
#include "tdebug.h"
|
|
#include "tpropertymap.h"
|
|
|
|
#ifdef _WIN32
|
|
# include <windows.h>
|
|
# include <io.h>
|
|
#else
|
|
# include <stdio.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifndef R_OK
|
|
# define R_OK 4
|
|
#endif
|
|
#ifndef W_OK
|
|
# define W_OK 2
|
|
#endif
|
|
|
|
#include "asffile.h"
|
|
#include "mpegfile.h"
|
|
#include "vorbisfile.h"
|
|
#include "flacfile.h"
|
|
#include "oggflacfile.h"
|
|
#include "mpcfile.h"
|
|
#include "mp4file.h"
|
|
#include "wavpackfile.h"
|
|
#include "speexfile.h"
|
|
#include "opusfile.h"
|
|
#include "trueaudiofile.h"
|
|
#include "aifffile.h"
|
|
#include "wavfile.h"
|
|
#include "apefile.h"
|
|
#include "modfile.h"
|
|
#include "s3mfile.h"
|
|
#include "itfile.h"
|
|
#include "xmfile.h"
|
|
#include "mp4file.h"
|
|
#include "dsffile.h"
|
|
#include "dsdifffile.h"
|
|
|
|
using namespace TagLib;
|
|
|
|
class File::FilePrivate
|
|
{
|
|
public:
|
|
FilePrivate(IOStream *stream, bool owner) :
|
|
stream(stream),
|
|
streamOwner(owner),
|
|
valid(true) {}
|
|
|
|
~FilePrivate()
|
|
{
|
|
if(streamOwner)
|
|
delete stream;
|
|
}
|
|
|
|
IOStream *stream;
|
|
bool streamOwner;
|
|
bool valid;
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// public members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
File::File(FileName fileName) :
|
|
d(new FilePrivate(new FileStream(fileName), true))
|
|
{
|
|
}
|
|
|
|
File::File(IOStream *stream) :
|
|
d(new FilePrivate(stream, false))
|
|
{
|
|
}
|
|
|
|
File::~File()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
FileName File::name() const
|
|
{
|
|
return d->stream->name();
|
|
}
|
|
|
|
PropertyMap File::properties() const
|
|
{
|
|
// ugly workaround until this method is virtual
|
|
if(dynamic_cast<const APE::File* >(this))
|
|
return dynamic_cast<const APE::File* >(this)->properties();
|
|
if(dynamic_cast<const FLAC::File* >(this))
|
|
return dynamic_cast<const FLAC::File* >(this)->properties();
|
|
if(dynamic_cast<const IT::File* >(this))
|
|
return dynamic_cast<const IT::File* >(this)->properties();
|
|
if(dynamic_cast<const Mod::File* >(this))
|
|
return dynamic_cast<const Mod::File* >(this)->properties();
|
|
if(dynamic_cast<const MPC::File* >(this))
|
|
return dynamic_cast<const MPC::File* >(this)->properties();
|
|
if(dynamic_cast<const MPEG::File* >(this))
|
|
return dynamic_cast<const MPEG::File* >(this)->properties();
|
|
if(dynamic_cast<const Ogg::FLAC::File* >(this))
|
|
return dynamic_cast<const Ogg::FLAC::File* >(this)->properties();
|
|
if(dynamic_cast<const Ogg::Speex::File* >(this))
|
|
return dynamic_cast<const Ogg::Speex::File* >(this)->properties();
|
|
if(dynamic_cast<const Ogg::Opus::File* >(this))
|
|
return dynamic_cast<const Ogg::Opus::File* >(this)->properties();
|
|
if(dynamic_cast<const Ogg::Vorbis::File* >(this))
|
|
return dynamic_cast<const Ogg::Vorbis::File* >(this)->properties();
|
|
if(dynamic_cast<const RIFF::AIFF::File* >(this))
|
|
return dynamic_cast<const RIFF::AIFF::File* >(this)->properties();
|
|
if(dynamic_cast<const RIFF::WAV::File* >(this))
|
|
return dynamic_cast<const RIFF::WAV::File* >(this)->properties();
|
|
if(dynamic_cast<const S3M::File* >(this))
|
|
return dynamic_cast<const S3M::File* >(this)->properties();
|
|
if(dynamic_cast<const TrueAudio::File* >(this))
|
|
return dynamic_cast<const TrueAudio::File* >(this)->properties();
|
|
if(dynamic_cast<const WavPack::File* >(this))
|
|
return dynamic_cast<const WavPack::File* >(this)->properties();
|
|
if(dynamic_cast<const XM::File* >(this))
|
|
return dynamic_cast<const XM::File* >(this)->properties();
|
|
if(dynamic_cast<const MP4::File* >(this))
|
|
return dynamic_cast<const MP4::File* >(this)->properties();
|
|
if(dynamic_cast<const ASF::File* >(this))
|
|
return dynamic_cast<const ASF::File* >(this)->properties();
|
|
if(dynamic_cast<const DSF::File* >(this))
|
|
return dynamic_cast<const DSF::File* >(this)->properties();
|
|
if(dynamic_cast<const DSDIFF::File* >(this))
|
|
return dynamic_cast<const DSDIFF::File* >(this)->properties();
|
|
return tag()->properties();
|
|
}
|
|
|
|
void File::removeUnsupportedProperties(const StringList &properties)
|
|
{
|
|
// here we only consider those formats that could possibly contain
|
|
// unsupported properties
|
|
if(dynamic_cast<APE::File* >(this))
|
|
dynamic_cast<APE::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<FLAC::File* >(this))
|
|
dynamic_cast<FLAC::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<MPC::File* >(this))
|
|
dynamic_cast<MPC::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<MPEG::File* >(this))
|
|
dynamic_cast<MPEG::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
|
|
dynamic_cast<Ogg::Vorbis::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<RIFF::AIFF::File* >(this))
|
|
dynamic_cast<RIFF::AIFF::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<RIFF::WAV::File* >(this))
|
|
dynamic_cast<RIFF::WAV::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<TrueAudio::File* >(this))
|
|
dynamic_cast<TrueAudio::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<WavPack::File* >(this))
|
|
dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<MP4::File* >(this))
|
|
dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<ASF::File* >(this))
|
|
dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<DSF::File* >(this))
|
|
dynamic_cast<DSF::File* >(this)->removeUnsupportedProperties(properties);
|
|
else if(dynamic_cast<DSDIFF::File* >(this))
|
|
dynamic_cast<DSDIFF::File* >(this)->removeUnsupportedProperties(properties);
|
|
else
|
|
tag()->removeUnsupportedProperties(properties);
|
|
}
|
|
|
|
PropertyMap File::setProperties(const PropertyMap &properties)
|
|
{
|
|
if(dynamic_cast<APE::File* >(this))
|
|
return dynamic_cast<APE::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<FLAC::File* >(this))
|
|
return dynamic_cast<FLAC::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<IT::File* >(this))
|
|
return dynamic_cast<IT::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<Mod::File* >(this))
|
|
return dynamic_cast<Mod::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<MPC::File* >(this))
|
|
return dynamic_cast<MPC::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<MPEG::File* >(this))
|
|
return dynamic_cast<MPEG::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<Ogg::FLAC::File* >(this))
|
|
return dynamic_cast<Ogg::FLAC::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<Ogg::Speex::File* >(this))
|
|
return dynamic_cast<Ogg::Speex::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<Ogg::Opus::File* >(this))
|
|
return dynamic_cast<Ogg::Opus::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
|
|
return dynamic_cast<Ogg::Vorbis::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<RIFF::AIFF::File* >(this))
|
|
return dynamic_cast<RIFF::AIFF::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<RIFF::WAV::File* >(this))
|
|
return dynamic_cast<RIFF::WAV::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<S3M::File* >(this))
|
|
return dynamic_cast<S3M::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<TrueAudio::File* >(this))
|
|
return dynamic_cast<TrueAudio::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<WavPack::File* >(this))
|
|
return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<XM::File* >(this))
|
|
return dynamic_cast<XM::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<MP4::File* >(this))
|
|
return dynamic_cast<MP4::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<ASF::File* >(this))
|
|
return dynamic_cast<ASF::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<DSF::File* >(this))
|
|
return dynamic_cast<DSF::File* >(this)->setProperties(properties);
|
|
else if(dynamic_cast<DSDIFF::File* >(this))
|
|
return dynamic_cast<DSDIFF::File* >(this)->setProperties(properties);
|
|
else
|
|
return tag()->setProperties(properties);
|
|
}
|
|
|
|
ByteVector File::readBlock(unsigned long length)
|
|
{
|
|
return d->stream->readBlock(length);
|
|
}
|
|
|
|
void File::writeBlock(const ByteVector &data)
|
|
{
|
|
d->stream->writeBlock(data);
|
|
}
|
|
|
|
long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
|
|
{
|
|
if(!d->stream || pattern.size() > 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 iteration, 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(bufferSize()); buffer.size() > 0; buffer = readBlock(bufferSize())) {
|
|
|
|
// (1) previous partial match
|
|
|
|
if(previousPartialMatch >= 0 && int(bufferSize()) > previousPartialMatch) {
|
|
const int patternOffset = (bufferSize() - previousPartialMatch);
|
|
if(buffer.containsAt(pattern, 0, patternOffset)) {
|
|
seek(originalPosition);
|
|
return bufferOffset - bufferSize() + previousPartialMatch;
|
|
}
|
|
}
|
|
|
|
if(!before.isEmpty() && beforePreviousPartialMatch >= 0 && int(bufferSize()) > beforePreviousPartialMatch) {
|
|
const int beforeOffset = (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.isEmpty() && buffer.find(before) >= 0) {
|
|
seek(originalPosition);
|
|
return -1;
|
|
}
|
|
|
|
// (3) partial match
|
|
|
|
previousPartialMatch = buffer.endsWithPartialMatch(pattern);
|
|
|
|
if(!before.isEmpty())
|
|
beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
|
|
|
|
bufferOffset += 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->stream || pattern.size() > 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.
|
|
|
|
if(fromOffset == 0)
|
|
fromOffset = length();
|
|
|
|
long bufferLength = bufferSize();
|
|
long bufferOffset = fromOffset + pattern.size();
|
|
|
|
// See the notes in find() for an explanation of this algorithm.
|
|
|
|
while(true) {
|
|
|
|
if(bufferOffset > bufferLength) {
|
|
bufferOffset -= bufferLength;
|
|
}
|
|
else {
|
|
bufferLength = bufferOffset;
|
|
bufferOffset = 0;
|
|
}
|
|
seek(bufferOffset);
|
|
|
|
buffer = readBlock(bufferLength);
|
|
if(buffer.isEmpty())
|
|
break;
|
|
|
|
// TODO: (1) previous partial match
|
|
|
|
// (2) pattern contained in current buffer
|
|
|
|
const long location = buffer.rfind(pattern);
|
|
if(location >= 0) {
|
|
seek(originalPosition);
|
|
return bufferOffset + location;
|
|
}
|
|
|
|
if(!before.isEmpty() && buffer.find(before) >= 0) {
|
|
seek(originalPosition);
|
|
return -1;
|
|
}
|
|
|
|
// TODO: (3) partial match
|
|
}
|
|
|
|
// Since we hit the end of the file, reset the status before continuing.
|
|
|
|
clear();
|
|
|
|
seek(originalPosition);
|
|
|
|
return -1;
|
|
}
|
|
|
|
void File::insert(const ByteVector &data, unsigned long start, unsigned long replace)
|
|
{
|
|
d->stream->insert(data, start, replace);
|
|
}
|
|
|
|
void File::removeBlock(unsigned long start, unsigned long length)
|
|
{
|
|
d->stream->removeBlock(start, length);
|
|
}
|
|
|
|
bool File::readOnly() const
|
|
{
|
|
return d->stream->readOnly();
|
|
}
|
|
|
|
bool File::isOpen() const
|
|
{
|
|
return d->stream->isOpen();
|
|
}
|
|
|
|
bool File::isValid() const
|
|
{
|
|
return isOpen() && d->valid;
|
|
}
|
|
|
|
void File::seek(long offset, Position p)
|
|
{
|
|
d->stream->seek(offset, IOStream::Position(p));
|
|
}
|
|
|
|
void File::truncate(long length)
|
|
{
|
|
d->stream->truncate(length);
|
|
}
|
|
|
|
void File::clear()
|
|
{
|
|
d->stream->clear();
|
|
}
|
|
|
|
long File::tell() const
|
|
{
|
|
return d->stream->tell();
|
|
}
|
|
|
|
long File::length()
|
|
{
|
|
return d->stream->length();
|
|
}
|
|
|
|
bool File::isReadable(const char *file)
|
|
{
|
|
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
|
|
|
return _access_s(file, R_OK) == 0;
|
|
|
|
#else
|
|
|
|
return access(file, R_OK) == 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
bool File::isWritable(const char *file)
|
|
{
|
|
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // VC++2005 or later
|
|
|
|
return _access_s(file, W_OK) == 0;
|
|
|
|
#else
|
|
|
|
return access(file, W_OK) == 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// protected members
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
unsigned int File::bufferSize()
|
|
{
|
|
return 1024;
|
|
}
|
|
|
|
void File::setValid(bool valid)
|
|
{
|
|
d->valid = valid;
|
|
}
|
|
|