Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix use of custom resolvers with iostream #1040

Merged
merged 6 commits into from Feb 27, 2022

Conversation

chouquette
Copy link
Contributor

The current code enforces a code path that will use fopen to convert a FileName to an actual File instance, even when FileRef::parse(IOStream*, ...) is used, which defeats the purpose of having an IOStream overload.

For reference, the associated issue on VLC's side is https://code.videolan.org/videolan/vlc/-/issues/26602

Copy link
Contributor

@sbooth sbooth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes make sense to me. I'll leave the merge decision to @ufleisch.

@ufleisch
Copy link
Contributor

ufleisch commented Feb 9, 2022

I will look at it a bit more thoroughly, but adding a new abstract method to FileTypeResolver looks like breaking all client code implementing a custom FileTypeResolver.

@chouquette
Copy link
Contributor Author

Indeed it would.

I can think of two options:

  • Add a default implementation for the IOStream overload. That should prevent build breakages, but is likely to be a bit kludgy and will still break the ABI
  • Add a new interface, which shouldn't break the API nor the ABI

If that's OK with you I'll try to add a new interface and see how it looks

@ufleisch
Copy link
Contributor

Possibly a third option might be:

  • Add a new class (e.g. StreamTypeResolver) which derives from FileTypeResolver and adds your new abstract method (maybe it would be better to give it another name, e.g. createFileFromStream()). Instances of such a class could still be registered using FileRef::addFileTypeResolver(), but internally we would have to do some dynamic_cast checks and probably have another list of StreamTypeResolvers. Maybe the FileTypeResolver could be marked deprecated and then be removed when TagLib makes the ABI breaking step. I have not verified this idea with code, I am just thinking loud...

@chouquette
Copy link
Contributor Author

Indeed that seems much cleaner to me! Thanks for the suggestion, I updated the MR with this

vlc-mirrorer pushed a commit to videolan/vlc that referenced this pull request Feb 17, 2022
Comment on lines 87 to 101
File *detectByResolvers(IOStream* stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
ResolverList::ConstIterator it = fileTypeResolvers.begin();
for(; it != fileTypeResolvers.end(); ++it) {
const FileRef::StreamTypeResolver* streamResolver =
dynamic_cast<const FileRef::StreamTypeResolver*>(*it);
if (!streamResolver)
continue;
File *file = streamResolver->createFileFromStream(stream, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
}

return 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
File *detectByResolvers(IOStream* stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
ResolverList::ConstIterator it = fileTypeResolvers.begin();
for(; it != fileTypeResolvers.end(); ++it) {
const FileRef::StreamTypeResolver* streamResolver =
dynamic_cast<const FileRef::StreamTypeResolver*>(*it);
if (!streamResolver)
continue;
File *file = streamResolver->createFileFromStream(stream, readAudioProperties, audioPropertiesStyle);
if(file)
return file;
}
return 0;
File *detectByResolvers(IOStream* stream, bool readAudioProperties,
AudioProperties::ReadStyle audioPropertiesStyle)
{
for(ResolverList::ConstIterator it = fileTypeResolvers.begin();
it != fileTypeResolvers.end(); ++it) {
if(const FileRef::StreamTypeResolver *streamResolver =
dynamic_cast<const FileRef::StreamTypeResolver*>(*it)) {
if(File *file = streamResolver->createFileFromStream(
stream, readAudioProperties, audioPropertiesStyle)) {
return file;
}
}
}
return 0;

I think it is better to narrow the scope of variables and only use continue when it would result in more readable code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with that change, but if I'm not mistaken this would require C++17 while taglib only requires C++11

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think TagLib actually requires C++11. At least in the not terribly recent past, it was all still C++98, and I'd removed the couple places that had C++11-isms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh my bad, I was looking at the tests CMakeLists.txt, not the one from the library itself.

Also my bad, I just realized that the proposed sample is not in a if statement with initializer so it's fine in C++98/03, I'll push a fix

Comment on lines 500 to 504
// Try user-defined resolvers.

d->file = detectByResolvers(stream->name(), readAudioProperties, audioPropertiesStyle);
d->file = detectByResolvers(stream, readAudioProperties, audioPropertiesStyle);
if(d->file)
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is only a FileTypeResolver registered (and not a StreamTypeResolver!), detectByResolvers() will return 0 instead of detecting by file name. So I would keep the check by file type after checking by stream.

  // Try user-defined stream type resolvers.

  d->file = detectByResolvers(stream, readAudioProperties, audioPropertiesStyle);
  if(d->file)
    return;

  // Try user-defined file type resolvers.

  d->file = detectByResolvers(stream->name(), readAudioProperties, audioPropertiesStyle);
  if(d->file)
    return;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good point, fixed

Comment on lines 59 to 74
{
return new Ogg::Vorbis::File(fileName);
}
virtual File *createFile(IOStream* s, bool, AudioProperties::ReadStyle) const
{
return new Ogg::Vorbis::File(s);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
return new Ogg::Vorbis::File(fileName);
}
virtual File *createFile(IOStream* s, bool, AudioProperties::ReadStyle) const
{
return new Ogg::Vorbis::File(s);
}
};
{
return new Ogg::Vorbis::File(fileName);
}
};
class DummyStreamResolver : public FileRef::StreamTypeResolver
{
public:
virtual File *createFile(FileName, bool, AudioProperties::ReadStyle) const
{
return 0;
}
virtual File *createFileFromStream(IOStream *s, bool, AudioProperties::ReadStyle) const
{
return new MP4::File(s);
}
};

This will not really work to test your resolver. First you have to implement a StreamTypeResolver and not just add a virtual method to a FileTypeResolver (and the method must be createFileFromStream()). You should also keep the test with a file type resolver to make sure it still works with them. The other problem is that with the existing test, createFileFromStream() will never be called, you have to explicitly create a FileRef by stream, so add another test to testFileResolver:

  void testFileResolver()
  {
    {
      FileRef f(TEST_FILE_PATH_C("xing.mp3"));
      CPPUNIT_ASSERT(dynamic_cast<MPEG::File *>(f.file()) != NULL);
    }

    DummyResolver resolver;
    FileRef::addFileTypeResolver(&resolver);

    {
      FileRef f(TEST_FILE_PATH_C("xing.mp3"));
      CPPUNIT_ASSERT(dynamic_cast<Ogg::Vorbis::File *>(f.file()) != NULL);
    }

    DummyStreamResolver streamResolver;
    FileRef::addFileTypeResolver(&streamResolver);

    {
      FileStream s(TEST_FILE_PATH_C("xing.mp3"));
      FileRef f(&s);
      CPPUNIT_ASSERT(dynamic_cast<MP4::File *>(f.file()) != NULL);
    }
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, thanks for providing the test code! Should be fixed in the new PR version

The main point of IOStream is to be able to use taglib with some content
that can't be fopen()'ed, for instance a network file, or a local file
on a system that doesn't support opening a file directly through fopen
& such.
This commit adds an overload for detectByResolvers that will stick to
using an IOStream all the way.
when no stream resolver is available
@chouquette
Copy link
Contributor Author

All remarks should be addressed now, let me know if you want me to squash some changes together

@ufleisch ufleisch merged commit e255e74 into taglib:master Feb 27, 2022
vlc-mirrorer pushed a commit to videolan/vlc that referenced this pull request Mar 1, 2022
This is the version that was merged upstream so it makes sense to use it
to facilitate future rebases
upstream PR: taglib/taglib#1040
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants