I bump the user_version to 3 at the same time. This table will be used
to hold listenbrainz listens that have not yet been submitted to the
listenbrainz server. I also give the Track table functions to get and
delete listens from this table as needed by the listenbrainz thread.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This lets us do a lot of the basic Thread operations through common
code, allowing us to focus on tagging in this file instead of basic
Thread controls.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This can be checked or connected to so other parts of the application
can easily know if all database tables have been loaded or not.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The New Tracks playlist shows tracks that have been added within the
past week. We should automatically reload it a few seconds after
midnight to keep it up to date as tracks drop off the list.
Implements: #58 ("Reload New Tracks playlist at midnight")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I found that deleted and restorted tracks were incorrectly showing up in
the "New Tracks" playlist. I can fix this by saving the track added date
when the tracks is deleted. The only thing I can't do easily is get the
added date for tracks that have already been deleted, so I set this to
the date of the database upgrade.
Fixes: #64 ("Save the tracks.added date when deleting")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Prepare for database modifications. The first step is to bump the
database version, and it's cleaner to do that in a separate patch.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is a wrapper function that takes a pathlib.Path object, reads it,
and calls the sqlite3 executescript() function. I update the main
db.Connection object to call this function to set up our database tables
while I'm at it.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This applies to both the Table and child Playlists models. I'm doing my
own idle handling already for searching, so we can rely on that instead
of needing Gtk to do it. The benefit to this is that we can select
playlists programmatically during startup, since we don't need to worry
about the Table not being fully loaded yet.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is used to notify the model that the rows have changed when it's
not automatically being detected. I first noticed this when attempting
to disable incremental filtering, due to the Artist list not getting
refiltered when new child Albums were added to the Artist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I scheduled the filter query with first=True so it can run without a
long delay during scanning. Additionally, I cancel any pending filter
calls to discard stale arguments that otherwise would be processed.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
If we have a high priority task, then we want to push it to the front of
the idle queue so it runs as soon as possible.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It's sometimes desireable to cancel a pending task and re-add it with
new parameters to cut out some unnecessary work.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I combine this with the table's Filter object to show playlists matching
the current search query.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_year(), remove_year(), and has_year() functions and make
sure we load the set of yearids during startup. Additionally, I have
Years add and remove themselves from Decades as they are created and
deleted.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_medium(), remove_medium(), and has_medium() functions
and make sure we load the set of mediumids during startup. Additionally,
I have Mediums add and remove themselves from Albums as they are created
and deleted.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I switch over to adding and removing Albums using the generic
add_child() and remove_child() functions. I also switch from using a
KeySet filter holding albumids to a Gtk.CustomFilter that calls
Artist.has_album() to check if an Album is in the set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
We want to filter out Medium playlists with empty names in the sidebar,
and the easiest way to do that is through a custom filter attached to
the Media table.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
It defaults to a None pointer, but calling add_children() will set one
up with an empty set. I also implement functions for adding, removing,
and verifying child playlists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is similar to a Gtk.FilterListModel, except we already know exactly
which rows are part of the model or not. So we can skip the entire
filtering step and show rows directly instead.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes implementing the __contains__() magic method, and adding
signals that are emitted when rows are added, removed, or directly set.
This will allow us to build a model around the rows represented by the
set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I'm going to expand on this and use it for more than just filtering
Gtk.FilterListModels. Renaming it to something more generic is the first
step.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Leaving the database in a dirty state could cause unintentional data
loss if the app crashes.
Fixes: #63 ("The database isn't being committed enough")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This lets us skip a handful of database calls if we detect that we're
setting the active playlist to the same value.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I do this unconditionally, so even if the track is already on the
playlist we set it as active. I think this is what the user would
expect, but I'm open to revising this approach later.
Fixes: #62 ("Queueing tracks doesn't activate the Queued Tracks playlist")
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Future proof. If we update the database schema, then we'll bump the
user_version field. If the user then tries to open the new database with
an old Emmental version then there could be a lot of issues. Let's
detect this and raise an error with a description of the problem.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This is much faster than removing tracks from their playlists one at a
time. I also clear and reload the Tracks table to clear out pointers to
old Tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes both creating new Tracks and updating existing Tracks when
their tags have changed. New tracks are added to playlists using
idle=True so Gtk can spread out UI updates for each playlist so we don't
slow things down too much for the user.
This patch also adds a library argument to the Tagger thread
get_result() function which we pass to the Tagger class to be used by
Tracks.creat(). I also add an mtime argument to the Tagger thread
tag_file() function to pass down to the audio.tagger layer so we can
skip updating tracks that have not changed since the last scan.
Implements: #41 (Check for new or modified tags during startup)
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the libraries_view to include additional playlist
properties, and configure the default sort order to sort by filepath.
I then set up the library_tracks_view to make it easy to select tracks
that belong to a specific Library.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the years_view to include additional playlist properties,
and configure the default sort order to stort by release date first.
I then set up the year_tracks_view to make it easy to select tracks that
belong to a specific year.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the decades_view to include additional playlist properties,
and configure the default sort order to sort by year first.
I then set up the decade_tracks_view to make it easy to select tracks
that belong to a specific decade.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the genres_view to include additional playlist properties,
and configure the default sort order to sort by artist, album, and track
number.
I then configure the Genres table to use the system_tracks table to
manage each genre's associated tracks and set up the genre_tracks_view
to make it easy for Tracks to find their Genres.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the media_view to include additional playlist properties,
and configure the default sort order to sort by track number.
I then set up the medium_tracks_view to make it easy to select tracks
that belong to a specific medium.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the albums_view to include additional playlist properties,
and configure the default sort order to sort by track numbers in an
intuitive way.
I then set up the album_tracks_view to make it easy to select tracks
that belong to a specific album.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I expand on the artists_view to include additional playlist properties,
and configure the default sort_order to sort albums in an intuitive way.
I then configure the Artists table to us the system_tracks table to
manage each artist's associated tracks and set up the artist_tracks_view
to make it easy for Tracks to find their Artists.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I need to do something slightly different for each Playlist.
* Collection: I load tracks from the collection_view, which filters
tracks to those where the library is enabled but not deleting.
* Favorite Tracks: I load tracks from the favorites_view, which filters
tracks based on the tracks.favorite and library.deleting column.
* Most Played Tracks: I load tracks with a playcount greater than the
average playcount of all tracks (rounded up to the nearest integer).
* New Tracks: I load tracks that have been added within the last week.
* Previous Tracks: I load tracks that have been played since startup
using the system_tracks table. I take care to clear these entries
in the table during startup.
* Queued Tracks: Load tracks from the user_tracks table.
* Unplayed Tracks: I load tracks with a playcount equal to 0 and remove
when they are played.
* User-Defined Playlists: Load tracks from the track_playlist_link
table.
Additionally, I implement move_track_up() and move_track_down() support
for user playlists and queued tracks.
Finally, I update the have-next-track property to take into account if
the Collection has tracks too.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I need to do something slightly different for each system Playlist:
* Collection: I disallow setting loop to "None".
* Favorite Tracks: I set the user-tracks property to "True"
* Most Played Tracks: I add playcount as the first sort field.
* Previous Tracks: I disallow changing loop, shuffle, and sort-order.
* Queued Tracks: I set the user-tracks and tracks-movable properties to "True"
User created playlists also set the user-tracks and tracks-movable
properties to "True". I also disable autodelete on the Table so
playlists aren't deleted unexpectedly.
New Tracks and Unplayed Tracks have no special properties set.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
* loop is based on the mpris loop property, and can be set to "None",
"Track", or "Playlist".
* shuffle is a boolean True / False value.
* sort-order saves a user-configured sort order for each playlist.
* current-trackid is an integer referring to the trackid of the currently
playing track.
* user-tracks: is a boolean representing if the user should be allowed
to manually add and remove tracks.
I also use the sort-order property to implement a get_track_order()
function to get the sort keys for tracks in a Playlist.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I implement add_track(), get_trackids(), and remove_track() functions that
either modify the 'system_playlist_tracks' table or call a virtual function
depending on the value of the 'system-tracks' property.
I also add the "autodelete" property. When set to True, Playlists will
be deleted when they have 0 tracks remaining.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Playlists use a tracks.TrackidSet to manage a set of trackids
representing the Tracks in this Playlist.
I have two functions for loading tracks: load_tracks() and
reload_tracks(). Calling load_tracks() checks if the tracks have been
loaded first before doing any work, but calling reload_tracks() will
force the Playlist to go to the database to load the latest tracks.
Finally, I add a have-next-track property to the main database
connection. This is set to True whenever the active playlist has one or
more tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The TrackidSet is intened to be used by Playlists to keep track of which
Tracks have been added without much overhead.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This includes the favorite status, playcount, last played timestamp, and
last started timestamp. These values will be restored if a Track with
the same mbid is created at a later time.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This patch adds extra handling for changing the value of the
Track:favorite property, if the Track is the current track. This lets us
have the Now Playing card bind to the current-favorite property to
update the UI (and have the UI update the Track).
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
Tracks now have start(), stop(), and restart() functions that can be
used by the application to update the laststarted, lastplayed,
playcount, active, and restarted properties.
The track Table implements their half of these functions in addition to
a mark_path_active() function so opening Emmental with a filepath can
update the current track before the database is loaded. The Table also
adjusts the necessary system playlists when tracks are marked as played.
Finally, the Table now has have-current-track and current-track
properties that can be wired up to the Now Playing card.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The Track Table does all the work for saving, loading, and managing
Track objects. I also create a SQLite View to link tracks to their
associated artists, albums, and mediums.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
The Track object represents a single track in the Library along with all
their corresponding metadata.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
I extract the artist, length, mbid, mtime, tracknumber, and title from
the tags to use when constructing Tracks.
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>