// SPDX-License-Identifier: GPL-2.0-or-later // Copyright The Music Player Daemon Project #ifndef MPD_DIRECTORY_HXX #define MPD_DIRECTORY_HXX #include "Ptr.hxx" #include "Song.hxx" // TODO eliminate this include, forward-declare only #include "db/Visitor.hxx" #include "db/PlaylistVector.hxx" #include "db/Ptr.hxx" #include "util/IntrusiveList.hxx" #include #include /** * Virtual directory that is really an archive file or a folder inside * the archive (special value for Directory::device). */ static constexpr unsigned DEVICE_INARCHIVE = -1; /** * Virtual directory that is really a song file with one or more "sub" * songs as specified by DecoderPlugin::container_scan() (special * value for Directory::device). */ static constexpr unsigned DEVICE_CONTAINER = -2; /** * Virtual directory that is really a playlist file (special value for * Directory::device). */ static constexpr unsigned DEVICE_PLAYLIST = -3; class SongFilter; struct Directory : IntrusiveListHook<> { /* Note: the #IntrusiveListHook is protected with the global #db_mutex. Read access in the update thread does not need protection. */ using List = IntrusiveList; /** * A doubly linked list of child directories. * * This attribute is protected with the global #db_mutex. * Read access in the update thread does not need protection. */ List children; /** * A doubly linked list of songs within this directory. * * This attribute is protected with the global #db_mutex. * Read access in the update thread does not need protection. */ IntrusiveList songs; PlaylistVector playlists; Directory *const parent; std::chrono::system_clock::time_point mtime = std::chrono::system_clock::time_point::min(); uint64_t inode = 0, device = 0; const std::string path; /** * If this is not nullptr, then this directory does not really * exist, but is a mount point for another #Database. */ DatabasePtr mounted_database; /** * This field is used by the database update to check whether * an item has disappeared. */ bool mark; public: Directory(std::string &&_path_utf8, Directory *_parent) noexcept; ~Directory() noexcept; /** * Create a new root #Directory object. */ [[gnu::malloc]] [[gnu::returns_nonnull]] static Directory *NewRoot() noexcept { return new Directory(std::string(), nullptr); } bool IsPlaylist() const noexcept { return device == DEVICE_PLAYLIST; } /** * Is this really a regular file which is being treated like a * directory? */ bool IsReallyAFile() const noexcept { return device == DEVICE_INARCHIVE || IsPlaylist() || device == DEVICE_CONTAINER; } bool IsMount() const noexcept { return mounted_database != nullptr; } /** * Checks whether this is a "special" directory * (e.g. #DEVICE_PLAYLIST) and whether the underlying plugin * is available. */ [[gnu::pure]] bool IsPluginAvailable() const noexcept; /** * Remove this #Directory object from its parent and free it. This * must not be called with the root Directory. * * Caller must lock the #db_mutex. */ void Delete() noexcept; /** * Create a new #Directory object as a child of the given one. * * Caller must lock the #db_mutex. * * @param name_utf8 the UTF-8 encoded name of the new sub directory */ Directory *CreateChild(std::string_view name_utf8) noexcept; /** * Caller must lock the #db_mutex. */ [[gnu::pure]] const Directory *FindChild(std::string_view name) const noexcept; [[gnu::pure]] Directory *FindChild(std::string_view name) noexcept { const Directory *cthis = this; return const_cast(cthis->FindChild(name)); } /** * Look up a sub directory, and create the object if it does not * exist. * * Caller must lock the #db_mutex. */ Directory *MakeChild(std::string_view name_utf8) noexcept { Directory *child = FindChild(name_utf8); if (child == nullptr) child = CreateChild(name_utf8); return child; } struct LookupResult { /** * The last directory that was found. If the given * URI could not be resolved at all, then this is the * root directory. */ Directory *directory; /** * The URI part which resolved to the #directory. */ std::string_view uri; /** * The remaining URI part (without leading slash) or * empty if the given URI was consumed completely. */ std::string_view rest; }; /** * Looks up a directory by its relative URI. * * @param uri the relative URI */ [[gnu::pure]] LookupResult LookupDirectory(std::string_view uri) noexcept; [[gnu::pure]] Song *LookupTargetSong(std::string_view target) noexcept; [[gnu::pure]] bool IsEmpty() const noexcept { return children.empty() && songs.empty() && playlists.empty(); } [[gnu::pure]] const char *GetPath() const noexcept { return path.c_str(); } /** * Returns the base name of the directory. */ [[gnu::pure]] std::string_view GetName() const noexcept; /** * Is this the root directory of the music database? */ [[gnu::pure]] bool IsRoot() const noexcept { return parent == nullptr; } template void ForEachChildSafe(T &&t) { const auto end = children.end(); for (auto i = children.begin(), next = i; i != end; i = next) { next = std::next(i); t(*i); } } template void ForEachSongSafe(T &&t) { const auto end = songs.end(); for (auto i = songs.begin(), next = i; i != end; i = next) { next = std::next(i); t(*i); } } /** * Look up a song in this directory by its name. * * Caller must lock the #db_mutex. */ [[gnu::pure]] const Song *FindSong(std::string_view name_utf8) const noexcept; [[gnu::pure]] Song *FindSong(std::string_view name_utf8) noexcept { const Directory *cthis = this; return const_cast(cthis->FindSong(name_utf8)); } /** * Add a song object to this directory. Its "parent" attribute must * be set already. */ void AddSong(SongPtr song) noexcept; /** * Remove a song object from this directory (which effectively * invalidates the song object, because the "parent" attribute becomes * stale), and return ownership to the caller. */ SongPtr RemoveSong(Song *song) noexcept; /** * Recursively walk through the whole tree and set all * `Song::in_playlist` fields to `false`. * * Caller must lock the #db_mutex. */ void ClearInPlaylist() noexcept; /** * Caller must lock the #db_mutex. */ void PruneEmpty() noexcept; /** * Sort all directory entries recursively. * * Caller must lock the #db_mutex. */ void Sort() noexcept; /** * Caller must lock #db_mutex. */ void Walk(bool recursive, const SongFilter *match, bool hide_playlist_targets, const VisitDirectory& visit_directory, const VisitSong& visit_song, const VisitPlaylist& visit_playlist) const; [[gnu::pure]] LightDirectory Export() const noexcept; }; #endif