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

iTunes: Modularize importer and use iTunesLibrary on macOS for compatibility with Music.app #11353

Merged
merged 52 commits into from
Apr 4, 2023

Conversation

fwcd
Copy link
Member

@fwcd fwcd commented Mar 10, 2023

This fixes

Description

This branch modularizes the iTunes library integration into importers:

  • ITunesMacOSImporter, using Apple's iTunesLibrary framework
    • Used by default on macOS
    • Supports regular and smart playlists, along with (nested) folders
    • Supports both the old and the new database format used by Music.app
    • The framework, however, only lets us read the user's default music library, therefore we'll fall back to the XML importer whenever a custom db path is provided on macOS
  • ITunesXMLImporter, the current XML parser
    • Used on non-macOS platforms or whenever a custom db path is provided

This modularity should also make it easy to drop in new importers in the future, e.g. a cross-platform parser for the new database format.

To do

  • Modularize iTunes feature
  • Migrate current parser to ITunesXMLImporter
  • Implement ITunesMacOSImporter
    • Set up Objective-C++
    • Import tracks
    • Import playlists
      • Support regular and smart playlists
      • Support folders
  • Fix the strange macOS CI errors during packaging (see e.g. this run)
  • Investigate whether the ad-hoc code signing identity is sufficient (see this thread on stackoverflow)
  • Investigate whether we need to declare new macOS entitlements (see this comment on stackoverflow)

Future Directions

  • Adapt the folder import logic (ingesting the parent playlist relationships into a multimap and building the TreeItem afterwards) for the XML importer
  • Read cover art via the iTunesLibrary framework too
    • iTunes and Music.app in many cases do not store the cover art in the track metadata, making it inaccessible to Mixxx
    • However, there is an API for fetching the cover art via an in-memory NSImage from an ITLibMediaItem: https://developer.apple.com/documentation/ituneslibrary/itlibmediaitem/1810529-artwork
    • Since cover art isn't stored in the database currently, however, we might either have to
      • modify the schema for this to either store the artwork in the database (possibly space-inefficient for large libraries) or...
      • modify the cover art loader to interface with the iTunesLibrary framework (e.g. querying the iTunes ID for a given track), which might be undesirable since this way the iTunes integration would become a leaky abstraction
  • Optimize the persistent ID -> database ID mapping
    • We need this since iTunes' persistent IDs use unsigned 64-bit ints, but SQLite only lets us store signed 64-bit ints
    • Currently we use a dictionary and generate a sequence of incrementing indices
      • One advantage is that we get reasonably deterministic indices (0, 1, 2, 3, ...)
      • This requires storing all IDs in memory though (not sure if this is an issue for very large libraries)
    • Potential solutions:
      • Cast the unsigned 64-bit int to a signed 64-bit int for storage in SQLite
        • This would however frequently generate negative indices, which might confuse users and the ORM
      • Perform some form of hashing and investigate how to avoid collisions
        • If we limit ourselves to the positive integers that SQLite can represent, we'd have an output space of 63 bits, which is probably enough, but we'd still want to check the math whether the likelihood of a birthday problem is acceptable
      • Update the database schema
        • Altering the iTunes id column type is a breaking change though, which we'd like to avoid
        • Adding another (unique-constrained) column for persistent IDs is possible without bumping the minimum compatible version, but would require querying the database for every mapping, which would result in a lot of database calls with large playlists

@fwcd fwcd changed the title ITunesFeature: Modularize importer and use iTunesLibrary on macOS by default iTunes: Modularize importer and use iTunesLibrary on macOS for compatibility with Music.app Mar 10, 2023
src/library/itunes/itunesmacosimporter.mm Outdated Show resolved Hide resolved
src/library/itunes/itunesmacosimporter.mm Outdated Show resolved Hide resolved
@Swiftb0y
Copy link
Member

Swiftb0y commented Mar 14, 2023

Do you have a good write-up on Obj-C(++) for C++ developers? Also, can you try to keep the Obj-C code minimal? introducing more programming languages increases the risk of pulling in code that we rely on but can't maintain. Especially when that programming language is not used outside the apple ecosystem (which we already don't have access to since none of the active core devs uses macos).

@fwcd
Copy link
Member Author

fwcd commented Mar 14, 2023

Also, can you try to keep the Obj-C code minimal?

Yeah, that's something I'm already trying to do, also because C++/Qt containers tend to be quite a bit more performant than ObjC types, the latter rely heavily on boxing.

Do you have a good write-up on Obj-C(++) for C++ developers?

http://www.jonhoyle.com/maccompanion/columns/200809.html might be worth looking into.

Since we don't use any of the 'fancy' ObjC features here and only what's necessary to interact with the APIs, this should boil down to learning the syntactic differences (only), e.g. [object method] instead of object.method(). One thing to note is that ObjC objects are always passed as raw pointers, the compiler still performs automatic memory management on them under the hood though (automatic reference counting/ARC to be precise, effectively what a shared_ptr does).

@fwcd fwcd force-pushed the macos-itunes-library branch from 4284f0f to ad7f428 Compare March 14, 2023 22:46
@fwcd fwcd force-pushed the macos-itunes-library branch 2 times, most recently from 8e5e707 to 2c146c3 Compare March 15, 2023 00:49
@daschuer
Copy link
Member

I am unsure what to do with this PR. Is the iTunes integration in 2.3 broken, after a user has received the music.app?

In the unlike case that this PR does any harm, is it possible to disable it and use the XML importer?

I am considering to merge it to 2.4. Do you think the 3 month beta is good enough for testing. Since I am on Linux, I am personally not able to do a sorrowly test. I think we need a real iTunes user to verify details.

Any thoughts?

@Swiftb0y
Copy link
Member

http://www.jonhoyle.com/maccompanion/columns/200809.html might be worth looking into.

Thank you, I'll familiarize and try to review ASAP.

@fwcd
Copy link
Member Author

fwcd commented Mar 15, 2023

Is the iTunes integration in 2.3 broken, after a user has received the music.app?

Kind of. Music.app no longer automatically generates an iTunes XML and keeps it in sync. This means the user might see an outdated view of their library, unless they manually export an XML through the UI, which is still possible through Music.app

In the unlike case that this PR does any harm, is it possible to disable it and use the XML importer?

The user should (even if Music.app is installed) always be able to select an XML file in which case we will fall back to the XML importer. The macOS importer is only used when "default library" is selected.

Do you think the 3 month beta is good enough for testing

Since the API surface that we use is relatively low and we reuse a large part of the existing DB infrastructure, I would say that is sufficient. Ideally some users, especially with large libraries, could test this integration.

@fwcd fwcd force-pushed the macos-itunes-library branch from c8da607 to fb3cc2a Compare March 22, 2023 18:41
Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

last comment. lgtm otherwise. I'll have to see whether I can conduct a manual test.

Comment on lines 311 to 316
#ifdef __MACOS_ITUNES_LIBRARY__
if (m_dbfile.isEmpty()) {
importer = std::make_unique<ITunesMacOSImporter>(this, m_database, m_cancelImport);
} else {
importer = std::make_unique<ITunesXMLImporter>(
this, m_dbfile, m_database, m_pathMapping, m_cancelImport);
Copy link
Member

Choose a reason for hiding this comment

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

I think we should log which importer was chosen here. Choosing the importer simply based on whether we found the db file seems somewhat brittle (though idk what else would be more robust).

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea is that e.g. a user on macOS can explicit select an XML file, but also click Use Default Library in the context menu to switch back to the native iTunes integration. Perhaps we should make this more explicit?

I agree that logging the importer is probably a good idea.

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 quite understand the workflow, what context menu exactly? Is "native iTunes integration" the XML-file or the iTunesLibrary Framework solution?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is "native iTunes integration" the XML-file or the iTunesLibrary Framework solution?

The iTunesLibrary framework solution.

I don't quite understand the workflow, what context menu exactly?

image

Selecting Use Default Library sets the dbfile path to an empty string which we interpret as "use the iTunesLibrary importer". If the dbfile path is set to an XML file, we use the XML importer instead. This should also handle existing users of the XML file integration on macOS gracefully (e.g. someone who has an iTunes XML on an external drive etc.)

Copy link
Member

Choose a reason for hiding this comment

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

Mhmm, maybe add a comment that we interpret it that way idk. You're free to improve it in a future PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added a short doc comment to m_dbfile and a short log message

@fwcd fwcd force-pushed the macos-itunes-library branch from 42209cd to bb11d5e Compare March 26, 2023 23:31
@fwcd fwcd force-pushed the macos-itunes-library branch from f33c67d to de5188d Compare March 29, 2023 14:16
@fwcd
Copy link
Member Author

fwcd commented Mar 29, 2023

I have made some minor stylistic improvements here and there and tested the XML importer more extensively, fixing a small regression that surfaced in the guessMusicLibraryMountpoint method (see 16b699e). Thus the path translation for relocated iTunes libraries should work correctly again.

Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

small comment, personally, i think the changes look good, but I'd like some more testing, preferably by another 3rd party as I currently don't have access to apple hardware for testing.

src/library/itunes/itunesfeature.cpp Outdated Show resolved Hide resolved
@Swiftb0y
Copy link
Member

@daschuer what do you think? Should we just merge for 2.4 and see what bug reports we get in the beta?

@fwcd fwcd force-pushed the macos-itunes-library branch from 28828bb to f4ab748 Compare March 30, 2023 17:51
@fwcd
Copy link
Member Author

fwcd commented Apr 3, 2023

Any update on this? As mentioned in the original post, there are some things I have in the pipeline for this (e.g. adding the folder logic to the XML importer too), but would move into a separate PR since they change the semantics of the existing importer rather than "just" copying the logic into a separate file.

@Swiftb0y
Copy link
Member

Swiftb0y commented Apr 3, 2023

Well, I would've liked to know @daschuer's opinion before merging since they're usually the ones taking care of coordinating the releases.

If you want to work on other things based on this PR, why don't just do it in a separate branch and occasionally rebase in case changes have been done here? Or am I misunderstanding?

@daschuer
Copy link
Member

daschuer commented Apr 3, 2023

I support merging this into 2.4. I think we need the confirmation that the legacy import is still working before merging.
Has this been tested?

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

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

I have just fired up my win 10 virtual machine and can confirm that this does not creates obvious regression with iTunes 12.12.7.1

So lets merge this and collect bugs if there are any from beta testers.

Thank you for the work.

@uklotzde
Copy link
Contributor

uklotzde commented Apr 4, 2023

So lets merge this and collect bugs if there are any from beta testers.

That's the right attitude for making progress and keeping trustworthy developers motivated ;)

@@ -51,22 +55,31 @@ class ITunesFeature : public BaseExternalLibraryFeature {
void clearTable(const QString& table_name);
Copy link
Member

@ronso0 ronso0 Apr 23, 2023

Choose a reason for hiding this comment

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

Some of the method declarations above are obselete now.
I removed them in #11208 when merging 2.4
85c9209
85c9209#diff-380635a0ce3c43f060770fd4001274c29a8142ebba2d56abdef1a9cf06620133

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants