diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index 5b739d6f..e5436cea 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -47,12 +47,12 @@ static bool stringManagementEnabled = true; void taglib_set_strings_unicode(BOOL unicode) { - unicodeStrings = bool(unicode); + unicodeStrings = (unicode != 0); } void taglib_set_string_management_enabled(BOOL management) { - stringManagementEnabled = bool(management); + stringManagementEnabled = (management != 0); } void taglib_free(void* pointer) @@ -66,32 +66,32 @@ void taglib_free(void* pointer) TagLib_File *taglib_file_new(const char *filename) { - return reinterpret_cast(FileRef::create(filename)); + return reinterpret_cast(new FileRef(filename)); } TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) { switch(type) { case TagLib_File_MPEG: - return reinterpret_cast(new MPEG::File(filename)); + return reinterpret_cast(new FileRef(new MPEG::File(filename))); case TagLib_File_OggVorbis: - return reinterpret_cast(new Ogg::Vorbis::File(filename)); + return reinterpret_cast(new FileRef(new Ogg::Vorbis::File(filename))); case TagLib_File_FLAC: - return reinterpret_cast(new FLAC::File(filename)); + return reinterpret_cast(new FileRef(new FLAC::File(filename))); case TagLib_File_MPC: - return reinterpret_cast(new MPC::File(filename)); + return reinterpret_cast(new FileRef(new MPC::File(filename))); case TagLib_File_OggFlac: - return reinterpret_cast(new Ogg::FLAC::File(filename)); + return reinterpret_cast(new FileRef(new Ogg::FLAC::File(filename))); case TagLib_File_WavPack: - return reinterpret_cast(new WavPack::File(filename)); + return reinterpret_cast(new FileRef(new WavPack::File(filename))); case TagLib_File_Speex: - return reinterpret_cast(new Ogg::Speex::File(filename)); + return reinterpret_cast(new FileRef(new Ogg::Speex::File(filename))); case TagLib_File_TrueAudio: - return reinterpret_cast(new TrueAudio::File(filename)); + return reinterpret_cast(new FileRef(new TrueAudio::File(filename))); case TagLib_File_MP4: - return reinterpret_cast(new MP4::File(filename)); + return reinterpret_cast(new FileRef(new MP4::File(filename))); case TagLib_File_ASF: - return reinterpret_cast(new ASF::File(filename)); + return reinterpret_cast(new FileRef(new ASF::File(filename))); default: return 0; } @@ -101,29 +101,29 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) void taglib_file_free(TagLib_File *file) { - delete reinterpret_cast(file); + delete reinterpret_cast(file); } BOOL taglib_file_is_valid(const TagLib_File *file) { - return reinterpret_cast(file)->isValid(); + return reinterpret_cast(file)->isValid(); } TagLib_Tag *taglib_file_tag(const TagLib_File *file) { - const File *f = reinterpret_cast(file); + const FileRef *f = reinterpret_cast(file); return reinterpret_cast(f->tag()); } const TagLib_AudioProperties *taglib_file_audioproperties(const TagLib_File *file) { - const File *f = reinterpret_cast(file); + const FileRef *f = reinterpret_cast(file); return reinterpret_cast(f->audioProperties()); } BOOL taglib_file_save(TagLib_File *file) { - return reinterpret_cast(file)->save(); + return reinterpret_cast(file)->save(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index f24f4c56..77c78069 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -59,24 +59,130 @@ using namespace TagLib; +namespace +{ + typedef List ResolverList; + typedef ResolverList::ConstIterator ResolverConstIterator; + + ResolverList fileTypeResolvers; + + RefCountPtr create( + FileName fileName, + bool readAudioProperties, + AudioProperties::ReadStyle audioPropertiesStyle) + { + RefCountPtr file; + for(ResolverConstIterator it = fileTypeResolvers.begin(); it != fileTypeResolvers.end(); ++it) + { + file.reset((*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle)); + if(file) + return file; + } + + String ext; + +#ifdef _WIN32 + // Avoids direct conversion from FileName to String + // because String can't decode strings in local encodings properly. + + if(!fileName.wstr().empty()) { + const wchar_t *pext = PathFindExtensionW(fileName.wstr().c_str()); + if(*pext == L'.') + ext = String(pext + 1).upper(); + } + else { + const char *pext = PathFindExtensionA(fileName.str().c_str()); + if(*pext == '.') + ext = String(pext + 1).upper(); + } +#else + { + String s = fileName; + const size_t pos = s.rfind("."); + if(pos != String::npos) + ext = s.substr(pos + 1).upper(); + } +#endif + + // If this list is updated, the method defaultFileExtensions() should also be + // updated. However at some point that list should be created at the same time + // that a default file type resolver is created. + + if(!ext.isEmpty()) { + if(ext == "MP3") + file.reset(new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "OGG") + file.reset(new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "OGA") { + /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ + file.reset(new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle)); + if(!file->isValid()) + file.reset(new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle)); + } + else if(ext == "FLAC") + file.reset(new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "MPC") + file.reset(new MPC::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "WV") + file.reset(new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "SPX") + file.reset(new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "OPUS") + file.reset(new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "TTA") + file.reset(new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") + file.reset(new MP4::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "WMA" || ext == "ASF") + file.reset(new ASF::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "AIF" || ext == "AIFF") + file.reset(new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "WAV") + file.reset(new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "APE") + file.reset(new APE::File(fileName, readAudioProperties, audioPropertiesStyle)); + // module, nst and wow are possible but uncommon extensions + else if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") + file.reset(new Mod::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "S3M") + file.reset(new S3M::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "IT") + file.reset(new IT::File(fileName, readAudioProperties, audioPropertiesStyle)); + else if(ext == "XM") + file.reset(new XM::File(fileName, readAudioProperties, audioPropertiesStyle)); + } + + return file; + } +} + class FileRef::FileRefPrivate { public: - FileRefPrivate(File *f) : file(f) {} + FileRefPrivate() + : file() + { + } + + FileRefPrivate(File *f) + : file(f) + { + } + + FileRefPrivate(RefCountPtr f) + : file(f) + { + } RefCountPtr file; - - static List fileTypeResolvers; }; -List FileRef::FileRefPrivate::fileTypeResolvers; - //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// FileRef::FileRef() - : d(new FileRefPrivate(0)) + : d(new FileRefPrivate()) { } @@ -118,6 +224,36 @@ Tag *FileRef::tag() const return d->file->tag(); } +PropertyMap FileRef::properties() const +{ + if(isNull()) { + debug("FileRef::properties() - Called without a valid file."); + return PropertyMap(); + } + + return d->file->properties(); +} + +void FileRef::removeUnsupportedProperties(const StringList& properties) +{ + if(isNull()) { + debug("FileRef::removeUnsupportedProperties() - Called without a valid file."); + return; + } + + d->file->removeUnsupportedProperties(properties); +} + +PropertyMap FileRef::setProperties(const PropertyMap &properties) +{ + if(isNull()) { + debug("FileRef::setProperties() - Called without a valid file."); + return PropertyMap(); + } + + return d->file->setProperties(properties); +} + AudioProperties *FileRef::audioProperties() const { if(isNull()) { @@ -143,7 +279,7 @@ bool FileRef::save() const FileRef::FileTypeResolver *FileRef::addFileTypeResolver(const FileRef::FileTypeResolver *resolver) // static { - FileRefPrivate::fileTypeResolvers.prepend(resolver); + fileTypeResolvers.prepend(resolver); return resolver; } @@ -182,9 +318,14 @@ StringList FileRef::defaultFileExtensions() return l; } +bool FileRef::isValid() const +{ + return (d->file && d->file->isValid()); +} + bool FileRef::isNull() const { - return !d->file || !d->file->isValid(); + return !isValid(); } FileRef &FileRef::operator=(const FileRef &ref) @@ -212,93 +353,3 @@ bool FileRef::operator!=(const FileRef &ref) const { return ref.d->file != d->file; } - -File *FileRef::create(FileName fileName, bool readAudioProperties, - AudioProperties::ReadStyle audioPropertiesStyle) // static -{ - - List::ConstIterator it = FileRefPrivate::fileTypeResolvers.begin(); - - for(; it != FileRefPrivate::fileTypeResolvers.end(); ++it) { - File *file = (*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle); - if(file) - return file; - } - - String ext; - -#ifdef _WIN32 - // Avoids direct conversion from FileName to String - // because String can't decode strings in local encodings properly. - - if(!fileName.wstr().empty()) { - const wchar_t *pext = PathFindExtensionW(fileName.wstr().c_str()); - if(*pext == L'.') - ext = String(pext + 1).upper(); - } - else { - const char *pext = PathFindExtensionA(fileName.str().c_str()); - if(*pext == '.') - ext = String(pext + 1).upper(); - } -#else - { - String s = fileName; - const size_t pos = s.rfind("."); - if(pos != String::npos) - ext = s.substr(pos + 1).upper(); - } -#endif - - // If this list is updated, the method defaultFileExtensions() should also be - // updated. However at some point that list should be created at the same time - // that a default file type resolver is created. - - if(!ext.isEmpty()) { - if(ext == "MP3") - return new MPEG::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OGG") - return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OGA") { - /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ - File *file = new Ogg::FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); - if (file->isValid()) - return file; - delete file; - return new Ogg::Vorbis::File(fileName, readAudioProperties, audioPropertiesStyle); - } - if(ext == "FLAC") - return new FLAC::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "MPC") - return new MPC::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "WV") - return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "SPX") - return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "OPUS") - return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "TTA") - return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") - return new MP4::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "WMA" || ext == "ASF") - return new ASF::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "AIF" || ext == "AIFF") - return new RIFF::AIFF::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "WAV") - return new RIFF::WAV::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "APE") - return new APE::File(fileName, readAudioProperties, audioPropertiesStyle); - // module, nst and wow are possible but uncommon extensions - if(ext == "MOD" || ext == "MODULE" || ext == "NST" || ext == "WOW") - return new Mod::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "S3M") - return new S3M::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "IT") - return new IT::File(fileName, readAudioProperties, audioPropertiesStyle); - if(ext == "XM") - return new XM::File(fileName, readAudioProperties, audioPropertiesStyle); - } - - return 0; -} diff --git a/taglib/fileref.h b/taglib/fileref.h index c323b086..465730f0 100644 --- a/taglib/fileref.h +++ b/taglib/fileref.h @@ -26,10 +26,10 @@ #ifndef TAGLIB_FILEREF_H #define TAGLIB_FILEREF_H +#include "taglib_export.h" #include "tfile.h" #include "tstringlist.h" - -#include "taglib_export.h" +#include "tpropertymap.h" #include "audioproperties.h" #if _WIN32 @@ -172,6 +172,41 @@ namespace TagLib { */ Tag *tag() const; + /*! + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (uppercase Strings) to StringLists of tag values. Calls the according + * specialization in the File subclasses. + * For each metadata object of the file that could not be parsed into the PropertyMap + * format, the returend map's unsupportedData() list will contain one entry identifying + * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() + * to remove (a subset of) them. + * For files that contain more than one tag (e.g. an MP3 with both an ID3v1 and an ID3v2 + * tag) only the most "modern" one will be exported (ID3v2 in this case). + */ + PropertyMap properties() const; + + /*! + * Removes unsupported properties, or a subset of them, from the file's metadata. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + */ + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. Calls the + * according specialization method in the subclasses of File to do the translation + * into the format-specific details. + * If some value(s) could not be written imported to the specific metadata format, + * the returned PropertyMap will contain those value(s). Otherwise it will be empty, + * indicating that no problems occured. + * With file types that support several tag formats (for instance, MP3 files can have + * ID3v1, ID3v2, and APEv2 tags), this function will create the most appropriate one + * (ID3v2 for MP3 files). Older formats will be updated as well, if they exist, but won't + * be taken into account for the return value of this function. + * See the documentation of the subclass implementations for detailed descriptions. + */ + PropertyMap setProperties(const PropertyMap &properties); + /*! * Returns the audio properties for this FileRef. If no audio properties * were read then this will returns a null pointer. @@ -232,8 +267,17 @@ namespace TagLib { */ static StringList defaultFileExtensions(); + /*! + * Returns true if the file is open and readable. + * + * \note Just a negative of isNull(). + */ + bool isValid() const; + /*! * Returns true if the file (and as such other pointers) are null. + * + * \note Just a negative of isValid(). */ bool isNull() const; @@ -264,22 +308,6 @@ namespace TagLib { */ bool operator!=(const FileRef &ref) const; - /*! - * A simple implementation of file type guessing. If \a readAudioProperties - * is true then the audio properties will be read using - * \a audioPropertiesStyle. If \a readAudioProperties is false then - * \a audioPropertiesStyle will be ignored. - * - * \note You generally shouldn't use this method, but instead the constructor - * directly. - * - * \deprecated - */ - static File *create(FileName fileName, - bool readAudioProperties = true, - AudioProperties::ReadStyle audioPropertiesStyle = AudioProperties::Average); - - private: class FileRefPrivate; RefCountPtr d; diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index bcd621df..4e3c4f1a 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -28,96 +28,76 @@ #include "tstring.h" #include "tdebug.h" #include "tpropertymap.h" - -#ifdef _WIN32 -# include -# include -#else -# include -# include -#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 "audioproperties.h" using namespace TagLib; -class File::FilePrivate +class File::FilePrivateBase { public: - FilePrivate(IOStream *stream, bool owner); + FilePrivateBase() + : valid(true) + { + } + + virtual ~FilePrivateBase() + { + } + + virtual IOStream *stream() const = 0; - IOStream *stream; - bool streamOwner; bool valid; - -#ifdef _WIN32 - - static const size_t bufferSize = 8192; - -#else - - static const size_t bufferSize = 1024; - -#endif }; -File::FilePrivate::FilePrivate(IOStream *stream, bool owner) : - stream(stream), - streamOwner(owner), - valid(true) +// FilePrivate implementation which takes ownership of the stream. + +class File::ManagedFilePrivate : public File::FilePrivateBase { -} +public: + ManagedFilePrivate(IOStream *stream) + : p(stream) + { + } + + virtual IOStream *stream() const + { + return p.get(); + } + +private: + NonRefCountPtr p; +}; + +// FilePrivate implementation which doesn't take ownership of the stream. + +class File::UnmanagedFilePrivate : public File::FilePrivateBase +{ +public: + UnmanagedFilePrivate(IOStream *stream) + : p(stream) + { + } + + virtual IOStream *stream() const + { + return p; + } + +private: + IOStream *p; +}; //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// -File::File(FileName fileName) -{ - IOStream *stream = new FileStream(fileName); - d = new FilePrivate(stream, true); -} - -File::File(IOStream *stream) -{ - d = new FilePrivate(stream, false); -} - File::~File() { - if(d->stream && d->streamOwner) - delete d->stream; - delete d; } FileName File::name() const { - return d->stream->name(); + return d->stream()->name(); } PropertyMap File::properties() const @@ -137,17 +117,17 @@ PropertyMap File::setProperties(const PropertyMap &properties) ByteVector File::readBlock(size_t length) { - return d->stream->readBlock(length); + return d->stream()->readBlock(length); } void File::writeBlock(const ByteVector &data) { - d->stream->writeBlock(data); + d->stream()->writeBlock(data); } offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVector &before) { - if(!d->stream || pattern.size() > d->bufferSize) + if(!d->stream() || pattern.size() > bufferSize()) return -1; // The position in the file that the current buffer starts at. @@ -196,20 +176,20 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe // (1) previous partial match if(previousPartialMatch != ByteVector::npos - && d->bufferSize > previousPartialMatch) + && bufferSize() > previousPartialMatch) { - const size_t patternOffset = (d->bufferSize - previousPartialMatch); + const size_t patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(pattern, 0, patternOffset)) { seek(originalPosition); - return bufferOffset - d->bufferSize + previousPartialMatch; + return bufferOffset - bufferSize() + previousPartialMatch; } } if(!before.isNull() && beforePreviousPartialMatch != ByteVector::npos - && d->bufferSize > beforePreviousPartialMatch) + && bufferSize() > beforePreviousPartialMatch) { - const size_t beforeOffset = (d->bufferSize - beforePreviousPartialMatch); + const size_t beforeOffset = (bufferSize() - beforePreviousPartialMatch); if(buffer.containsAt(before, 0, beforeOffset)) { seek(originalPosition); return -1; @@ -236,7 +216,7 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe if(!before.isNull()) beforePreviousPartialMatch = buffer.endsWithPartialMatch(before); - bufferOffset += d->bufferSize; + bufferOffset += bufferSize(); } // Since we hit the end of the file, reset the status before continuing. @@ -251,21 +231,9 @@ offset_t File::find(const ByteVector &pattern, offset_t fromOffset, const ByteVe offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteVector &before) { - if(!d->stream || pattern.size() > d->bufferSize) + 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. @@ -275,17 +243,21 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV offset_t bufferOffset; if(fromOffset == 0) { - seek(-1 * int(d->bufferSize), End); + seek(-1 * int(bufferSize()), End); bufferOffset = tell(); } else { - seek(fromOffset + -1 * int(d->bufferSize), Beginning); + seek(fromOffset + -1 * int(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)) { + while(true) + { + ByteVector buffer = readBlock(bufferSize()); + if(buffer.isEmpty()) + break; // TODO: (1) previous partial match @@ -304,7 +276,7 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV // TODO: (3) partial match - bufferOffset -= d->bufferSize; + bufferOffset -= bufferSize(); seek(bufferOffset); } @@ -319,22 +291,22 @@ offset_t File::rfind(const ByteVector &pattern, offset_t fromOffset, const ByteV void File::insert(const ByteVector &data, offset_t start, size_t replace) { - d->stream->insert(data, start, replace); + d->stream()->insert(data, start, replace); } void File::removeBlock(offset_t start, size_t length) { - d->stream->removeBlock(start, length); + d->stream()->removeBlock(start, length); } bool File::readOnly() const { - return d->stream->readOnly(); + return d->stream()->readOnly(); } bool File::isOpen() const { - return d->stream->isOpen(); + return d->stream()->isOpen(); } bool File::isValid() const @@ -344,57 +316,27 @@ bool File::isValid() const void File::seek(offset_t offset, Position p) { - d->stream->seek(offset, IOStream::Position(p)); + d->stream()->seek(offset, IOStream::Position(p)); } void File::truncate(offset_t length) { - d->stream->truncate(length); + d->stream()->truncate(length); } void File::clear() { - d->stream->clear(); + d->stream()->clear(); } offset_t File::tell() const { - return d->stream->tell(); + return d->stream()->tell(); } offset_t 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 - + return d->stream()->length(); } String File::toString() const @@ -415,9 +357,19 @@ String File::toString() const // protected members //////////////////////////////////////////////////////////////////////////////// +File::File(const FileName &fileName) + : d(new ManagedFilePrivate(new FileStream(fileName))) +{ +} + +File::File(IOStream *stream) + : d(new UnmanagedFilePrivate(stream)) +{ +} + size_t File::bufferSize() { - return FilePrivate::bufferSize; + return FileStream::bufferSize(); } void File::setValid(bool valid) diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 14d2fe89..680958e0 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -235,21 +235,6 @@ namespace TagLib { */ offset_t length(); - /*! - * Returns true if \a file can be opened for reading. If the file does not - * exist, this will return false. - * - * \deprecated - */ - static bool isReadable(const char *file); - - /*! - * Returns true if \a file can be opened for writing. - * - * \deprecated - */ - static bool isWritable(const char *name); - /*! * Returns description of the audio file and its tags. */ @@ -257,13 +242,12 @@ namespace TagLib { 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. + * Construct a File object and opens the file specified by \a fileName. * * \note Constructor is protected since this class should only be * instantiated through subclasses. */ - File(FileName file); + File(const FileName &fileName); /*! * Construct a File object and use the \a stream instance. @@ -297,8 +281,10 @@ namespace TagLib { File(const File &); File &operator=(const File &); - class FilePrivate; - FilePrivate *d; + class FilePrivateBase; + class ManagedFilePrivate; + class UnmanagedFilePrivate; + NonRefCountPtr d; }; } diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 2af442e5..f3877f68 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -43,8 +43,9 @@ namespace // Using Win32 native API instead of standard C file I/O to reduce the resource consumption. typedef FileName FileNameHandle; - typedef HANDLE FileHandle; + + const size_t BufferSize = 8192; const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -120,6 +121,8 @@ namespace }; typedef FILE* FileHandle; + + const size_t BufferSize = 1024; const FileHandle InvalidFileHandle = 0; inline FileHandle openFile(const FileName &path, bool readOnly) @@ -160,8 +163,6 @@ public: FileNameHandle name; bool readOnly; offset_t size; - - static const uint bufferSize = 1024; }; //////////////////////////////////////////////////////////////////////////////// @@ -195,8 +196,6 @@ FileStream::~FileStream() { if(isOpen()) closeFile(d->file); - - delete d; } FileName FileStream::name() const @@ -215,7 +214,7 @@ ByteVector FileStream::readBlock(size_t length) return ByteVector::null; const offset_t streamLength = FileStream::length(); - if(length > FileStreamPrivate::bufferSize && static_cast(length) > streamLength) + if(length > BufferSize && static_cast(length) > streamLength) length = static_cast(streamLength); ByteVector buffer(length); @@ -275,10 +274,10 @@ void FileStream::insert(const ByteVector &data, offset_t start, size_t replace) // the *differnce* in the tag sizes. We want to avoid overwriting parts // that aren't yet in memory, so this is necessary. - size_t bufferLength = FileStreamPrivate::bufferSize; + size_t bufferLength = BufferSize; while(data.size() - replace > bufferLength) - bufferLength += FileStreamPrivate::bufferSize; + bufferLength += BufferSize; // Set where to start the reading and writing. @@ -333,7 +332,7 @@ void FileStream::removeBlock(offset_t start, size_t length) return; } - size_t bufferLength = FileStreamPrivate::bufferSize; + size_t bufferLength = BufferSize; offset_t readPosition = start + length; offset_t writePosition = start; @@ -525,6 +524,11 @@ offset_t FileStream::length() #endif } +size_t FileStream::bufferSize() +{ + return BufferSize; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tfilestream.h b/taglib/toolkit/tfilestream.h index 413245c3..80240ad5 100644 --- a/taglib/toolkit/tfilestream.h +++ b/taglib/toolkit/tfilestream.h @@ -135,9 +135,14 @@ namespace TagLib { */ void truncate(offset_t length); + /*! + * Returns the buffer size that is used for internal buffering. + */ + static size_t bufferSize(); + private: class FileStreamPrivate; - FileStreamPrivate *d; + NonRefCountPtr d; }; } diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 06663c97..8cf1e8df 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -37,6 +37,7 @@ public: string newname = copy.fileName(); FileRef *f = new FileRef(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); CPPUNIT_ASSERT(!f->isNull()); f->tag()->setArtist("test artist"); f->tag()->setTitle("test title"); @@ -48,6 +49,7 @@ public: delete f; f = new FileRef(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); CPPUNIT_ASSERT(!f->isNull()); CPPUNIT_ASSERT_EQUAL(f->tag()->artist(), String("test artist")); CPPUNIT_ASSERT_EQUAL(f->tag()->title(), String("test title")); @@ -65,6 +67,7 @@ public: delete f; f = new FileRef(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); CPPUNIT_ASSERT(!f->isNull()); CPPUNIT_ASSERT_EQUAL(f->tag()->artist(), String("ttest artist")); CPPUNIT_ASSERT_EQUAL(f->tag()->title(), String("ytest title")); @@ -73,6 +76,27 @@ public: CPPUNIT_ASSERT_EQUAL(f->tag()->track(), TagLib::uint(7)); CPPUNIT_ASSERT_EQUAL(f->tag()->year(), TagLib::uint(2080)); delete f; + + f = new FileRef(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); + CPPUNIT_ASSERT(!f->isNull()); + PropertyMap prop = f->properties(); + CPPUNIT_ASSERT_EQUAL(prop["ARTIST"].front(), String("ttest artist")); + CPPUNIT_ASSERT_EQUAL(prop["TITLE" ].front(), String("ytest title")); + prop["ARTIST"].front() = "a test artist"; + prop["TITLE" ].front() = "b test title"; + f->setProperties(prop); + f->save(); + delete f; + + f = new FileRef(newname.c_str()); + CPPUNIT_ASSERT(f->isValid()); + CPPUNIT_ASSERT(!f->isNull()); + prop = f->properties(); + CPPUNIT_ASSERT_EQUAL(prop["ARTIST"].front(), String("a test artist")); + CPPUNIT_ASSERT_EQUAL(prop["TITLE" ].front(), String("b test title")); + delete f; + } void testMusepack()