Compare commits

...

193 Commits

Author SHA1 Message Date
Anna Schumaker 45d2422be3 gui: Refilter playlists when adding tracks
I was running into a situation where new tracks added to a playlist were
showing up even if they didn't match the current filter text.  Let's fix
this by refiltering the current playlist when new tracks are added to
it.

Fixes #114: New tracks added to filtered view
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-08-28 08:58:45 -04:00
Anna Schumaker 54138d8814 Ocarina 6.5.10-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-08-20 08:34:15 -04:00
Anna Schumaker 6ab3cff28f gui/playlist: Support drag and drop for adding tracks to playlists
Dragging onto a sidebar row without a valid playlist will prompt the
user to create a new playlist.  I could probably add a "New Playlist"
row at some point, but this is easier for now :)

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker 0972e027ed gui/treeview: Connect to the drag-and-drop signals
We only support drag-and-drop one row at a time, even though multiple
rows can be selected in the treeview.  If this is a problem, then we can
figure it out later!

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker 77efa0c631 gui/model: Configure the gui model as a drag source
We'll need to do this to enable drag and drop to reorder playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker a9970c455f gui/sidebar: Add a function for getting an iter from x, y coordinates
This will be used for drag and drop, so that we can figure out what
playlist tracks are being added to.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker fc5e6eb043 gui/treeview: Add a function for accessing the tree selection
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker a41652ab28 core/playlist: Add a function for manually rearranging playlists
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker eca857cb3b core/playlists: Pass sort fields to playlist_generic_alloc()
This lets us configure sorting individually for each allocated playlist.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker d7fb67ed51 core/playlists: Remove unsorted playlist_generic_init()
We can just pass 0 to the sorted function to indicate that the playlist
shouldn't be sorted by default.  Let's also take this chance to rename
playlist_generic_init_sorted() -> playlist_generic_init()

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker 7df129d533 core/playlists: Accept an argument list for playlists initial sort order
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker a4cdac7f22 core/playlists/user: Don't sort user playlists by default
Users should be able to add tracks in the order they want.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker d5b0752497 core/playlists/generic: Add album sort to the default fields
If we have two albums by the same artist in the same year (such as a CD1
or CD2 postfix), then we'll end up mixing them together when sorting.
Fix this by changing the default sort order to
Artist -> Year -> Album -> Track Number

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-16 07:35:28 -04:00
Anna Schumaker 5fb46dc663 Ocarina 6.5.9
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-11 11:43:17 -04:00
Anna Schumaker d149289e00 gui/audio: Close popover menu after 10 seconds
And don't touch the pause count so we don't surprise the user later.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-02 11:13:32 -04:00
Anna Schumaker f167f968ba gui/audio: Add a popover menu to clear automatic pausing
The popover is shown whenever the user pauses manually with automatic
pausing enabled.  This will give the user a chance to disable pausing if
it is no longer needed.

Implements #113: Cancel "pause after" configuration when user manually pauses
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-05-02 10:45:23 -04:00
Anna Schumaker 94f3a7f387 Ocarina 6.5.9-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-04-20 09:16:05 -04:00
Anna Schumaker 60e6e2a9eb gui/audio: Remove old pause_after combobox
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:42:55 -05:00
Anna Schumaker 1836104f40 gui/audio: Respond to the user editing the pause entry
We have to disable the up and down buttons when typing in the entry in
case the user decides to type a minus or plus sign.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:42:31 -05:00
Anna Schumaker cd7364300e gui/audio: Wire up the "spin button" increment and decrement buttons
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:42:21 -05:00
Anna Schumaker 994234caf2 gui/audio: Change down-button sensitivity based on pause count
The pause count can't go below -1, so disable the button once we reach
this limit.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:42:20 -05:00
Anna Schumaker af5bafb03e gui/audio: Set the pause entry text based on remaining tracks
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:40:48 -05:00
Anna Schumaker fd68cdf70a gui/audio: Add basic spin-button-like widgets
I set the "linked" property on the hbox to make everything look like a
single widget.  I'm doing this on my own to cut out the GtkAdjustment
and to just use the counter in the audio layer in its place.
Additionally, this will give me the ability to use "+" and "-" keyboard
shortcuts to activate the widgets.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:31:57 -05:00
Anna Schumaker 8a2c631a9b core/audio: Add a function for getting the current pause count
This will be needed by the gui to find the current count at any time,
without waiting for a callback.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:31:57 -05:00
Anna Schumaker e6ab06cf2b core/audio: Change audio_pause_after() to return a boolean
This will be useful later to let the gui know if their change had an
effect.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:31:57 -05:00
Anna Schumaker e6fb772cad Ocarina 6.5.8
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-03-02 16:18:27 -05:00
Anna Schumaker cce8666140 core/tags/album: Check for empty artist names before fetching album art
Otherwise we might crash when we attempt to look at ar_tokens[0].

Reported-by: Josh Larson <themutatedshrimp@gmail.com>
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-28 10:28:59 -05:00
Anna Schumaker edcba6a353 Ocarina 6.5.8-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-27 08:29:21 -05:00
Anna Schumaker 59506d45e7 core/file: Rename cache_file_import() -> file_import()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-27 08:25:40 -05:00
Anna Schumaker e1f13a7ef4 core/file: Create new functions for reading data from files
The readd(), readu(), and readhu() functions are all used to read
various forms of integers.  The readl() and readw() are for reading
either lines or individual whitespace-delimited words

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-21 16:01:15 -05:00
Anna Schumaker 111fcd4e72 core/file: Rename data_file_writef() -> file_writef()
For writing to files with printf-style formatting.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-21 08:19:19 -05:00
Anna Schumaker c5494811f4 core/file: Rework binary file IO
I rename cache_file_write() to file_write(), and implement the
corresponding file_read() for binary data.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:59:42 -05:00
Anna Schumaker 07f832ad26 core/file: Rename data_file_version() -> file_version()
Now it can be used by cache files, too.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:18:50 -05:00
Anna Schumaker 35d53855f5 core/file: Add write support to file_open()
I also take this opportunity to add a OPEN_WRITE_BINARY mode to support
writing binary data to files.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:18:49 -05:00
Anna Schumaker a848d5d03c core/file: Create a single file_open() function
This function currently only supports opening files for reading, but it
will soon be expanded to supporting writes as well.  To support binary
reads, I add a new OPEN_READ_BINARY open mode.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:16:23 -05:00
Anna Schumaker 0aaafcb5f7 core/file: Move versioning info into the main file structure
I'll eventually want to support text and binary files in both locations,
so cache files might need access to this too.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:16:22 -05:00
Anna Schumaker 8f13765b08 core/file: Make a common file_remove() function
This change means removing cache files is now supported.  As a bonus,
we try to remove empty subdirectories to keep down the clutter.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:09:42 -05:00
Anna Schumaker 84a1022bdf core/file: Create a single file_close() function
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:06:50 -05:00
Anna Schumaker 659aaff6a1 core/file: Move f_mode out of data files
This might also be useful for cache files to track their current state.
To help with accurate tracking, I take this opportunity to add a CLOSED
state that files default to.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:06:49 -05:00
Anna Schumaker 3736b6cf3b core/file: Create a single file_exists() function
We can use the single file_path() function to check if files exist, too.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:03:32 -05:00
Anna Schumaker 3fdf89c75e core/file: Create a single file_write_path() function
Similar to file_path(), I rely on the file ops struct to put files in
the right place.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:03:31 -05:00
Anna Schumaker 22854b2f25 core/file: Create a single file_path() function
I also introduce a "file_operations" struct that will be used in the
future to do slightly different things for cache and data files.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 16:01:14 -05:00
Anna Schumaker b6d45e666e core/file: Remove struct cache_file
And rework the init functions at the same time to reflect that data
files can now be placed under a subdirectory.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker 842547d735 core/file: Move common items out of cache and data file structs
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker 198fbf7f9b core/file: Create a new, unified file structure
We'll spend the next several patches slowly switching over to the new
way of accessing files.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker 82280edfa2 core/file: Rename struct file -> struct data_file
I'm going to create a unified struct file that should be able to share
code between data and cache files.  The first step is to push the old
structure to the side so it can still be used in other places.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker 48f79bdb49 core/file: Make name and subdir strings const for cache files
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker d7d553b80f core/tags/album: Create a new structure to store name and subdir strings
I eventually want to make these fields const in the file code, so we
need a different way to manage and eventually free these strings.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker a2854ef31e core/file: Remove the CACHE_FILE_INIT macro
It was only used by the testing code, so it's not seeing any use in the
real world.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker d96e8ca1ca core/audio: Don't clear NULL audio_pipeline state
Doing so can cause a deadlock somewhere deep inside gstreamer.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2018-02-20 15:39:33 -05:00
Anna Schumaker 1940a31a77 Ocarina 6.5.7
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-21 09:16:15 -05:00
Anna Schumaker 07196a7cc8 gui/sidebar: Conditionally hide the rename menu option
We can only rename user-created playlists, so let's not even show this
option for the other playlist types.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 15:19:36 -05:00
Anna Schumaker 64e27c1221 gui/sidebar: Add a right click menu
Implements #112: Add sidebar right-click menu
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 15:19:24 -05:00
Anna Schumaker 31cda0eebd gui/sidebar: Add a function for selecting paths
Normally GTK will take care of this, but we'll need it for the
right-click menu so we might as well implement it.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:58:57 -05:00
Anna Schumaker 9b9be4e322 gui/sidebar: Rename playlists when pressing the Backspace key
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:58:57 -05:00
Anna Schumaker 1374a025e1 gui/sidebar: Split out __gui_sidebar_delete() into a new function
This gives me a function that I can reuse in the sidebar right-click
menu, and it cleans up the main keypress handler so more keys can be
added.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:58:57 -05:00
Anna Schumaker 21e1796b14 gui/sidebar: Add a function for finding the current playlist
This seems like a useful function to have, and we already need it for
deleting playlists.  Let's make it a real function so we don't need to
duplicate code.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:58:57 -05:00
Anna Schumaker b4347d5a34 ocarina.ui: Make sidebar shrinkable
Enabling this option resolves several "Negative content width" warnings
that I was seeing.  Normally I'd just ignore them, but these were
generating SIGTRAP during testing and causing the tests to fail
unnecessarily.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:57:42 -05:00
Anna Schumaker 386514ac5c Ocarina 6.5.7-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 15:00:01 -04:00
Anna Schumaker 7558a32940 gui/ocarina: Load filepaths from the command line
If multiple paths are passed, then only load the first one to keep
things simple.

Implements #102: Allow outside tracks
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:46 -04:00
Anna Schumaker a8f94e9443 core/audio: Add a function to load tracks by filepath
If a matching track isn't found in the track database, then use the new
track_alloc_external() function to allocate an external track with the
correct tags.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker cc464ed198 core/tags/track: Add support for allocating external tracks
There might be rare occasions where users want to play a track that
exists outside their music library.  This patch adds support for
allocating and freeing these external tracks without adding them to the
track database.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker f9a573b6a3 core/tags/track: Add a function to look up tracks using only the path
I'm struggling to come up with a good way to combine code for this with
code for track_add(), since the lookup mechanism is basically the same.
I'll keep them separate for now and think on it some more.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker e7ceed9b5d core/library: Improve subdirectory handling for lookup and find
It's possible that the user may pass us a subdirectory of a path that
has already been added to the library.  We can use this to prevent
double-adding tracks if the user does this.  Additionally, this function
can be used to help look up tracks using only a filepath.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker 497ed57057 core/string: Add a function for checking if a string is a subdirectory
I want to use this to prevent users from adding a subdirectory of a path
that has already been added to the library.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker e6f34d34f0 core/audio: Rework audio message handling
Let's just do everything inline and cut out the extra function calls to
make the code simpler.  I also created functions to help tests send EOS
or error messages.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker 4986bdad13 core/audio: Rework how tracks are loaded
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker fc6e3ff464 core/audio: Listen for GstStateChanged messages
And use these to trigger the state changed callback.  Additionally, this
callback can now be used by tests to determine when we're done seeking.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker c03530f318 core/audio: Remove playbin-based player
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:43 -04:00
Anna Schumaker 0f2e30589d core/audio: Switch controls to the new pipeline player
Implements #103: Switch to custom GstPipeline
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:59:22 -04:00
Anna Schumaker 76e400e156 core/audio: Build up the rest of the pipeline
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:12:13 -04:00
Anna Schumaker 9c6a9f7759 core/audio: Add gstreamer elements for decoding files
We only play local music, so we know that we're using a file and we
don't need a uridecodebin to figure it out for us.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:12:13 -04:00
Anna Schumaker 1182e55df9 core/audio: Fix return type of audio_duration()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:12:13 -04:00
Anna Schumaker b558a9043c core/audio: Rearrange global variables
Add some whitespace to make it easier to follow what everything is.  I
also rename audio_bus to audio_old_id to make it easier to add in a new
audio bus.  Additionally, I changed test_audio_player() to
test_old_player() to make room for the new pipeline.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:12:13 -04:00
Anna Schumaker d95c693db2 tests: Rename gui.h -> loop.h
I want to run a main loop during the audio tests, which aren't in gui/.
Let's rename this file to make me feel better about using it :)

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:12:13 -04:00
Anna Schumaker 36349e9890 Ocarina 6.5.6
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-09-05 14:09:40 -04:00
Anna Schumaker a7280356c8 gui/sidebar: Start editing on a double-middle click
A single middle click could happen by accident, especially if the user
has a touchpad.  A double click is more likely to be deliberate.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-29 09:38:31 -04:00
Anna Schumaker 8b17962b4e gui/sidebar: Don't set user playlists as editable by default
Instead, watch for a middle click and set the editable state manually
from there.  This fixes a bug where double clicking on a user playlist
put us in editing mode instead of selecting the playlist for playback.

Fixes #110: Cannot select user playlists
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-25 13:36:52 -04:00
Anna Schumaker 7e303fa2b1 gui/sidebar: Add functions for setting and getting editable state
I want to be able to change this when the user middle-clicks a row, so
let's start by adding in some support functions.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-25 13:16:11 -04:00
Anna Schumaker 79accb5bb0 core/playlists/system: Load favorites and hidden playlists first
If we don't, then the Collection playlist will include hidden tracks
when it initializes.

Fixes #109: Hidden tracks showing up in Collection
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 10:04:02 -04:00
Anna Schumaker 8614aa37cd Ocarina 6.5.6-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:35:13 -04:00
Anna Schumaker 4990d68711 gui/playlists/user: Respond to the "edited" signal
We use this to perform user playlist renames with the user-entered text.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:16:20 -04:00
Anna Schumaker fac383e9fc gui/playlists/user: Respond to the "editing-started" signal
We use this to set the playlist name in the provided GtkEntry, clearing
out the number of tracks

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:16:13 -04:00
Anna Schumaker 466d9ce291 gui/sidebar: Enable editing playlist treeview entries
But only for user playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:16:02 -04:00
Anna Schumaker 3f9372051f gui/sidebar: Add a function for setting an iterator from string
The GtkTreeView editing callbacks give me a path string that I'll need
to convert into an iterator to find the edited playlist.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:15:48 -04:00
Anna Schumaker 2cfccb8177 gui/sidebar: Add a playlist update function that takes a playlist
Once we change the name of a playlist, we won't be able to find it in
the sidebar model.  So let's add a way to update an iterator by passing
the playlist to use for updating.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:15:31 -04:00
Anna Schumaker e773ae6f82 core/playlists/user: Add support for renaming playlists
Users may make typos in naming playlists, or they might think up better
names for playlists later.  Let's be friendly and give users a way to
change playlist names if desired.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:15:22 -04:00
Anna Schumaker 042cddb65b core/idle: Don't allocate the thread pool unless we really need it
This saves ~75MB in my testing, and can be a useful memory savor for the
case where all album art has been downloaded.

Implements #96: Allocate g_thread_pool as needed
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:15:04 -04:00
Anna Schumaker 2a95031ee7 core/idle: Schedule async idle tasks directly
Rather than processing them twice.  This should speed things up a tiny
bit.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:14:18 -04:00
Anna Schumaker df21aa1299 gui: Header file cleanup
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:14:13 -04:00
Anna Schumaker 432d3e5d62 core: Add useful headers to core.h
This way we don't need to keep including the same core libraries in
almost every file.

Implements #83: Include useful headers in core/core.h
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:13:46 -04:00
Anna Schumaker a15ad67029 core: Remove struct core_init_data and pass parameters directly
The core_init_data only held three items, most of which don't get set
during testing.  Removing this struct means several tests no longer need
to define a "dummy" initdata object to pass around during init.

Implements #92: Remove core_init_data
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:13:13 -04:00
Anna Schumaker 5247bf2de0 core/audio: Rename audio_ops -> audio_callbacks
And make them optional, that way tests don't need to keep creating fake
callbacks if they don't need anything from the audio layer.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:12:33 -04:00
Anna Schumaker d3df9a69f2 gui/ocarina: Add a --sync option
Passing this option tells Ocarina that it should only process
synchronous idle tasks, such as library or playlist updates.  This
effectively disables album art fetching (and any async jobs we add in
the future).

Implements #91: Add --no-fetch option
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:10:52 -04:00
Anna Schumaker f46ef37630 core/idle: Change idle_init() to take an idle_sync_t
Rather than offering two init functions for testing.  This lets the UI
select if they want async idle tasks (like Album Art fetching).

Implements #90: Give idle_init an async flag
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-08-22 09:10:08 -04:00
Anna Schumaker 76ebfaa6d4 Ocarina 6.5.5
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-06-19 07:59:30 -04:00
Anna Schumaker cc5f65bf82 core/playlists/system: Reset queued tracks when empty
Users might not remember any sorting or random settings from the last
time they queued tracks, so saving these values could result in
selecting tracks in an unexpected order.  Fix this by resetting the
queued tracks options whenever the playlist is emptied.

Fixes #108: Unset random and sorting when finishing queued tracks
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-06-12 10:40:57 -04:00
Anna Schumaker bb40ef479f core/playlists/generic: Initialize pl_random and pl_length
These fields were working fine for statically initialized playlists, but
they get set to garbage values when g_malloc()-ing a playlist.  Let's
make sure they get initialized properly with the rest of the playlist.

Fixes #107: Initialize all values in playlist_generic_init()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-06-12 08:19:21 -04:00
Anna Schumaker d1c682501f Ocarina 6.5.5-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 09:31:53 -04:00
Anna Schumaker 448b4a16f4 Remove core/queue.c and associated files
Everything has been merged into the playlist layer to better match how
playlists are actually used.  This means we can remove the queue files.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:05 -04:00
Anna Schumaker 3286b61dcf core/playlist: Add playlist_generic_{alloc,free}() functions
Artist and library playlists are allocated manually, so there should be
generic functions that both can use to get a playlist pointer and free
it when we're done.

I also add a callback for telling the UI when new playlists have been
allocated.  This isn't needed for Library playlists, but this is the
only way the UI can know about new Artists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 7f54562b71 core/playlist: Add a playlist_init_sorted() function
Sometimes we want to initialize the playlist with sort data, other times
we don't.  This gives us functions we can call for both!

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker cdbf8b1736 core/playlist: Rename pl_private -> pl_search
And change its type to gchar **.  This lets the playlist code manage
setting and freeing search strings.  The UI is still responsible for
how this string is used.

This patch also  lets me remove the now-unused queue_deinit() function
and associated callback.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 1dd0b7c2aa core/playlist: Move playlist tracks into the playlist struct
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker a808cac04c gui/model: Store a GList in the GtkTreeIter
Storing the current link from the playlist should give us immediate
access to the track, rather than needing to use playlist_at() all the
time.  Additionally, we can now use g_list_next() whenever we need to do
a gtk_tree_model_iter_next()

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 1c6305e24e core/playlist: Replace queue_at() with playlist_at()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 8bf5aefd1a core/playlist: Replace queue_iter with a playlist_iter
The iterator only needs to point to the current position on the GQueue,
so we can do away with manual position tracking.  This lets us use a
GList pointer as an iterator instead of something more complicated.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 03e7346900 core/playlist: Move current track into the playlist struct
I keep using the queue_iter struct for now to reduce code churn in this
patch.  I'll be replacing it in the next patch.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 18f1bfe801 core/playlist: Move playlist iterator functions into the playlist layer
I also take this chance to add extra functions for directly manipulating
the current track, since this is something we do fairly often.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker f25bdab367 core/playlist: Move playlist sort order into the playlist struct
Rather than using a variable from the queue layer.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 0c197c10f9 core/playlist: Move playlist length into the playlist struct
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker fda29aaf13 core/playlist: Add a playlist_generic_deinit() function
We'll need this to clean up playlists once queue_deinit() goes away.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 2fb27178ee core/playlist: Implement playlist_add() directly
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker efbbc4ceff core/playlist: Implement playlist_size() directly
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 92bb742f8f core/playlist: Implement playlist_generic_add_front() directly
This function adds a track directly to the front of the playlist,
without any existence checks.  This lets us use it for the History
playlist, which allows multiple tracks.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 6dbc574954 core/playlist: Implement playlist_generic_clear() directly
I can reuse the "removed" callback to for this, rather than implementing
a new "cleared" callback directly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 859ff8656f core/playlist: Implement playlist_has() directly
We can make use of g_queue_find() to find the track for us, and then
convert its return value into a boolean.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker a45c7d6889 gui/playlist: Implement playlist_generic_remove() directly
Rather than iterating over the entire playlist ourselves, we can instead
use the g_queue_remove_all() function to do most of the work for us and
then send an appropriate number of "removed" callbacks.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker ca6c5293c6 core/playlist: Add a playlist_generic_update() function
This function simply triggers the "updated" callback for the given
playlist and track.  I updated the gui model to handle taking tracks
instead of row indexes, since this lets me reuse the same for-each
function that we do for sorting.  Additionally, this prevents UI updates
for playlists that aren't currently visible.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 287c6e0e9c core/queue: Remove unused queue_selected()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 1d09e967d0 core/playlist: Move random setting into the playlist struct
I move the random variable into the playlist code since it is no longer
used by the queue layer.  This gives me the opportunity to change it
into a boolean rather than a bit flag.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker f670a3796b core/playlist: Implement playlist_next() directly
Let's have the playlist generic functions pick the next track rather
than redirecting to the queue code.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker bd8df2a169 core/playlist: Add playlist_selected() for selected tracks
In most cases this function just triggers a UI update, but system
playlists have a little extra bookkeeping to do to remove the track from
the Queued Tracks playlist.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 07bf09c2ad core/playlist: Add a playlist_generic_resort() function
This function is called to force-sort the playlist.  Additionally, we
trigger the "playlist-sorted" callback to to notify the gui that
playlist rows need to be updated.

Additionally, I implement the gui_model_update_all() function to loop
over the model and update all rows of the current playlist.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker e74c1c053c core/playlist: Implement sorting directly
Rather than passing through to queue_sort().  Additionally, I remove the
queue_sort() function.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 793a8a5817 core/playlist: Add a playlist_clear_sort() function
This replaces the "reset" field that had been passed to sort.  I think
this makes things a little more straightforward, and gives us a function
we can call when freeing playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker d2335f5c6e core/playlist: Add a playlist_generic_save() function
This function uses the playlist save flags enum to determine what
exactly to save, including support for backwards compatibility with
6.4.x playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 37d95656e9 core/playlist: Add playlist_generic_load() function
This function uses the playlist save flags enum to determine what
exactly to load, including support for backwards compatibility with
6.4.x playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker a87373f335 core/playlist: Add playlist_callbacks struct
This will be used to gradually phase out the queue_ops struct over the
next several patches.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker 93cb7145e6 core/playlist: Clean up playlist headers
I renamed "type.h" to "playlist.h" to better match what it does.
Additionally, I moved generic playlist functions into a new header file.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker ed095eb987 core/audio: Clean up loading tracks
This lets us call playlist_played() whenever we change tracks to send UI
updates once, rather than using multiple updates to change playcount and
now-playing status.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:45:04 -04:00
Anna Schumaker ee8825745b Ocarina 6.5.4
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-13 08:42:01 -04:00
Anna Schumaker 4ad9b39398 Ocarina 6.5.4-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-05-01 07:36:53 -04:00
Anna Schumaker ae604ab4a8 core/playlists/system: Remove sys_playlist struct
It isn't needed now that playlist differences are handled in the upper
layer.  This also lets me create a shared set of operations for dynamic
playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 2a8288e1b3 core/playlists/system: Cleanup system playlist updating
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 8af9148606 core/playlists/system: Clean up system playlist initialization
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 8a5163fb68 core/playlists/system: Clean up loading system playlists
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 8220dd9932 core/playlists/system: Clean up saving system playlists
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 0754f10883 core/playlist: Remove playlist_update()
This was only used by system playlists to keep the unplayed, most played,
and least played playlist up to date.  We can handle this internally
through the playlist_played() handler.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker a33decf549 core/queue: Remove Q_REPEAT flag
It is unused now that the queued tracks playlist manually removes tracks
when picked.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 9fa5f0b0db core/queue: Remove unused queue_erase() function
It's just added complexity that we don't need to keep around at this
point.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker b17585237a core/playlist: Add a playlist_played() function
This is used to notify when tracks have been played so dynamic playlists
can be updated, and so the model can display the correct playcount.

The old system playlist tests are mostly unnecessary at this point, so I
remove them as part of this patch.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 7655b0cae8 core/playlist: playlist_{next,prev}() uses a playlist function pointer
Allowing us to access the playlist directly, rather than going through
the playlist-type interface.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 1fa31c51a6 core/playlist: Remove playlist_get_queue()
We can just look up the playlist instead, and then access the queue
variable from there.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 72d0f9a248 core/playlist: Remove playlist_get_id() function
And replace its use with direct access of the playlist->pl_id variable.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker b5c1af263a core/playlist: Rename playlist_cur() -> playlist_current()
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 5c215df0bf core/playlist: playlist_select() takes a playlist pointer
And stores it for future reference, so we don't have to keep looking up
current and previous playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 73825dd916 core/playlist: Add a function to look up playlists by id
This is much more straightforward than converting an id to a name and
then looking up playlists from there.  This patch removes the now-unused
playlist_get_name() function.

This patch also lets me simplify the system playlist unit test, since I
can now check several similar operations using a loop.  Additionally, I
change calls to pl_system_lookup() into pl_system_get() for efficiency.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 09e358b96b core/playlist: Rename playlist_get() -> playlist_lookup()
I think "lookup" is a better name for this function, since it's similar
to performing a dictionary lookup.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker bef0c70e5a core/playlist: playlist_sort() takes a playlist pointer
Additionally, it also uses a playlist-level function pointer to decide
what to do.  In most cases this calls the generic sort function, but the
history playlist should never be sorted.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 61e3137131 core/playlist: playlist_{get,set}_random() take a playlist pointer
Additionally, playlist_set_random() uses a playlist-level function
pointer to decide what to do.  In most cases this will simply toggle the
flag, but the history playlist does not support random playback.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 0c1147513e core/playlist: playlist_size() takes a playlist pointer
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 0e523ed279 core/playlist: playlist_has() takes a playlist pointer
Rather than going through a lookup step.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 3364d4733e core/playlist: playlist_remove() uses a playlist-level function pointer
Rather than going through playlist-type operations.  Additionally, I
take this opportunity to change playlist_remove() to take a playlist
struct directly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 1b9cdf5a5c core/playlist: playlist_add() uses a playlist-level function pointer
Rather than going through playlist-type operations.  Additionally, I
take this opportunity to change playlist_add() to take a playlist struct
directly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 066027ecb6 core/playlist: playlist_delete() uses a playlist-level function pointer
Rather than going through the playlist-type operations.  Additionally, I
take this opportunity to change playlist_delete() to take a playlist
struct directly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker ca5f0701e9 core/playlist: playlist_new() returns a playlist pointer
This is much more useful than a boolean status, since we can use the
playlist pointer right away.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 846f7df9c1 gui/sidebar: Add a gui_sidebar_iter_playlist() function
It's frequently useful to get the playlist directly, rather than looking
up type and name first.  Let's add a function to do just that!

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 4d68ce8ce6 gui/playlists/library: Return a new playlist pointer
This pointer can be used immediately by testing code instead of simply
looking at the return status.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 249de0da21 core/playlists/system: Add tests for new tracks and deleting tracks
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 58cd43c330 core/database: Pass database index when allocating dbes
This is useful for user playlists so they can store their playlist-id
directly, letting us remove the entire playlist_get_id() function.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 9b4a1785a1 core/queue: Remove Q_ADD_FRONT flag
And replace its use with a queue_add_front() function instead.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker c59b097638 core/queue: Remove Q_ENABLED flag
Queues are always enabled now that we can select different playlists as
the default.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 7e3e4194f3 core/queue: Remove Q_NO_SORT
This flag was only used by the History playlist, but we have a noop
function to handle this instead.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 186367afe0 core/queue: Remove qop_save()
This callback is no longer used now that saving happens in the playlist
layer.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 62e494f2af core/queue: Remove Q_SAVE_FLAGS and Q_SAVE_SORT
These flags are unused now that the playlist layer handles saving.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:40:02 -04:00
Anna Schumaker 15ceda1194 Ocarina 6.5.3
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-28 09:38:17 -04:00
Anna Schumaker 638caeaa91 gui/sidebar: Set the playlist before changing the random button
Otherwise we could end up changing the random setting on the previous
playlist when displaying a new one.

Fixes #106: Switching playlists clears random setting
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-26 14:25:03 -04:00
Anna Schumaker a7565340d2 Ocarina 6.5.3-rc
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:59:08 -04:00
Anna Schumaker 0b231119d2 gui/artwork: Rework importing album art
And add a test to check that everything is set correctly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker fc1e917aee gui/artwork: Rework setting album art
I cleaned up several of the functions and added tests for making sure
everything is set correctly.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker bc1c462d36 gui/artwork: Add an accessor function for the artwork image
This patch also adds a unit test checking that the image is initialized
properly.  In addition, I simplify things by changing the image widget
to be a direct child of the GtkButton.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 8fd4e4c637 gui/audio: Add a test for the volume button
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 96e5749e7f gui/audio: Add a test for seeking
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 2a845feb38 gui/audio: Move audio_ops struct towards the top
This keeps all the audio operations together, and matches how we define
operations structs throughout the rest of Ocarina.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker f8c0668e5e gui/audio: Add an accessor function for the pause_after combo box
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 07d735eeee gui/audio: Add accessor functions for control buttons
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 943ab02aa5 gui/audio: Add position and duration label accessor functions
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 869e83b7bd gui/audio: Add accessor functions for audio tags
And begin working on new, cleaner unit test code.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 88bf71ac22 Remove gui/view.c
This code is obsolete now that gui/playlist.c handles keypress and
right-click events on the main treeview.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 3bf99b12eb gui/playlist: Respond to right-clicking the main treeview
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker e660e3f0b2 gui/treeview: Add a "select path at pos" function
This function isn't easy to test without knowledge of screen
coordinates, so I didn't include a unit test this time.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 3ae5e0f535 gui/playlist: Handle key press events on the main treeview
Setting up a unit test for this is almost impossible, so we don't do
that here.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 39bc7492d4 gui/playlist: Clean up file and rewrite unit test
The unit test now checks how we respond to the row-activated signal.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker e7d2fa5c4d gui/treeview: Add a function for listing selected tracks
This will be used by higher layers to add tracks to playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 155b9c3ee6 gui/treeview: Add a test for the row-activated signal
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker 7fbef057bf core: Add a field to initdata for enabling async idle tasks
I'll use this when testing gui album art to enable fetching the artwork.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker d373c55320 core/queue: Add a queue_erase_track() function
Used to trigger the erase callback on tracks, rather than indexes.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:58:05 -04:00
Anna Schumaker f147c30c30 Ocarina 6.5.2
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-14 13:56:39 -04:00
Anna Schumaker 1e6ab2e23c gui/playlist: Unbold the previous playlist
Looks like this wasn't happening automatically, so let's be sure to
update the previous playlist's row after selecting a new playlist.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-04 11:27:47 -04:00
Anna Schumaker 83a21863b9 gui/sidebar: Select the current playlist on startup
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-04 11:27:45 -04:00
Anna Schumaker e876f8125f gui/sidebar: Restore expanded rows on startup
Fixes #94: Playlist heading not open by default
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-04 11:27:24 -04:00
Anna Schumaker dc53ae271b gui/sidebar: Store the row expanded setting for later use
I want to use this to track if the user has expanded or collapsed any of
the sidebar rows.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-04-04 10:04:33 -04:00
104 changed files with 5028 additions and 4748 deletions

View File

@ -10,7 +10,7 @@ option(CONFIG_TESTING_ARTWORK "Enable album artwork fetching tests" ON)
# Configure settings # Configure settings
set(CONFIG_MAJOR 6) set(CONFIG_MAJOR 6)
set(CONFIG_MINOR 5) set(CONFIG_MINOR 5)
set(CONFIG_MICRO 2) set(CONFIG_MICRO 10)
set(CONFIG_RC ON) set(CONFIG_RC ON)
set(CONFIG_VERSION "${CONFIG_MAJOR}.${CONFIG_MINOR}.${CONFIG_MICRO}") set(CONFIG_VERSION "${CONFIG_MAJOR}.${CONFIG_MINOR}.${CONFIG_MICRO}")

View File

@ -1,6 +1,6 @@
# Maintainer: Anna Schumaker <anna@nowheycreamery.com> # Maintainer: Anna Schumaker <anna@nowheycreamery.com>
pkgname=ocarina pkgname=ocarina
pkgver=6.5.1 pkgver=6.5.9
pkgrel=1 pkgrel=1
pkgdesc="A simple GTK+ and GStreamer based music player." pkgdesc="A simple GTK+ and GStreamer based music player."
url="http://www.nowheycreamery.com/" url="http://www.nowheycreamery.com/"

View File

@ -6,77 +6,117 @@
#include <core/playlist.h> #include <core/playlist.h>
#include <core/settings.h> #include <core/settings.h>
#define LOAD_PLAYING (1 << 0) /* Begin playback after loading */
#define LOAD_HISTORY (1 << 1) /* Add the track to the history */
#define LOAD_DEFAULT (LOAD_PLAYING | LOAD_HISTORY)
static const char *SETTINGS_TRACK = "core.audio.cur"; static const char *SETTINGS_TRACK = "core.audio.cur";
static const char *SETTINGS_VOLUME = "core.audio.volume"; static const char *SETTINGS_VOLUME = "core.audio.volume";
static struct file audio_file = FILE_INIT("cur_track", 0);
static struct track *audio_track = NULL; static struct file audio_file = FILE_INIT_DATA("", "cur_track", 0);
static GstElement *audio_player = NULL; static struct track *audio_track = NULL;
static struct audio_ops *audio_ops = NULL; static int audio_pause_count = -1;
static int audio_pause_count = -1;
static guint audio_bus = 0; static GstElement *audio_pipeline = NULL;
static GstElement *audio_source = NULL;
static GstElement *audio_decoder = NULL;
static GstElement *audio_converter = NULL;
static GstElement *audio_volume = NULL;
static GstElement *audio_sink = NULL;
static guint audio_bus_id = 0;
static struct audio_callbacks *audio_cb = NULL;
static bool __audio_change_state(GstState state) static bool __audio_change_state(GstState state)
{ {
GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE; if (audio_cur_state() == state)
if (audio_cur_state() != state)
ret = gst_element_set_state(audio_player, state);
if (ret == GST_STATE_CHANGE_FAILURE)
return false; return false;
return gst_element_set_state(audio_pipeline, state) != GST_STATE_CHANGE_FAILURE;
audio_ops->on_state_change(state);
return true;
} }
/* Load a track, but don't add it to the history. */ static struct track *__audio_load(struct track *track, unsigned int flags)
static struct track *__audio_load_basic(struct track *track, GstState state)
{ {
struct track *prev = audio_track; struct track *prev = audio_track;
gchar *path, *uri; gchar *path;
if (!track) if (!track)
return NULL; return NULL;
path = track_path(track);
uri = gst_filename_to_uri(path, NULL);
audio_track = track; audio_track = track;
path = track_path(track);
if (audio_cur_state() != GST_STATE_NULL)
gst_element_set_state(audio_pipeline, GST_STATE_READY);
g_object_set(G_OBJECT(audio_source), "location", path, NULL);
gst_element_set_state(audio_pipeline, flags & LOAD_PLAYING ?
GST_STATE_PLAYING : GST_STATE_PAUSED);
gst_element_set_state(audio_player, GST_STATE_NULL); playlist_played(prev);
g_object_set(G_OBJECT(audio_player), "uri", uri, NULL); if (prev && TRACK_IS_EXTERNAL(prev))
audio_ops->on_load(track); track_free_external(prev);
__audio_change_state(state);
queue_updated(playlist_get_queue(PL_SYSTEM, "Queued Tracks"), prev); playlist_selected(track);
queue_updated(playlist_get_queue(PL_SYSTEM, "Collection"), prev); if (flags & LOAD_HISTORY && !TRACK_IS_EXTERNAL(track))
queue_updated(playlist_get_queue(PL_SYSTEM, "Collection"), audio_track); playlist_add(playlist_lookup(PL_SYSTEM, "History"), track);
if (audio_cb)
audio_cb->audio_cb_load(track);
audio_save(); audio_save();
g_free(uri);
g_free(path); g_free(path);
return track; return track;
} }
static struct track *__audio_load(struct track *track, GstState state) static void __audio_pad_added(GstElement *element, GstPad *pad, gpointer data)
{ {
if (__audio_load_basic(track, state)) GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink");
playlist_add(PL_SYSTEM, "History", track);
return track;
}
static struct track *__audio_next(GstState state) gst_element_link(element, audio_converter);
{ gst_pad_link(pad, sink);
return __audio_load(playlist_next(), state); gst_object_unref(sink);
} }
static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data) static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data)
{ {
GstObject *source = GST_OBJECT(GST_MESSAGE_SRC(message));
gchar *debug = NULL;
GError *error = NULL;
GstState old, state, next;
unsigned int load_flags = LOAD_DEFAULT;
switch (GST_MESSAGE_TYPE(message)) { switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR: case GST_MESSAGE_ERROR:
audio_error(message); gst_message_parse_error(message, &error, &debug);
g_printerr("ERROR from element %s: %s\n",
GST_OBJECT_NAME(source), error->message);
g_printerr("DEBUG details: %s\n", debug ? debug : "none");
g_error_free(error);
g_free(debug);
if (audio_cur_state() != GST_STATE_PLAYING)
load_flags = LOAD_HISTORY;
__audio_load(playlist_next(), load_flags);
break; break;
case GST_MESSAGE_EOS: case GST_MESSAGE_EOS:
audio_eos(); track_played(audio_track);
if (audio_pause_count >= 0) {
audio_pause_after(audio_pause_count - 1);
if (audio_pause_count == -1)
load_flags = LOAD_HISTORY;
}
__audio_load(playlist_next(), load_flags);
break;
case GST_MESSAGE_STATE_CHANGED:
if (!audio_cb || source != GST_OBJECT(audio_pipeline))
break;
gst_message_parse_state_changed(message, &old, &state, &next);
if (state == GST_STATE_PLAYING || state == GST_STATE_PAUSED) {
if (next == GST_STATE_VOID_PENDING)
audio_cb->audio_cb_state_change(state);
}
default: default:
break; break;
} }
@ -90,29 +130,40 @@ static bool __audio_init_idle(void *data)
if (settings_has(SETTINGS_TRACK)) { if (settings_has(SETTINGS_TRACK)) {
track = settings_get(SETTINGS_TRACK); track = settings_get(SETTINGS_TRACK);
__audio_load(track_get(track), GST_STATE_PAUSED); __audio_load(track_get(track), LOAD_HISTORY);
} else if (file_open(&audio_file, OPEN_READ)) { } else if (file_open(&audio_file, OPEN_READ)) {
file_readf(&audio_file, "%u", &track); track = file_readu(&audio_file);
file_close(&audio_file); file_close(&audio_file);
file_remove(&audio_file); file_remove(&audio_file);
__audio_load(track_get(track), GST_STATE_PAUSED); __audio_load(track_get(track), LOAD_HISTORY);
} }
return true; return true;
} }
void audio_init(int *argc, char ***argv, struct audio_ops *ops) void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks)
{ {
unsigned int volume = 100; unsigned int volume = 100;
GstBus *bus; GstBus *bus;
gst_init(argc, argv); gst_init(argc, argv);
audio_player = gst_element_factory_make("playbin", "ocarina_player"); audio_cb = callbacks;
audio_ops = ops; audio_pipeline = gst_pipeline_new("pipeline");
audio_source = gst_element_factory_make("filesrc", "source");
audio_decoder = gst_element_factory_make("decodebin", "decoder");
audio_converter = gst_element_factory_make("audioconvert", "converter");
audio_volume = gst_element_factory_make("volume", "volume");
audio_sink = gst_element_factory_make("autoaudiosink", "sink");
bus = gst_pipeline_get_bus(GST_PIPELINE(audio_pipeline));
audio_bus_id = gst_bus_add_watch(bus, __audio_message, NULL);
bus = gst_pipeline_get_bus(GST_PIPELINE(audio_player)); gst_bin_add_many(GST_BIN(audio_pipeline), audio_source, audio_decoder,
audio_bus = gst_bus_add_watch(bus, __audio_message, NULL); audio_converter, audio_volume,
audio_sink, NULL);
gst_element_link(audio_source, audio_decoder);
gst_element_link_many(audio_converter, audio_volume, audio_sink, NULL);
g_signal_connect(audio_decoder, "pad-added", G_CALLBACK(__audio_pad_added), NULL);
gst_object_unref(bus); gst_object_unref(bus);
if (settings_has(SETTINGS_VOLUME)) if (settings_has(SETTINGS_VOLUME))
@ -124,19 +175,24 @@ void audio_init(int *argc, char ***argv, struct audio_ops *ops)
void audio_deinit() void audio_deinit()
{ {
gst_element_set_state(audio_player, GST_STATE_NULL); gst_element_set_state(audio_pipeline, GST_STATE_NULL);
gst_object_unref(GST_ELEMENT(audio_player)); gst_object_unref(GST_ELEMENT(audio_pipeline));
g_source_remove(audio_bus); g_source_remove(audio_bus_id);
audio_player = NULL; audio_pipeline = NULL;
audio_track = NULL; audio_source = NULL;
audio_decoder = NULL;
audio_converter = NULL;
audio_volume = NULL;
audio_sink = NULL;
audio_track = NULL;
gst_deinit(); gst_deinit();
} }
void audio_save() void audio_save()
{ {
if (audio_track) if (audio_track && !TRACK_IS_EXTERNAL(audio_track))
settings_set(SETTINGS_TRACK, track_index(audio_track)); settings_set(SETTINGS_TRACK, track_index(audio_track));
} }
@ -144,7 +200,19 @@ bool audio_load(struct track *track)
{ {
if (track == audio_track) if (track == audio_track)
return false; return false;
return __audio_load(track, GST_STATE_PLAYING); return __audio_load(track, LOAD_DEFAULT) != NULL;
}
bool audio_load_filepath(const gchar *filepath)
{
struct track *track;
if (!filepath)
return false;
track = track_lookup(filepath);
if (!track)
track = track_alloc_external(filepath);
return audio_load(track);
} }
struct track *audio_cur_track() struct track *audio_cur_track()
@ -155,8 +223,8 @@ struct track *audio_cur_track()
GstState audio_cur_state() GstState audio_cur_state()
{ {
GstState cur = GST_STATE_NULL; GstState cur = GST_STATE_NULL;
if (audio_player) if (audio_pipeline)
gst_element_get_state(audio_player, gst_element_get_state(audio_pipeline,
&cur, NULL, &cur, NULL,
GST_CLOCK_TIME_NONE); GST_CLOCK_TIME_NONE);
return cur; return cur;
@ -171,13 +239,13 @@ void audio_set_volume(unsigned int volume)
vol = (gdouble)volume / 100; vol = (gdouble)volume / 100;
settings_set(SETTINGS_VOLUME, volume); settings_set(SETTINGS_VOLUME, volume);
g_object_set(G_OBJECT(audio_player), "volume", vol, NULL); g_object_set(G_OBJECT(audio_volume), "volume", vol, NULL);
} }
unsigned int audio_get_volume() unsigned int audio_get_volume()
{ {
gdouble volume; gdouble volume;
g_object_get(G_OBJECT(audio_player), "volume", &volume, NULL); g_object_get(G_OBJECT(audio_volume), "volume", &volume, NULL);
return volume * 100; return volume * 100;
} }
@ -199,7 +267,7 @@ bool audio_seek(gint64 offset)
{ {
if (!audio_track) if (!audio_track)
return false; return false;
return gst_element_seek_simple(audio_player, return gst_element_seek_simple(audio_pipeline,
GST_FORMAT_TIME, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH, GST_SEEK_FLAG_FLUSH,
offset); offset);
@ -208,17 +276,17 @@ bool audio_seek(gint64 offset)
gint64 audio_position() gint64 audio_position()
{ {
gint64 position; gint64 position;
if (gst_element_query_position(audio_player, if (gst_element_query_position(audio_pipeline,
GST_FORMAT_TIME, GST_FORMAT_TIME,
&position)) &position))
return position; return position;
return 0; return 0;
} }
int64_t audio_duration() gint64 audio_duration()
{ {
gint64 duration; gint64 duration;
if (gst_element_query_duration(audio_player, if (gst_element_query_duration(audio_pipeline,
GST_FORMAT_TIME, GST_FORMAT_TIME,
&duration)) &duration))
return duration; return duration;
@ -229,65 +297,48 @@ int64_t audio_duration()
struct track *audio_next() struct track *audio_next()
{ {
return __audio_next(GST_STATE_PLAYING); return __audio_load(playlist_next(), LOAD_DEFAULT);
} }
struct track *audio_prev() struct track *audio_prev()
{ {
return __audio_load_basic(playlist_prev(), GST_STATE_PLAYING); return __audio_load(playlist_prev(), LOAD_PLAYING);
} }
struct track *audio_eos() bool audio_pause_after(int n)
{ {
/* Mark current track as played */ if (n >= -1 && n != audio_pause_count) {
if (audio_track) {
track_played(audio_track);
playlist_update(PL_SYSTEM, "Unplayed");
playlist_update(PL_SYSTEM, "Most Played");
playlist_update(PL_SYSTEM, "Least Played");
}
/* Check pause count and pick the next track */
if (audio_pause_count >= 0) {
audio_pause_after(audio_pause_count - 1);
if (audio_pause_count == -1)
return __audio_next(GST_STATE_PAUSED);
}
return __audio_next(GST_STATE_PLAYING);
}
void audio_error(GstMessage *error)
{
gchar *path = NULL, *debug = NULL;
GError *err = NULL;
if (audio_track)
path = track_path(audio_track);
if (error)
gst_message_parse_error(error, &err, &debug);
g_print("Error: %s (%s)\n", err->message, path);
if (debug)
g_print("Debug details: %s\n", debug);
__audio_next(audio_cur_state());
g_error_free(err);
g_free(debug);
g_free(path);
}
void audio_pause_after(int n)
{
if (n != audio_pause_count) {
audio_pause_count = n; audio_pause_count = n;
audio_ops->on_config_pause(audio_pause_count); if (audio_cb)
audio_cb->audio_cb_config_pause(audio_pause_count);
return true;
} }
return false;
}
int audio_get_pause_count(void)
{
return audio_pause_count;
} }
#ifdef CONFIG_TESTING #ifdef CONFIG_TESTING
GstElement *test_audio_player() void test_audio_eos()
{ {
return audio_player; GstMessage *message = gst_message_new_eos(GST_OBJECT(audio_pipeline));
__audio_message(NULL, message, NULL);
gst_message_unref(message);
}
void test_audio_error(GError *error, gchar *debug)
{
GstMessage *message = gst_message_new_error(
GST_OBJECT(audio_pipeline), error, debug);
__audio_message(NULL, message, NULL);
gst_message_unref(message);
}
GstElement *test_audio_pipeline()
{
return audio_pipeline;
} }
#endif /* CONFIG_TESTING */ #endif /* CONFIG_TESTING */

View File

@ -1,13 +1,7 @@
/* /*
* Copyright 2014 (c) Anna Schumaker. * Copyright 2014 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
#include <core/tags/tags.h>
static bool core_defragment(void *data) static bool core_defragment(void *data)
{ {
@ -18,17 +12,14 @@ static bool core_defragment(void *data)
return true; return true;
} }
void core_init(int *argc, char ***argv, struct core_init_data *init) void core_init(int *argc, char ***argv, struct playlist_callbacks *playlist_cb,
struct audio_callbacks *audio_cb, enum idle_sync_t idle_sync)
{ {
#ifdef CONFIG_TESTING idle_init(idle_sync);
idle_init_sync();
#else
idle_init();
#endif /* CONFIG_TESTING */
settings_init(); settings_init();
tags_init(); tags_init();
playlist_init(init->playlist_ops); playlist_init(playlist_cb);
audio_init(argc, argv, init->audio_ops); audio_init(argc, argv, audio_cb);
idle_schedule(IDLE_SYNC, core_defragment, NULL); idle_schedule(IDLE_SYNC, core_defragment, NULL);
} }

View File

@ -36,11 +36,9 @@ static struct db_entry *__dbe_next(const struct database *db, unsigned int index
static struct db_entry *__dbe_read(struct database *db, unsigned int index) static struct db_entry *__dbe_read(struct database *db, unsigned int index)
{ {
struct db_entry *dbe = NULL; struct db_entry *dbe = NULL;
int valid;
file_readf(&db->db_file, "%d", &valid); if (file_readd(&db->db_file))
if (valid) dbe = db->db_ops->dbe_read(&db->db_file, index);
dbe = db->db_ops->dbe_read(&db->db_file);
g_ptr_array_index(db->db_entries, index) = dbe; g_ptr_array_index(db->db_entries, index) = dbe;
return dbe; return dbe;
@ -76,7 +74,7 @@ void db_init(struct database *db, const char *filepath, bool autosave,
db->db_autosave = autosave; db->db_autosave = autosave;
db->db_entries = g_ptr_array_new(); db->db_entries = g_ptr_array_new();
db->db_keys = g_hash_table_new(g_str_hash, g_str_equal); db->db_keys = g_hash_table_new(g_str_hash, g_str_equal);
file_init(&db->db_file, filepath, fmin); file_init_data(&db->db_file, "", filepath, fmin);
} }
void db_deinit(struct database *db) void db_deinit(struct database *db)
@ -118,7 +116,7 @@ void db_load(struct database *db)
if (file_open(&db->db_file, OPEN_READ) == false) if (file_open(&db->db_file, OPEN_READ) == false)
return; return;
file_readf(&db->db_file, "%u", &size); size = file_readu(&db->db_file);
g_ptr_array_set_size(db->db_entries, size); g_ptr_array_set_size(db->db_entries, size);
for (unsigned int i = 0; i < size; i++) { for (unsigned int i = 0; i < size; i++) {
if (__dbe_read(db, i)) if (__dbe_read(db, i))
@ -137,7 +135,7 @@ struct db_entry *db_insert(struct database *db, const gchar *key)
struct db_entry *item = NULL; struct db_entry *item = NULL;
if (key) if (key)
item = db->db_ops->dbe_alloc(key); item = db->db_ops->dbe_alloc(key, db_actual_size(db));
if (item) { if (item) {
g_ptr_array_add(db->db_entries, item); g_ptr_array_add(db->db_entries, item);

View File

@ -26,14 +26,14 @@ void date_today(struct date *date)
void date_read(struct file *f, struct date *date) void date_read(struct file *f, struct date *date)
{ {
file_readf(f, "%u %u %u", &date->d_year, &date->d_month, &date->d_day); date->d_year = file_readu(f);
date->d_month = file_readu(f);
date->d_day = file_readu(f);
} }
void date_read_stamp(struct file *f, struct date *date) void date_read_stamp(struct file *f, struct date *date)
{ {
uint32_t stamp; date->d_stamp = be32toh(file_readu(f));
file_readf(f, "%u", &stamp);
date->d_stamp = be32toh(stamp);
} }
void date_write(struct file *f, struct date *date) void date_write(struct file *f, struct date *date)

View File

@ -12,67 +12,43 @@
g_printerr("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, error) g_printerr("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, error)
#define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno)) #define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno))
static gchar *__file_path(const gchar *base, const gchar *dir, static void __file_init_common(struct file *file, const gchar *subdir,
const gchar *name) const gchar *name, unsigned int min,
const gchar *(*user_dir)(void))
{ {
return g_build_filename(base, OCARINA_NAME, dir ? dir : "", name, NULL); file->f_file = NULL;
file->f_name = name;
file->f_subdir = subdir;
file->f_mode = CLOSED;
file->f_version = OCARINA_MINOR_VERSION;
file->f_prev = 0;
file->f_min = min;
file->f_user_dir = user_dir;
} }
static gchar *__file_build_path(const gchar *base, const gchar *dir, static bool __file_open(struct file *file, enum open_mode mode)
const gchar *name)
{ {
if (string_length(name) == 0) gchar *cmode, *path;
return g_strdup("");
return __file_path(base, dir, name);
}
static gchar *__file_build_tmp(const gchar *base, const gchar *dir, if (mode == OPEN_READ || mode == OPEN_READ_BINARY) {
const gchar *name) cmode = "r";
{ path = file_path(file);
gchar *tmp, *res; } else {
cmode = "w";
if (string_length(name) == 0) path = file_write_path(file);
return g_strdup("");
tmp = g_strdup_printf(".%s.tmp", name);
res = __file_path(base, dir, tmp);
g_free(tmp);
return res;
}
static bool __file_exists(gchar *path)
{
bool ret = g_file_test(path, G_FILE_TEST_EXISTS);
g_free(path);
return ret;
}
static FILE *__file_open(gchar *path, const gchar *mode)
{
FILE *ret = g_fopen(path, mode);
if (!ret)
REPORT_ERRNO(path);
g_free(path);
return ret;
}
static void __file_close(FILE *file, gchar *path, gchar *tmp)
{
if (file) {
fclose(file);
if (path && tmp)
g_rename(tmp, path);
} }
file->f_file = g_fopen(path, cmode);
if (!file->f_file)
REPORT_ERRNO(path);
g_free(path); g_free(path);
g_free(tmp); return file->f_file != NULL;
} }
static bool __file_mkdir(const gchar *basedir, const gchar *subdir) static bool __file_mkdir(struct file *file)
{ {
gchar *dir = __file_path(basedir, subdir, NULL); gchar *dir = g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, NULL);
int ret = g_mkdir_with_parents(dir, 0755); int ret = g_mkdir_with_parents(dir, 0755);
if (ret != 0) if (ret != 0)
@ -94,43 +70,38 @@ static bool __file_can_write(struct file *file)
} }
void file_init(struct file *file, const gchar *name, unsigned int min) void file_init_data(struct file *file, const gchar *subdir,
const gchar *name, unsigned int min)
{ {
file->f_mode = OPEN_READ; __file_init_common(file, subdir, name, min, g_get_user_data_dir);
file->f_version = OCARINA_MINOR_VERSION;
file->f_prev = 0;
file->f_min = min;
file->f_file = NULL;
file->f_name = name;
} }
void cache_file_init(struct cache_file *file, gchar *subdir, gchar *name) void file_init_cache(struct file *file, const gchar *subdir, const gchar *name)
{ {
file->cf_file = NULL; __file_init_common(file, subdir, name, 0, g_get_user_cache_dir);
file->cf_name = name;
file->cf_subdir = subdir;
} }
gchar *file_path(struct file *file) gchar *file_path(struct file *file)
{ {
return __file_build_path(g_get_user_data_dir(), NULL, file->f_name); if (string_length(file->f_name) == 0)
} return g_strdup("");
return g_build_filename(file->f_user_dir(), OCARINA_NAME,
gchar *cache_file_path(struct cache_file *file) file->f_subdir, file->f_name, NULL);
{
return __file_build_path(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
} }
gchar *file_write_path(struct file *file) gchar *file_write_path(struct file *file)
{ {
return __file_build_tmp(g_get_user_data_dir(), NULL, file->f_name); gchar *tmp, *res;
}
gchar *cache_file_write_path(struct cache_file *file) if (string_length(file->f_name) == 0)
{ return g_strdup("");
return __file_build_tmp(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name); tmp = g_strdup_printf(".%s.tmp", file->f_name);
res = g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, tmp, NULL);
g_free(tmp);
return res;
} }
const unsigned int file_version(struct file *file) const unsigned int file_version(struct file *file)
@ -142,26 +113,24 @@ const unsigned int file_version(struct file *file)
bool file_exists(struct file *file) bool file_exists(struct file *file)
{ {
return __file_exists(file_path(file)); gchar *path = file_path(file);
bool ret = g_file_test(path, G_FILE_TEST_EXISTS);
g_free(path);
return ret;
} }
bool cache_file_exists(struct cache_file *file) static bool __file_open_read(struct file *file, enum open_mode mode)
{
return __file_exists(cache_file_path(file));
}
static bool __file_open_read(struct file *file)
{ {
if (!file_exists(file)) if (!file_exists(file))
return false; return false;
if (!__file_open(file, mode))
file->f_file = __file_open(file_path(file), "r");
if (!file->f_file)
return false; return false;
file->f_mode = OPEN_READ; file->f_mode = mode;
if (file_readf(file, "%u\n", &file->f_prev) != 1) if (mode == OPEN_READ_BINARY)
return false; return true;
file->f_prev = file_readu(file);
if (file->f_prev < file->f_min) { if (file->f_prev < file->f_min) {
REPORT_ERROR(file->f_name, "File too old to be upgraded."); REPORT_ERROR(file->f_name, "File too old to be upgraded.");
file_close(file); file_close(file);
@ -175,18 +144,18 @@ static bool __file_open_read(struct file *file)
return true; return true;
} }
static bool __file_open_write(struct file *file) static bool __file_open_write(struct file *file, enum open_mode mode)
{ {
if (!__file_mkdir(g_get_user_data_dir(), NULL)) if (!__file_mkdir(file))
return false; return false;
if (!__file_can_write(file)) if (!__file_can_write(file))
return false; return false;
if (!__file_open(file, OPEN_WRITE))
file->f_file = __file_open(file_write_path(file), "w");
if (!file->f_file)
return false; return false;
file->f_mode = OPEN_WRITE; file->f_mode = mode;
if (mode == OPEN_WRITE_BINARY)
return true;
return file_writef(file, "%d\n", file->f_version) > 0; return file_writef(file, "%d\n", file->f_version) > 0;
} }
@ -194,62 +163,49 @@ bool file_open(struct file *file, enum open_mode mode)
{ {
if ((string_length(file->f_name) == 0) || (file->f_file != NULL)) if ((string_length(file->f_name) == 0) || (file->f_file != NULL))
return false; return false;
if (mode == CLOSED)
if (mode == OPEN_READ)
return __file_open_read(file);
return __file_open_write(file);
}
bool cache_file_open(struct cache_file *file, enum open_mode mode)
{
if (mode == OPEN_READ)
return false;
if ((string_length(file->cf_name) == 0) || (file->cf_file != NULL))
return false;
if (!__file_mkdir(g_get_user_cache_dir(), file->cf_subdir))
return false; return false;
file->cf_file = __file_open(cache_file_write_path(file), "wb"); if (mode == OPEN_READ || mode == OPEN_READ_BINARY)
return file->cf_file != NULL; return __file_open_read(file, mode);
return __file_open_write(file, mode);
} }
void file_close(struct file *file) void file_close(struct file *file)
{ {
__file_close(file->f_file, gchar *path = file_path(file);
file->f_mode == OPEN_WRITE ? file_path(file) : NULL, gchar *tmp = file_write_path(file);
file->f_mode == OPEN_WRITE ? file_write_path(file) : NULL);
if (file->f_file) {
fclose(file->f_file);
if (file->f_mode == OPEN_WRITE || file->f_mode == OPEN_WRITE_BINARY)
g_rename(tmp, path);
}
file->f_file = NULL; file->f_file = NULL;
file->f_mode = CLOSED;
g_free(path);
g_free(tmp);
} }
void cache_file_close(struct cache_file *file) gchar *file_readw(struct file *file)
{ {
__file_close(file->cf_file, gchar *s;
cache_file_path(file), return fscanf(file->f_file, "%ms%*c", &s) ? s : g_strdup("");
cache_file_write_path(file));
file->cf_file = NULL;
}
int file_readf(struct file *file, const char *fmt, ...)
{
va_list argp;
int ret;
va_start(argp, fmt);
ret = vfscanf(file->f_file, fmt, argp);
va_end(argp);
return ret;
} }
gchar *file_readl(struct file *file) gchar *file_readl(struct file *file)
{ {
gchar *res; gchar *s = NULL;
size_t len = 0;
return getline(&s, &len, file->f_file) ? g_strchomp(s) : g_strdup("");
}
if (file_readf(file, "%m[^\n]\n", &res) == 0) unsigned int file_readu(struct file *file)
return g_strdup(""); {
unsigned int u;
g_strstrip(res); return fscanf(file->f_file, "%u%*c", &u) ? u : 0;
return res;
} }
int file_writef(struct file *file, const char *fmt, ...) int file_writef(struct file *file, const char *fmt, ...)
@ -266,36 +222,57 @@ int file_writef(struct file *file, const char *fmt, ...)
return ret; return ret;
} }
int cache_file_write(struct cache_file *file, const void *data, size_t len) gchar *file_read(struct file *file)
{ {
if (fwrite(data, len, 1, file->cf_file) == 1) int fd = fileno(file->f_file);
struct stat st;
gchar *buf;
if (fstat(fd, &st) < 0)
return NULL;
buf = g_malloc0(st.st_size + 1);
if (fread(buf, st.st_size, 1, file->f_file) == 1)
return buf;
g_free(buf);
return NULL;
}
int file_write(struct file *file, const void *data, size_t len)
{
if (fwrite(data, len, 1, file->f_file) == 1)
return len; return len;
return -1; return -1;
} }
bool cache_file_import(struct cache_file *file, const gchar *srcpath) bool file_import(struct file *file, const gchar *srcpath)
{ {
gchar *contents = NULL; gchar *contents = NULL;
gsize length = 0; gsize length = 0;
if (!file->cf_file || !srcpath) if (!file->f_file || !srcpath)
return false; return false;
if (!g_file_get_contents(srcpath, &contents, &length, NULL)) if (!g_file_get_contents(srcpath, &contents, &length, NULL))
return false; return false;
cache_file_write(file, contents, length); file_write(file, contents, length);
return true; return true;
} }
bool file_remove(struct file *file) bool file_remove(struct file *file)
{ {
gchar *path, *dir;
int ret = -1; int ret = -1;
gchar *path;
if (!file->f_file) { if (!file->f_file) {
path = file_path(file); path = file_path(file);
ret = g_unlink(path); ret = g_unlink(path);
dir = g_path_get_dirname(path);
if (string_length(file->f_subdir) > 0)
g_rmdir(dir);
g_free(path); g_free(path);
g_free(dir);
} }
return ret == 0; return ret == 0;

View File

@ -8,11 +8,11 @@
struct idle_task { struct idle_task {
bool (*idle_func)(void *); bool (*idle_func)(void *);
void *idle_data; void *idle_data;
enum idle_sync_t idle_sync;
}; };
static GThreadPool *idle_pool = NULL; static GThreadPool *idle_pool = NULL;
static GQueue idle_queue = G_QUEUE_INIT; static GQueue idle_queue = G_QUEUE_INIT;
static enum idle_sync_t idle_mode = IDLE_SYNC;
static unsigned int queued = 0; static unsigned int queued = 0;
static unsigned int serviced = 0; static unsigned int serviced = 0;
@ -38,17 +38,11 @@ void __idle_thread(gpointer task, gpointer data)
} }
void idle_init() void idle_init(enum idle_sync_t sync)
{ {
idle_pool = g_thread_pool_new(__idle_thread, NULL, 1, true, NULL); idle_mode = sync;
} }
#ifdef CONFIG_TESTING
void idle_init_sync()
{
}
#endif /* CONFIG_TESTING */
void idle_deinit() void idle_deinit()
{ {
struct idle_task *task; struct idle_task *task;
@ -58,8 +52,10 @@ void idle_deinit()
g_free(task); g_free(task);
} }
if (idle_pool) if (idle_pool) {
g_thread_pool_free(idle_pool, true, false); g_thread_pool_free(idle_pool, true, true);
idle_pool = NULL;
}
queued = 0; queued = 0;
serviced = 0; serviced = 0;
@ -67,12 +63,24 @@ void idle_deinit()
void idle_schedule(enum idle_sync_t sync, bool (*func)(void *), void *data) void idle_schedule(enum idle_sync_t sync, bool (*func)(void *), void *data)
{ {
struct idle_task *task = g_malloc(sizeof(struct idle_task)); struct idle_task *task;
if (sync == IDLE_ASYNC && idle_mode == IDLE_SYNC)
return;
task = g_malloc(sizeof(struct idle_task));
task->idle_func = func; task->idle_func = func;
task->idle_data = data; task->idle_data = data;
task->idle_sync = sync;
g_queue_push_tail(&idle_queue, task); if (sync == IDLE_SYNC)
g_queue_push_tail(&idle_queue, task);
else {
if (!idle_pool)
idle_pool = g_thread_pool_new(__idle_thread, NULL, 1,
false, NULL);
g_thread_pool_push(idle_pool, task, NULL);
}
g_atomic_int_inc(&queued); g_atomic_int_inc(&queued);
} }
@ -82,12 +90,7 @@ bool idle_run_task()
if (!g_queue_is_empty(&idle_queue)) { if (!g_queue_is_empty(&idle_queue)) {
task = g_queue_pop_head(&idle_queue); task = g_queue_pop_head(&idle_queue);
if (task->idle_sync == IDLE_ASYNC) { if (!__idle_run_task(task))
if (idle_pool)
g_thread_pool_push(idle_pool, task, NULL);
else
__idle_free_task(task);
} else if (!__idle_run_task(task))
g_queue_push_tail(&idle_queue, task); g_queue_push_tail(&idle_queue, task);
} }

View File

@ -10,6 +10,9 @@ static const gchar *SETTINGS_CUR_ID = "core.playlist.cur.id";
static const gchar *SETTINGS_PREV_TYPE = "core.playlist.prev.type"; static const gchar *SETTINGS_PREV_TYPE = "core.playlist.prev.type";
static const gchar *SETTINGS_PREV_ID = "core.playlist.prev.id"; static const gchar *SETTINGS_PREV_ID = "core.playlist.prev.id";
static struct playlist *current = NULL;
static struct playlist *previous = NULL;
struct playlist_type *playlist_types[] = { struct playlist_type *playlist_types[] = {
[PL_SYSTEM] = &pl_system, [PL_SYSTEM] = &pl_system,
[PL_ARTIST] = &pl_artist, [PL_ARTIST] = &pl_artist,
@ -18,19 +21,30 @@ struct playlist_type *playlist_types[] = {
}; };
void playlist_init(struct queue_ops *ops) static struct playlist *__playlist_saved(const gchar *s_type, const gchar *s_id)
{ {
pl_system_init(ops); unsigned int type, id;
pl_artist_init(ops);
pl_user_init(ops);
pl_library_init(ops);
if (!settings_has(SETTINGS_CUR_TYPE) || if (!settings_has(s_type) || !settings_has(s_id))
!settings_has(SETTINGS_CUR_ID)) { return NULL;
playlist_select(PL_SYSTEM, "Collection");
if (playlist_size(PL_SYSTEM, "Queued Tracks") > 0) type = settings_get(s_type);
playlist_select(PL_SYSTEM, "Queued Tracks"); id = settings_get(s_id);
} return playlist_types[type]->pl_get(id);
}
void playlist_init(struct playlist_callbacks *cb)
{
playlist_generic_set_callbacks(cb);
pl_system_init();
pl_artist_init();
pl_user_init();
pl_library_init();
current = __playlist_saved(SETTINGS_CUR_TYPE, SETTINGS_CUR_ID);
previous = __playlist_saved(SETTINGS_PREV_TYPE, SETTINGS_PREV_ID);
if (!current)
current = playlist_lookup(PL_SYSTEM, "Collection");
} }
void playlist_deinit() void playlist_deinit()
@ -48,140 +62,175 @@ void playlist_save()
playlist_types[i]->pl_save(); playlist_types[i]->pl_save();
} }
bool playlist_select(enum playlist_type_t type, const gchar *name) void playlist_played(struct track *track)
{ {
if (!playlist_types[type]->pl_can_select(name)) unsigned int i;
return false;
if ((settings_get(SETTINGS_CUR_TYPE) == type) &&
settings_get(SETTINGS_CUR_ID) == playlist_get_id(type, name))
return true;
settings_set(SETTINGS_PREV_TYPE, settings_get(SETTINGS_CUR_TYPE)); if (track && !TRACK_IS_EXTERNAL(track)) {
settings_set(SETTINGS_PREV_ID, settings_get(SETTINGS_CUR_ID)); for (i = 0; i < PL_MAX_TYPE; i++)
settings_set(SETTINGS_CUR_TYPE, type); playlist_types[i]->pl_played(track);
settings_set(SETTINGS_CUR_ID, playlist_get_id(type, name)); }
return true;
} }
bool playlist_new(enum playlist_type_t type, const gchar *name) void playlist_selected(struct track *track)
{ {
return playlist_types[type]->pl_new(name); unsigned int i;
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_selected(track);
if (playlist_size(current) == 0)
playlist_select(previous);
}
} }
bool playlist_delete(enum playlist_type_t type, const gchar *name) struct playlist *playlist_new(enum playlist_type_t type, const gchar *name)
{ {
return playlist_types[type]->pl_delete(name); if (type < PL_MAX_TYPE && playlist_types[type]->pl_new)
return playlist_types[type]->pl_new(name);
return NULL;
} }
bool playlist_add(enum playlist_type_t type, const gchar *name, bool playlist_delete(struct playlist *playlist)
struct track *track)
{ {
bool ret; enum playlist_type_t type;
bool ret;
if (!track) if (!playlist || !playlist->pl_ops->pl_delete)
return false; return false;
ret = playlist_types[type]->pl_add_track(name, track); type = playlist->pl_type;
if (type == PL_SYSTEM && string_match(name, "Queued Tracks")) ret = playlist->pl_ops->pl_delete(playlist);
playlist_select(PL_SYSTEM, "Queued Tracks"); if (ret)
playlist_types[type]->pl_save();
return ret; return ret;
} }
bool playlist_remove(enum playlist_type_t type, const gchar *name, struct playlist *playlist_lookup(enum playlist_type_t type, const gchar *name)
struct track *track)
{ {
if (!track) if (type >= PL_MAX_TYPE)
return NULL;
return playlist_types[type]->pl_lookup(name);
}
struct playlist *playlist_get(enum playlist_type_t type, unsigned int id)
{
if (type >= PL_MAX_TYPE)
return NULL;
return playlist_types[type]->pl_get(id);
}
struct playlist *playlist_current(void)
{
return current;
}
bool playlist_select(struct playlist *playlist)
{
if (!playlist || (playlist == current))
return false; return false;
return playlist_types[type]->pl_remove_track(name, track); if (!playlist->pl_ops->pl_can_select)
} return false;
if (!playlist->pl_ops->pl_can_select(playlist))
void playlist_update(enum playlist_type_t type, const gchar *name)
{
playlist_types[type]->pl_update(name);
}
bool playlist_has(enum playlist_type_t type, const gchar *name,
struct track *track)
{
struct queue *queue = playlist_get_queue(type, name);
if (!track || !queue)
return false; return false;
return queue_has(queue, track);
}
unsigned int playlist_size(enum playlist_type_t type, const gchar *name) previous = current;
{ current = playlist;
struct queue *queue = playlist_get_queue(type, name);
return queue ? queue_size(queue) : 0;
}
void playlist_set_random(enum playlist_type_t type, const gchar *name, settings_set(SETTINGS_CUR_TYPE, current->pl_type);
bool enabled) settings_set(SETTINGS_CUR_ID, current->pl_id);
{
playlist_types[type]->pl_set_flag(name, Q_RANDOM, enabled);
}
bool playlist_get_random(enum playlist_type_t type, const gchar *name) if (previous) {
{ settings_set(SETTINGS_PREV_TYPE, previous->pl_type);
struct queue *queue = playlist_get_queue(type, name); settings_set(SETTINGS_PREV_ID, previous->pl_id);
return queue ? queue_has_flag(queue, Q_RANDOM) : false; }
} return true;
void playlist_sort(enum playlist_type_t type, const gchar *name,
enum compare_t sort, bool reset)
{
playlist_types[type]->pl_sort(name, sort, reset);
} }
struct track *playlist_next(void) struct track *playlist_next(void)
{ {
enum playlist_type_t type = settings_get(SETTINGS_CUR_TYPE); struct track *track = playlist_generic_next(current);
unsigned int id = settings_get(SETTINGS_CUR_ID); if (track && current->pl_type < PL_MAX_TYPE)
gchar *name = playlist_get_name(type, id); playlist_types[current->pl_type]->pl_save();
struct track *track = playlist_types[type]->pl_next(name);
if (playlist_size(type, name) == 0) {
settings_set(SETTINGS_CUR_ID, settings_get(SETTINGS_PREV_ID));
settings_set(SETTINGS_CUR_TYPE, settings_get(SETTINGS_PREV_TYPE));
}
g_free(name);
return track; return track;
} }
struct track *playlist_prev(void) struct track *playlist_prev(void)
{ {
return playlist_types[PL_SYSTEM]->pl_next("History"); return playlist_generic_next(playlist_lookup(PL_SYSTEM, "History"));
} }
struct playlist *playlist_get(enum playlist_type_t type, const gchar *name) bool playlist_add(struct playlist *playlist, struct track *track)
{ {
return playlist_types[type]->pl_get_playlist(name); bool ret;
}
struct playlist *playlist_cur(void) if (!track || !playlist || !playlist->pl_ops->pl_add)
{ return false;
enum playlist_type_t type = settings_get(SETTINGS_CUR_TYPE);
unsigned int id = settings_get(SETTINGS_CUR_ID);
gchar *name = playlist_get_name(type, id);
struct playlist *ret = playlist_get(type, name);
g_free(name); ret = playlist->pl_ops->pl_add(playlist, track);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
if (playlist == playlist_lookup(PL_SYSTEM, "Queued Tracks"))
playlist_select(playlist);
return ret; return ret;
} }
struct queue *playlist_get_queue(enum playlist_type_t type, const gchar *name) bool playlist_remove(struct playlist *playlist, struct track *track)
{ {
struct playlist *playlist = playlist_get(type, name); bool ret;
return playlist ? &playlist->pl_queue : NULL;
if (!track || !playlist || !playlist->pl_ops->pl_remove)
return false;
ret = playlist->pl_ops->pl_remove(playlist, track);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
} }
unsigned int playlist_get_id(enum playlist_type_t type, const gchar *name) void playlist_set_random(struct playlist *playlist, bool enabled)
{ {
return playlist_types[type]->pl_get_id(name); if (playlist && playlist->pl_ops->pl_set_random) {
playlist->pl_ops->pl_set_random(playlist, enabled);
if (playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
}
} }
gchar *playlist_get_name(enum playlist_type_t type, unsigned int id) bool playlist_sort(struct playlist *playlist, enum compare_t sort)
{ {
return playlist_types[type]->pl_get_name(id); if (!playlist || !playlist->pl_ops->pl_sort)
return false;
playlist->pl_ops->pl_sort(playlist, sort);
if (playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return g_slist_length(playlist->pl_sort) > 0;
}
bool playlist_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
bool ret;
if (!playlist || !playlist->pl_ops->pl_rearrange)
return false;
ret = playlist->pl_ops->pl_rearrange(playlist, old_pos, new_pos);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
}
void playlist_set_search(struct playlist *playlist, const gchar *text)
{
gchar **tokens = NULL;
if (!playlist)
return;
if (playlist->pl_search)
g_strfreev(playlist->pl_search);
if (strlen(text) > 0)
tokens = g_str_tokenize_and_fold(text, NULL, NULL);
playlist->pl_search = tokens;
} }

View File

@ -5,27 +5,21 @@
#include <core/playlists/artist.h> #include <core/playlists/artist.h>
#include <core/string.h> #include <core/string.h>
static struct queue_ops *artist_ops = NULL; static struct file artist_file = FILE_INIT_DATA("", "playlist.artist", 0);
static struct file artist_file = FILE_INIT("playlist.artist", 0);
static struct playlist_ops pl_artist_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static struct playlist *__artist_pl_alloc(gchar *name) static struct playlist *__artist_pl_alloc(struct artist *artist)
{ {
struct playlist *playlist = g_malloc(sizeof(struct playlist)); return playlist_generic_alloc(artist->ar_name, PL_ARTIST,
artist_index(artist), &pl_artist_ops,
playlist->pl_name = name; 3, COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
playlist->pl_type = PL_ARTIST;
playlist_generic_init(playlist, Q_REPEAT, artist_ops);
return playlist;
}
static void __artist_pl_free(struct playlist *playlist)
{
if (playlist) {
queue_deinit(&playlist->pl_queue);
g_free(playlist);
}
} }
static bool __artist_pl_add(void *data) static bool __artist_pl_add(void *data)
@ -34,13 +28,12 @@ static bool __artist_pl_add(void *data)
struct artist *artist = artist_lookup(playlist->pl_name); struct artist *artist = artist_lookup(playlist->pl_name);
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
queue_set_flag(&playlist->pl_queue, Q_ADD_FRONT);
db_for_each(dbe, next, track_db_get()) { db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_album->al_artist == artist) if (TRACK(dbe)->tr_album->al_artist == artist)
queue_add(&playlist->pl_queue, TRACK(dbe)); playlist_generic_add_front(playlist, TRACK(dbe));
} }
queue_unset_flag(&playlist->pl_queue, Q_ADD_FRONT);
playlist_generic_resort(playlist);
return true; return true;
} }
@ -59,17 +52,14 @@ static bool __artist_pl_load(void *data)
if (!file_open(&artist_file, OPEN_READ)) if (!file_open(&artist_file, OPEN_READ))
return true; return true;
file_readf(&artist_file, "%u\n", &n); n = file_readu(&artist_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
name = file_readl(&artist_file); name = file_readl(&artist_file);
playlist = __artist_pl_lookup(name); playlist = __artist_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &artist_file,
PL_SAVE_METADATA);
g_free(name); g_free(name);
if (!playlist)
continue;
queue_load_flags(&playlist->pl_queue, &artist_file, true);
queue_iter_set(&playlist->pl_queue, &playlist->pl_queue.q_cur,
playlist->pl_queue.q_cur.it_pos);
} }
file_close(&artist_file); file_close(&artist_file);
@ -89,99 +79,46 @@ static void pl_artist_save(void)
db_for_each(dbe, next, artist_db_get()) { db_for_each(dbe, next, artist_db_get()) {
playlist = ARTIST(dbe)->ar_playlist; playlist = ARTIST(dbe)->ar_playlist;
file_writef(&artist_file, "%s\n", playlist->pl_name); file_writef(&artist_file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, &artist_file, true); playlist_generic_save(playlist, &artist_file, PL_SAVE_METADATA);
} }
file_close(&artist_file); file_close(&artist_file);
} }
static struct playlist *pl_artist_get_playlist(const gchar *name) static struct playlist *pl_artist_lookup(const gchar *name)
{ {
return __artist_pl_lookup(name); return __artist_pl_lookup(name);
} }
static unsigned int pl_artist_get_id(const gchar *name) static struct playlist *pl_artist_get(unsigned int id)
{ {
struct artist *artist = artist_lookup(name); struct artist *artist = artist_get(id);
return artist ? artist->ar_dbe.dbe_index : -1; return artist ? artist->ar_playlist : NULL;
} }
static bool pl_artist_can_select(const gchar *name) static void pl_artist_played(struct track *track)
{ {
struct playlist *playlist = __artist_pl_lookup(name); struct artist *artist = track->tr_album->al_artist;
return playlist ? playlist_generic_can_select(playlist) : false; playlist_generic_update(artist->ar_playlist, track);
}
static gchar *pl_artist_get_name(unsigned int id)
{
struct artist *artist = ARTIST(db_at(artist_db_get(), id));
return artist ? g_strdup(artist->ar_name) : NULL;
}
static bool pl_artist_new_delete(const gchar *name)
{
return false;
}
static bool pl_artist_add_rm(const gchar *name, struct track *track)
{
return false;
}
static void pl_artist_update(const gchar *name)
{
}
static void pl_artist_set_flag(const gchar *name, enum queue_flags flag,
bool enabled)
{
struct playlist *playlist = __artist_pl_lookup(name);
playlist_generic_set_flag(playlist, flag, enabled);
pl_artist_save();
}
static void pl_artist_sort(const gchar *name, enum compare_t sort, bool reset)
{
struct playlist *playlist = __artist_pl_lookup(name);
playlist_generic_sort(playlist, sort, reset);
pl_artist_save();
}
static struct track *pl_artist_next(const gchar *name)
{
struct playlist *playlist = __artist_pl_lookup(name);
struct track *track = playlist_generic_next(playlist);;
pl_artist_save();
return track;
} }
struct playlist_type pl_artist = { struct playlist_type pl_artist = {
.pl_save = pl_artist_save, .pl_save = pl_artist_save,
.pl_get_playlist = pl_artist_get_playlist, .pl_lookup = pl_artist_lookup,
.pl_get_id = pl_artist_get_id, .pl_get = pl_artist_get,
.pl_get_name = pl_artist_get_name, .pl_played = pl_artist_played,
.pl_can_select = pl_artist_can_select, .pl_selected = pl_artist_played,
.pl_new = pl_artist_new_delete,
.pl_delete = pl_artist_new_delete,
.pl_add_track = pl_artist_add_rm,
.pl_remove_track = pl_artist_add_rm,
.pl_update = pl_artist_update,
.pl_set_flag = pl_artist_set_flag,
.pl_sort = pl_artist_sort,
.pl_next = pl_artist_next,
}; };
void pl_artist_init(struct queue_ops *ops) void pl_artist_init(void)
{ {
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
struct playlist *playlist; struct playlist *playlist;
artist_ops = ops;
db_for_each(dbe, next, artist_db_get()) { db_for_each(dbe, next, artist_db_get()) {
playlist = __artist_pl_alloc(ARTIST(dbe)->ar_name); playlist = __artist_pl_alloc(ARTIST(dbe));
ARTIST(dbe)->ar_playlist = playlist; ARTIST(dbe)->ar_playlist = playlist;
idle_schedule(IDLE_SYNC, __artist_pl_add, playlist); idle_schedule(IDLE_SYNC, __artist_pl_add, playlist);
@ -193,13 +130,10 @@ void pl_artist_init(struct queue_ops *ops)
void pl_artist_deinit() void pl_artist_deinit()
{ {
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, artist_db_get()) { db_for_each(dbe, next, artist_db_get()) {
playlist = ARTIST(dbe)->ar_playlist; playlist_generic_free(ARTIST(dbe)->ar_playlist);
ARTIST(dbe)->ar_playlist = NULL; ARTIST(dbe)->ar_playlist = NULL;
__artist_pl_free(playlist);
} }
} }
@ -209,18 +143,15 @@ void pl_artist_new_track(struct track *track)
struct playlist *playlist = (struct playlist *)artist->ar_playlist; struct playlist *playlist = (struct playlist *)artist->ar_playlist;
if (!playlist) { if (!playlist) {
playlist = __artist_pl_alloc(artist->ar_name); playlist = __artist_pl_alloc(artist);
artist->ar_playlist = playlist; artist->ar_playlist = playlist;
} }
playlist_generic_add_track(playlist, track); playlist_generic_add(playlist, track);
} }
void pl_artist_delete_track(struct track *track) void pl_artist_delete_track(struct track *track)
{ {
struct artist *artist = track->tr_album->al_artist; struct artist *artist = track->tr_album->al_artist;
struct playlist *playlist = (struct playlist *)artist->ar_playlist; playlist_generic_remove(artist->ar_playlist, track);
if (playlist)
playlist_generic_remove_track(playlist, track);
} }

View File

@ -2,118 +2,312 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/idle.h> #include <core/idle.h>
#include <core/playlists/type.h> #include <core/playlists/generic.h>
#include <stdlib.h>
static struct playlist_callbacks *callbacks = NULL;
/* static int __playlist_generic_find_sort(gconstpointer a, gconstpointer b)
* Noop playlist operations.
*/
bool playlist_noop_can_select(struct playlist *playlist)
{ {
return false; return abs(GPOINTER_TO_INT(a)) - abs(GPOINTER_TO_INT(b));
} }
void playlist_noop_clear(struct playlist *playlist) static int __playlist_generic_less_than(gconstpointer a, gconstpointer b,
gpointer data)
{ {
} struct track *lhs = (struct track *)a;
struct track *rhs = (struct track *)b;
GSList *cur = ((struct playlist *)data)->pl_sort;
int res, field;
void playlist_noop_set_flag(struct playlist *playlist, while (cur) {
enum queue_flags flag, bool enabled) field = GPOINTER_TO_INT(cur->data);
{ res = track_compare(lhs, rhs, abs(field));
} if (res != 0)
break;
cur = g_slist_next(cur);
};
void playlist_noop_sort(struct playlist *playlist, return (field > 0) ? res : -res;
enum compare_t sort, bool reset)
{
} }
/* void playlist_generic_set_callbacks(struct playlist_callbacks *cb)
* Generic playlist operations.
*/
void playlist_generic_init(struct playlist *playlist, unsigned int flags,
struct queue_ops *ops)
{ {
queue_init(&playlist->pl_queue, flags | Q_ENABLED, ops, playlist); callbacks = cb;
queue_sort(&playlist->pl_queue, COMPARE_ARTIST, true); }
queue_sort(&playlist->pl_queue, COMPARE_YEAR, false);
queue_sort(&playlist->pl_queue, COMPARE_TRACK, false); static void __playlist_generic_vinit(struct playlist *playlist,
playlist->pl_private = NULL; unsigned int nargs, va_list argp)
{
unsigned int i;
if (!playlist)
return;
g_queue_init(&playlist->pl_tracks);
playlist->pl_length = 0;
playlist->pl_random = false;
playlist->pl_current = NULL;
playlist->pl_sort = NULL;
playlist->pl_search = NULL;
for (i = 0; i < nargs; i++)
playlist_generic_sort(playlist, va_arg(argp, unsigned int));
}
void playlist_generic_init(struct playlist *playlist, unsigned int nargs, ...)
{
va_list argp;
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
va_end(argp);
}
void playlist_generic_deinit(struct playlist *playlist)
{
if (playlist) {
playlist_generic_clear(playlist);
playlist_clear_sort(playlist);
g_strfreev(playlist->pl_search);
playlist->pl_search = NULL;
playlist->pl_length = 0;
}
}
struct playlist *playlist_generic_alloc(gchar *name, enum playlist_type_t type,
unsigned int id, struct playlist_ops *ops,
unsigned int nargs, ...)
{
struct playlist *playlist = g_malloc(sizeof(struct playlist));
va_list argp;
playlist->pl_name = name;
playlist->pl_type = type;
playlist->pl_id = id;
playlist->pl_ops = ops;
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
if (callbacks)
callbacks->pl_cb_alloc(playlist);
va_end(argp);
return playlist;
}
void playlist_generic_free(struct playlist *playlist)
{
if (playlist) {
playlist_generic_deinit(playlist);
g_free(playlist);
}
}
void playlist_generic_save(struct playlist *playlist, struct file *file,
unsigned int flags)
{
playlist_iter it;
GSList *sort;
int field;
if (!playlist)
return;
if (flags & PL_SAVE_ITER)
file_writef(file, "%u ", playlist_current_index(playlist));
if (flags & PL_SAVE_FLAGS) {
sort = playlist->pl_sort;
file_writef(file, "%u ", playlist->pl_random ? PL_RANDOM : 0);
file_writef(file, "%u", g_slist_length(sort));
while (sort) {
field = GPOINTER_TO_INT(sort->data);
file_writef(file, " %u %d", abs(field) - 1, field > 0);
sort = g_slist_next(sort);
}
file_writef(file, "\n");
}
if (flags & PL_SAVE_TRACKS) {
file_writef(file, "%u", playlist_size(playlist));
playlist_for_each(playlist, it)
file_writef(file, " %u",
track_index(playlist_iter_track(it)));
file_writef(file, "\n");
}
}
void playlist_generic_load(struct playlist *playlist, struct file *file,
unsigned int flags)
{
unsigned int f, n, i, t, it = 0;
int field, ascending;
if (!playlist)
return;
if (flags & PL_SAVE_ITER)
it = file_readu(file);
if (flags & PL_SAVE_FLAGS) {
f = file_readu(file);
n = file_readu(file);
playlist_clear_sort(playlist);
for (i = 0; i < n; i++) {
field = file_readu(file) + 1;
ascending = file_readd(file);
if (!ascending)
field = -field;
playlist->pl_sort = g_slist_append(playlist->pl_sort,
GINT_TO_POINTER(field));
}
playlist_generic_resort(playlist);
}
if (flags & PL_SAVE_TRACKS) {
n = file_readu(file);
for (i = 0; i < n; i++) {
t = file_readu(file);
playlist_generic_add(playlist, track_get(t));
}
}
playlist_generic_set_random(playlist, f == PL_RANDOM);
playlist_current_set(playlist, it);
} }
bool playlist_generic_can_select(struct playlist *playlist) bool playlist_generic_can_select(struct playlist *playlist)
{ {
return queue_size(&playlist->pl_queue) > 0; return playlist_size(playlist) > 0;
} }
void playlist_generic_clear(struct playlist *playlist) void playlist_generic_clear(struct playlist *playlist)
{ {
queue_clear(&playlist->pl_queue); unsigned int n;
}
bool playlist_generic_add_track(struct playlist *playlist, struct track *track) if (!playlist)
{
if (queue_has(&playlist->pl_queue, track))
return false;
queue_add(&playlist->pl_queue, track);
return true;
}
bool playlist_generic_remove_track(struct playlist *playlist, struct track *track)
{
return queue_remove_all(&playlist->pl_queue, track);
}
struct pl_update_data {
struct playlist *pud_playlist;
bool (*pud_func)(struct playlist *, struct track *);
};
static bool __playlist_generic_update(void *data)
{
struct pl_update_data *pud = data;
struct db_entry *dbe, *next;
db_for_each(dbe, next, track_db_get()) {
if (!pud->pud_func(pud->pud_playlist, TRACK(dbe)))
playlist_generic_remove_track(pud->pud_playlist, TRACK(dbe));
}
queue_unset_flag(&pud->pud_playlist->pl_queue, Q_ADD_FRONT);
g_free(pud);
return true;
}
void playlist_generic_update(struct playlist *playlist,
bool (*func)(struct playlist *, struct track *))
{
struct pl_update_data *data;
if (!func)
return; return;
data = g_malloc(sizeof(struct pl_update_data)); n = playlist_size(playlist);
data->pud_playlist = playlist; g_queue_clear(&playlist->pl_tracks);
data->pud_func = func; playlist->pl_length = 0;
playlist->pl_current = NULL;
idle_schedule(IDLE_SYNC, __playlist_generic_update, data); if (callbacks)
callbacks->pl_cb_removed(playlist, NULL, n);
} }
void playlist_generic_set_flag(struct playlist *playlist, bool playlist_generic_add(struct playlist *playlist, struct track *track)
enum queue_flags flag, bool enabled)
{ {
if (enabled) if (!playlist || !track || playlist_has(playlist, track))
return queue_set_flag(&playlist->pl_queue, flag); return false;
return queue_unset_flag(&playlist->pl_queue, flag);
playlist->pl_length += track->tr_length;
if (playlist->pl_sort) {
g_queue_insert_sorted(&playlist->pl_tracks, track,
__playlist_generic_less_than, playlist);
} else
g_queue_push_tail(&playlist->pl_tracks, track);
if (callbacks)
callbacks->pl_cb_added(playlist, track);
return true;
} }
void playlist_generic_sort(struct playlist *playlist, bool playlist_generic_add_front(struct playlist *playlist, struct track *track)
enum compare_t sort, bool reset)
{ {
queue_sort(&playlist->pl_queue, sort, reset); if (!playlist || !track)
return false;
playlist->pl_length += track->tr_length;
g_queue_push_head(&playlist->pl_tracks, track);
if (callbacks)
callbacks->pl_cb_added(playlist, track);
return true;
}
bool playlist_generic_remove(struct playlist *playlist, struct track *track)
{
unsigned int count;
if (!playlist || !track)
return false;
while (playlist_current_track(playlist) == track)
playlist_current_previous(playlist);
count = g_queue_remove_all(&playlist->pl_tracks, track);
playlist->pl_length -= (count * track->tr_length);
if (callbacks)
callbacks->pl_cb_removed(playlist, track, count);
return count > 0;
}
void playlist_generic_update(struct playlist *playlist, struct track *track)
{
if (playlist && callbacks)
callbacks->pl_cb_updated(playlist, track);
}
void playlist_generic_set_random(struct playlist *playlist, bool enabled)
{
playlist->pl_random = enabled;
}
void playlist_generic_sort(struct playlist *playlist, enum compare_t field)
{
gpointer sort = GINT_TO_POINTER(field);
GSList *found = g_slist_find_custom(playlist->pl_sort, sort,
__playlist_generic_find_sort);
if (found)
found->data = GINT_TO_POINTER(-field);
else
playlist->pl_sort = g_slist_append(playlist->pl_sort, sort);
playlist_generic_resort(playlist);
}
void playlist_generic_resort(struct playlist *playlist)
{
if (!playlist || !playlist->pl_sort)
return;
g_queue_sort(&playlist->pl_tracks, __playlist_generic_less_than, playlist);
playlist_generic_update(playlist, NULL);
}
bool playlist_generic_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
GList *nth;
if (old_pos == new_pos || old_pos >= playlist_size(playlist) ||
new_pos > playlist_size(playlist))
return false;
playlist_clear_sort(playlist);
nth = g_queue_pop_nth_link(&playlist->pl_tracks, old_pos);
g_queue_push_nth_link(&playlist->pl_tracks, new_pos, nth);
playlist_generic_update(playlist, NULL);
return true;
} }
struct track *playlist_generic_next(struct playlist *playlist) struct track *playlist_generic_next(struct playlist *playlist)
{ {
return queue_next(&playlist->pl_queue); unsigned int pos, size = playlist_size(playlist);
if (size == 0)
return NULL;
else if (playlist->pl_random) {
pos = playlist_current_index(playlist);
pos += g_random_int_range(1, size);
playlist_current_set(playlist, pos % size);
} else if (!playlist_current_next(playlist))
playlist_current_set(playlist, 0);
return playlist_current_track(playlist);
} }

View File

@ -5,6 +5,7 @@
#include <core/playlists/artist.h> #include <core/playlists/artist.h>
#include <core/playlists/library.h> #include <core/playlists/library.h>
#include <core/playlists/system.h> #include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <unistd.h> #include <unistd.h>
struct scan_data { struct scan_data {
@ -13,27 +14,17 @@ struct scan_data {
}; };
static bool __lib_pl_scan_dir(void *); static bool __lib_pl_scan_dir(void *);
static struct queue_ops *lib_ops = NULL; static struct file lib_file = FILE_INIT_DATA("", "playlist.library", 0);
static struct file lib_file = FILE_INIT("playlist.library", 0);
static struct playlist_ops pl_library_ops;
static struct playlist *__lib_pl_alloc(struct library *library) static struct playlist *__lib_pl_alloc(struct library *library)
{ {
struct playlist *playlist = g_malloc(sizeof(struct playlist)); return playlist_generic_alloc(library->li_path, PL_LIBRARY,
library_index(library), &pl_library_ops,
playlist->pl_name = library->li_path; 4, COMPARE_ARTIST, COMPARE_YEAR,
playlist->pl_type = PL_LIBRARY; COMPARE_ALBUM, COMPARE_TRACK);
playlist_generic_init(playlist, Q_REPEAT, lib_ops);
return playlist;
}
static void __lib_pl_free(struct playlist *playlist)
{
if (playlist) {
queue_deinit(&playlist->pl_queue);
g_free(playlist);
}
} }
static bool __lib_pl_add(void *data) static bool __lib_pl_add(void *data)
@ -42,13 +33,12 @@ static bool __lib_pl_add(void *data)
struct library *library = library_lookup(playlist->pl_name); struct library *library = library_lookup(playlist->pl_name);
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
queue_set_flag(&playlist->pl_queue, Q_ADD_FRONT);
db_for_each(dbe, next, track_db_get()) { db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_library == library) if (TRACK(dbe)->tr_library == library)
queue_add(&playlist->pl_queue, TRACK(dbe)); playlist_generic_add_front(playlist, TRACK(dbe));
} }
queue_unset_flag(&playlist->pl_queue, Q_ADD_FRONT);
playlist_generic_resort(playlist);
return true; return true;
} }
@ -67,17 +57,14 @@ static bool __lib_pl_load(void *data)
if (!file_open(&lib_file, OPEN_READ)) if (!file_open(&lib_file, OPEN_READ))
return true; return true;
file_readf(&lib_file, "%u\n", &n); n = file_readu(&lib_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
name = file_readl(&lib_file); name = file_readl(&lib_file);
playlist = __lib_pl_lookup(name); playlist = __lib_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &lib_file,
PL_SAVE_METADATA);
g_free(name); g_free(name);
if (!playlist)
continue;
queue_load_flags(&playlist->pl_queue, &lib_file, true);
queue_iter_set(&playlist->pl_queue, &playlist->pl_queue.q_cur,
playlist->pl_queue.q_cur.it_pos);
} }
file_close(&lib_file); file_close(&lib_file);
@ -106,7 +93,7 @@ static void __lib_pl_read_path(struct scan_data *scan, const gchar *name)
else { else {
track = track_add(scan->sd_library, path); track = track_add(scan->sd_library, path);
if (track) { if (track) {
queue_add(&playlist->pl_queue, track); playlist_generic_add(playlist, track);
pl_system_new_track(track); pl_system_new_track(track);
pl_artist_new_track(track); pl_artist_new_track(track);
} }
@ -156,7 +143,7 @@ static bool __lib_pl_update(void *data)
if (g_access(path, F_OK) < 0) { if (g_access(path, F_OK) < 0) {
pl_system_delete_track(TRACK(dbe)); pl_system_delete_track(TRACK(dbe));
pl_artist_delete_track(TRACK(dbe)); pl_artist_delete_track(TRACK(dbe));
queue_remove_all(&playlist->pl_queue, TRACK(dbe)); playlist_generic_remove(playlist, TRACK(dbe));
track_remove(TRACK(dbe)); track_remove(TRACK(dbe));
} }
g_free(path); g_free(path);
@ -168,6 +155,36 @@ static bool __lib_pl_update(void *data)
} }
static bool pl_library_delete(struct playlist *playlist)
{
struct library *library = library_lookup(playlist->pl_name);
playlist_iter it;
if (!library)
return false;
playlist_for_each(playlist, it) {
pl_system_delete_track(playlist_iter_track(it));
pl_artist_delete_track(playlist_iter_track(it));
pl_user_delete_track(playlist_iter_track(it));
}
playlist_generic_free(playlist);
track_remove_all(library);
library_remove(library);
return true;
}
static struct playlist_ops pl_library_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_delete = pl_library_delete,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static void pl_library_save(void) static void pl_library_save(void)
{ {
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
@ -180,136 +197,65 @@ static void pl_library_save(void)
db_for_each(dbe, next, library_db_get()) { db_for_each(dbe, next, library_db_get()) {
playlist = LIBRARY(dbe)->li_playlist; playlist = LIBRARY(dbe)->li_playlist;
file_writef(&lib_file, "%s\n", playlist->pl_name); file_writef(&lib_file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, &lib_file, true); playlist_generic_save(playlist, &lib_file, PL_SAVE_METADATA);
} }
file_close(&lib_file); file_close(&lib_file);
} }
static struct playlist *pl_library_get_playlist(const gchar *name) static struct playlist *pl_library_lookup(const gchar *name)
{ {
return __lib_pl_lookup(name); return __lib_pl_lookup(name);
} }
static unsigned int pl_library_get_id(const gchar *name) static struct playlist *pl_library_get(unsigned int id)
{
struct library *library = library_find(name);
return library ? library->li_dbe.dbe_index : -1;
}
static gchar *pl_library_get_name(unsigned int id)
{ {
struct library *library = LIBRARY(db_at(library_db_get(), id)); struct library *library = LIBRARY(db_at(library_db_get(), id));
return library ? g_strdup(library->li_path) : NULL; return library ? library->li_playlist : NULL;
} }
static bool pl_library_can_select(const gchar *name) static struct playlist *pl_library_new(const gchar *name)
{
struct playlist *playlist = __lib_pl_lookup(name);
return playlist ? playlist_generic_can_select(playlist) : false;
}
static bool pl_library_new(const gchar *name)
{ {
struct library *library; struct library *library;
if (__lib_pl_lookup(name) || !g_file_test(name, G_FILE_TEST_IS_DIR)) if (__lib_pl_lookup(name) || !g_file_test(name, G_FILE_TEST_IS_DIR))
return false; return NULL;
library = library_find(name); library = library_find(name);
library->li_playlist = __lib_pl_alloc(library); library->li_playlist = __lib_pl_alloc(library);
__lib_pl_scan_dir_idle(library, name); __lib_pl_scan_dir_idle(library, name);
return true; return library->li_playlist;
} }
static bool pl_library_delete(const gchar *name) static void pl_library_played(struct track *track)
{ {
struct playlist *playlist = __lib_pl_lookup(name); struct library *library = track->tr_library;
struct library *library = library_lookup(name); playlist_generic_update(library->li_playlist, track);
struct queue_iter it;
if (!library)
return false;
queue_for_each(&playlist->pl_queue, &it) {
pl_system_delete_track(queue_iter_val(&it));
pl_artist_delete_track(queue_iter_val(&it));
}
__lib_pl_free(playlist);
track_remove_all(library);
library_remove(library);
pl_library_save();
return true;
}
static bool pl_library_add_rm(const gchar *name, struct track *track)
{
return false;
}
static void pl_library_update(const gchar *name)
{
struct playlist *playlist = __lib_pl_lookup(name);
if (playlist)
idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
}
static void pl_library_set_flag(const gchar *name, enum queue_flags flag,
bool enabled)
{
struct playlist *playlist = __lib_pl_lookup(name);
playlist_generic_set_flag(playlist, flag, enabled);
pl_library_save();
}
static void pl_library_sort(const gchar *name, enum compare_t sort, bool reset)
{
struct playlist *playlist = __lib_pl_lookup(name);
playlist_generic_sort(playlist, sort, reset);
pl_library_save();
}
static struct track *pl_library_next(const gchar *name)
{
struct playlist *playlist = __lib_pl_lookup(name);
struct track *track = playlist_generic_next(playlist);
pl_library_save();
return track;
} }
struct playlist_type pl_library = { struct playlist_type pl_library = {
.pl_save = pl_library_save, .pl_save = pl_library_save,
.pl_get_playlist = pl_library_get_playlist, .pl_lookup = pl_library_lookup,
.pl_get_id = pl_library_get_id, .pl_get = pl_library_get,
.pl_get_name = pl_library_get_name, .pl_new = pl_library_new,
.pl_can_select = pl_library_can_select, .pl_played = pl_library_played,
.pl_new = pl_library_new, .pl_selected = pl_library_played,
.pl_delete = pl_library_delete,
.pl_add_track = pl_library_add_rm,
.pl_remove_track = pl_library_add_rm,
.pl_update = pl_library_update,
.pl_set_flag = pl_library_set_flag,
.pl_sort = pl_library_sort,
.pl_next = pl_library_next,
}; };
void pl_library_init(struct queue_ops *ops) void pl_library_init(void)
{ {
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
struct playlist *playlist; struct playlist *playlist;
lib_ops = ops;
db_for_each(dbe, next, library_db_get()) { db_for_each(dbe, next, library_db_get()) {
playlist = __lib_pl_alloc(LIBRARY(dbe)); playlist = __lib_pl_alloc(LIBRARY(dbe));
LIBRARY(dbe)->li_playlist = playlist; LIBRARY(dbe)->li_playlist = playlist;
idle_schedule(IDLE_SYNC, __lib_pl_add, playlist); idle_schedule(IDLE_SYNC, __lib_pl_add, playlist);
pl_library_update(playlist->pl_name); idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
} }
idle_schedule(IDLE_SYNC, __lib_pl_load, NULL); idle_schedule(IDLE_SYNC, __lib_pl_load, NULL);
@ -318,12 +264,14 @@ void pl_library_init(struct queue_ops *ops)
void pl_library_deinit() void pl_library_deinit()
{ {
struct db_entry *dbe, *next; struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, library_db_get()) { db_for_each(dbe, next, library_db_get()) {
playlist = LIBRARY(dbe)->li_playlist; playlist_generic_free(LIBRARY(dbe)->li_playlist);
LIBRARY(dbe)->li_playlist = NULL; LIBRARY(dbe)->li_playlist = NULL;
__lib_pl_free(playlist);
} }
} }
void pl_library_update(struct playlist *playlist)
{
idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
}

View File

@ -5,83 +5,84 @@
#include <core/playlists/system.h> #include <core/playlists/system.h>
#include <core/string.h> #include <core/string.h>
static bool pl_system_add_track(const gchar *, struct track *); static struct playlist *pl_system_lookup(const gchar *);
static bool pl_system_remove_track(const gchar *, struct track *); static struct playlist *pl_system_get(unsigned int);
static void pl_system_update(const gchar *); static void pl_system_save();
static inline struct queue *__sys_pl_queue(enum sys_playlist_t);
static void __sys_pl_save();
static bool __sys_pl_load();
static struct file sys_file = FILE_INIT("playlist.db", 0); static struct file sys_file = FILE_INIT_DATA("", "playlist.db", 0);
static struct file sys_deck_f = FILE_INIT("deck", 1); static struct file sys_deck_f = FILE_INIT_DATA("", "deck", 1);
static struct file sys_collection_f = FILE_INIT("library.q", 0); static struct file sys_collection_f = FILE_INIT_DATA("", "library.q", 0);
static struct file sys_pl_file = FILE_INIT("playlist.system", 0); static struct file sys_pl_file = FILE_INIT_DATA("", "playlist.system", 0);
static struct sys_playlist *sys_playlists[SYS_PL_NUM_PLAYLISTS];
/* /*
* Generic system playlist operations. * Generic system playlist operations.
*/ */
static void sys_pl_generic_init(struct playlist *playlist, unsigned int flags, static bool sys_pl_delete_clear(struct playlist *playlist)
struct queue_ops *ops)
{ {
playlist_generic_init(playlist, Q_REPEAT | Q_ADD_FRONT, ops); playlist_generic_clear(playlist);
pl_system_save();
return false;
} }
static void sys_pl_update_init(struct playlist *playlist, unsigned int flags, static bool sys_pl_update_check(struct playlist *playlist, struct track *track)
struct queue_ops *ops)
{ {
sys_pl_generic_init(playlist, flags, ops); switch (playlist->pl_id) {
pl_system_update(playlist->pl_name); case SYS_PL_UNPLAYED:
return track->tr_count == 0;
case SYS_PL_LEAST_PLAYED:
return track->tr_count <= track_db_average_plays() &&
track->tr_count > 0;
case SYS_PL_MOST_PLAYED:
return track->tr_count > track_db_average_plays();
}
return true;
} }
static void sys_pl_save_partial(struct playlist *playlist, struct file *file) static bool sys_pl_update_func(void *data)
{ {
file_writef(file, "%s\n", playlist->pl_name); struct playlist *playlist = (struct playlist *)data;
queue_save_flags(&playlist->pl_queue, file, true); struct db_entry *dbe, *next;
db_for_each(dbe, next, track_db_get()) {
struct track *track = TRACK(dbe);
if (!sys_pl_update_check(playlist, track))
playlist_generic_remove(playlist, track);
else if (!playlist_has(pl_system_get(SYS_PL_HIDDEN), track) &&
!playlist_has(playlist, track))
playlist_generic_add_front(playlist, track);
}
playlist_generic_resort(playlist);
return true;
} }
static void sys_pl_save_full(struct playlist *playlist, struct file *file) static void sys_pl_update(struct playlist *playlist)
{ {
sys_pl_save_partial(playlist, file); switch (playlist->pl_id) {
queue_save_tracks(&playlist->pl_queue, file); case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED:
case SYS_PL_LEAST_PLAYED:
case SYS_PL_MOST_PLAYED:
idle_schedule(IDLE_SYNC, sys_pl_update_func, playlist);
default:
break;
}
} }
static void sys_pl_load_partial(struct playlist *playlist, struct file *file)
{
queue_load_flags(&playlist->pl_queue, file, true);
}
static void sys_pl_load_full(struct playlist *playlist, struct file *file)
{
sys_pl_load_partial(playlist, file);
queue_load_tracks(&playlist->pl_queue, file);
}
static bool sys_pl_generic_add(struct playlist *playlist, struct track *track)
{
if (queue_has(__sys_pl_queue(SYS_PL_HIDDEN), track))
return false;
return playlist_generic_add_track(playlist, track);
}
/* /*
* Favorite tracks playlist operations. * Favorite tracks playlist operations.
*/ */
static struct sys_playlist sys_favorites = { static struct playlist_ops favorites_ops = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Favorites"), .pl_add = playlist_generic_add,
.spl_init = playlist_generic_init, .pl_can_select = playlist_generic_can_select,
.spl_save = sys_pl_save_full, .pl_delete = sys_pl_delete_clear,
.spl_load = sys_pl_load_full, .pl_remove = playlist_generic_remove,
.spl_can_select = playlist_generic_can_select, .pl_set_random = playlist_generic_set_random,
.spl_add = playlist_generic_add_track, .pl_sort = playlist_generic_sort,
.spl_remove = playlist_generic_remove_track, .pl_rearrange = playlist_generic_rearrange,
.spl_clear = playlist_generic_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
}; };
@ -90,46 +91,49 @@ static struct sys_playlist sys_favorites = {
*/ */
static bool sys_pl_hidden_add(struct playlist *playlist, struct track *track) static bool sys_pl_hidden_add(struct playlist *playlist, struct track *track)
{ {
bool ret = playlist_generic_add_track(playlist, track); bool ret = playlist_generic_add(pl_system_get(SYS_PL_HIDDEN), track);
pl_system_remove_track("Collection", track); playlist_generic_remove(pl_system_get(SYS_PL_COLLECTION), track);
pl_system_remove_track("Unplayed", track); playlist_generic_remove(pl_system_get(SYS_PL_UNPLAYED), track);
pl_system_remove_track("Most Played", track); playlist_generic_remove(pl_system_get(SYS_PL_MOST_PLAYED), track);
pl_system_remove_track("Least Played", track); playlist_generic_remove(pl_system_get(SYS_PL_LEAST_PLAYED), track);
return ret; return ret;
} }
static bool sys_pl_hidden_remove(struct playlist *playlist, struct track *track) static bool sys_pl_hidden_remove(struct playlist *playlist, struct track *track)
{ {
bool ret = playlist_generic_remove_track(playlist, track); bool ret = playlist_generic_remove(playlist, track);
pl_system_add_track("Collection", track); unsigned int average = track_db_average_plays();
pl_system_add_track("Unplayed", track); unsigned int add_id = SYS_PL_LEAST_PLAYED;
pl_system_add_track("Most Played", track);
pl_system_add_track("Least Played", track); add_id = (track->tr_count == 0) ? SYS_PL_UNPLAYED : add_id;
add_id = (track->tr_count > average) ? SYS_PL_MOST_PLAYED : add_id;
playlist_generic_add(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_add(pl_system_get(add_id), track);
return ret; return ret;
} }
static void sys_pl_hidden_clear(struct playlist *playlist) static bool sys_pl_hidden_clear(struct playlist *playlist)
{ {
struct track *track; struct track *track;
while (queue_size(&playlist->pl_queue) > 0) { while (playlist_size(playlist) > 0) {
track = queue_at(&playlist->pl_queue, 0); track = playlist_at(playlist, 0);
sys_pl_hidden_remove(playlist, track); sys_pl_hidden_remove(playlist, track);
} }
pl_system_save();
return false;
} }
static struct sys_playlist sys_hidden = { static struct playlist_ops hidden_ops = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Hidden"), .pl_add = sys_pl_hidden_add,
.spl_init = playlist_generic_init, .pl_can_select = playlist_generic_can_select,
.spl_save = sys_pl_save_full, .pl_delete = sys_pl_hidden_clear,
.spl_load = sys_pl_load_full, .pl_remove = sys_pl_hidden_remove,
.spl_can_select = playlist_generic_can_select, .pl_set_random = playlist_generic_set_random,
.spl_add = sys_pl_hidden_add, .pl_sort = playlist_generic_sort,
.spl_remove = sys_pl_hidden_remove, .pl_rearrange = playlist_generic_rearrange,
.spl_clear = sys_pl_hidden_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
}; };
@ -138,20 +142,20 @@ static struct sys_playlist sys_hidden = {
*/ */
static bool sys_pl_queued_load() static bool sys_pl_queued_load()
{ {
struct playlist *playlist = &sys_playlists[SYS_PL_QUEUED]->spl_playlist; struct playlist *playlist = pl_system_get(SYS_PL_QUEUED);
struct queue *queue = &playlist->pl_queue;
unsigned int num, i, flags = 0; unsigned int num, i, flags = 0;
if (!file_open(&sys_deck_f, OPEN_READ)) if (!file_open(&sys_deck_f, OPEN_READ))
return true; return true;
file_readf(&sys_deck_f, "%u", &num); num = file_readu(&sys_deck_f);
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
file_readf(&sys_deck_f, "%u", &flags); flags = file_readu(&sys_deck_f);
flags &= ~(Q_SAVE_SORT | Q_SAVE_FLAGS); flags &= PL_RANDOM;
if (i == 0) if (i == 0)
queue->q_flags |= flags; playlist_generic_set_random(playlist,
queue_load_tracks(queue, &sys_deck_f); flags == PL_RANDOM);
playlist_generic_load(playlist, &sys_deck_f, PL_SAVE_TRACKS);
} }
file_close(&sys_deck_f); file_close(&sys_deck_f);
@ -159,24 +163,29 @@ static bool sys_pl_queued_load()
return true; return true;
} }
static void sys_pl_queued_init(struct playlist *playlist, static bool sys_pl_queued_delete(struct playlist *playlist)
unsigned int flags, struct queue_ops *ops)
{ {
queue_init(&playlist->pl_queue, Q_ENABLED, ops, playlist); playlist_generic_set_random(playlist, false);
playlist_clear_sort(playlist);
return sys_pl_delete_clear(playlist);
} }
static struct sys_playlist sys_queued = { static bool sys_pl_queued_remove(struct playlist *playlist, struct track *track)
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Queued Tracks"), {
.spl_init = sys_pl_queued_init, bool ret = playlist_generic_remove(playlist, track);
.spl_save = sys_pl_save_full, if (playlist_size(playlist) == 0)
.spl_load = sys_pl_load_full, sys_pl_queued_delete(playlist);
.spl_can_select = playlist_generic_can_select, return ret;
.spl_add = playlist_generic_add_track, }
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_generic_clear, static struct playlist_ops queued_ops = {
.spl_set_flag = playlist_generic_set_flag, .pl_add = playlist_generic_add,
.spl_sort = playlist_generic_sort, .pl_can_select = playlist_generic_can_select,
.spl_next = playlist_generic_next, .pl_delete = sys_pl_queued_delete,
.pl_remove = sys_pl_queued_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
}; };
@ -185,12 +194,10 @@ static struct sys_playlist sys_queued = {
*/ */
static bool sys_pl_collection_load() static bool sys_pl_collection_load()
{ {
struct playlist *playlist = &sys_playlists[SYS_PL_COLLECTION]->spl_playlist; struct playlist *playlist = pl_system_get(SYS_PL_COLLECTION);
if (file_open(&sys_collection_f, OPEN_READ)) { if (file_open(&sys_collection_f, OPEN_READ)) {
queue_load_flags(&playlist->pl_queue, &sys_collection_f, false); playlist_generic_load(playlist, &sys_collection_f, PL_SAVE_FLAGS);
queue_unset_flag(&playlist->pl_queue, Q_SAVE_FLAGS);
queue_unset_flag(&playlist->pl_queue, Q_SAVE_SORT);
file_close(&sys_collection_f); file_close(&sys_collection_f);
file_remove(&sys_collection_f); file_remove(&sys_collection_f);
} }
@ -198,227 +205,94 @@ static bool sys_pl_collection_load()
return true; return true;
} }
static bool sys_pl_collection_can_select(struct playlist *playlist) static struct playlist_ops collection_ops = {
{ .pl_can_select = playlist_generic_can_select,
return true; .pl_remove = sys_pl_hidden_add,
} .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
static bool sys_pl_collection_update(struct playlist *playlist, .pl_rearrange = playlist_generic_rearrange,
struct track *track)
{
return sys_pl_generic_add(playlist, track) || true;
}
static struct sys_playlist sys_collection = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Collection"),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_can_select = sys_pl_collection_can_select,
.spl_add = sys_pl_generic_add,
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_noop_clear,
.spl_update = sys_pl_collection_update,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
}; };
/* /*
* History playlist operations. * History playlist operations.
*/ */
static void sys_pl_history_init(struct playlist *playlist,
unsigned int flags, struct queue_ops *ops)
{
queue_init(&playlist->pl_queue,
Q_ENABLED | Q_REPEAT | Q_ADD_FRONT | Q_NO_SORT,
ops, playlist);
}
static bool sys_pl_history_add(struct playlist *playlist, struct track *track) static bool sys_pl_history_add(struct playlist *playlist, struct track *track)
{ {
queue_add(&playlist->pl_queue, track); playlist_generic_add_front(playlist, track);
queue_iter_set(&playlist->pl_queue, &playlist->pl_queue.q_cur, 0); playlist_current_set(playlist, 0);
return true; return true;
} }
static struct sys_playlist sys_history = { static struct playlist_ops history_ops = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "History"), .pl_add = sys_pl_history_add,
.spl_init = sys_pl_history_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_can_select = playlist_noop_can_select,
.spl_add = sys_pl_history_add,
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_noop_clear,
.spl_set_flag = playlist_noop_set_flag,
.spl_sort = playlist_noop_sort,
.spl_next = playlist_generic_next,
}; };
/* /*
* Unplayed tracks playlist operations. * Unplayed, Most Played, and Least Played tracks playlist operations.
*/ */
static bool sys_pl_unplayed_add(struct playlist *playlist, struct track *track) static struct playlist_ops dynamic_ops = {
{ .pl_can_select = playlist_generic_can_select,
if (track->tr_count > 0) .pl_set_random = playlist_generic_set_random,
return false; .pl_sort = playlist_generic_sort,
return sys_pl_generic_add(playlist, track); .pl_rearrange = playlist_generic_rearrange,
}
static bool sys_pl_unplayed_update(struct playlist *playlist,
struct track *track)
{
if (track->tr_count > 0)
return false;
return sys_pl_generic_add(playlist, track) || true;
}
static struct sys_playlist sys_unplayed = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Unplayed"),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_can_select = playlist_generic_can_select,
.spl_add = sys_pl_unplayed_add,
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_noop_clear,
.spl_update = sys_pl_unplayed_update,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
}; };
/* #define SYS_PLAYLIST(id, name, ops) \
* Most played tracks playlist operations. [id] = DEFINE_PLAYLIST(PL_SYSTEM, name, id, ops)
*/
static bool sys_pl_most_played_add(struct playlist *playlist, struct track *track)
{
unsigned int average = track_db_average_plays();
if (track->tr_count <= average)
return false;
return sys_pl_generic_add(playlist, track);
}
static bool sys_pl_most_played_update(struct playlist *playlist, static struct playlist sys_playlists[SYS_PL_NUM_PLAYLISTS] = {
struct track *track) SYS_PLAYLIST(SYS_PL_FAVORITES, "Favorites", &favorites_ops),
{ SYS_PLAYLIST(SYS_PL_HIDDEN, "Hidden", &hidden_ops),
unsigned int average = track_db_average_plays(); SYS_PLAYLIST(SYS_PL_QUEUED, "Queued Tracks", &queued_ops),
if (track->tr_count <= average) SYS_PLAYLIST(SYS_PL_COLLECTION, "Collection", &collection_ops),
return false; SYS_PLAYLIST(SYS_PL_HISTORY, "History", &history_ops),
return sys_pl_generic_add(playlist, track) || true; SYS_PLAYLIST(SYS_PL_UNPLAYED, "Unplayed", &dynamic_ops),
} SYS_PLAYLIST(SYS_PL_MOST_PLAYED, "Most Played", &dynamic_ops),
SYS_PLAYLIST(SYS_PL_LEAST_PLAYED, "Least Played", &dynamic_ops),
static struct sys_playlist sys_most_played = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Most Played"),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_can_select = playlist_generic_can_select,
.spl_add = sys_pl_most_played_add,
.spl_remove = playlist_generic_remove_track,
.spl_update = sys_pl_most_played_update,
.spl_clear = playlist_noop_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
}; };
/*
* Least played tracks playlist operations.
*/
static bool sys_pl_least_played_add(struct playlist *playlist, struct track *track)
{
unsigned int average = track_db_average_plays();
if (!track->tr_count || track->tr_count > average)
return false;
return sys_pl_generic_add(playlist, track);
}
static bool sys_pl_least_played_update(struct playlist *playlist,
struct track *track)
{
unsigned int average = track_db_average_plays();
if (!track->tr_count || track->tr_count > average)
return false;
return sys_pl_generic_add(playlist, track) || true;
}
static struct sys_playlist sys_least_played = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Least Played"),
.spl_init = sys_pl_update_init,
.spl_save = sys_pl_save_partial,
.spl_load = sys_pl_load_partial,
.spl_can_select = playlist_generic_can_select,
.spl_clear = playlist_noop_clear,
.spl_add = sys_pl_least_played_add,
.spl_remove = playlist_generic_remove_track,
.spl_update = sys_pl_least_played_update,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
};
static struct sys_playlist *sys_playlists[SYS_PL_NUM_PLAYLISTS] = {
[SYS_PL_FAVORITES] = &sys_favorites,
[SYS_PL_HIDDEN] = &sys_hidden,
[SYS_PL_QUEUED] = &sys_queued,
[SYS_PL_COLLECTION] = &sys_collection,
[SYS_PL_HISTORY] = &sys_history,
[SYS_PL_UNPLAYED] = &sys_unplayed,
[SYS_PL_MOST_PLAYED] = &sys_most_played,
[SYS_PL_LEAST_PLAYED] = &sys_least_played,
};
static struct sys_playlist * __sys_pl_lookup(const gchar *name)
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
if (string_match(name, sys_playlists[i]->spl_playlist.pl_name))
return sys_playlists[i];
}
return NULL;
}
static inline struct queue *__sys_pl_queue(enum sys_playlist_t plist)
{
return &sys_playlists[plist]->spl_playlist.pl_queue;
}
static void __sys_pl_save()
{
struct sys_playlist *sys_pl;
unsigned int i;
if (!file_open(&sys_pl_file, OPEN_WRITE))
return;
file_writef(&sys_pl_file, "%u\n", SYS_PL_NUM_PLAYLISTS);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_save(&sys_pl->spl_playlist, &sys_pl_file);
}
file_close(&sys_pl_file);
}
static bool __sys_pl_update_save() static bool __sys_pl_update_save()
{ {
__sys_pl_save(); pl_system_save();
return true;
}
static bool __sys_pl_load()
{
struct playlist *playlist;
unsigned int i, n;
gchar *name;
if (!file_open(&sys_file, OPEN_READ))
return true;
n = file_readu(&sys_file);
for (i = 0; i < n; i++) {
file_readu(&sys_file);
name = file_readl(&sys_file);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
playlist = pl_system_lookup(name);
g_free(name);
if (playlist)
playlist_generic_load(playlist, &sys_file, PL_SAVE_TRACKS);
}
file_close(&sys_file);
file_remove(&sys_file);
return true; return true;
} }
static bool __sys_pl_load_new() static bool __sys_pl_load_new()
{ {
struct sys_playlist *sys_pl; struct playlist *playlist;
unsigned int i, n; unsigned int i, n, load;
gchar *name; gchar *name;
if (!file_open(&sys_pl_file, OPEN_READ)) { if (!file_open(&sys_pl_file, OPEN_READ)) {
@ -429,213 +303,152 @@ static bool __sys_pl_load_new()
return true; return true;
} }
file_readf(&sys_pl_file, "%u\n", &n); n = file_readu(&sys_pl_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
name = file_readl(&sys_pl_file); load = PL_SAVE_METADATA;
sys_pl = __sys_pl_lookup(name); name = file_readl(&sys_pl_file);
if (sys_pl)
sys_pl->spl_load(&sys_pl->spl_playlist, &sys_pl_file); if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
playlist = pl_system_lookup(name);
g_free(name); g_free(name);
switch (i) {
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_QUEUED:
load = PL_SAVE_ALL;
}
playlist_generic_load(playlist, &sys_pl_file, load);
} }
file_close(&sys_pl_file); file_close(&sys_pl_file);
return true; return true;
} }
static bool __sys_pl_load()
{
struct sys_playlist *plist;
unsigned int i, n;
gchar *name;
if (!file_open(&sys_file, OPEN_READ))
return true;
file_readf(&sys_file, "%u\n", &n);
for (i = 0; i < n; i++) {
file_readf(&sys_file, "%*u %m[^\n]\n", &name);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
plist = __sys_pl_lookup(name);
if (plist)
queue_load_tracks(&plist->spl_playlist.pl_queue, &sys_file);
g_free(name);
}
file_close(&sys_file);
file_remove(&sys_file);
return true;
}
static void pl_system_save(void) static void pl_system_save(void)
{ {
__sys_pl_save(); struct playlist *playlist;
unsigned int i, save;
if (!file_open(&sys_pl_file, OPEN_WRITE))
return;
file_writef(&sys_pl_file, "%u\n", SYS_PL_NUM_PLAYLISTS);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
save = PL_SAVE_METADATA;
playlist = pl_system_get(i);
switch (i) {
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_QUEUED:
save = PL_SAVE_ALL;
}
file_writef(&sys_pl_file, "%s\n", playlist->pl_name);
playlist_generic_save(playlist, &sys_pl_file, save);
}
file_close(&sys_pl_file);
} }
static struct playlist *pl_system_get_playlist(const gchar *name) static struct playlist *pl_system_lookup(const gchar *name)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
return sys_pl ? &sys_pl->spl_playlist : NULL;
}
static unsigned int pl_system_get_id(const gchar *name)
{ {
unsigned int i; unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) { for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
if (string_match(name, sys_playlists[i]->spl_playlist.pl_name)) if (string_match(name, pl_system_get(i)->pl_name))
return i; return pl_system_get(i);
} }
return SYS_PL_NUM_PLAYLISTS;
}
static gchar *pl_system_get_name(unsigned int id)
{
if (id < SYS_PL_NUM_PLAYLISTS)
return g_strdup(sys_playlists[id]->spl_playlist.pl_name);
return NULL; return NULL;
} }
static bool pl_system_can_select(const gchar *name) static struct playlist *pl_system_get(unsigned int id)
{ {
struct sys_playlist *sys_pl = __sys_pl_lookup(name); return (id < SYS_PL_NUM_PLAYLISTS) ? &sys_playlists[id] : NULL;
return sys_pl ? sys_pl->spl_can_select(&sys_pl->spl_playlist) : false;
} }
static bool pl_system_new(const gchar *name) static void pl_system_played(struct track *track)
{ {
return false; unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_update(pl_system_get(i), track);
sys_pl_update(pl_system_lookup("Unplayed"));
sys_pl_update(pl_system_lookup("Most Played"));
sys_pl_update(pl_system_lookup("Least Played"));
} }
static bool pl_system_delete(const gchar *name) static void pl_system_selected(struct track *track)
{ {
struct sys_playlist *sys_pl = __sys_pl_lookup(name); unsigned int i;
if (sys_pl) {
sys_pl->spl_clear(&sys_pl->spl_playlist);
__sys_pl_save();
}
return false; /* Don't remove the playlist from the sidebar. */
}
static bool pl_system_add_track(const gchar *name, struct track *track) sys_pl_queued_remove(pl_system_get(SYS_PL_QUEUED), track);
{ for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
struct sys_playlist *sys_pl = __sys_pl_lookup(name); playlist_generic_update(pl_system_get(i), track);
bool ret = false;
if (sys_pl) {
ret = sys_pl->spl_add(&sys_pl->spl_playlist, track);
if (ret)
__sys_pl_save();
}
return ret;
}
static bool pl_system_remove_track(const gchar *name, struct track *track)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
bool ret = false;
if (sys_pl) {
ret = sys_pl->spl_remove(&sys_pl->spl_playlist, track);
if (ret)
__sys_pl_save();
}
return ret;
}
static void pl_system_update(const gchar *name)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
if (sys_pl)
playlist_generic_update(&sys_pl->spl_playlist, sys_pl->spl_update);
}
static void pl_system_set_flag(const gchar *name, enum queue_flags flag,
bool enabled)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
if (sys_pl) {
sys_pl->spl_set_flag(&sys_pl->spl_playlist, flag, enabled);
__sys_pl_save();
}
}
static void pl_system_sort(const gchar *name, enum compare_t sort, bool reset)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
if (sys_pl) {
sys_pl->spl_sort(&sys_pl->spl_playlist, sort, reset);
__sys_pl_save();
}
}
static struct track *pl_system_next(const gchar *name)
{
struct sys_playlist *sys_pl = __sys_pl_lookup(name);
struct track *track = NULL;;
if (sys_pl) {
track = sys_pl->spl_next(&sys_pl->spl_playlist);
__sys_pl_save();
}
return track;
} }
struct playlist_type pl_system = { struct playlist_type pl_system = {
.pl_save = pl_system_save, .pl_save = pl_system_save,
.pl_get_playlist = pl_system_get_playlist, .pl_lookup = pl_system_lookup,
.pl_get_id = pl_system_get_id, .pl_get = pl_system_get,
.pl_get_name = pl_system_get_name, .pl_played = pl_system_played,
.pl_can_select = pl_system_can_select, .pl_selected = pl_system_selected,
.pl_new = pl_system_new,
.pl_delete = pl_system_delete,
.pl_add_track = pl_system_add_track,
.pl_remove_track = pl_system_remove_track,
.pl_update = pl_system_update,
.pl_set_flag = pl_system_set_flag,
.pl_sort = pl_system_sort,
.pl_next = pl_system_next,
}; };
void pl_system_init(struct queue_ops *ops) void pl_system_init(void)
{ {
struct sys_playlist *sys_pl; struct playlist *playlist;
unsigned int i; unsigned int i;
idle_schedule(IDLE_SYNC, __sys_pl_load_new, NULL); idle_schedule(IDLE_SYNC, __sys_pl_load_new, NULL);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) { for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i]; playlist = pl_system_get(i);
sys_pl->spl_init(&sys_pl->spl_playlist, Q_REPEAT, ops);
switch (i) {
case SYS_PL_QUEUED:
case SYS_PL_HISTORY:
playlist_generic_init(playlist, 0);
break;
case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED:
case SYS_PL_MOST_PLAYED:
case SYS_PL_LEAST_PLAYED:
sys_pl_update(playlist);
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
playlist_generic_init(playlist, 4, COMPARE_ARTIST,
COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
break;
}
} }
} }
void pl_system_deinit() void pl_system_deinit()
{ {
unsigned int i; for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_deinit(pl_system_get(i));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
queue_deinit(__sys_pl_queue(i));
} }
void pl_system_new_track(struct track *track) void pl_system_new_track(struct track *track)
{ {
pl_system_add_track("Collection", track); playlist_generic_add(pl_system_lookup("Collection"), track);
pl_system_add_track("Unplayed", track); playlist_generic_add(pl_system_lookup("Unplayed"), track);
} }
void pl_system_delete_track(struct track *track) void pl_system_delete_track(struct track *track)
{ {
struct sys_playlist *sys_pl; for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
unsigned int i; playlist_generic_remove(pl_system_get(i), track);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_remove(&sys_pl->spl_playlist, track);
}
} }

View File

@ -3,29 +3,31 @@
*/ */
#include <core/playlists/user.h> #include <core/playlists/user.h>
static struct queue_ops *user_pl_ops = NULL; static struct database user_db;
static struct database user_db; static struct playlist_ops user_ops;
static struct user_playlist *__user_db_alloc(gchar *name) static struct user_playlist *__user_db_alloc(gchar *name, unsigned int index)
{ {
struct user_playlist *playlist = g_malloc(sizeof(struct user_playlist)); struct user_playlist *playlist = g_malloc(sizeof(struct user_playlist));
dbe_init(&playlist->pl_dbe, playlist); dbe_init(&playlist->pl_dbe, playlist);
playlist->pl_playlist.pl_name = name; playlist->pl_playlist.pl_name = name;
playlist->pl_playlist.pl_type = PL_USER; playlist->pl_playlist.pl_type = PL_USER;
playlist_generic_init(&playlist->pl_playlist, Q_REPEAT, user_pl_ops); playlist->pl_playlist.pl_id = index;
playlist->pl_playlist.pl_ops = &user_ops;
playlist_generic_init(&playlist->pl_playlist, 0);
return playlist; return playlist;
} }
static struct db_entry *user_db_alloc(const gchar *name) static struct db_entry *user_db_alloc(const gchar *name, unsigned int index)
{ {
return &__user_db_alloc(g_strdup(name))->pl_dbe; return &__user_db_alloc(g_strdup(name), index)->pl_dbe;
} }
static void user_db_free(struct db_entry *dbe) static void user_db_free(struct db_entry *dbe)
{ {
queue_deinit(&USER_PLAYLIST(dbe)->pl_playlist.pl_queue); playlist_generic_deinit(&USER_PLAYLIST(dbe)->pl_playlist);
g_free(USER_PLAYLIST(dbe)->pl_playlist.pl_name); g_free(USER_PLAYLIST(dbe)->pl_playlist.pl_name);
g_free(USER_PLAYLIST(dbe)); g_free(USER_PLAYLIST(dbe));
} }
@ -35,17 +37,12 @@ static gchar *user_db_key(struct db_entry *dbe)
return g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name); return g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name);
} }
static struct db_entry *user_db_read(struct file *file) static struct db_entry *user_db_read(struct file *file, unsigned int index)
{ {
gchar *name = file_readl(file); gchar *name = file_readl(file);
struct user_playlist *playlist = __user_db_alloc(name); struct user_playlist *playlist = __user_db_alloc(name, index);
queue_load_flags(&playlist->pl_playlist.pl_queue, file, true);
queue_load_tracks(&playlist->pl_playlist.pl_queue, file);
queue_iter_set(&playlist->pl_playlist.pl_queue,
&playlist->pl_playlist.pl_queue.q_cur,
playlist->pl_playlist.pl_queue.q_cur.it_pos);
playlist_generic_load(&playlist->pl_playlist, file, PL_SAVE_ALL);
return &playlist->pl_dbe; return &playlist->pl_dbe;
} }
@ -54,8 +51,7 @@ static void user_db_write(struct file *file, struct db_entry *dbe)
struct playlist *playlist = &USER_PLAYLIST(dbe)->pl_playlist; struct playlist *playlist = &USER_PLAYLIST(dbe)->pl_playlist;
file_writef(file, "%s\n", playlist->pl_name); file_writef(file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, file, true); playlist_generic_save(playlist, file, PL_SAVE_ALL);
queue_save_tracks(&playlist->pl_queue, file);
} }
@ -68,50 +64,9 @@ static const struct db_ops user_db_ops = {
}; };
static struct playlist *__user_pl_lookup(const gchar *name) static bool pl_user_delete(struct playlist *playlist)
{ {
struct db_entry *dbe = db_get(&user_db, name); struct db_entry *dbe = db_get(&user_db, playlist->pl_name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
static void pl_user_save(void)
{
db_save(&user_db);
}
static struct playlist *pl_user_get_playlist(const gchar *name)
{
return __user_pl_lookup(name);
}
static unsigned int pl_user_get_id(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
return dbe ? dbe->dbe_index : -1;
}
static gchar *pl_user_get_name(unsigned int id)
{
struct db_entry *dbe = db_at(&user_db, id);
return dbe ? g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name) : NULL;
}
static bool pl_user_can_select(const gchar *name)
{
return db_get(&user_db, name) != NULL;
}
static bool pl_user_new(const gchar *name)
{
if (db_get(&user_db, name))
return false;
db_insert(&user_db, name);
return true;
}
static bool pl_user_delete(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
if (dbe) { if (dbe) {
db_remove(&user_db, dbe); db_remove(&user_db, dbe);
db_defrag(&user_db); db_defrag(&user_db);
@ -119,82 +74,65 @@ static bool pl_user_delete(const gchar *name)
return dbe != NULL; return dbe != NULL;
} }
static bool pl_user_add(const gchar *name, struct track *track)
static struct playlist_ops user_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = pl_user_delete,
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static void pl_user_save(void)
{ {
struct playlist *playlist = __user_pl_lookup(name); db_save(&user_db);
bool ret = false;
if (playlist) {
ret = playlist_generic_add_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
} }
static bool pl_user_remove(const gchar *name, struct track *track) static struct playlist *pl_user_lookup(const gchar *name)
{ {
struct playlist *playlist = __user_pl_lookup(name); struct db_entry *dbe = db_get(&user_db, name);
bool ret = false; return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
if (playlist) {
ret = playlist_generic_remove_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
} }
static void pl_user_update(const gchar *name) static struct playlist *pl_user_get(unsigned int id)
{ {
struct db_entry *dbe = db_at(&user_db, id);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
} }
static void pl_user_set_flag(const gchar *name, enum queue_flags flag, static struct playlist *pl_user_new(const gchar *name)
bool enabled)
{ {
struct playlist *playlist = __user_pl_lookup(name); struct db_entry *dbe;
playlist_generic_set_flag(playlist, flag, enabled);
pl_user_save(); if (db_get(&user_db, name))
return NULL;
dbe = db_insert(&user_db, name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
} }
static void pl_user_sort(const gchar *name, enum compare_t sort, bool reset) static void pl_user_played(struct track *track)
{ {
struct playlist *playlist = __user_pl_lookup(name); struct db_entry *dbe, *next;
playlist_generic_sort(playlist, sort, reset); db_for_each(dbe, next, &user_db)
pl_user_save(); playlist_generic_update(&USER_PLAYLIST(dbe)->pl_playlist, track);
}
static struct track *pl_user_next(const gchar *name)
{
struct playlist *playlist = __user_pl_lookup(name);
struct track *track = playlist_generic_next(playlist);
pl_user_save();
return track;
} }
struct playlist_type pl_user = { struct playlist_type pl_user = {
.pl_save = pl_user_save, .pl_save = pl_user_save,
.pl_get_playlist = pl_user_get_playlist, .pl_lookup = pl_user_lookup,
.pl_get_id = pl_user_get_id, .pl_get = pl_user_get,
.pl_get_name = pl_user_get_name, .pl_new = pl_user_new,
.pl_can_select = pl_user_can_select, .pl_played = pl_user_played,
.pl_new = pl_user_new, .pl_selected = pl_user_played,
.pl_delete = pl_user_delete,
.pl_add_track = pl_user_add,
.pl_remove_track = pl_user_remove,
.pl_update = pl_user_update,
.pl_set_flag = pl_user_set_flag,
.pl_sort = pl_user_sort,
.pl_next = pl_user_next,
}; };
void pl_user_init(struct queue_ops *ops) void pl_user_init(void)
{ {
user_pl_ops = ops;
db_init(&user_db, "playlist.user", true, &user_db_ops, 0); db_init(&user_db, "playlist.user", true, &user_db_ops, 0);
db_load(&user_db); db_load(&user_db);
} }
@ -208,3 +146,33 @@ struct database *pl_user_db_get()
{ {
return &user_db; return &user_db;
} }
void pl_user_delete_track(struct track *track)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, &user_db) {
playlist = &USER_PLAYLIST(dbe)->pl_playlist;
playlist_generic_remove(playlist, track);
}
}
bool pl_user_rename(struct playlist *playlist, const gchar *name)
{
struct db_entry *dbe;
if (!playlist || db_get(&user_db, name))
return false;
dbe = db_get(&user_db, playlist->pl_name);
if (!dbe)
return false;
g_free(playlist->pl_name);
playlist->pl_name = g_strdup(name);
db_rekey(&user_db, dbe);
pl_user_save();
return true;
}

View File

@ -1,385 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/queue.h>
#include <core/string.h>
#include <stdlib.h>
static int track_less_than(const void *a, const void *b, void *data)
{
struct track *lhs = (struct track *)a;
struct track *rhs = (struct track *)b;
GSList *cur = (GSList *)data;
int res, field;
while (cur) {
field = GPOINTER_TO_INT(cur->data);
if (field > 0)
res = track_compare(lhs, rhs, field);
else
res = track_compare(rhs, lhs, abs(field));
if (res == 0) {
cur = g_slist_next(cur);
continue;
}
break;
};
return res;
}
static inline void *__queue_init(struct queue *queue, void *data)
{
if (queue->q_ops)
return queue->q_ops->qop_init(queue, data);
return NULL;
}
static inline void __queue_deinit(struct queue *queue)
{
if (queue->q_ops)
queue->q_ops->qop_deinit(queue);
}
static inline unsigned int __queue_added(struct queue *queue,
struct track *track,
unsigned int pos)
{
queue->q_length += track->tr_length;
if (queue->q_ops)
queue->q_ops->qop_added(queue, pos);
return pos;
}
static inline unsigned int __queue_add_head(struct queue *queue,
struct track *track)
{
g_queue_push_head(&queue->q_tracks, track);
return __queue_added(queue, track, 0);
}
static inline unsigned int __queue_add_tail(struct queue *queue,
struct track *track)
{
g_queue_push_tail(&queue->q_tracks, track);
return __queue_added(queue, track, queue_size(queue) - 1);
}
static inline unsigned int __queue_add_sorted(struct queue *queue,
struct track *track)
{
struct queue_iter it;
queue_for_each(queue, &it) {
if (track_less_than(queue_iter_val(&it), track, queue->q_sort) > 0) {
g_queue_insert_before(&queue->q_tracks, it.it_iter, track);
return __queue_added(queue, track, it.it_pos);
}
}
return __queue_add_tail(queue, track);
}
static inline bool __queue_erase(struct queue *queue, struct queue_iter *it)
{
struct track *track = queue_iter_val(it);
if (queue->q_ops)
return queue->q_ops->qop_erase(queue, track);
return true;
}
static inline void __queue_remove(struct queue *queue, struct queue_iter *it)
{
struct track *track = queue_iter_val(it);
unsigned int pos = it->it_pos;
GList *link = it->it_iter;
queue_iter_prev(it);
g_queue_delete_link(&queue->q_tracks, link);
queue->q_length -= track->tr_length;
if (queue->q_ops)
queue->q_ops->qop_removed(queue, pos);
}
static inline void __queue_clear(struct queue *queue, unsigned int n)
{
queue->q_length = 0;
if (queue->q_ops)
queue->q_ops->qop_cleared(queue, n);
}
static inline void __queue_updated(struct queue *queue, unsigned int pos)
{
if (queue->q_ops)
queue->q_ops->qop_updated(queue, pos);
}
static inline struct track *__queue_selected(struct queue *queue, unsigned int pos)
{
struct track *track = queue_iter_val(&queue->q_cur);
if (queue_has_flag(queue, Q_REPEAT) == false)
__queue_remove(queue, &queue->q_cur);
else
__queue_updated(queue, pos);
return track;
}
static inline void __queue_save(struct queue *queue, enum queue_flags flag)
{
if (queue->q_ops && queue_has_flag(queue, flag))
queue->q_ops->qop_save(queue, flag);
}
void queue_init(struct queue *queue, unsigned int flags,
const struct queue_ops *ops, void *data)
{
queue->q_flags = flags;
queue->q_length = 0;
queue->q_sort = NULL;
queue->q_ops = ops;
g_queue_init(&queue->q_tracks);
queue_iter_init(queue, &queue->q_cur);
queue->q_private = __queue_init(queue, data);
}
void queue_deinit(struct queue *queue)
{
queue_clear(queue);
__queue_deinit(queue);
g_slist_free(queue->q_sort);
queue->q_sort = NULL;
}
void queue_save_flags(struct queue *queue, struct file *file, bool iter)
{
GSList *cur = queue->q_sort;
int field;
if (iter)
file_writef(file, "%u ", queue->q_cur.it_pos);
file_writef(file, "%u %u", queue->q_flags, g_slist_length(cur));
while (cur) {
field = GPOINTER_TO_INT(cur->data);
file_writef(file, " %u %d", abs(field) - 1, field > 0);
cur = g_slist_next(cur);
}
file_writef(file, "\n");
}
void queue_save_tracks(struct queue *queue, struct file *file)
{
struct queue_iter it;
file_writef(file, "%u", queue_size(queue));
queue_for_each(queue, &it)
file_writef(file, " %u", track_index(queue_iter_val(&it)));
file_writef(file, "\n");
}
void queue_load_flags(struct queue *queue, struct file *file, bool iter)
{
unsigned int flags, n, i;
int field, ascending;
unsigned int it = 0;
gchar *line;
if (iter)
file_readf(file, "%u", &it);
file_readf(file, "%u %u", &flags, &n);
for (i = 0; i < n; i++) {
file_readf(file, "%u %d", &field, &ascending);
queue_sort(queue, field + 1, (i == 0) ? true : false);
if (ascending == false)
queue_sort(queue, field + 1, false);
}
queue->q_flags |= flags;
queue->q_cur.it_pos = it;
if (file_readf(file, "%m\n", &line))
g_free(line);
}
void queue_load_tracks(struct queue *queue, struct file *file)
{
unsigned int i, n, t;
gchar *line;
file_readf(file, "%u ", &n);
for (i = 0; i < n; i++) {
file_readf(file, "%u", &t);
queue_add(queue, track_get(t));
}
if (file_readf(file, "%m\n", &line))
g_free(line);
}
void queue_set_flag(struct queue *queue, enum queue_flags flag)
{
queue->q_flags |= flag;
__queue_save(queue, Q_SAVE_FLAGS);
}
void queue_unset_flag(struct queue *queue, enum queue_flags flag)
{
if (!queue_has_flag(queue, flag))
return;
queue->q_flags &= ~flag;
if (flag == Q_ADD_FRONT) {
queue_resort(queue);
queue_iter_set(queue, &queue->q_cur, queue->q_cur.it_pos);
}
__queue_save(queue, Q_SAVE_FLAGS);
}
unsigned int queue_add(struct queue *queue, struct track *track)
{
if (queue_has_flag(queue, Q_ADD_FRONT))
return __queue_add_head(queue, track);
if (queue->q_sort)
return __queue_add_sorted(queue, track);
return __queue_add_tail(queue, track);
}
void queue_erase(struct queue *queue, unsigned int index)
{
struct queue_iter it;
queue_iter_set(queue, &it, index);
if (__queue_erase(queue, &it))
__queue_remove(queue, &it);
}
void queue_remove(struct queue *queue, unsigned int index)
{
struct queue_iter it;
queue_iter_set(queue, &it, index);
__queue_remove(queue, &it);
}
unsigned int queue_remove_all(struct queue *queue, struct track *track)
{
unsigned int count = 0;
struct queue_iter it;
while (queue_at(queue, 0) == track) {
queue_remove(queue, 0);
count++;
}
queue_for_each(queue, &it) {
if (queue_iter_val(&it) == track) {
__queue_remove(queue, &it);
count++;
}
}
return count;
}
void queue_clear(struct queue *queue)
{
unsigned int n = queue_size(queue);
g_queue_clear(&queue->q_tracks);
__queue_clear(queue, n);
}
bool queue_has(struct queue *queue, struct track *track)
{
struct queue_iter it;
queue_for_each(queue, &it) {
if (queue_iter_val(&it) == track)
return true;
}
return false;
}
void queue_updated(struct queue *queue, struct track *track)
{
struct queue_iter it;
queue_for_each(queue, &it) {
if (queue_iter_val(&it) == track)
__queue_updated(queue, it.it_pos);
}
}
struct track *queue_selected(struct queue *queue, unsigned int index)
{
if (index > queue_size(queue))
return NULL;
if (queue->q_cur.it_pos != index)
queue_iter_set(queue, &queue->q_cur, index);
return __queue_selected(queue, index);
}
struct track *queue_next(struct queue *queue)
{
unsigned int pos, size = queue_size(queue);
if (!queue_has_flag(queue, Q_ENABLED))
return NULL;
else if (size == 0)
return NULL;
if (size == 1)
queue_iter_set(queue, &queue->q_cur, 0);
else if (queue_has_flag(queue, Q_RANDOM)) {
pos = g_random_int_range(1, (size < 15) ? size : size / 3);
pos += queue->q_cur.it_pos;
queue_iter_set(queue, &queue->q_cur, pos % size);
} else {
queue_iter_next(&queue->q_cur);
if (!queue->q_cur.it_iter)
queue_iter_set(queue, &queue->q_cur, 0);
}
return __queue_selected(queue, queue->q_cur.it_pos);
}
void queue_resort(struct queue *queue)
{
g_queue_sort(&queue->q_tracks, track_less_than, queue->q_sort);
for (unsigned int i = 0; i < queue_size(queue); i++)
__queue_updated(queue, i);
}
void queue_sort(struct queue *queue, enum compare_t sort, bool reset)
{
GSList *cur = NULL;
int field;
if (queue_has_flag(queue, Q_NO_SORT))
return;
if (reset) {
g_slist_free(queue->q_sort);
queue->q_sort = NULL;
}
cur = queue->q_sort;
while (cur) {
field = GPOINTER_TO_INT(cur->data);
if (abs(field) == sort) {
cur->data = GINT_TO_POINTER(-field);
goto out_sort;
}
cur = g_slist_next(cur);
}
queue->q_sort = g_slist_append(queue->q_sort, GINT_TO_POINTER(sort));
out_sort:
queue_resort(queue);
__queue_save(queue, Q_SAVE_SORT);
}

View File

@ -5,7 +5,7 @@
#include <core/settings.h> #include <core/settings.h>
static GHashTable *gui_settings = NULL; static GHashTable *gui_settings = NULL;
static struct file gui_settings_file = FILE_INIT("settings", 0); static struct file gui_settings_file = FILE_INIT_DATA("", "settings", 0);
static void __settings_save_item(gpointer key, gpointer value, gpointer data) static void __settings_save_item(gpointer key, gpointer value, gpointer data)
@ -29,9 +29,11 @@ static void __settings_read()
unsigned int num, i, value; unsigned int num, i, value;
gchar *key; gchar *key;
file_readf(&gui_settings_file, "%u\n", &num); num = file_readu(&gui_settings_file);
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
file_readf(&gui_settings_file, "%m[^ ] %u\n", &key, &value); key = file_readw(&gui_settings_file);
value = file_readu(&gui_settings_file);
g_hash_table_insert(gui_settings, key, GUINT_TO_POINTER(value)); g_hash_table_insert(gui_settings, key, GUINT_TO_POINTER(value));
} }
} }

View File

@ -81,3 +81,25 @@ bool string_match_token(const gchar *prefix, gchar **tokens)
return false; return false;
} }
bool string_is_subdir(const gchar *a, const gchar *b)
{
gchar **parent = b ? g_strsplit(b, "/", -1) : NULL;
gchar **child = a ? g_strsplit(a, "/", -1) : NULL;
bool subdir = true;
int i;
if (!parent || !child)
return false;
for (i = 0; parent[i]; i++) {
if (!child[i] || g_utf8_collate(parent[i], child[i]) != 0) {
subdir = false;
break;
}
}
g_strfreev(parent);
g_strfreev(child);
return subdir;
}

View File

@ -19,24 +19,35 @@
static struct database album_db; static struct database album_db;
static bool album_db_upgraded = false; static bool album_db_upgraded = false;
struct album_cache_file {
struct file ac_file;
gchar *ac_subdir;
gchar *ac_name;
};
static inline void __album_init_file(struct album *al, struct cache_file *f) struct album_cache_file *__album_alloc_file(struct album *al)
{ {
struct album_cache_file *ret = g_malloc(sizeof(struct album_cache_file));
gchar *name = g_uri_escape_string(al->al_name, " ", true); gchar *name = g_uri_escape_string(al->al_name, " ", true);
cache_file_init(f, g_strdup_printf("%d", al->al_year),
g_strdup_printf("%s.jpg", name)); ret->ac_subdir = g_strdup_printf("%d", al->al_year);
ret->ac_name = g_strdup_printf("%s.jpg", name);
file_init_cache(&ret->ac_file, ret->ac_subdir, ret->ac_name);
g_free(name); g_free(name);
return ret;
} }
static inline void __album_deinit_file(struct cache_file *f) static inline void __album_free_file(struct album_cache_file *acf)
{ {
g_free(f->cf_subdir); g_free(acf->ac_subdir);
g_free(f->cf_name); g_free(acf->ac_name);
g_free(acf);
} }
static bool __album_fetch_cover(struct album *album, gchar *releaseid) static bool __album_fetch_cover(struct album *album, gchar *releaseid)
{ {
struct cache_file file; struct album_cache_file *file;
CaaImageData image; CaaImageData image;
CaaCoverArt *caa; CaaCoverArt *caa;
gchar error[256]; gchar error[256];
@ -52,13 +63,13 @@ static bool __album_fetch_cover(struct album *album, gchar *releaseid)
goto out; goto out;
} }
__album_init_file(album, &file); file = __album_alloc_file(album);
if (cache_file_open(&file, OPEN_WRITE)) { if (file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
cache_file_write(&file, caa_imagedata_data(image), file_write(&file->ac_file, caa_imagedata_data(image),
caa_imagedata_size(image)); caa_imagedata_size(image));
cache_file_close(&file); file_close(&file->ac_file);
} }
__album_deinit_file(&file); __album_free_file(file);
caa_imagedata_delete(image); caa_imagedata_delete(image);
out: out:
@ -127,7 +138,8 @@ static bool __album_query_artist(struct album *album, struct artist *al_artist,
gchar *release, *artist, *year; gchar *release, *artist, *year;
bool found = false; bool found = false;
if (!al_artist || strcmp(al_artist->ar_tokens[0], "various") == 0) if (!al_artist || !string_length(al_artist->ar_name) ||
strcmp(al_artist->ar_tokens[0], "various") == 0)
return false; return false;
release = g_strdup_printf("release:\"%s\"~", lower); release = g_strdup_printf("release:\"%s\"~", lower);
@ -203,7 +215,7 @@ static struct db_entry *__album_alloc_v0(const gchar *key)
return &__album_alloc(NULL, NULL, name, year)->al_dbe; return &__album_alloc(NULL, NULL, name, year)->al_dbe;
} }
static struct db_entry *album_alloc(const gchar *key) static struct db_entry *album_alloc(const gchar *key, unsigned int index)
{ {
unsigned int artist_id, genre_id, year, n; unsigned int artist_id, genre_id, year, n;
gchar *name; gchar *name;
@ -241,7 +253,7 @@ static struct album *__album_parse_v0(gchar *line)
return __album_alloc(NULL, NULL, name, year); return __album_alloc(NULL, NULL, name, year);
} }
static struct db_entry *album_read(struct file *file) static struct db_entry *album_read(struct file *file, unsigned int index)
{ {
unsigned int year, artist_id, genre_id, n; unsigned int year, artist_id, genre_id, n;
struct album *album; struct album *album;
@ -351,40 +363,40 @@ bool album_match_token(struct album *album, const gchar *string)
bool album_artwork_exists(struct album *album) bool album_artwork_exists(struct album *album)
{ {
struct cache_file file; struct album_cache_file *file;
bool ret; bool ret;
__album_init_file(album, &file); file = __album_alloc_file(album);
ret = cache_file_exists(&file); ret = file_exists(&file->ac_file);
__album_deinit_file(&file); __album_free_file(file);
return ret; return ret;
} }
gchar *album_artwork_path(struct album *album) gchar *album_artwork_path(struct album *album)
{ {
struct cache_file file; struct album_cache_file *file;
gchar *ret = NULL; gchar *ret = NULL;
__album_init_file(album, &file); file = __album_alloc_file(album);
if (cache_file_exists(&file)) if (file_exists(&file->ac_file))
ret = cache_file_path(&file); ret = file_path(&file->ac_file);
__album_deinit_file(&file); __album_free_file(file);
return ret; return ret;
} }
bool album_artwork_import(struct album *album, gchar *path) bool album_artwork_import(struct album *album, gchar *path)
{ {
struct cache_file file; struct album_cache_file *file;
bool ret = false; bool ret = false;
__album_init_file(album, &file); file = __album_alloc_file(album);
if (path && cache_file_open(&file, OPEN_WRITE)) { if (path && file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
ret = cache_file_import(&file, path); ret = file_import(&file->ac_file, path);
cache_file_close(&file); file_close(&file->ac_file);
} }
__album_deinit_file(&file); __album_free_file(file);
return ret; return ret;
} }

View File

@ -20,7 +20,7 @@ static struct artist *__artist_alloc(gchar *name)
} }
static struct db_entry *artist_alloc(const gchar *name) static struct db_entry *artist_alloc(const gchar *name, unsigned int index)
{ {
return &__artist_alloc(g_strdup(name))->ar_dbe; return &__artist_alloc(g_strdup(name))->ar_dbe;
} }
@ -37,7 +37,7 @@ static gchar *artist_key(struct db_entry *dbe)
return ARTIST(dbe)->ar_name; return ARTIST(dbe)->ar_name;
} }
struct db_entry *artist_read(struct file *file) struct db_entry *artist_read(struct file *file, unsigned int index)
{ {
return &__artist_alloc(file_readl(file))->ar_dbe; return &__artist_alloc(file_readl(file))->ar_dbe;
} }

View File

@ -18,7 +18,7 @@ static struct genre *__genre_alloc(gchar *name)
return genre; return genre;
} }
static struct db_entry *genre_alloc(const gchar *name) static struct db_entry *genre_alloc(const gchar *name, unsigned int index)
{ {
return &__genre_alloc(g_strdup(name))->ge_dbe; return &__genre_alloc(g_strdup(name))->ge_dbe;
} }
@ -35,7 +35,7 @@ static gchar *genre_key(struct db_entry *dbe)
return GENRE(dbe)->ge_name; return GENRE(dbe)->ge_name;
} }
static struct db_entry *genre_read(struct file *file) static struct db_entry *genre_read(struct file *file, unsigned int index)
{ {
return &__genre_alloc(file_readl(file))->ge_dbe; return &__genre_alloc(file_readl(file))->ge_dbe;
} }

View File

@ -1,6 +1,7 @@
/* /*
* Copyright 2014 (c) Anna Schumaker. * Copyright 2014 (c) Anna Schumaker.
*/ */
#include <core/string.h>
#include <core/tags/library.h> #include <core/tags/library.h>
#define LIBRARY_DB_MIN 0 /* Ocarina 6.0 */ #define LIBRARY_DB_MIN 0 /* Ocarina 6.0 */
@ -17,7 +18,7 @@ static struct library *__library_alloc(gchar *path)
return library; return library;
} }
static struct db_entry *library_alloc(const gchar *path) static struct db_entry *library_alloc(const gchar *path, unsigned int index)
{ {
return &__library_alloc(g_strdup(path))->li_dbe; return &__library_alloc(g_strdup(path))->li_dbe;
} }
@ -32,15 +33,15 @@ static gchar *library_key(struct db_entry *dbe)
return LIBRARY(dbe)->li_path; return LIBRARY(dbe)->li_path;
} }
static struct db_entry *library_read(struct file *file) static struct db_entry *library_read(struct file *file, unsigned int index)
{ {
int enabled;
gchar *path; gchar *path;
/* Old "enabled" flag */
if (file_version(file) == 0) if (file_version(file) == 0)
file_readf(file, "%d", &enabled); file_readd(file);
file_readf(file, " %m[^\n]", &path); path = file_readl(file);
return &__library_alloc(path)->li_dbe; return &__library_alloc(path)->li_dbe;
} }
@ -82,12 +83,22 @@ const struct database *library_db_get()
struct library *library_find(const gchar *path) struct library *library_find(const gchar *path)
{ {
return LIBRARY(db_find(&library_db, path)); struct library *library = library_lookup(path);
if (library)
return library;
return LIBRARY(db_insert(&library_db, path));
} }
struct library *library_lookup(const gchar *path) struct library *library_lookup(const gchar *path)
{ {
return LIBRARY(db_get(&library_db, path)); struct db_entry *dbe, *next;
db_for_each(dbe, next, &library_db) {
if (string_is_subdir(path, LIBRARY(dbe)->li_path))
return LIBRARY(dbe);
}
return NULL;
} }
struct library *library_get(const unsigned int index) struct library *library_get(const unsigned int index)

View File

@ -41,26 +41,18 @@ static struct track *__track_alloc()
return track; return track;
} }
static struct track *__track_alloc_filepath(const gchar *filepath)
struct db_entry *track_alloc(const gchar *key)
{ {
TagLib_File *file = taglib_file_new(filepath);
const TagLib_AudioProperties *audio; const TagLib_AudioProperties *audio;
struct library *library; struct track *track = NULL;
struct artist *artist; struct artist *artist;
struct genre *genre; struct genre *genre;
struct track *track = NULL; TagLib_Tag *tag;
unsigned int lib_id;
TagLib_File *file;
TagLib_Tag *tag;
char *fullpath, *path;
sscanf(key, "%u/%m[^\n]", &lib_id, &path);
library = library_get(lib_id);
fullpath = library_file(library, path);
file = taglib_file_new(fullpath);
if (!file || !taglib_file_is_valid(file)) { if (!file || !taglib_file_is_valid(file)) {
g_printerr("WARNING: Could not read tags for: %s\n", fullpath); g_printerr("WARNING: Could not read tags for: %s\n", filepath);
goto out; return NULL;
} }
track = __track_alloc(); track = __track_alloc();
@ -69,24 +61,50 @@ struct db_entry *track_alloc(const gchar *key)
artist = artist_find(taglib_tag_artist(tag)); artist = artist_find(taglib_tag_artist(tag));
genre = genre_find(taglib_tag_genre(tag)); genre = genre_find(taglib_tag_genre(tag));
track->tr_album = album_find(artist, genre, taglib_tag_album(tag), track->tr_album = album_find(artist, genre, taglib_tag_album(tag),
taglib_tag_year(tag)); taglib_tag_year(tag));
track->tr_library = library;
unplayed_count++;
track->tr_count = 0; track->tr_count = 0;
track->tr_length = taglib_audioproperties_length(audio); track->tr_length = taglib_audioproperties_length(audio);
track->tr_track = taglib_tag_track(tag); track->tr_track = taglib_tag_track(tag);
date_set(&track->tr_date, 0, 0, 0); date_set(&track->tr_date, 0, 0, 0);
track->tr_path = g_strdup(key);
track->tr_title = g_strdup(taglib_tag_title(tag)); track->tr_title = g_strdup(taglib_tag_title(tag));
track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL, track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
&track->tr_alts); &track->tr_alts);
taglib_tag_free_strings(); taglib_tag_free_strings();
taglib_file_free(file); taglib_file_free(file);
out: return track;
}
static void __track_free(struct track *track)
{
g_strfreev(track->tr_tokens);
g_strfreev(track->tr_alts);
g_free(track->tr_title);
g_free(track);
}
struct db_entry *track_alloc(const gchar *key, unsigned int index)
{
struct library *library;
char *fullpath, *path;
struct track *track;
unsigned int lib_id;
sscanf(key, "%u/%m[^\n]", &lib_id, &path);
library = library_get(lib_id);
fullpath = library_file(library, path);
track = __track_alloc_filepath(fullpath);
if (track) {
track->tr_library = library;
track->tr_path = g_strdup(key);
unplayed_count++;
}
g_free(path); g_free(path);
g_free(fullpath); g_free(fullpath);
return track ? &track->tr_dbe : NULL; return track ? &track->tr_dbe : NULL;
@ -100,10 +118,7 @@ static void track_free(struct db_entry *dbe)
if (track->tr_count == 0) if (track->tr_count == 0)
unplayed_count--; unplayed_count--;
g_strfreev(track->tr_tokens); __track_free(track);
g_strfreev(track->tr_alts);
g_free(track->tr_title);
g_free(track);
} }
static gchar *track_key(struct db_entry *dbe) static gchar *track_key(struct db_entry *dbe)
@ -113,42 +128,35 @@ static gchar *track_key(struct db_entry *dbe)
static void track_read_v0(struct file *file, struct track *track) static void track_read_v0(struct file *file, struct track *track)
{ {
unsigned int artist_id, album_id, genre_id; struct artist *artist = artist_get(file_readu(file));
struct artist *artist; struct album *album = album_get( file_readu(file));
struct genre *genre; struct genre *genre = genre_get( file_readu(file));
struct album *album;
file_readf(file, "%u %u %u %hu", &artist_id, &album_id, &genre_id, track->tr_track = file_readhu(file);
&track->tr_track);
date_read(file, &track->tr_date); date_read(file, &track->tr_date);
album = album_get(album_id);
artist = artist_get(artist_id);
genre = genre_get(genre_id);
if (album->al_artist != artist || album->al_genre != genre) if (album->al_artist != artist || album->al_genre != genre)
album = album_find(artist, genre, album->al_name, album->al_year); album = album_find(artist, genre, album->al_name, album->al_year);
track->tr_album = album; track->tr_album = album;
} }
static struct db_entry *track_read(struct file *file) static struct db_entry *track_read(struct file *file, unsigned int index)
{ {
struct track *track = __track_alloc(); struct track *track = __track_alloc();
unsigned int library_id, album_id;
file_readf(file, "%u", &library_id); track->tr_library = library_get(file_readu(file));
track->tr_library = library_get(library_id);
if (file_version(file) == 0) if (file_version(file) == 0)
track_read_v0(file, track); track_read_v0(file, track);
else { else {
file_readf(file, "%u %hu", &album_id, &track->tr_track); track->tr_album = album_get(file_readu(file));
track->tr_album = album_get(album_id); track->tr_track = file_readhu(file);
date_read_stamp(file, &track->tr_date); date_read_stamp(file, &track->tr_date);
} }
file_readf(file, "%hu %hu", &track->tr_count, &track->tr_length); track->tr_count = file_readhu(file);
track->tr_length = file_readhu(file);
play_count += track->tr_count; play_count += track->tr_count;
if (track->tr_count == 0) if (track->tr_count == 0)
@ -251,6 +259,24 @@ unsigned int track_db_average_plays()
return play_count / (track_db.db_size - unplayed_count); return play_count / (track_db.db_size - unplayed_count);
} }
struct track *track_alloc_external(const gchar *filepath)
{
struct track *track = __track_alloc_filepath(filepath);
if (track) {
track->tr_library = NULL;
track->tr_path = g_strdup(filepath);
}
return track;
}
void track_free_external(struct track *track)
{
if (TRACK_IS_EXTERNAL(track)) {
g_free(track->tr_path);
__track_free(track);
}
}
struct track *track_add(struct library *library, const gchar *filepath) struct track *track_add(struct library *library, const gchar *filepath)
{ {
unsigned int offset = strlen(library->li_path) + 1; unsigned int offset = strlen(library->li_path) + 1;
@ -285,6 +311,23 @@ struct track *track_get(const unsigned int index)
return TRACK(db_at(&track_db, index)); return TRACK(db_at(&track_db, index));
} }
struct track *track_lookup(const gchar *filepath)
{
struct library *library = library_lookup(filepath);
unsigned int offset;
struct track *track;
gchar *key;
if (!library)
return NULL;
offset = strlen(library->li_path) + 1;
key = __track_key(library, g_strdup(filepath + offset));
track = TRACK(db_get(&track_db, key));
g_free(key);
return track;
}
int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare) int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare)
{ {
switch (compare) { switch (compare) {
@ -329,11 +372,13 @@ gchar *track_path(struct track *track)
g_free(path); g_free(path);
return res; return res;
} }
return g_strdup(""); return g_strdup(track->tr_path);
} }
void track_played(struct track *track) void track_played(struct track *track)
{ {
if (TRACK_IS_EXTERNAL(track))
return;
if (track->tr_count == 0) if (track->tr_count == 0)
unplayed_count--; unplayed_count--;
track->tr_count++; track->tr_count++;

View File

@ -2,28 +2,25 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/audio.h> #include <core/audio.h>
#include <gui/artwork.h>
#include <gui/window.h> #include <gui/window.h>
#include <glib/gi18n.h> #include <glib/gi18n.h>
#define ARTWORK_PREVIEW_SIZE 150 #define ARTWORK_PREVIEW_SIZE 150
static struct album *__artwork_cur_album = NULL; static struct album *artwork_current = NULL;
static unsigned int __artwork_timeo_id = 0; static unsigned int artwork_timeout = 0;
static cairo_surface_t *__artwork_scale_pixbuf(GdkPixbuf *pix) static cairo_surface_t *__gui_artwork_scale(cairo_surface_t *orig, int new_h)
{ {
int old_h = gdk_pixbuf_get_height(pix); int old_h = cairo_image_surface_get_height(orig);
int old_w = gdk_pixbuf_get_width(pix); int old_w = cairo_image_surface_get_width(orig);
int new_h = gui_builder_widget_height("o_position") +
gui_builder_widget_height("o_tags");
int new_w = (old_w * new_h) / old_h; int new_w = (old_w * new_h) / old_h;
int scale = gtk_widget_get_scale_factor(gui_builder_widget("o_cover")); int scale = gtk_widget_get_scale_factor(GTK_WIDGET(gui_artwork()));
cairo_surface_t *orig = gdk_cairo_surface_create_from_pixbuf(pix, 0,
NULL);
cairo_content_t content = cairo_surface_get_content(orig); cairo_content_t content = cairo_surface_get_content(orig);
cairo_surface_t *new = cairo_surface_create_similar(orig, content, cairo_surface_t *scaled = cairo_surface_create_similar(orig, content,
new_w, new_h); new_w, new_h);
cairo_t *cairo = cairo_create(new); cairo_t *cairo = cairo_create(scaled);
cairo_scale(cairo, (double)(scale * new_w) / old_w, cairo_scale(cairo, (double)(scale * new_w) / old_w,
(double)(scale * new_h) / old_h); (double)(scale * new_h) / old_h);
@ -31,92 +28,77 @@ static cairo_surface_t *__artwork_scale_pixbuf(GdkPixbuf *pix)
cairo_paint(cairo); cairo_paint(cairo);
cairo_destroy(cairo); cairo_destroy(cairo);
cairo_surface_destroy(orig); return scaled;
return new;
} }
static cairo_surface_t *__artwork_get_surface(struct track *track) static cairo_surface_t *__gui_artwork_get(gchar *path, int new_h)
{ {
gchar *path = album_artwork_path(track->tr_album); GdkPixbuf *pixbuf = path ? gdk_pixbuf_new_from_file(path, NULL) : NULL;
cairo_surface_t *surface; cairo_surface_t *surface = NULL;
GdkPixbuf *pix; cairo_surface_t *scaled = NULL;
if (!path) if (!pixbuf)
return NULL; return NULL;
pix = gdk_pixbuf_new_from_file(path, NULL); surface = gdk_cairo_surface_create_from_pixbuf(pixbuf, 0, NULL);
if (!pix)
return NULL;
surface = __artwork_scale_pixbuf(pix);
g_object_unref(G_OBJECT(pix));
g_free(path);
return surface;
}
static bool __artwork_set_pixbuf(void)
{
GtkImage *cover = GTK_IMAGE(gui_builder_widget("o_cover"));
struct track *track = audio_cur_track();
cairo_surface_t *surface;
if (!track || track->tr_album == __artwork_cur_album)
return true;
surface = __artwork_get_surface(track);
if (surface) { if (surface) {
gtk_image_set_from_surface(cover, surface); scaled = __gui_artwork_scale(surface, new_h);
cairo_surface_destroy(surface);
}
g_object_unref(G_OBJECT(pixbuf));
return scaled;
}
static bool __gui_artwork_set_path(GtkImage *image, gchar *path, int new_h)
{
cairo_surface_t *surface = __gui_artwork_get(path, new_h);
bool status = surface != NULL;
if (surface) {
gtk_image_set_from_surface(image, surface);
cairo_surface_destroy(surface); cairo_surface_destroy(surface);
} else } else
gtk_image_set_from_icon_name(cover, "image-missing", GTK_ICON_SIZE_DIALOG); gtk_image_set_from_icon_name(image, "image-missing",
GTK_ICON_SIZE_DIALOG);
gtk_widget_set_sensitive(GTK_WIDGET(cover), surface != NULL); return status;
__artwork_cur_album = surface ? track->tr_album : NULL;
return (surface != NULL);
} }
static gboolean __artwork_timeout(gpointer data) static gboolean __gui_artwork_timeout(gpointer data)
{ {
if (__artwork_set_pixbuf()) { struct track *track = audio_cur_track();
__artwork_timeo_id = 0; int height = gui_builder_widget_height("artwork");
gchar *path;
bool status;
if (!track || track->tr_album == artwork_current)
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE; path = album_artwork_path(track->tr_album);
status = __gui_artwork_set_path(gui_artwork(), path, height);
gtk_widget_set_sensitive(GTK_WIDGET(gui_artwork()), status);
artwork_current = status ? track->tr_album : NULL;
artwork_timeout = status ? artwork_timeout : 0;
g_free(path);
return status ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
} }
void gui_artwork_set_cover(void) void __gui_artwork_update_preview(GtkFileChooser *chooser, gpointer data)
{
if (!__artwork_set_pixbuf() && __artwork_timeo_id == 0)
__artwork_timeo_id = g_timeout_add(2000, __artwork_timeout, NULL);
}
void __artwork_update_preview(GtkFileChooser *chooser, gpointer data)
{ {
GtkWidget *preview = gtk_file_chooser_get_preview_widget(chooser); GtkWidget *preview = gtk_file_chooser_get_preview_widget(chooser);
gchar *path = gtk_file_chooser_get_preview_filename(chooser); gchar *path = gtk_file_chooser_get_preview_filename(chooser);
GdkPixbuf *pix = gdk_pixbuf_new_from_file(path, NULL);
unsigned int old_w, scale;
cairo_surface_t *surface;
if (pix) {
old_w = gdk_pixbuf_get_width(pix);
scale = old_w / ARTWORK_PREVIEW_SIZE;
surface = gdk_cairo_surface_create_from_pixbuf(pix, scale, NULL);
if (surface) {
gtk_image_set_from_surface(GTK_IMAGE(preview), surface);
cairo_surface_destroy(surface);
}
}
__gui_artwork_set_path(GTK_IMAGE(preview), path, ARTWORK_PREVIEW_SIZE);
g_free(path); g_free(path);
} }
void __artwork_select_cover(GtkButton *button) void __gui_artwork_select_cover(GtkButton *button)
{ {
struct track *track = audio_cur_track(); struct track *track = audio_cur_track();
GtkWidget *preview, *dialog;
GtkFileFilter *filter; GtkFileFilter *filter;
GtkWidget *dialog;
GtkWidget *preview;
gchar *path; gchar *path;
if (!track) if (!track)
@ -136,15 +118,30 @@ void __artwork_select_cover(GtkButton *button)
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview); gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview);
gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog), false); gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog), false);
g_signal_connect(dialog, "update-preview", (GCallback)__artwork_update_preview, NULL); g_signal_connect(dialog, "update-preview",
(GCallback)__gui_artwork_update_preview, NULL);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
album_artwork_import(track->tr_album, path); gui_artwork_import(track, path);
__artwork_cur_album = NULL;
gui_artwork_set_cover();
g_free(path); g_free(path);
} }
gtk_widget_destroy(dialog); gtk_widget_destroy(dialog);
} }
void gui_artwork_set_cover(void)
{
if (__gui_artwork_timeout(NULL) != G_SOURCE_CONTINUE)
return;
artwork_timeout = g_timeout_add(2000, __gui_artwork_timeout, NULL);
}
void gui_artwork_import(struct track *track, gchar *path)
{
album_artwork_import(track->tr_album, path);
if (track == audio_cur_track()) {
artwork_current = NULL;
gui_artwork_set_cover();
}
}

View File

@ -1,92 +1,169 @@
/* /*
* Copyright 2014 (c) Anna Schumaker. * Copyright 2014 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/playlist.h>
#include <core/string.h> #include <core/string.h>
#include <gui/artwork.h> #include <gui/artwork.h>
#include <gui/audio.h> #include <gui/audio.h>
#include <gui/idle.h> #include <gui/idle.h>
#include <gui/playlist.h> #include <gui/playlist.h>
#include <gui/treeview.h> #include <gui/treeview.h>
#include <gui/view.h>
#include <gui/window.h> #include <gui/window.h>
static guint audio_timeout = 0; static guint audio_timeout = 0;
static guint popover_timeout = 0;
static inline void __audio_set_label(const gchar *label, const gchar *size, static inline void __gui_audio_set_label_markup(GtkLabel *label,
const gchar *text) const gchar *size,
const gchar *text)
{ {
gchar *markup = g_markup_printf_escaped("<span size='%s'>%s</span>", const gchar *fmt = "<span size='%s'>%s</span>";
size, text); gchar *markup = g_markup_printf_escaped(fmt, size, text);
gtk_label_set_markup(GTK_LABEL(gui_builder_widget(label)), markup); gtk_label_set_markup(label, markup);
g_free(markup); g_free(markup);
} }
static inline void __audio_set_time_label(const gchar *label, unsigned int time) static void __gui_audio_load(struct track *track)
{ {
gchar *str = string_sec2str(time); gchar *duration = string_sec2str(track->tr_length);
__audio_set_label(label, "large", str);
g_free(str);
}
static void __audio_load(struct track *track) __gui_audio_set_label_markup(gui_title_tag(), "xx-large",
{ track->tr_title);
__audio_set_label("o_title", "xx-large", track->tr_title); __gui_audio_set_label_markup(gui_album_tag(), "x-large",
__audio_set_label("o_artist", "x-large", track->tr_album->al_artist->ar_name); track->tr_album->al_name);
__audio_set_label("o_album", "x-large", track->tr_album->al_name); __gui_audio_set_label_markup(gui_artist_tag(), "x-large",
__audio_set_time_label("o_duration", track->tr_length); track->tr_album->al_artist->ar_name);
__gui_audio_set_label_markup(gui_duration(), "large", duration);
__gui_audio_set_label_markup(gui_position(), "large", "0:00");
gtk_adjustment_set_upper(gui_seek(), track->tr_length);
gui_pl_system_track_loaded(track); gui_pl_system_track_loaded(track);
gui_treeview_scroll(); gui_treeview_scroll();
gui_artwork_set_cover(); gui_artwork_set_cover();
gui_idle_enable(); gui_idle_enable();
g_free(duration);
} }
static void __audio_change_state(GstState state) static void __gui_audio_set_pause_text(int n, GstState state)
{ {
if (state == GST_STATE_PLAYING) { bool sensitive = true;
gtk_widget_hide(gui_builder_widget("o_play")); gchar *text;
gtk_widget_show(gui_builder_widget("o_pause"));
} else { if (n == -1) {
gtk_widget_show(gui_builder_widget("o_play")); sensitive = false;
gtk_widget_hide(gui_builder_widget("o_pause")); if (state == GST_STATE_PLAYING)
text = g_strdup("Keep playing");
else
text = g_strdup("Paused");
} else if (n == 0)
text = g_strdup("Pause after this track");
else if (n == 1)
text = g_strdup("Pause after next track");
else
text = g_strdup_printf("Pause after %d tracks", n);
gtk_widget_set_sensitive(GTK_WIDGET(gui_pause_down()), sensitive);
gtk_entry_set_text(gui_pause_entry(), text);
g_free(text);
}
static void __gui_audio_change_state(GstState state)
{
bool playing = (state == GST_STATE_PLAYING);
gtk_widget_set_visible(GTK_WIDGET(gui_play_button()), !playing);
gtk_widget_set_visible(GTK_WIDGET(gui_pause_button()), playing);
__gui_audio_set_pause_text(audio_get_pause_count(), state);
}
static void __gui_audio_config_pause(int n)
{
__gui_audio_set_pause_text(n, audio_cur_state());
}
struct audio_callbacks audio_cb = {
.audio_cb_load = __gui_audio_load,
.audio_cb_state_change = __gui_audio_change_state,
.audio_cb_config_pause = __gui_audio_config_pause,
};
void __gui_audio_pause(GtkButton *button, gpointer data)
{
audio_pause();
if (audio_get_pause_count() > -1) {
gtk_popover_popup(gui_pause_popover());
popover_timeout = g_timeout_add_seconds(10,
gui_audio_popover_timeout, NULL);
} }
} }
static void __audio_config_pause(int n) void __gui_audio_pause_change_text(GtkEntry *entry, gpointer data)
{ {
GtkComboBox *combo = GTK_COMBO_BOX(gui_builder_widget("o_pause_after")); const gchar *text = gtk_entry_get_text(entry);
gtk_combo_box_set_active(combo, n + 1); int n = audio_get_pause_count();
unsigned int i;
if (g_str_match_string("Keep", text, true))
n = -1;
else if (g_str_match_string("This", text, true))
n = 0;
else if (g_str_match_string("Next", text, true))
n = 1;
else {
for (i = 0; text[i] != '\0'; i++) {
if (!g_ascii_isdigit(text[i]))
continue;
if (i > 0 && text[i-1] == '-')
i -= 1;
n = g_strtod(text + i, NULL);
break;
}
}
if (!audio_pause_after(n))
__gui_audio_set_pause_text(audio_get_pause_count(), audio_cur_state());
} }
void __audio_pause_changed(GtkComboBox *combo, gpointer data) void __gui_audio_pause_inc(GtkButton *button, gpointer data)
{ {
int val = gtk_combo_box_get_active(combo) - 1; audio_pause_after(audio_get_pause_count() + 1);
audio_pause_after(val);
} }
void __audio_seek(GtkRange *range, GtkScrollType type, double value, gpointer data) void __gui_audio_pause_dec(GtkButton *button, gpointer data)
{
audio_pause_after(audio_get_pause_count() - 1);
}
void __gui_audio_pause_popover_popdown(GtkButton *button, gpointer data)
{
gtk_popover_popdown(gui_pause_popover());
#ifdef CONFIG_TESTING
gtk_widget_hide(GTK_WIDGET(gui_pause_popover()));
#endif /* CONFIG_TESTING */
g_source_remove(popover_timeout);
popover_timeout = 0;
}
void __gui_audio_pause_popover_clear(GtkButton *button, gpointer data)
{
audio_pause_after(-1);
__gui_audio_pause_popover_popdown(button, data);
}
void __gui_audio_seek(GtkRange *range, GtkScrollType type,
double value, gpointer data)
{ {
audio_seek(value * GST_SECOND); audio_seek(value * GST_SECOND);
} }
void __audio_volume_changed(GtkScaleButton *button, gdouble value, gpointer data) void __gui_audio_volume_changed(GtkScaleButton *button, gdouble value,
gpointer data)
{ {
audio_set_volume((unsigned int)value); audio_set_volume((unsigned int)value);
} }
static int __audio_timeout(gpointer data) gboolean __gui_audio_can_accel(GtkWidget *widget, guint signal_id)
{
GtkAdjustment *progress = data;
gtk_adjustment_set_upper(progress, audio_duration() / GST_SECOND);
gtk_adjustment_set_value(progress, audio_position() / GST_SECOND);
__audio_set_time_label("o_position", audio_position() / GST_SECOND);
return G_SOURCE_CONTINUE;
}
gboolean __audio_can_accel(GtkWidget *widget, guint signal_id)
{ {
g_signal_stop_emission_by_name(widget, "can-activate-accel"); g_signal_stop_emission_by_name(widget, "can-activate-accel");
return !GTK_IS_ENTRY(gtk_window_get_focus(gui_window())) && return !GTK_IS_ENTRY(gtk_window_get_focus(gui_window())) &&
@ -94,31 +171,35 @@ gboolean __audio_can_accel(GtkWidget *widget, guint signal_id)
gtk_widget_is_sensitive(widget); gtk_widget_is_sensitive(widget);
} }
struct audio_ops audio_ops = {
__audio_load,
__audio_change_state,
__audio_config_pause,
};
void gui_audio_init() void gui_audio_init()
{ {
GtkScaleButton *volume = GTK_SCALE_BUTTON(gui_builder_widget("o_volume")); gtk_scale_button_set_value(gui_volume_button(), audio_get_volume());
audio_timeout = g_timeout_add(500, __audio_timeout, gtk_button_set_relief(GTK_BUTTON(gui_volume_button()), GTK_RELIEF_NORMAL);
gui_builder_object("o_progress"));
gtk_scale_button_set_value(volume, audio_get_volume()); audio_timeout = g_timeout_add(500, gui_audio_timeout, NULL);
gtk_button_set_relief(GTK_BUTTON(volume), GTK_RELIEF_NORMAL);
} }
void gui_audio_deinit() void gui_audio_deinit()
{ {
g_source_remove(audio_timeout); g_source_remove(audio_timeout);
if (popover_timeout > 0)
g_source_remove(popover_timeout);
} }
#ifdef CONFIG_TESTING int gui_audio_timeout(gpointer data)
void test_gui_audio_timeout()
{ {
__audio_timeout(gui_builder_object("o_progress")); gchar *position = string_sec2str(audio_position() / GST_SECOND);
gtk_adjustment_set_upper(gui_seek(), audio_duration() / GST_SECOND);
gtk_adjustment_set_value(gui_seek(), audio_position() / GST_SECOND);
__gui_audio_set_label_markup(gui_position(), "large", position);
g_free(position);
return G_SOURCE_CONTINUE;
}
int gui_audio_popover_timeout(gpointer data)
{
__gui_audio_pause_popover_popdown(NULL, data);
return G_SOURCE_REMOVE;
} }
#endif /* CONFIG_TESTING */

View File

@ -42,7 +42,7 @@ static gboolean __gui_filter_visible_func(GtkTreeModel *model,
gpointer data) gpointer data)
{ {
unsigned int i, how = gtk_combo_box_get_active(gui_filter_how()); unsigned int i, how = gtk_combo_box_get_active(gui_filter_how());
gchar **search = gui_model_get_playlist()->pl_private; gchar **search = gui_model_get_playlist()->pl_search;
struct track *track; struct track *track;
if (!search) if (!search)
@ -58,15 +58,8 @@ static gboolean __gui_filter_visible_func(GtkTreeModel *model,
void __gui_filter_search_changed(GtkSearchEntry *search, gpointer data) void __gui_filter_search_changed(GtkSearchEntry *search, gpointer data)
{ {
const gchar *text = gtk_entry_get_text(GTK_ENTRY(search)); playlist_set_search(gui_model_get_playlist(),
struct playlist *playlist = gui_model_get_playlist(); gtk_entry_get_text(GTK_ENTRY(search)));
if (!playlist)
return;
gui_filter_clear_search(playlist);
if (strlen(text) > 0)
playlist->pl_private = g_str_tokenize_and_fold(text, NULL, NULL);
gtk_tree_model_filter_refilter(gui_filter_get()); gtk_tree_model_filter_refilter(gui_filter_get());
} }
@ -91,20 +84,9 @@ void gui_filter_deinit()
g_object_unref(G_OBJECT(filter_model)); g_object_unref(G_OBJECT(filter_model));
} }
void gui_filter_clear_search(struct playlist *playlist)
{
gchar **text;
if (playlist && playlist->pl_private) {
text = playlist->pl_private;
g_strfreev(text);
playlist->pl_private = NULL;
}
}
void gui_filter_set_playlist(struct playlist *playlist) void gui_filter_set_playlist(struct playlist *playlist)
{ {
gchar **search = playlist ? (gchar **)playlist->pl_private : NULL; gchar **search = playlist ? (gchar **)playlist->pl_search : NULL;
gchar *text = search ? g_strjoinv(" ", search) : g_strdup(""); gchar *text = search ? g_strjoinv(" ", search) : g_strdup("");
gui_model_set_playlist(playlist); gui_model_set_playlist(playlist);
@ -131,7 +113,7 @@ void gui_filter_path_load_track(GtkTreePath *path)
unsigned int index = gtk_tree_path_get_indices(path)[0]; unsigned int index = gtk_tree_path_get_indices(path)[0];
audio_load(track); audio_load(track);
queue_selected(&gui_model_get_playlist()->pl_queue, index); playlist_current_set(gui_model_get_playlist(), index);
} }
unsigned int gui_filter_path_get_index(GtkTreePath *path) unsigned int gui_filter_path_get_index(GtkTreePath *path)
@ -152,3 +134,9 @@ GtkTreePath *gui_filter_path_from_index(unsigned int index)
gtk_tree_path_free(real); gtk_tree_path_free(real);
return path; return path;
} }
void gui_filter_refilter(struct playlist *playlist)
{
if (!playlist || playlist == gui_model_get_playlist())
gtk_tree_model_filter_refilter(gui_filter_get());
}

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/audio.h> #include <core/audio.h>
#include <core/playlist.h>
#include <core/string.h> #include <core/string.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/model.h> #include <gui/model.h>
@ -29,6 +28,12 @@ static GType gui_model_columns[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_FONT] = G_TYPE_STRING, [GUI_MODEL_FONT] = G_TYPE_STRING,
}; };
const GtkTargetEntry gui_model_drag_targets[] = {
{ GUI_DRAG_DATA, GTK_TARGET_SAME_APP, 0 },
};
const unsigned int gui_model_n_targets = G_N_ELEMENTS(gui_model_drag_targets);
static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model) static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model)
{ {
return GTK_TREE_MODEL_LIST_ONLY; return GTK_TREE_MODEL_LIST_ONLY;
@ -61,23 +66,25 @@ static gboolean __gui_model_get_iter(GtkTreeModel *model, GtkTreeIter *iter,
static GtkTreePath *__gui_model_get_path(GtkTreeModel *model, GtkTreeIter *iter) static GtkTreePath *__gui_model_get_path(GtkTreeModel *model, GtkTreeIter *iter)
{ {
GtkTreePath *path; GtkTreePath *path;
unsigned int pos;
g_return_val_if_fail(iter != NULL, FALSE); g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data2, FALSE); g_return_val_if_fail(iter->user_data, FALSE);
path = gtk_tree_path_new(); path = gtk_tree_path_new();
gtk_tree_path_append_index(path, GPOINTER_TO_UINT(iter->user_data)); pos = playlist_iter_index(cur_playlist, iter->user_data);
gtk_tree_path_append_index(path, pos);
return path; return path;
} }
static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter, static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
gint column, GValue *value) gint column, GValue *value)
{ {
struct track *track = iter->user_data2; struct track *track = playlist_iter_track(iter->user_data);
gchar *str; gchar *str;
g_return_if_fail(iter != NULL); g_return_if_fail(iter != NULL);
g_return_if_fail(iter->user_data2 != NULL); g_return_if_fail(iter->user_data != NULL);
g_return_if_fail(column < GUI_MODEL_N_COLUMNS); g_return_if_fail(column < GUI_MODEL_N_COLUMNS);
g_value_init(value, gui_model_columns[column]); g_value_init(value, gui_model_columns[column]);
@ -124,13 +131,11 @@ static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
static gboolean __gui_model_iter_next(GtkTreeModel *model, GtkTreeIter *iter) static gboolean __gui_model_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
{ {
unsigned int pos; g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
g_return_val_if_fail(iter != NULL, FALSE); iter->user_data = playlist_iter_next(iter->user_data);
g_return_val_if_fail(iter->user_data2, FALSE); return iter->user_data != NULL;
pos = GPOINTER_TO_UINT(iter->user_data) + 1;
return __gui_model_iter_nth_child(model, iter, NULL, pos);
} }
static gboolean __gui_model_iter_children(GtkTreeModel *model, GtkTreeIter *iter, static gboolean __gui_model_iter_children(GtkTreeModel *model, GtkTreeIter *iter,
@ -148,7 +153,7 @@ static gint __gui_model_iter_n_children(GtkTreeModel *model, GtkTreeIter *iter)
{ {
if (iter != NULL || !cur_playlist) if (iter != NULL || !cur_playlist)
return 0; return 0;
return queue_size(&cur_playlist->pl_queue); return playlist_size(cur_playlist);
} }
static gboolean __gui_model_iter_nth_child(GtkTreeModel *model, static gboolean __gui_model_iter_nth_child(GtkTreeModel *model,
@ -156,15 +161,12 @@ static gboolean __gui_model_iter_nth_child(GtkTreeModel *model,
GtkTreeIter *parent, GtkTreeIter *parent,
gint n) gint n)
{ {
struct queue *queue = &cur_playlist->pl_queue; if (parent || !cur_playlist || n >= playlist_size(cur_playlist))
if (parent || !cur_playlist || n >= queue_size(queue))
return FALSE; return FALSE;
iter->stamp = gui_model->gm_stamp; iter->stamp = gui_model->gm_stamp;
iter->user_data = GUINT_TO_POINTER(n); iter->user_data = playlist_iter_get(cur_playlist, n);
iter->user_data2 = queue_at(queue, n); return iter->user_data != NULL;
return TRUE;
} }
static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter, static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter,
@ -173,6 +175,27 @@ static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter,
return FALSE; return FALSE;
} }
static gboolean __gui_model_drag_data_get(GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data)
{
struct gui_model_drag_data *data = g_malloc(sizeof(*data));
data->drag_row = gtk_tree_path_get_indices(path)[0];
data->drag_track = gui_model_path_get_track(path);
gtk_selection_data_set(selection_data, gdk_atom_intern(GUI_DRAG_DATA, false),
8 /* bytes */, (void *)data, sizeof(*data));
g_free(data);
return true;
}
static gboolean __gui_model_drag_data_delete(GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
return true;
}
static void __gui_model_init(GuiModel *model) static void __gui_model_init(GuiModel *model)
{ {
model->gm_stamp = g_random_int(); model->gm_stamp = g_random_int();
@ -207,6 +230,12 @@ static void __gui_tree_model_init(GtkTreeModelIface *iface)
iface->iter_parent = __gui_model_iter_parent; iface->iter_parent = __gui_model_iter_parent;
} }
static void __gui_drag_source_init(GtkTreeDragSourceIface *iface)
{
iface->drag_data_get = __gui_model_drag_data_get;
iface->drag_data_delete = __gui_model_drag_data_delete;
}
static const GTypeInfo gui_model_type_info = { static const GTypeInfo gui_model_type_info = {
.class_size = sizeof(GuiModelClass), .class_size = sizeof(GuiModelClass),
.base_init = NULL, .base_init = NULL,
@ -225,6 +254,12 @@ static const GInterfaceInfo gui_tree_model = {
.interface_data = NULL, .interface_data = NULL,
}; };
static const GInterfaceInfo gui_drag_source = {
.interface_init = (GInterfaceInitFunc)__gui_drag_source_init,
.interface_finalize = NULL,
.interface_data = NULL,
};
void gui_model_init(void) void gui_model_init(void)
{ {
@ -233,6 +268,9 @@ void gui_model_init(void)
(GTypeFlags)0); (GTypeFlags)0);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_MODEL, g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_MODEL,
&gui_tree_model); &gui_tree_model);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_DRAG_SOURCE,
&gui_drag_source);
gui_model = g_object_new(gui_model_type, NULL); gui_model = g_object_new(gui_model_type, NULL);
g_assert(gui_model != NULL); g_assert(gui_model != NULL);
@ -251,12 +289,20 @@ static void __gui_model_set_runtime(void)
gchar *len = NULL; gchar *len = NULL;
if (cur_playlist) if (cur_playlist)
len = string_sec2str_long(cur_playlist->pl_queue.q_length); len = string_sec2str_long(cur_playlist->pl_length);
gtk_label_set_text(gui_model_runtime(), len); gtk_label_set_text(gui_model_runtime(), len);
g_free(len); g_free(len);
} }
static gboolean __gui_model_foreach_changed(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
if (!data || data == gui_model_iter_get_track(iter))
gtk_tree_model_row_changed(model, path, iter);
return FALSE;
}
GuiModel *gui_model_get(void) GuiModel *gui_model_get(void)
{ {
return gui_model; return gui_model;
@ -267,7 +313,7 @@ GType gui_model_get_type()
return gui_model_type; return gui_model_type;
} }
void gui_model_add(struct playlist *playlist, unsigned int row) void gui_model_add(struct playlist *playlist, struct track *track)
{ {
GtkTreePath *path; GtkTreePath *path;
GtkTreeIter iter; GtkTreeIter iter;
@ -275,32 +321,21 @@ void gui_model_add(struct playlist *playlist, unsigned int row)
if (cur_playlist != playlist) if (cur_playlist != playlist)
return; return;
path = gtk_tree_path_new_from_indices(row, -1); path = gtk_tree_path_new_from_indices(0, -1);
__gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path); __gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(gui_model), path, &iter); gtk_tree_model_row_inserted(GTK_TREE_MODEL(gui_model), path, &iter);
__gui_model_set_runtime();
gtk_tree_path_free(path); gtk_tree_path_free(path);
__gui_model_set_runtime();
} }
void gui_model_remove(struct playlist *playlist, unsigned int row) void gui_model_remove(struct playlist *playlist, struct track *track,
{ unsigned int n)
GtkTreePath *path;
if (cur_playlist != playlist)
return;
path = gtk_tree_path_new_from_indices(row, -1);
gtk_tree_model_row_deleted(GTK_TREE_MODEL(gui_model), path);
__gui_model_set_runtime();
gtk_tree_path_free(path);
}
void gui_model_clear(struct playlist *playlist, unsigned int n)
{ {
GtkTreePath *path; GtkTreePath *path;
unsigned int i; unsigned int i;
if (!gui_model || cur_playlist != playlist) if (cur_playlist != playlist)
return; return;
path = gtk_tree_path_new_from_indices(n - 1, -1); path = gtk_tree_path_new_from_indices(n - 1, -1);
@ -308,35 +343,29 @@ void gui_model_clear(struct playlist *playlist, unsigned int n)
gtk_tree_model_row_deleted(GTK_TREE_MODEL(gui_model), path); gtk_tree_model_row_deleted(GTK_TREE_MODEL(gui_model), path);
gtk_tree_path_prev(path); gtk_tree_path_prev(path);
} }
gtk_tree_path_free(path);
__gui_model_set_runtime(); __gui_model_set_runtime();
gtk_tree_path_free(path);
} }
void gui_model_update(struct playlist *playlist, unsigned int row) void gui_model_update(struct playlist *playlist, struct track *track)
{ {
GtkTreePath *path; if (cur_playlist == playlist)
GtkTreeIter iter; gtk_tree_model_foreach(GTK_TREE_MODEL(gui_model),
__gui_model_foreach_changed, track);
if (cur_playlist != playlist)
return;
path = gtk_tree_path_new_from_indices(row, -1);
__gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path);
gtk_tree_model_row_changed(GTK_TREE_MODEL(gui_model), path, &iter);
__gui_model_set_runtime(); __gui_model_set_runtime();
gtk_tree_path_free(path);
} }
void gui_model_set_playlist(struct playlist *playlist) void gui_model_set_playlist(struct playlist *playlist)
{ {
if (cur_playlist) if (cur_playlist)
gui_model_clear(cur_playlist, queue_size(&cur_playlist->pl_queue)); gui_model_remove(cur_playlist, NULL, playlist_size(cur_playlist));
cur_playlist = playlist; cur_playlist = playlist;
__gui_model_set_runtime(); __gui_model_set_runtime();
if (playlist && queue_size(&playlist->pl_queue) > 0) if (playlist && playlist_size(playlist) > 0)
gui_model_add(playlist, 0); gui_model_add(playlist, 0);
} }

View File

@ -2,7 +2,6 @@
* Copyright 2014 (c) Anna Schumaker. * Copyright 2014 (c) Anna Schumaker.
*/ */
#include <core/core.h> #include <core/core.h>
#include <core/audio.h>
#include <gui/audio.h> #include <gui/audio.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/filter.h> #include <gui/filter.h>
@ -11,18 +10,18 @@
#include <gui/playlist.h> #include <gui/playlist.h>
#include <gui/sidebar.h> #include <gui/sidebar.h>
#include <gui/treeview.h> #include <gui/treeview.h>
#include <gui/view.h>
#include <gui/window.h> #include <gui/window.h>
#define OCARINA_FLAGS (G_APPLICATION_HANDLES_COMMAND_LINE) #define OCARINA_FLAGS (G_APPLICATION_HANDLES_COMMAND_LINE)
static const GOptionEntry ocarina_options[] = { static const GOptionEntry ocarina_options[] = {
{ "next", 'n', 0, G_OPTION_ARG_NONE, NULL, "Play next track", NULL }, { "next", 'n', 0, G_OPTION_ARG_NONE, NULL, "Play next track", NULL },
{ "pause", 'P', 0, G_OPTION_ARG_NONE, NULL, "Pause playback", NULL }, { "pause", 'P', 0, G_OPTION_ARG_NONE, NULL, "Pause playback", NULL },
{ "play", 'p', 0, G_OPTION_ARG_NONE, NULL, "Start playback", NULL }, { "play", 'p', 0, G_OPTION_ARG_NONE, NULL, "Start playback", NULL },
{ "previous", 'N', 0, G_OPTION_ARG_NONE, NULL, "Play previous track", NULL }, { "previous", 'N', 0, G_OPTION_ARG_NONE, NULL, "Play previous track", NULL },
{ "toggle", 't', 0, G_OPTION_ARG_NONE, NULL, "Toggle playback state", NULL }, { "sync", 's', 0, G_OPTION_ARG_NONE, NULL, "Don't run background tasks", NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, NULL, "Print version and exit", NULL }, { "toggle", 't', 0, G_OPTION_ARG_NONE, NULL, "Toggle playback state", NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, NULL, "Print version and exit", NULL },
{ NULL }, { NULL },
}; };
@ -32,11 +31,7 @@ const static gchar *OCARINA_APP = "org.gtk.ocarina";
const static gchar *OCARINA_APP = "org.gtk.ocarina-debug"; const static gchar *OCARINA_APP = "org.gtk.ocarina-debug";
#endif #endif
struct core_init_data init_data = { static enum idle_sync_t idle_sync = IDLE_ASYNC;
&playlist_ops,
&audio_ops,
};
static int startup_argc; static int startup_argc;
static char **startup_argv; static char **startup_argv;
@ -59,6 +54,8 @@ static void __ocarina_activate(GApplication *application, gpointer data)
static int __ocarina_local_options(GApplication *application, static int __ocarina_local_options(GApplication *application,
GVariantDict *options, gpointer data) GVariantDict *options, gpointer data)
{ {
if (g_variant_dict_contains(options, "sync"))
idle_sync = IDLE_SYNC;
if (!g_variant_dict_contains(options, "version")) if (!g_variant_dict_contains(options, "version"))
return -1; return -1;
g_printf("Ocarina %s\n", get_version()); g_printf("Ocarina %s\n", get_version());
@ -73,9 +70,16 @@ static int __ocarina_command_line(GApplication *application,
gpointer data) gpointer data)
{ {
GVariantDict *options; GVariantDict *options;
gchar **args;
g_application_activate(application); g_application_activate(application);
args = g_application_command_line_get_arguments(command, NULL);
if (args && args[1]) {
audio_load_filepath(args[1]);
g_strfreev(args);
}
options = g_application_command_line_get_options_dict(command); options = g_application_command_line_get_options_dict(command);
if (g_variant_dict_contains(options, "next")) if (g_variant_dict_contains(options, "next"))
audio_next(); audio_next();
@ -100,13 +104,12 @@ static void __ocarina_startup(GApplication *application, gpointer data)
gchar *icon = find_file_path("ocarina.png"); gchar *icon = find_file_path("ocarina.png");
gui_builder_init(ui); gui_builder_init(ui);
core_init(&startup_argc, &startup_argv, &init_data); core_init(&startup_argc, &startup_argv, &playlist_cb, &audio_cb, idle_sync);
gui_window_init(icon); gui_window_init(icon);
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();
gui_treeview_init(); gui_treeview_init();
gui_sidebar_init(); gui_sidebar_init();
gui_view_init();
gui_playlist_init(); gui_playlist_init();
gui_audio_init(); gui_audio_init();

View File

@ -6,6 +6,7 @@
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
#include <gui/playlist.h> #include <gui/playlist.h>
#include <gui/treeview.h>
#include <gui/sidebar.h> #include <gui/sidebar.h>
static void (*update_size[PL_MAX_TYPE])(struct playlist *) = { static void (*update_size[PL_MAX_TYPE])(struct playlist *) = {
@ -15,89 +16,235 @@ static void (*update_size[PL_MAX_TYPE])(struct playlist *) = {
[PL_USER] = gui_pl_user_update, [PL_USER] = gui_pl_user_update,
}; };
static void (*select_playlist[PL_MAX_TYPE])(struct playlist *) = {
[PL_SYSTEM] = gui_pl_system_select,
[PL_ARTIST] = gui_pl_artist_select,
[PL_LIBRARY] = gui_pl_library_select,
[PL_USER] = gui_pl_user_select,
};
static inline void __gui_playlist_update_size(struct playlist *playlist) static inline void __gui_playlist_update_size(struct playlist *playlist)
{ {
update_size[playlist->pl_type](playlist); update_size[playlist->pl_type](playlist);
} }
void __playlist_row_activated(GtkTreeView *treeview, GtkTreePath *path, static void __gui_playlist_alloc(struct playlist *playlist)
GtkTreeViewColumn *col, gpointer data)
{ {
gui_sidebar_filter_path_select(path);
__gui_playlist_update_size(playlist_cur());
}
static void *__playlist_init(struct queue *queue, void *data)
{
struct playlist *playlist = (struct playlist *)data;
if (playlist->pl_type == PL_ARTIST) if (playlist->pl_type == PL_ARTIST)
gui_pl_artist_add(playlist); gui_pl_artist_add(playlist);
return playlist;
} }
static void __playlist_deinit(struct queue *queue) static void __gui_playlist_added(struct playlist *playlist, struct track *track)
{ {
gui_filter_clear_search(queue->q_private); gui_model_add(playlist, track);
gui_filter_refilter(playlist);
__gui_playlist_update_size(playlist);
} }
static void __playlist_added(struct queue *queue, unsigned int row) static void __gui_playlist_removed(struct playlist *playlist, struct track *track,
unsigned int n)
{ {
gui_model_add(queue->q_private, row); gui_model_remove(playlist, track, n);
__gui_playlist_update_size(queue->q_private); __gui_playlist_update_size(playlist);
} }
static void __playlist_removed(struct queue *queue, unsigned int row)
struct playlist_callbacks playlist_cb = {
.pl_cb_alloc = __gui_playlist_alloc,
.pl_cb_added = __gui_playlist_added,
.pl_cb_removed = __gui_playlist_removed,
.pl_cb_updated = gui_model_update,
};
static void __gui_playlist_add_selected_to(struct playlist *playlist)
{ {
gui_model_remove(queue->q_private, row); GList *cur, *list = NULL;
__gui_playlist_update_size(queue->q_private); struct track *track;
if (!playlist)
return;
list = gui_treeview_list_selected_tracks();
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
playlist_add(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
} }
static void __playlist_cleared(struct queue *queue, unsigned int n) void __gui_playlist_add_favorites(GtkMenuItem *item, gpointer data)
{ {
gui_model_clear(queue->q_private, n); __gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Favorites"));
__gui_playlist_update_size(queue->q_private);
} }
static void __playlist_updated(struct queue *queue, unsigned int n) void __gui_playlist_add_hidden(GtkMenuItem *item, gpointer data)
{ {
gui_model_update(queue->q_private, n); __gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Hidden"));
} }
static bool __playlist_erase(struct queue *queue, struct track *track) void __gui_playlist_add_user(GtkMenuItem *item, gpointer data)
{ {
struct playlist *playlist = queue->q_private; __gui_playlist_add_selected_to(gui_pl_user_add_dialog());
enum playlist_type_t type = playlist->pl_type; }
const gchar *name = playlist->pl_name;
switch (type) { void __gui_playlist_add_other(GtkMenuItem *item, gpointer data)
case PL_SYSTEM: {
if (string_match(name, "Collection")) __gui_playlist_add_selected_to(data);
name = "Hidden"; }
else if (!string_match(name, "Favorites") &&
!string_match(name, "Hidden") && void __gui_playlist_add_queued(GtkMenuItem *item, gpointer data)
!string_match(name, "Queued Tracks")) {
break; __gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Queued Tracks"));
case PL_USER: }
playlist_remove(type, name, track);
void __gui_playlist_delete(GtkMenuItem *item, gpointer data)
{
struct playlist *playlist = gui_model_get_playlist();
GList *cur, *list = NULL;
struct track *track;
if (!playlist)
return;
list = gui_treeview_list_selected_tracks();
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
playlist_remove(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
}
void __gui_playlist_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data)
{
switch (event->keyval) {
case GDK_KEY_f:
__gui_playlist_add_favorites(NULL, NULL);
break;
case GDK_KEY_h:
__gui_playlist_add_hidden(NULL, NULL);
break;
case GDK_KEY_p:
__gui_playlist_add_user(NULL, NULL);
break;
case GDK_KEY_q:
__gui_playlist_add_queued(NULL, NULL);
break;
case GDK_KEY_Delete:
__gui_playlist_delete(NULL, NULL);
default: default:
break; break;
}; }
}
return false; static GtkWidget *__gui_playlist_build_submenu(void)
{
struct playlist *playlist;
GList *list = gui_pl_user_list();
GList *cur = g_list_first(list);
GtkWidget *submenu, *item;
if (!list)
return NULL;
submenu = gtk_menu_new();
while (cur) {
playlist = (struct playlist *)cur->data;
item = gtk_menu_item_new_with_label(playlist->pl_name);
gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
g_signal_connect(item, "activate",
G_CALLBACK(__gui_playlist_add_other),
playlist);
cur = g_list_next(cur);
}
gtk_widget_show_all(submenu);
g_list_free(list);
return submenu;
}
bool __gui_playlist_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkWidget *submenu;
if (event->button != GDK_BUTTON_SECONDARY)
return false;
submenu = __gui_playlist_build_submenu();
gtk_menu_item_set_submenu(gui_rc_add_to_other(), submenu);
gtk_widget_set_visible(GTK_WIDGET(gui_rc_add_to_other()),
submenu != NULL);
gui_treeview_select_path_at_pos(event->x, event->y);
gtk_menu_popup_at_pointer(gui_rc_menu(), (GdkEvent *)event);
return true;
}
void __gui_playlist_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
struct playlist *prev = playlist_current();
gui_sidebar_filter_path_select(path);
__gui_playlist_update_size(prev);
__gui_playlist_update_size(playlist_current());
}
void __gui_playlist_row_collapsed(GtkTreeView *treeview, GtkTreeIter *iter,
GtkTreePath *path, gpointer data)
{
gui_sidebar_filter_row_expanded(iter, false);
}
void __gui_playlist_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter,
GtkTreePath *path, gpointer data)
{
gui_sidebar_filter_row_expanded(iter, true);
}
void __gui_playlist_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (gui_sidebar_iter_from_xy(x, y, &iter))
playlist = gui_sidebar_iter_playlist(&iter);
if (!playlist)
playlist = gui_pl_user_add_dialog();
if (!playlist)
goto out;
if (playlist == playlist_lookup(PL_SYSTEM, "Collection") &&
gui_model_get_playlist() == playlist_lookup(PL_SYSTEM, "Hidden"))
__gui_playlist_delete(NULL, NULL);
else if (playlist != playlist_lookup(PL_SYSTEM, "History"))
__gui_playlist_add_selected_to(playlist);
out:
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
} }
bool __gui_playlist_init_idle() bool __gui_playlist_init_idle()
{ {
GtkTreeSelection *selection; struct playlist *playlist = playlist_current();
GtkTreeModel *filter; GtkTreeModel *filter = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter; GtkTreeIter iter;
filter = GTK_TREE_MODEL(gui_sidebar_filter());
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
gtk_tree_model_get_iter_first(filter, &iter); gtk_tree_model_get_iter_first(filter, &iter);
gtk_tree_selection_select_iter(selection, &iter); do {
gui_sidebar_filter_set_expand(&iter);
} while (gtk_tree_model_iter_next(filter, &iter));
select_playlist[playlist->pl_type](playlist);
return true; return true;
} }
@ -110,20 +257,3 @@ void gui_playlist_init()
idle_schedule(IDLE_SYNC, __gui_playlist_init_idle, NULL); idle_schedule(IDLE_SYNC, __gui_playlist_init_idle, NULL);
} }
const gchar *gui_playlist_cur()
{
if (gui_model_get_playlist())
return gui_model_get_playlist()->pl_name;
return NULL;
}
struct queue_ops playlist_ops = {
.qop_init = __playlist_init,
.qop_deinit = __playlist_deinit,
.qop_added = __playlist_added,
.qop_erase = __playlist_erase,
.qop_removed = __playlist_removed,
.qop_cleared = __playlist_cleared,
.qop_updated = __playlist_updated,
};

View File

@ -56,3 +56,15 @@ void gui_pl_artist_update(struct playlist *playlist)
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type)) if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&child); gui_sidebar_iter_update(&child);
} }
void gui_pl_artist_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_artist_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}

View File

@ -77,20 +77,20 @@ void gui_pl_library_init()
idle_schedule(IDLE_SYNC, __gui_pl_library_init_idle, NULL); idle_schedule(IDLE_SYNC, __gui_pl_library_init_idle, NULL);
} }
bool gui_pl_library_add(const gchar *filename) struct playlist *gui_pl_library_add(const gchar *filename)
{ {
struct playlist *playlist; struct playlist *playlist;
GtkTreeIter iter; GtkTreeIter iter;
if (!__gui_pl_library_header(&iter)) if (!__gui_pl_library_header(&iter))
return false; return false;
if (!playlist_new(PL_LIBRARY, filename))
return false;
playlist = playlist_get(PL_LIBRARY, filename); playlist = playlist_new(PL_LIBRARY, filename);
gui_sidebar_iter_sort_child(&iter, playlist, "folder"); if (playlist) {
gui_idle_enable(); gui_sidebar_iter_sort_child(&iter, playlist, "folder");
return true; gui_idle_enable();
}
return playlist;
} }
void gui_pl_library_update(struct playlist *playlist) void gui_pl_library_update(struct playlist *playlist)
@ -104,3 +104,14 @@ void gui_pl_library_update(struct playlist *playlist)
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type)) if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&child); gui_sidebar_iter_update(&child);
} }
void gui_pl_library_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_library_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}

View File

@ -8,6 +8,9 @@
#include <gui/playlists/system.h> #include <gui/playlists/system.h>
#include <gui/sidebar.h> #include <gui/sidebar.h>
static struct playlist *favorites;
static struct playlist *hidden;
static bool __gui_pl_system_is_playlist(struct playlist *playlist) static bool __gui_pl_system_is_playlist(struct playlist *playlist)
{ {
return string_match(playlist->pl_name, "Favorites") || return string_match(playlist->pl_name, "Favorites") ||
@ -36,18 +39,18 @@ static bool __gui_pl_system_find_descend_header(GtkTreeIter *iter,
void __gui_pl_system_favorite_toggled(GtkToggleButton *toggle, gpointer data) void __gui_pl_system_favorite_toggled(GtkToggleButton *toggle, gpointer data)
{ {
if (gtk_toggle_button_get_active(toggle)) if (gtk_toggle_button_get_active(toggle))
playlist_add(PL_SYSTEM, "Favorites", audio_cur_track()); playlist_add(favorites, audio_cur_track());
else else
playlist_remove(PL_SYSTEM, "Favorites", audio_cur_track()); playlist_remove(favorites, audio_cur_track());
} }
void __gui_pl_system_hide_toggled(GtkToggleButton *toggle, gpointer data) void __gui_pl_system_hide_toggled(GtkToggleButton *toggle, gpointer data)
{ {
if (gtk_toggle_button_get_active(toggle)) { if (gtk_toggle_button_get_active(toggle)) {
if (playlist_add(PL_SYSTEM, "Hidden", audio_cur_track())) if (playlist_add(hidden, audio_cur_track()))
audio_next(); audio_next();
} else } else
playlist_remove(PL_SYSTEM, "Hidden", audio_cur_track()); playlist_remove(hidden, audio_cur_track());
} }
static bool __gui_pl_system_init_idle() static bool __gui_pl_system_init_idle()
@ -56,27 +59,27 @@ static bool __gui_pl_system_init_idle()
/* Add toplevel playlists. */ /* Add toplevel playlists. */
gui_sidebar_iter_first(&iter); gui_sidebar_iter_first(&iter);
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "Queued Tracks"), gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Queued Tracks"),
"audio-x-generic"); "audio-x-generic");
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "Collection"), gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Collection"),
"media-optical"); "media-optical");
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "History"), gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "History"),
"document-open-recent"); "document-open-recent");
/* Add user-modifiable playlists. */ /* Add user-modifiable playlists. */
gui_sidebar_iter_find(&iter, "Playlists", PL_MAX_TYPE); gui_sidebar_iter_find(&iter, "Playlists", PL_MAX_TYPE);
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Favorites"), gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Favorites"),
"emblem-favorite"); "emblem-favorite");
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Hidden"), gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Hidden"),
"window-close"); "window-close");
/* Add dynamic playlists. */ /* Add dynamic playlists. */
gui_sidebar_iter_find(&iter, "Dynamic", PL_MAX_TYPE); gui_sidebar_iter_find(&iter, "Dynamic", PL_MAX_TYPE);
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Most Played"), gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Most Played"),
"go-up"); "go-up");
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Least Played"), gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Least Played"),
"go-down"); "go-down");
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Unplayed"), gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Unplayed"),
"audio-x-generic"); "audio-x-generic");
return true; return true;
@ -84,6 +87,8 @@ static bool __gui_pl_system_init_idle()
void gui_pl_system_init() void gui_pl_system_init()
{ {
favorites = playlist_lookup(PL_SYSTEM, "Favorites");
hidden = playlist_lookup(PL_SYSTEM, "Hidden");
idle_schedule(IDLE_SYNC, __gui_pl_system_init_idle, NULL); idle_schedule(IDLE_SYNC, __gui_pl_system_init_idle, NULL);
} }
@ -106,10 +111,29 @@ void gui_pl_system_update(struct playlist *playlist)
gui_sidebar_iter_update(&iter); gui_sidebar_iter_update(&iter);
} }
void gui_pl_system_select(struct playlist *playlist)
{
GtkTreeIter iter;
if (__gui_pl_system_is_playlist(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Playlists"))
return;
} else if (__gui_pl_system_is_dynamic(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Dynamic"))
return;
} else {
if (!gui_sidebar_iter_first(&iter))
return;
}
if (gui_sidebar_iter_find(&iter, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&iter);
}
void gui_pl_system_track_loaded(struct track *track) void gui_pl_system_track_loaded(struct track *track)
{ {
gtk_toggle_button_set_active(gui_favorite_button(), gtk_toggle_button_set_active(gui_favorite_button(),
playlist_has(PL_SYSTEM, "Favorites", track)); playlist_has(favorites, track));
gtk_toggle_button_set_active(gui_hide_button(), gtk_toggle_button_set_active(gui_hide_button(),
playlist_has(PL_SYSTEM, "Hidden", track)); playlist_has(hidden, track));
} }

View File

@ -38,6 +38,36 @@ static bool __gui_pl_user_init_idle()
return true; return true;
} }
void __gui_pl_user_editing_started(GtkCellRenderer *renderer,
GtkCellEditable *editable,
gchar *path, gpointer data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!gui_sidebar_iter_from_string(path, &iter))
return;
playlist = gui_sidebar_iter_playlist(&iter);
if (GTK_IS_ENTRY(editable))
gtk_entry_set_text(GTK_ENTRY(editable), playlist->pl_name);
}
void __gui_pl_user_edited(GtkCellRendererText *renderer, gchar *path,
gchar *new_name, gpointer data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!gui_sidebar_iter_from_string(path, &iter))
return;
playlist = gui_sidebar_iter_playlist(&iter);
pl_user_rename(playlist, new_name);
gui_sidebar_iter_update_playlist(&iter, playlist);
gui_sidebar_iter_set_editable(&iter, false);
}
void gui_pl_user_init() void gui_pl_user_init()
{ {
idle_schedule(IDLE_SYNC, __gui_pl_user_init_idle, NULL); idle_schedule(IDLE_SYNC, __gui_pl_user_init_idle, NULL);
@ -50,11 +80,10 @@ struct playlist *gui_pl_user_add(const gchar *name)
if (!__gui_pl_user_header(&iter)) if (!__gui_pl_user_header(&iter))
return NULL; return NULL;
if (!playlist_new(PL_USER, name))
return NULL;
playlist = playlist_get(PL_USER, name); playlist = playlist_new(PL_USER, name);
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic"); if (playlist)
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic");
return playlist; return playlist;
} }
@ -96,6 +125,18 @@ void gui_pl_user_update(struct playlist *playlist)
gui_sidebar_iter_update(&child); gui_sidebar_iter_update(&child);
} }
void gui_pl_user_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_user_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}
GList *gui_pl_user_list(void) GList *gui_pl_user_list(void)
{ {
struct db_entry *user, *next; struct db_entry *user, *next;

View File

@ -11,6 +11,7 @@ enum sidebar_columns {
SB_IMAGE, SB_IMAGE,
SB_NAME, SB_NAME,
SB_TYPE, SB_TYPE,
SB_EDITABLE,
}; };
const gchar *SIDEBAR_SETTING = "gui.sidebar.pos"; const gchar *SIDEBAR_SETTING = "gui.sidebar.pos";
@ -23,8 +24,8 @@ static gchar *__gui_sidebar_size_str(struct playlist *playlist)
if (!playlist) if (!playlist)
return NULL; return NULL;
size = playlist_size(playlist->pl_type, playlist->pl_name); size = playlist_size(playlist);
if (playlist_cur() == playlist) if (playlist_current() == playlist)
fmt = "<b>%s\n%d track%s</b>"; fmt = "<b>%s\n%d track%s</b>";
return g_markup_printf_escaped(fmt, playlist->pl_name, size, return g_markup_printf_escaped(fmt, playlist->pl_name, size,
(size != 1) ? "s" : ""); (size != 1) ? "s" : "");
@ -33,9 +34,10 @@ static gchar *__gui_sidebar_size_str(struct playlist *playlist)
static void __gui_sidebar_set(GtkTreeIter *iter, const gchar *name, static void __gui_sidebar_set(GtkTreeIter *iter, const gchar *name,
const gchar *image, enum playlist_type_t type) const gchar *image, enum playlist_type_t type)
{ {
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, name, gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, name,
SB_IMAGE, image, SB_IMAGE, image,
SB_TYPE, type, -1); SB_TYPE, type,
SB_EDITABLE, false, -1);
} }
static void __gui_sidebar_set_playlist(GtkTreeIter *iter, static void __gui_sidebar_set_playlist(GtkTreeIter *iter,
@ -83,23 +85,23 @@ static inline void __gui_sidebar_filter_iter_convert(GtkTreeIter *iter,
child, iter); child, iter);
} }
static gchar *__gui_sidebar_filter_iter_name(GtkTreeIter *iter)
{
GtkTreeIter child;
__gui_sidebar_filter_iter_convert(iter, &child);
return gui_sidebar_iter_name(&child);
}
static gboolean __gui_sidebar_visible_func(GtkTreeModel *model, static gboolean __gui_sidebar_visible_func(GtkTreeModel *model,
GtkTreeIter *iter, GtkTreeIter *iter,
gpointer data) gpointer data)
{ {
enum playlist_type_t type = gui_sidebar_iter_type(iter); enum playlist_type_t type = gui_sidebar_iter_type(iter);
gboolean ret = TRUE;
gchar *name;
if (type == PL_SYSTEM || type == PL_ARTIST) { if (type == PL_SYSTEM || type == PL_ARTIST)
name = gui_sidebar_iter_name(iter); return playlist_size(gui_sidebar_iter_playlist(iter)) > 0;
if (name) { return TRUE;
ret = playlist_size(type, name) > 0;
g_free(name);
}
}
return ret;
} }
static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection, static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection,
@ -115,49 +117,123 @@ static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection,
void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data) void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
{ {
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
bool active = false, sensitive = false; bool active = false, sensitive = false;
struct playlist *playlist = NULL; struct playlist *playlist = NULL;
enum playlist_type_t type; GtkTreeIter iter;
GtkTreeIter iter, child;
gchar *name;
if (gtk_tree_selection_get_selected(selection, &model, &iter)) { if (gui_sidebar_iter_current(&iter)) {
__gui_sidebar_filter_iter_convert(&iter, &child); playlist = gui_sidebar_iter_playlist(&iter);
active = playlist->pl_random;
name = gui_sidebar_iter_name(&child); sensitive = (playlist->pl_ops->pl_set_random != NULL);
type = gui_sidebar_iter_type(&child);
playlist = playlist_get(type, name);
active = playlist_get_random(type, name);
sensitive = (type != PL_SYSTEM) ||
!string_match(name, "History");
g_free(name);
} }
gui_treeview_set_playlist(playlist);
gtk_toggle_button_set_active(gui_random_button(), active); gtk_toggle_button_set_active(gui_random_button(), active);
gtk_widget_set_sensitive(GTK_WIDGET(gui_random_button()), sensitive); gtk_widget_set_sensitive(GTK_WIDGET(gui_random_button()), sensitive);
gui_treeview_set_playlist(playlist); }
static void __gui_sidebar_do_rename(GtkTreePath *path)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreeModel *model = gtk_tree_view_get_model(treeview);
GtkTreeViewColumn *column = gtk_tree_view_get_column(treeview, SB_NAME);
GtkTreeIter iter, child;
if (!gtk_tree_model_get_iter(model, &iter, path))
return;
__gui_sidebar_filter_iter_convert(&iter, &child);
gui_sidebar_iter_set_editable(&child, true);
gtk_tree_view_set_cursor(treeview, path, column, true);
}
static GtkTreePath *__gui_sidebar_current_path(void)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkTreeModel *model = gtk_tree_view_get_model(treeview);
GtkTreeIter iter;
if (!gtk_tree_selection_get_selected(selection, &model, &iter))
return NULL;
return gtk_tree_model_get_path(model, &iter);
}
bool __gui_sidebar_rename(GtkMenuItem *item, gpointer data)
{
GtkTreePath *path = __gui_sidebar_current_path();
if (path) {
__gui_sidebar_do_rename(path);
gtk_tree_path_free(path);
}
return path != NULL;
}
bool __gui_sidebar_select(GtkMenuItem *item, gpointer data)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreePath *path = __gui_sidebar_current_path();
if (path) {
gtk_tree_view_row_activated(treeview, path, NULL);
gtk_tree_path_free(path);
}
return path != NULL;
}
bool __gui_sidebar_delete(GtkMenuItem *item, gpointer data)
{
GtkTreeIter iter;
if (!gui_sidebar_iter_current(&iter))
return false;
if (playlist_delete(gui_model_get_playlist()))
gtk_tree_store_remove(gui_sidebar_store(), &iter);
return true;
} }
bool __gui_sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event, bool __gui_sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data) gpointer data)
{ {
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); switch (event->keyval) {
struct playlist *playlist = gui_model_get_playlist(); case GDK_KEY_BackSpace:
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter()); return __gui_sidebar_rename(NULL, NULL);
GtkTreeIter iter, child; case GDK_KEY_Return:
return __gui_sidebar_select(NULL, NULL);
if (!playlist || event->keyval != GDK_KEY_Delete) case GDK_KEY_Delete:
return __gui_sidebar_delete(NULL, NULL);
default:
return false; return false;
if (!gtk_tree_selection_get_selected(selection, &model, &iter)) }
}
bool __gui_sidebar_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
enum playlist_type_t type = PL_MAX_TYPE;
GtkTreePath *path;
GtkTreeIter iter;
bool ret = true;
if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
return false; return false;
__gui_sidebar_filter_iter_convert(&iter, &child); if (event->button == GDK_BUTTON_SECONDARY) {
if (playlist_delete(playlist->pl_type, playlist->pl_name)) gtk_tree_view_set_cursor(treeview, path, NULL, false);
gtk_tree_store_remove(gui_sidebar_store(), &child); if (gui_sidebar_iter_current(&iter))
return true; type = gui_sidebar_iter_type(&iter);
gtk_widget_set_visible(gui_builder_widget("rc_sidebar_rename"),
type == PL_USER);
gtk_menu_popup_at_pointer(gui_sidebar_menu(), (GdkEvent *)event);
} else if (event->type == GDK_2BUTTON_PRESS &&
event->button == GDK_BUTTON_MIDDLE) {
__gui_sidebar_do_rename(path);
} else
ret = false;
gtk_tree_path_free(path);
return ret;
} }
void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data) void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data)
@ -170,10 +246,8 @@ void __gui_sidebar_random_toggled(GtkToggleButton *button, gpointer data)
struct playlist *playlist = gui_model_get_playlist(); struct playlist *playlist = gui_model_get_playlist();
bool active = gtk_toggle_button_get_active(button); bool active = gtk_toggle_button_get_active(button);
if (playlist) { if (playlist)
playlist_set_random(playlist->pl_type, playlist_set_random(playlist, active);
playlist->pl_name, active);
}
} }
void gui_sidebar_init() void gui_sidebar_init()
@ -182,6 +256,10 @@ void gui_sidebar_init()
GtkTreeSelection *selection; GtkTreeSelection *selection;
GtkTreeIter iter; GtkTreeIter iter;
gtk_tree_view_enable_model_drag_dest(gui_sidebar_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
if (!gui_sidebar_iter_first(&iter)) { if (!gui_sidebar_iter_first(&iter)) {
__gui_sidebar_add_header(&iter, "Playlists", "emblem-documents"); __gui_sidebar_add_header(&iter, "Playlists", "emblem-documents");
__gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic"); __gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic");
@ -200,6 +278,19 @@ void gui_sidebar_init()
gtk_paned_set_position(gui_sidebar(), pos); gtk_paned_set_position(gui_sidebar(), pos);
} }
gboolean gui_sidebar_iter_current(GtkTreeIter *iter)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter it;
if (!gtk_tree_selection_get_selected(selection, &model, &it))
return false;
__gui_sidebar_filter_iter_convert(&it, iter);
return true;
}
gboolean gui_sidebar_iter_first(GtkTreeIter *iter) gboolean gui_sidebar_iter_first(GtkTreeIter *iter)
{ {
return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter); return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter);
@ -243,6 +334,23 @@ enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *iter)
return type; return type;
} }
bool gui_sidebar_iter_editable(GtkTreeIter *iter)
{
gboolean editable;
gtk_tree_model_get(gui_sidebar_model(), iter, SB_EDITABLE, &editable, -1);
return editable == TRUE;
}
struct playlist *gui_sidebar_iter_playlist(GtkTreeIter *iter)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
gchar *name = gui_sidebar_iter_name(iter);
struct playlist *playlist = playlist_lookup(type, name);
g_free(name);
return playlist;
}
void gui_sidebar_iter_add(GtkTreeIter *iter, struct playlist *playlist, void gui_sidebar_iter_add(GtkTreeIter *iter, struct playlist *playlist,
const gchar *image) const gchar *image)
{ {
@ -281,39 +389,83 @@ void gui_sidebar_iter_append_child(GtkTreeIter *iter, struct playlist *playlist,
__gui_sidebar_set_playlist(&new, playlist, image); __gui_sidebar_set_playlist(&new, playlist, image);
} }
void gui_sidebar_iter_update(GtkTreeIter *iter) void gui_sidebar_iter_update_playlist(GtkTreeIter *iter,
struct playlist *playlist)
{ {
enum playlist_type_t type = gui_sidebar_iter_type(iter); gchar *text;
gchar *name, *text;
if (type >= PL_MAX_TYPE) if (!playlist)
return; return;
name = gui_sidebar_iter_name(iter); text = __gui_sidebar_size_str(playlist);
text = __gui_sidebar_size_str(playlist_get(type, name));
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, text, -1); gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, text, -1);
g_free(name);
g_free(text); g_free(text);
} }
void gui_sidebar_iter_update(GtkTreeIter *iter)
{
gui_sidebar_iter_update_playlist(iter, gui_sidebar_iter_playlist(iter));
}
void gui_sidebar_iter_select(GtkTreeIter *iter)
{
GtkTreeSelection *selection;
GtkTreeIter filter;
gtk_tree_model_filter_convert_child_iter_to_iter(gui_sidebar_filter(),
&filter, iter);
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
gtk_tree_selection_select_iter(selection, &filter);
}
bool gui_sidebar_iter_set_editable(GtkTreeIter *iter, bool editable)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
if (type != PL_USER)
return false;
gtk_tree_store_set(gui_sidebar_store(), iter, SB_EDITABLE, editable, -1);
return true;
}
void gui_sidebar_filter_path_select(GtkTreePath *path) void gui_sidebar_filter_path_select(GtkTreePath *path)
{ {
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter()); GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
enum playlist_type_t type;
GtkTreeIter iter, child; GtkTreeIter iter, child;
gchar *name;
gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, &child); __gui_sidebar_filter_iter_convert(&iter, &child);
type = gui_sidebar_iter_type(&child); if (playlist_select(gui_sidebar_iter_playlist(&child)))
if (type >= PL_MAX_TYPE)
return;
name = gui_sidebar_iter_name(&child);
if (playlist_select(type, name))
gui_sidebar_iter_update(&child); gui_sidebar_iter_update(&child);
}
void gui_sidebar_filter_set_expand(GtkTreeIter *iter)
{
gchar *name = __gui_sidebar_filter_iter_name(iter);
gchar *setting = g_strdup_printf("gui.sidebar.expand.%s", name);
GtkTreePath *path;
if (settings_get(setting) == true) {
path = gtk_tree_model_get_path(
GTK_TREE_MODEL(gui_sidebar_filter()), iter);
gtk_tree_view_expand_row(gui_sidebar_treeview(), path, false);
gtk_tree_path_free(path);
}
g_free(setting);
g_free(name);
}
void gui_sidebar_filter_row_expanded(GtkTreeIter *iter, bool expanded)
{
gchar *name = __gui_sidebar_filter_iter_name(iter);
gchar *setting = g_strdup_printf("gui.sidebar.expand.%s", name);
settings_set(setting, expanded);
g_free(setting);
g_free(name); g_free(name);
} }
@ -327,3 +479,30 @@ gboolean gui_sidebar_iter_find(GtkTreeIter *iter, const gchar *name,
return FALSE; return FALSE;
} }
gboolean gui_sidebar_iter_from_string(const gchar *path, GtkTreeIter *child)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
return FALSE;
__gui_sidebar_filter_iter_convert(&iter, child);
return TRUE;
}
gboolean gui_sidebar_iter_from_xy(gint x, gint y, GtkTreeIter *child)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreePath *path;
GtkTreeIter iter;
if (!gtk_tree_view_get_path_at_pos(gui_sidebar_treeview(), x, y,
&path, NULL, NULL, NULL))
return false;
gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, child);
gtk_tree_path_free(path);
return true;
}

View File

@ -31,7 +31,7 @@ static bool can_scroll = true;
static int __gui_treeview_colum_match_sort(enum compare_t compare) static int __gui_treeview_colum_match_sort(enum compare_t compare)
{ {
struct playlist *playlist = gui_model_get_playlist(); struct playlist *playlist = gui_model_get_playlist();
GSList *cur = playlist ? playlist->pl_queue.q_sort : NULL; GSList *cur = playlist ? playlist->pl_sort : NULL;
while (cur) { while (cur) {
int field = GPOINTER_TO_INT(cur->data); int field = GPOINTER_TO_INT(cur->data);
@ -126,14 +126,14 @@ static void __gui_treeview_column_clicked(GtkTreeViewColumn *col,
{ {
struct playlist *playlist = gui_model_get_playlist(); struct playlist *playlist = gui_model_get_playlist();
enum compare_t compare = GPOINTER_TO_UINT(data); enum compare_t compare = GPOINTER_TO_UINT(data);
bool reset = (sort_count == 0);
gchar *text; gchar *text;
if (!playlist) if (!playlist)
return; return;
playlist_sort(playlist->pl_type, playlist->pl_name, compare, reset); if (sort_count == 0)
if (!playlist->pl_queue.q_sort) playlist_clear_sort(playlist);
if (!playlist_sort(playlist, compare))
return; return;
__gui_treeview_set_sort_indicators(); __gui_treeview_set_sort_indicators();
@ -160,6 +160,43 @@ void __gui_treeview_row_activated(GtkTreeView *treeview, GtkTreePath *path,
can_scroll = true; can_scroll = true;
} }
void __gui_treeview_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct gui_model_drag_data *drag_data;
unsigned int to, from;
GtkTreePath *path;
drag_data = (void *)gtk_selection_data_get_data(data);
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
gtk_tree_path_prev(path);
else if (!gtk_tree_view_get_visible_range(gui_treeview(), NULL, &path))
return;
from = drag_data->drag_row;
to = gui_filter_path_get_index(path);
if (playlist_rearrange(gui_model_get_playlist(), from, to)) {
gtk_tree_selection_unselect_all(gui_treeview_selection());
gtk_tree_selection_select_path(gui_treeview_selection(), path);
__gui_treeview_set_sort_indicators();
}
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
gtk_tree_path_free(path);
}
bool __gui_treeview_drag_drop(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, guint time, gpointer user_data)
{
gtk_drag_get_data(GTK_WIDGET(treeview), context,
gdk_atom_intern(GUI_DRAG_DATA, false), time);
return true;
}
void gui_treeview_init() void gui_treeview_init()
{ {
GtkTreeViewColumn *col; GtkTreeViewColumn *col;
@ -167,6 +204,12 @@ void gui_treeview_init()
gtk_tree_view_set_model(gui_treeview(), gtk_tree_view_set_model(gui_treeview(),
GTK_TREE_MODEL(gui_filter_get())); GTK_TREE_MODEL(gui_filter_get()));
gtk_tree_view_enable_model_drag_source(gui_treeview(), GDK_BUTTON1_MASK,
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
gtk_tree_view_enable_model_drag_dest(gui_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) { for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i); col = gtk_tree_view_get_column(gui_treeview(), i);
@ -201,11 +244,9 @@ void gui_treeview_set_playlist(struct playlist *playlist)
void gui_treeview_scroll() void gui_treeview_scroll()
{ {
struct playlist *playlist = gui_model_get_playlist(); int pos = playlist_current_index(gui_model_get_playlist());
GtkTreePath *path; GtkTreePath *path;
int pos;
pos = playlist ? playlist->pl_queue.q_cur.it_pos : -1;
if (!can_scroll || pos < 0) if (!can_scroll || pos < 0)
return; return;
@ -217,3 +258,32 @@ void gui_treeview_scroll()
gtk_tree_view_scroll_to_cell(gui_treeview(), path, NULL, true, 0.5, 0.5); gtk_tree_view_scroll_to_cell(gui_treeview(), path, NULL, true, 0.5, 0.5);
gtk_tree_path_free(path); gtk_tree_path_free(path);
} }
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y)
{
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(gui_treeview_selection(), path);
gtk_tree_path_free(path);
}
}
GList *gui_treeview_list_selected_tracks(void)
{
GtkTreeSelection *selection = gui_treeview_selection();
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_first(rows);
GList *list = NULL;
while (cur) {
list = g_list_append(list, gui_filter_path_get_track(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
gtk_tree_selection_unselect_all(selection);
return list;
}

View File

@ -1,213 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/playlist.h>
#include <core/settings.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/playlist.h>
#include <gui/treeview.h>
#include <gui/view.h>
#include <gui/window.h>
#include <glib/gi18n.h>
#include <stdlib.h>
static GtkTreeView *view_treeview = NULL;
struct view_add_data {
enum playlist_type_t vad_type;
const gchar *vad_name;
};
static void __view_add_to_playlist(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
struct view_add_data *vad = (struct view_add_data *)data;
playlist_add(vad->vad_type, vad->vad_name, gui_filter_path_get_track(path));
}
static void __view_delete_selection(GtkTreeSelection *selection)
{
struct playlist *playlist = gui_model_get_playlist();
struct queue *queue = playlist ? &playlist->pl_queue : NULL;
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
queue_erase(queue, gui_filter_path_get_index(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
static void __view_process_selection(GtkTreeView *treeview, unsigned int keyval)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct view_add_data vad_data;
struct playlist *playlist;
switch (keyval) {
case GDK_KEY_f:
vad_data.vad_type = PL_SYSTEM;
vad_data.vad_name = "Favorites";
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
&vad_data);
break;
case GDK_KEY_p:
playlist = gui_pl_user_add_dialog();
if (!playlist)
break;
vad_data.vad_type = PL_USER;
vad_data.vad_name = playlist->pl_name;
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
&vad_data);
break;
case GDK_KEY_q:
vad_data.vad_type = PL_SYSTEM;
vad_data.vad_name = "Queued Tracks";
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
&vad_data);
break;
case GDK_KEY_Delete:
__view_delete_selection(selection);
break;
}
}
void __view_keypress(GtkTreeView *treeview, GdkEventKey *event, gpointer data)
{
__view_process_selection(treeview, event->keyval);
}
void __view_rc_new_queue(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_q);
}
void __view_rc_add_to_queue(GtkMenuItem *item, gpointer data)
{
unsigned int i;
gchar *name;
for (i = 0; i < 10; i++) {
name = g_strdup_printf("o_queue_%d", i);
if (GTK_WIDGET(item) == gui_builder_widget(name))
__view_process_selection(view_treeview, GDK_KEY_0 + i);
g_free(name);
}
}
void __view_rc_add_favorites(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_f);
}
void __view_rc_add_hidden(GtkMenuItem *item, gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(view_treeview);
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
playlist_add(PL_SYSTEM, "Hidden", gui_filter_path_get_track(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
void __view_rc_add_new(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_p);
}
static void __view_rc_add_other(GtkWidget *item, gpointer data)
{
GtkTreeView *treeview = gui_treeview();
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct playlist *playlist = (struct playlist *)data;
struct view_add_data vad_data = {
.vad_type = PL_USER,
.vad_name = playlist->pl_name,
};
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
&vad_data);
}
static GtkWidget *__view_rc_build_submenu(void)
{
struct playlist *playlist;
GtkWidget *submenu, *item;
GList *iter, *list = NULL;
if (pl_user_db_get()->db_size == 0)
return NULL;
submenu = gtk_menu_new();
list = gui_pl_user_list();
iter = g_list_first(list);
while (iter) {
playlist = (struct playlist *)iter->data;
item = gtk_menu_item_new_with_label(playlist->pl_name);
gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
g_signal_connect(item, "activate",
G_CALLBACK(__view_rc_add_other), playlist);
iter = g_list_next(iter);
}
g_list_free(list);
return submenu;
}
bool __view_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkMenu *menu = GTK_MENU(gui_builder_widget("o_menu"));
GtkWidget *submenu = NULL;
GtkMenuItem *other;
GtkTreePath *path;
if (event->button != GDK_BUTTON_SECONDARY)
return false;
/* Select path if it isn't already selected */
if (gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
/* Set the "Other Playlists" submenu. */
other = GTK_MENU_ITEM(gui_builder_widget("o_add_to_other"));
submenu = __view_rc_build_submenu();
gtk_menu_item_set_submenu(other, submenu);
if (!submenu)
gtk_widget_hide(GTK_WIDGET(other));
else
gtk_widget_show_all(GTK_WIDGET(other));
gtk_menu_popup_at_pointer(menu, (GdkEvent *)event);
return true;
}
void gui_view_init()
{
view_treeview = gui_treeview();
}
void gui_view_set_playlist(struct playlist *playlist)
{
gui_treeview_set_playlist(playlist);
}

View File

@ -11,20 +11,20 @@
#include <gst/gst.h> #include <gst/gst.h>
struct audio_ops { struct audio_callbacks {
/* Called when a track is loaded. */ /* Called when a track is loaded. */
void (*on_load)(struct track *); void (*audio_cb_load)(struct track *);
/* Called when playback state changes. */ /* Called when playback state changes. */
void (*on_state_change)(GstState); void (*audio_cb_state_change)(GstState);
/* Called when the automatic pause state changes. */ /* Called when the automatic pause state changes. */
void (*on_config_pause)(int); void (*audio_cb_config_pause)(int);
}; };
/* Called to initialize the audio manager. */ /* Called to initialize the audio manager. */
void audio_init(int *, char ***, struct audio_ops *); void audio_init(int *, char ***, struct audio_callbacks *);
/* Called to deinitialize the audio manager. */ /* Called to deinitialize the audio manager. */
void audio_deinit(); void audio_deinit();
@ -33,8 +33,9 @@ void audio_deinit();
void audio_save(); void audio_save();
/* Called to load a track for playback. */ /* Called to load either a track or file for playback. */
bool audio_load(struct track *); bool audio_load(struct track *);
bool audio_load_filepath(const gchar *);
/* Called to get the current track. */ /* Called to get the current track. */
struct track *audio_cur_track(); struct track *audio_cur_track();
@ -72,16 +73,16 @@ struct track *audio_next();
/* Called to load the previous track. */ /* Called to load the previous track. */
struct track *audio_prev(); struct track *audio_prev();
/* Called when playback has reached the end-of-stream position. */ /*
struct track *audio_eos(); * Called to configure automatic pausing.
* Returns true if the value has been changed.
/* Called when gstreamer has received an error. */ */
void audio_error(GstMessage *); bool audio_pause_after(int);
int audio_get_pause_count(void);
/* Called to configure automatic pausing. */
void audio_pause_after(int);
#ifdef CONFIG_TESTING #ifdef CONFIG_TESTING
GstElement *test_audio_player(); void test_audio_eos();
void test_audio_error(GError *, gchar *);
GstElement *test_audio_pipeline();
#endif /* CONFIG_TESTING */ #endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_AUDIO_H */ #endif /* OCARINA_CORE_AUDIO_H */

View File

@ -3,17 +3,16 @@
*/ */
#ifndef OCARINA_CORE_CORE_H #ifndef OCARINA_CORE_CORE_H
#define OCARINA_CORE_CORE_H #define OCARINA_CORE_CORE_H
#include <core/queue.h> #include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
struct core_init_data { #include <core/settings.h>
struct queue_ops *playlist_ops; #include <core/tags/tags.h>
struct audio_ops *audio_ops; #include <stdbool.h>
};
/* Called to initialize all core Ocarina components. */ /* Called to initialize all core Ocarina components. */
void core_init(int *, char ***, struct core_init_data *); void core_init(int *, char ***, struct playlist_callbacks *,
struct audio_callbacks *, enum idle_sync_t);
/* Called to deinitialize all core Ocarina componentns. */ /* Called to deinitialize all core Ocarina componentns. */
void core_deinit(); void core_deinit();

View File

@ -48,7 +48,7 @@ static inline void *DBE_DATA(struct db_entry *dbe)
struct db_ops { struct db_ops {
/* Allocate a new struct db_entry from a given key. */ /* Allocate a new struct db_entry from a given key. */
struct db_entry *(*dbe_alloc)(const gchar *); struct db_entry *(*dbe_alloc)(const gchar *, unsigned int);
/* Free a struct db_entry. */ /* Free a struct db_entry. */
void (*dbe_free)(struct db_entry *); void (*dbe_free)(struct db_entry *);
@ -57,7 +57,7 @@ struct db_ops {
gchar *(*dbe_key)(struct db_entry *); gchar *(*dbe_key)(struct db_entry *);
/* Read a single struct db_entry from disk. */ /* Read a single struct db_entry from disk. */
struct db_entry *(*dbe_read)(struct file *); struct db_entry *(*dbe_read)(struct file *, unsigned int);
/* Write a single struct db_entry to disk. */ /* Write a single struct db_entry to disk. */
void (*dbe_write)(struct file *, struct db_entry *); void (*dbe_write)(struct file *, struct db_entry *);
@ -78,7 +78,7 @@ struct database {
{ \ { \
.db_size = 0, \ .db_size = 0, \
.db_autosave = autosave, \ .db_autosave = autosave, \
.db_file = FILE_INIT(fname, fmin), \ .db_file = FILE_INIT_DATA("", fname, fmin), \
.db_entries = g_ptr_array_new(), \ .db_entries = g_ptr_array_new(), \
.db_keys = g_hash_table_new(g_str_hash, g_str_equal), \ .db_keys = g_hash_table_new(g_str_hash, g_str_equal), \
.db_ops = ops, \ .db_ops = ops, \

View File

@ -30,105 +30,100 @@
enum open_mode { enum open_mode {
OPEN_READ, CLOSED, /* File is not open. */
OPEN_WRITE, OPEN_READ, /* File is open for reading text. */
OPEN_READ_BINARY, /* File is open for reading binary data. */
OPEN_WRITE, /* File is open for writing text. */
OPEN_WRITE_BINARY, /* File is open for writing binary data. */
}; };
struct file { struct file {
FILE *f_file; /* The file's IO stream. */
const gchar *f_name; /* The file's basename. */
const gchar *f_subdir; /* The file's subdirectory. */
enum open_mode f_mode; /* The file's current open mode. */ enum open_mode f_mode; /* The file's current open mode. */
unsigned int f_version; /* The file's current data version. */ unsigned int f_version; /* The file's current data version. */
unsigned int f_prev; /* The file's on-disk data version. */ unsigned int f_prev; /* The file's on-disk data version. */
unsigned int f_min; /* The file's minimum data version. */ unsigned int f_min; /* The file's minimum data version. */
FILE *f_file; /* The file's IO stream. */
const gchar *f_name; /* The file's basename. */ const gchar *(*f_user_dir)(void); /* The file's user directory. */
}; };
#define FILE_INIT(fname, min) \ #define FILE_INIT_DATA(fdir, fname, min) \
{ \ { \
.f_mode = OPEN_READ, \ .f_file = NULL, \
.f_version = OCARINA_MINOR_VERSION, \ .f_name = fname, \
.f_prev = 0, \ .f_subdir = fdir, \
.f_min = min, \ .f_mode = CLOSED, \
.f_file = NULL, \ .f_version = OCARINA_MINOR_VERSION, \
.f_name = fname, \ .f_prev = 0, \
.f_min = min, \
.f_user_dir = g_get_user_data_dir, \
} }
/* Initialize a file object. */
struct cache_file { void file_init_data(struct file *, const gchar *, const gchar *, unsigned int);
FILE *cf_file; /* The cache file's IO stream. */ void file_init_cache(struct file *, const gchar *, const gchar *);
gchar *cf_name; /* The cache file's basename. */
gchar *cf_subdir; /* The cache file's subdirectory. */
};
#define CACHE_FILE_INIT(cfsubdir, cfname) \
{ \
.cf_file = NULL, \
.cf_subdir = cfsubdir, \
.cf_name = cfname, \
}
/* Initialize a new file object. */
void file_init(struct file *, const gchar *, unsigned int);
void cache_file_init(struct cache_file *, gchar *, gchar *);
/* /*
* Returns the full path of the file or an empty string if filename is not set. * Returns the full path of the file or an empty string if filename is not set.
* These functions allocates a new string that MUST be freed with g_free(). * NOTE: This function allocates a new string that MUST be freed with g_free().
*/ */
gchar *file_path(struct file *); gchar *file_path(struct file *);
gchar *cache_file_path(struct cache_file *);
/* /*
* Returns the path to the temporary file used for writes. * Returns the path to the temporary file used for writes.
* This function allocates a new string that MUST be freed with g_free(). * NOTE: This function allocates a new string that MUST be freed with g_free().
*/ */
gchar *file_write_path(struct file *); gchar *file_write_path(struct file *);
gchar *cache_file_write_path(struct cache_file *);
/* Returns the version number of the file. */ /* Returns the version number of the file. */
const unsigned int file_version(struct file *); const unsigned int file_version(struct file *);
/* Returns true if the file exists on disk and false otherwise. */ /* Returns true if the file exists on disk and false otherwise. */
bool file_exists(struct file *); bool file_exists(struct file *);
bool cache_file_exists(struct cache_file *);
/* /*
* Call to open a file for either reading or writing. Callers * Call to open a file for either reading or writing. Callers
* are expected to call file_close() when IO is completed. * are expected to call file_close() when IO is completed.
* *
* When opening a file for reading (OPEN_READ): * When opening a file for reading (OPEN_READ / OPEN_READ_BINARY):
* - Check if the file exists. * - Check if the file exists
* - Read in file->_prev_version from the start of the file. * - If open for reading text (OPEN_READ):
* - Read in file->_prev_version from the start of the file.
* *
* When opening a file for writing (OPEN_WRITE): * When opening a file for writing (OPEN_WRITE / OPEN_WRITE_BINARY):
* - Create missing directories as needed. * - Create missing directories as needed.
* - Open a temporary file to protect data if Ocarina crashes. * - Open a temporary file to protect data if Ocarina crashes.
* - Write file->_version to the start of the file (data files only). * - If open for writing text (OPEN_WRITE):
* - Write file->_version to the start of the file (data files only).
* *
* Returns true if the open was successful and false otherwise. * Returns true if the open was successful and false otherwise.
* Oening a cache file with OPEN_READ is currently unsupported.
*/ */
bool file_open(struct file *, enum open_mode); bool file_open(struct file *, enum open_mode);
bool cache_file_open(struct cache_file *, enum open_mode);
/* /*
* Closes an open file, setting file->{f|cf}_file to NULL. If the file was opened * Closes an open file, setting file->f_file to NULL and file->f_mode
* with OPEN_WRITE, then rename the temporary file to file_path(). * to CLOSED. If the file was opened for writing, then rename the
* temporary file to file_path().
*/ */
void file_close(struct file *); void file_close(struct file *);
void cache_file_close(struct cache_file *);
/* /*
* Read an entire line from the file and return it to the caller. * Called to read an unsigned int, signed int, single word, or entire
* This function allocates a new string that MUST be freed with g_free(). * line from the file.
* NOTE: file_readw() and file_readl() both return a new string that
* MUST be freed with g_free()
*/ */
gchar *file_readw(struct file *);
gchar *file_readl(struct file *); gchar *file_readl(struct file *);
/* unsigned int file_readu(struct file *);
* Read from a file with an fscanf(3) style format string. static inline int file_readd(struct file *file)
* Returns the number of items matched. { return (int)file_readu(file); }
*/ static inline unsigned short int file_readhu(struct file *file)
int file_readf(struct file *, const char *, ...); { return (unsigned short int)file_readu(file); }
/* /*
* Write to a file with an fprintf(3) style format string. * Write to a file with an fprintf(3) style format string.
@ -137,13 +132,19 @@ int file_readf(struct file *, const char *, ...);
int file_writef(struct file *, const char *, ...); int file_writef(struct file *, const char *, ...);
/* /*
* Write binary data to a cache file similar to fwrite(3). * Reads the contents of a file as binary data.
* NOTE: This function returns a new string which MUST be freed with g_free().
*/
gchar *file_read(struct file *);
/*
* Write binary data a cache file, similar to fwrite(3).
* Returns the number of bytes successfully written. * Returns the number of bytes successfully written.
*/ */
int cache_file_write(struct cache_file *, const void *, size_t); int file_write(struct file *, const void *, size_t);
/* Import a file into the cache. */ /* Import a file into the cache. */
bool cache_file_import(struct cache_file *, const gchar *); bool file_import(struct file *, const gchar *);
/* Removes a closed file from disk. */ /* Removes a closed file from disk. */
bool file_remove(struct file *); bool file_remove(struct file *);

View File

@ -24,10 +24,7 @@ enum idle_sync_t {
/* Called to initialize the idle queue. */ /* Called to initialize the idle queue. */
void idle_init(); void idle_init(enum idle_sync_t);
#ifdef CONFIG_TESTING
void idle_init_sync();
#endif /* CONFIG_TESTING */
/* Called to deinitialize the idle queue. */ /* Called to deinitialize the idle queue. */
void idle_deinit(); void idle_deinit();

View File

@ -7,16 +7,15 @@
*/ */
#ifndef OCARINA_CORE_PLAYLIST_H #ifndef OCARINA_CORE_PLAYLIST_H
#define OCARINA_CORE_PLAYLIST_H #define OCARINA_CORE_PLAYLIST_H
#include <core/playlists/artist.h> #include <core/playlists/artist.h>
#include <core/playlists/generic.h>
#include <core/playlists/library.h> #include <core/playlists/library.h>
#include <core/playlists/system.h> #include <core/playlists/system.h>
#include <core/playlists/user.h> #include <core/playlists/user.h>
#include <core/queue.h>
/* Called to initialize the playlist manager. */ /* Called to initialize the playlist manager. */
void playlist_init(struct queue_ops *); void playlist_init(struct playlist_callbacks *);
/* Called to deinitialize the playlist manager. */ /* Called to deinitialize the playlist manager. */
void playlist_deinit(); void playlist_deinit();
@ -24,43 +23,30 @@ void playlist_deinit();
/* Called to force-save all playlists. */ /* Called to force-save all playlists. */
void playlist_save(); void playlist_save();
/* Called to select the current playlist. */ /* Called to notify all playlists that a track has been played. */
bool playlist_select(enum playlist_type_t, const gchar *); void playlist_played(struct track *);
/* Called to notify all playlists that a track has been selected. */
void playlist_selected(struct track *);
/* Called to create a new playlist. */ /* Called to create a new playlist. */
bool playlist_new(enum playlist_type_t, const gchar *); struct playlist *playlist_new(enum playlist_type_t, const gchar *);
/* Called to delete a playlist. */ /* Called to delete a playlist. */
bool playlist_delete(enum playlist_type_t, const gchar *); bool playlist_delete(struct playlist *);
/* Called to add a track to a playlist. */ /* Called to look up playlists either by name or id. */
bool playlist_add(enum playlist_type_t, const gchar *, struct track *); struct playlist *playlist_lookup(enum playlist_type_t, const gchar *);
struct playlist *playlist_get(enum playlist_type_t, unsigned int);
/* Called to remove a track from a playlist. */ /* Called to access the current playlist. */
bool playlist_remove(enum playlist_type_t, const gchar *, struct track *); struct playlist *playlist_current(void);
/* Called to update tracks on a playlist. */
void playlist_update(enum playlist_type_t, const gchar *);
/* Called to check if a specific track is in the playlist. */ /* Called to select the current playlist. */
bool playlist_has(enum playlist_type_t, const gchar *, struct track *); bool playlist_select(struct playlist *);
/* Called to find the number of tracks in the playlist. */
unsigned int playlist_size(enum playlist_type_t, const gchar *);
/* Called to set the playlist's random flag. */
void playlist_set_random(enum playlist_type_t, const gchar *, bool);
/* Called to check the playlist's random flag. */
bool playlist_get_random(enum playlist_type_t, const gchar *);
/* Called to change the sort order of the playlist. */
void playlist_sort(enum playlist_type_t, const gchar *, enum compare_t, bool);
/* Called to get the next track from the default playlist. */ /* Called to get the next track from the default playlist. */
struct track *playlist_next(void); struct track *playlist_next(void);
@ -69,19 +55,27 @@ struct track *playlist_next(void);
struct track *playlist_prev(void); struct track *playlist_prev(void);
/* Called to access the playlist. */ /* Called to add a track to a playlist. */
struct playlist *playlist_get(enum playlist_type_t, const gchar *); bool playlist_add(struct playlist *, struct track *);
/* Called to access the current playlist. */ /* Called to remove a track from a playlist. */
struct playlist *playlist_cur(void); bool playlist_remove(struct playlist *, struct track *);
/* Called to access the playlist queue. */ /* Called to check if a specific track is in the playlist. */
struct queue *playlist_get_queue(enum playlist_type_t, const gchar *); bool playlist_has(struct playlist *, struct track *);
/* Called to convert a playlist name to an integer id. */
unsigned int playlist_get_id(enum playlist_type_t, const gchar *);
/* Called to convert a playlist id to a name. */ /* Called to set the playlist's random flag. */
gchar *playlist_get_name(enum playlist_type_t, unsigned int); void playlist_set_random(struct playlist *, bool);
/* Called to change the sort order of the playlist. */
bool playlist_sort(struct playlist *, enum compare_t);
/* Called to manually rearrange the order of the playlist. */
bool playlist_rearrange(struct playlist *, unsigned int, unsigned int);
/* Called to set the playlist's search text */
void playlist_set_search(struct playlist *, const gchar *);
#endif /* OCARINA_CORE_PLAYLIST_H */ #endif /* OCARINA_CORE_PLAYLIST_H */

View File

@ -3,14 +3,14 @@
*/ */
#ifndef OCARINA_CORE_PLAYLISTS_ARTIST_H #ifndef OCARINA_CORE_PLAYLISTS_ARTIST_H
#define OCARINA_CORE_PLAYLISTS_ARTIST_H #define OCARINA_CORE_PLAYLISTS_ARTIST_H
#include <core/playlists/type.h> #include <core/playlists/generic.h>
/* Artist playlist type. */ /* Artist playlist type. */
extern struct playlist_type pl_artist; extern struct playlist_type pl_artist;
/* Called to initialize artist playlists. */ /* Called to initialize artist playlists. */
void pl_artist_init(struct queue_ops *ops); void pl_artist_init(void);
/* Called to deinitialize library playlists. */ /* Called to deinitialize library playlists. */
void pl_artist_deinit(); void pl_artist_deinit();

View File

@ -0,0 +1,91 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_GENERIC_H
#define OCARINA_CORE_PLAYLISTS_GENERIC_H
#include <core/playlists/iterator.h>
#include <core/playlists/playlist.h>
enum playlist_save_flags {
PL_SAVE_FLAGS = (1 << 0), /* Save playlist random and sort data. */
PL_SAVE_ITER = (1 << 1), /* Save playlist iterator position. */
PL_SAVE_TRACKS = (1 << 2), /* Save playlist tracks. */
};
#define PL_SAVE_METADATA (PL_SAVE_FLAGS | PL_SAVE_ITER)
#define PL_SAVE_ALL (PL_SAVE_TRACKS | PL_SAVE_METADATA)
struct playlist_callbacks {
/* Called to notify that a new playlist has been allocated. */
void (*pl_cb_alloc)(struct playlist *);
/* Called to notify that a track has been added. */
void (*pl_cb_added)(struct playlist *, struct track *);
/*
* Called to notify that N instances of a track have been removed.
* Track may be NULL to indicate that several different tracks were
* removed at once.
*/
void (*pl_cb_removed)(struct playlist *, struct track *, unsigned int n);
/*
* Called to notify that a track has been updated.
* If the track is NULL, then the entire playlist should be updated.
*/
void (*pl_cb_updated)(struct playlist *, struct track *);
};
/* Called to set playlist callbacks. */
void playlist_generic_set_callbacks(struct playlist_callbacks *);
/* Generic playlist init functions. */
void playlist_generic_init(struct playlist *, unsigned int, ...);
/* Generic playlist deinit function. */
void playlist_generic_deinit(struct playlist *);
/* Generic playlist alloc function. */
struct playlist *playlist_generic_alloc(gchar *, enum playlist_type_t,
unsigned int, struct playlist_ops *,
unsigned int, ...);
/* Generic playlist free function. */
void playlist_generic_free(struct playlist *);
/* Generic playlist save function. */
void playlist_generic_save(struct playlist *, struct file *, unsigned int);
/* Generic playlist load function. */
void playlist_generic_load(struct playlist *, struct file *, unsigned int);
/* Generic playlist can-select function. */
bool playlist_generic_can_select(struct playlist *);
/* Generic playlist clear operation. */
void playlist_generic_clear(struct playlist *);
/* Generic playlist add track operations. */
bool playlist_generic_add(struct playlist *, struct track *);
bool playlist_generic_add_front(struct playlist *, struct track *);
/* Generic playlist remove track operation. */
bool playlist_generic_remove(struct playlist *, struct track *);
/* Generic playlist update track operation. */
void playlist_generic_update(struct playlist *, struct track *);
/* Generic playlist set_random operation. */
void playlist_generic_set_random(struct playlist *, bool);
/* Generic playlist sorting operations. */
void playlist_generic_sort(struct playlist *, enum compare_t);
void playlist_generic_resort(struct playlist *);
/* Generic playlist rearranging operation. */
bool playlist_generic_rearrange(struct playlist *, unsigned int, unsigned int);
/* Generic playlist next track operation. */
struct track *playlist_generic_next(struct playlist *);
#endif /* OCARINA_CORE_PLAYLISTS_GENERIC_H */

View File

@ -0,0 +1,83 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*
* NOTE: The playlist_iter type is defined in include/core/playlists/playlist.h
*/
#ifndef OCARINA_CORE_PLAYLISTS_ITERATOR_H
#define OCARINA_CORE_PLAYLISTS_ITERATOR_H
#include <core/playlists/playlist.h>
/* Called to set the playlist iterator to a specific position. */
static inline playlist_iter playlist_iter_get(struct playlist *playlist,
unsigned int n)
{
return playlist ? g_queue_peek_nth_link(&playlist->pl_tracks, n) : NULL;
}
/* Called to advance the requested playlist iterator. */
static inline playlist_iter playlist_iter_next(playlist_iter iter)
{
return g_list_next(iter);
}
/* Called to get a pointer to the track at the requested iterator. */
static inline struct track *playlist_iter_track(playlist_iter iter)
{
return iter ? iter->data : NULL;
}
/* Called to find the playlist index of the requested iterator. */
static inline int playlist_iter_index(struct playlist *playlist,
playlist_iter iter)
{
return (playlist && iter) ? g_queue_link_index(&playlist->pl_tracks, iter) : -1;
}
/* Called to iterate over the entire playlist. */
#define playlist_for_each(playlist, it) \
for (it = playlist_iter_get(playlist, 0); it; it = playlist_iter_next(it))
/* Called to set the index of the current track. */
static inline bool playlist_current_set(struct playlist *playlist,
unsigned int n)
{
if (playlist)
playlist->pl_current = playlist_iter_get(playlist, n);
return playlist && playlist->pl_current;
}
/* Called to advance the current track. */
static inline bool playlist_current_next(struct playlist *playlist)
{
if (playlist)
playlist->pl_current = playlist_iter_next(playlist->pl_current);
return playlist && playlist->pl_current;
}
/* Called to rewind the current track. */
static inline bool playlist_current_previous(struct playlist *playlist)
{
if (playlist)
playlist->pl_current = g_list_previous(playlist->pl_current);
return playlist && playlist->pl_current;
}
/* Called to get a pointer to the current track. */
static inline struct track *playlist_current_track(struct playlist *playlist)
{
return playlist ? playlist_iter_track(playlist->pl_current) : NULL;
}
/* Called to get the playlist index of the current track. */
static inline int playlist_current_index(struct playlist *playlist)
{
return playlist ? playlist_iter_index(playlist, playlist->pl_current) : -1;
}
/* Called to find the nth track on a playlist. */
static inline struct track *playlist_at(struct playlist *playlist, unsigned int n)
{
return playlist_iter_track(playlist_iter_get(playlist, n));
}
#endif /* OCARINA_CORE_PLAYLISTS_ITERATOR_H */

View File

@ -3,17 +3,19 @@
*/ */
#ifndef OCARINA_CORE_PLAYLISTS_LIBRARY_H #ifndef OCARINA_CORE_PLAYLISTS_LIBRARY_H
#define OCARINA_CORE_PLAYLISTS_LIBRARY_H #define OCARINA_CORE_PLAYLISTS_LIBRARY_H
#include <core/playlists/type.h> #include <core/playlists/generic.h>
/* Library playlist type. */ /* Library playlist type. */
extern struct playlist_type pl_library; extern struct playlist_type pl_library;
/* Called to initialize library playlists. */ /* Called to initialize library playlists. */
void pl_library_init(struct queue_ops *); void pl_library_init(void);
/* Called to deinitialize system playlists. */ /* Called to deinitialize system playlists. */
void pl_library_deinit(); void pl_library_deinit();
/* Called to update a library path. */
void pl_library_update(struct playlist *);
#endif /* OCARINA_CORE_PLAYLISTS_LIBRARY_H */ #endif /* OCARINA_CORE_PLAYLISTS_LIBRARY_H */

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_PLAYLIST_H
#define OCARINA_CORE_PLAYLISTS_PLAYLIST_H
#include <core/tags/track.h>
#include <stdbool.h>
typedef GList * playlist_iter;
struct playlist;
enum playlist_type_t {
PL_SYSTEM,
PL_ARTIST,
PL_LIBRARY,
PL_USER,
PL_MAX_TYPE,
};
#define PL_RANDOM (1 << 1)
struct playlist_ops {
/* Called to add a track to a playlist. */
bool (*pl_add)(struct playlist *, struct track *);
/* Called to check if a playlist can be selected. */
bool (*pl_can_select)(struct playlist *);
/* Called to delete a playlist. */
bool (*pl_delete)(struct playlist *);
/* Called to remove a track from the playlist. */
bool (*pl_remove)(struct playlist *, struct track *);
/* Called to set a playlist flag. */
void (*pl_set_random)(struct playlist *, bool);
/* Called to sort the playlist. */
void (*pl_sort)(struct playlist *, enum compare_t);
/* Called to rearrange the playlist. */
bool (*pl_rearrange)(struct playlist *, unsigned int, unsigned int);
};
struct playlist {
enum playlist_type_t pl_type; /* This playlist's type. */
gchar *pl_name; /* This playlist's name. */
unsigned int pl_id; /* This playlist's identifier. */
GQueue pl_tracks; /* This playlist's queue of tracks. */
unsigned int pl_length; /* This playlist's length, in seconds. */
bool pl_random; /* This playlist's random setting. */
playlist_iter pl_current; /* This playlist's current track. */
GSList *pl_sort; /* This playlist's sort order. */
gchar **pl_search; /* This playlist's search text. */
const struct playlist_ops *pl_ops; /* This playlist's supported operations. */
};
#define DEFINE_PLAYLIST(type, name, id, ops) { \
.pl_type = type, \
.pl_name = name, \
.pl_id = id, \
.pl_ops = ops, \
}
struct playlist_type {
/* Called to save all playlists of the given type. */
void (*pl_save)(void);
/* Called to look up playlists. */
struct playlist *(*pl_lookup)(const gchar *);
struct playlist *(*pl_get)(unsigned int);
/* Called to create a new playlist. */
struct playlist *(*pl_new)(const gchar *);
/* Called to notify that a track has been played. */
void (*pl_played)(struct track *);
/* Called to notify that a track has been selected. */
void (*pl_selected)(struct track *);
};
/* Called to check if the playlist contains a specific track. */
static inline bool playlist_has(struct playlist *playlist, struct track *track)
{
return playlist ? g_queue_find(&playlist->pl_tracks, track) != NULL : false;
}
/* Called to find the size of a playlist. */
static inline unsigned int playlist_size(struct playlist *playlist)
{
return playlist ? g_queue_get_length(&playlist->pl_tracks) : 0;
}
/* Called to clear the sort order of the playlist. */
static inline void playlist_clear_sort(struct playlist *playlist)
{
if (playlist) {
g_slist_free(playlist->pl_sort);
playlist->pl_sort = NULL;
}
}
#endif /* OCARINA_CORE_PLAYLISTS_PLAYLIST_H */

View File

@ -3,8 +3,7 @@
*/ */
#ifndef OCARINA_CORE_PLAYLISTS_SYSTEM_H #ifndef OCARINA_CORE_PLAYLISTS_SYSTEM_H
#define OCARINA_CORE_PLAYLISTS_SYSTEM_H #define OCARINA_CORE_PLAYLISTS_SYSTEM_H
#include <core/playlists/type.h> #include <core/playlists/generic.h>
enum sys_playlist_t { enum sys_playlist_t {
SYS_PL_FAVORITES, /* Songs that the user likes. */ SYS_PL_FAVORITES, /* Songs that the user likes. */
@ -18,29 +17,13 @@ enum sys_playlist_t {
SYS_PL_NUM_PLAYLISTS, /* Number of system playlists. */ SYS_PL_NUM_PLAYLISTS, /* Number of system playlists. */
}; };
struct sys_playlist {
struct playlist spl_playlist;
void (*spl_init)(struct playlist *, unsigned int, struct queue_ops *);
void (*spl_save)(struct playlist *, struct file *);
void (*spl_load)(struct playlist *, struct file *);
bool (*spl_can_select)(struct playlist *);
bool (*spl_add)(struct playlist *, struct track *);
bool (*spl_remove)(struct playlist *, struct track *);
void (*spl_clear)(struct playlist *);
bool (*spl_update)(struct playlist *, struct track *);
void (*spl_set_flag)(struct playlist *, enum queue_flags, bool);
void (*spl_sort)(struct playlist *, enum compare_t, bool);
struct track *(*spl_next)(struct playlist *);
};
/* System playlist type. */ /* System playlist type. */
extern struct playlist_type pl_system; extern struct playlist_type pl_system;
/* Called to initialize system playlists. */ /* Called to initialize system playlists. */
void pl_system_init(struct queue_ops *); void pl_system_init(void);
/* Called to deinitialize system playlists. */ /* Called to deinitialize system playlists. */
void pl_system_deinit(); void pl_system_deinit();

View File

@ -1,116 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_TYPE_H
#define OCARINA_CORE_PLAYLISTS_TYPE_H
#include <core/queue.h>
#include <core/tags/track.h>
#include <glib.h>
#include <stdbool.h>
enum playlist_type_t {
PL_SYSTEM,
PL_ARTIST,
PL_LIBRARY,
PL_USER,
PL_MAX_TYPE,
};
struct playlist {
enum playlist_type_t pl_type; /* This playlist's type. */
gchar *pl_name; /* This playlist's name. */
void *pl_private; /* This playlist's private data. */
struct queue pl_queue; /* This playlist's queue of tracks. */
};
#define DEFINE_PLAYLIST(type, name) { .pl_type = type, .pl_name = name }
struct playlist_type {
/* Called to save all playlists of the given type. */
void (*pl_save)(void);
/* Called to get the playlist. */
struct playlist *(*pl_get_playlist)(const gchar *);
/* Called to convert a playlist name to an integer id. */
unsigned int (*pl_get_id)(const gchar *);
/* Called to convert a playlist id to a name. */
gchar *(*pl_get_name)(unsigned int);
/* Called to check if a playlist can be selected. */
bool (*pl_can_select)(const gchar *);
/* Called to create a new playlist. */
bool (*pl_new)(const gchar *);
/* Called to delete a playlist. */
bool (*pl_delete)(const gchar *);
/* Called to add a track to the playlist. */
bool (*pl_add_track)(const gchar *, struct track *);
/* Called to remove a track from the playlist. */
bool (*pl_remove_track)(const gchar *, struct track *);
/* Called to update a playlist. */
void (*pl_update)(const gchar *);
/* Called to set a playlist flag. */
void (*pl_set_flag)(const gchar *, enum queue_flags, bool);
/* Called to sort a playlist. */
void (*pl_sort)(const gchar *, enum compare_t, bool);
/* Called to pick the next track from a playlist. */
struct track *(*pl_next)(const gchar *);
};
/* Noop playlist can-select operation. */
bool playlist_noop_can_select(struct playlist *);
/* Noop playlist clear operation. */
void playlist_noop_clear(struct playlist *);
/* Noop playlist set_flag operation. */
void playlist_noop_set_flag(struct playlist *, enum queue_flags, bool);
/* Noop playlist sorting operation. */
void playlist_noop_sort(struct playlist *, enum compare_t, bool);
/* Generic playlist init function. */
void playlist_generic_init(struct playlist *, unsigned int, struct queue_ops *);
/* Generic playlist can-select function. */
bool playlist_generic_can_select(struct playlist *);
/* Generic playlist clear operation. */
void playlist_generic_clear(struct playlist *);
/* Generic playlist add track operation. */
bool playlist_generic_add_track(struct playlist *, struct track *);
/* Generic playlist remove track operation. */
bool playlist_generic_remove_track(struct playlist *, struct track *);
/* Generic playlist update operation. */
void playlist_generic_update(struct playlist *,
bool (*)(struct playlist *, struct track *));
/* Generic playlist set_flag operation. */
void playlist_generic_set_flag(struct playlist *, enum queue_flags, bool);
/* Generic playlist sorting operation. */
void playlist_generic_sort(struct playlist *, enum compare_t, bool);
/* Generic playlist next track operation. */
struct track *playlist_generic_next(struct playlist *);
#endif /* OCARINA_CORE_PLAYLISTS_TYPE_H */

View File

@ -3,8 +3,7 @@
*/ */
#ifndef OCARINA_CORE_PLAYLISTS_USER_H #ifndef OCARINA_CORE_PLAYLISTS_USER_H
#define OCARINA_CORE_PLAYLISTS_USER_H #define OCARINA_CORE_PLAYLISTS_USER_H
#include <core/playlists/type.h> #include <core/playlists/generic.h>
struct user_playlist { struct user_playlist {
struct playlist pl_playlist; struct playlist pl_playlist;
@ -19,10 +18,16 @@ extern struct playlist_type pl_user;
/* Called to initialize user playlists. */ /* Called to initialize user playlists. */
void pl_user_init(struct queue_ops *ops); void pl_user_init(void);
/* Called to deinitialize user playlists. */ /* Called to deinitialize user playlists. */
void pl_user_deinit(); void pl_user_deinit();
/* Called to tell user playlists that a track is getting deleted. */
void pl_user_delete_track(struct track *);
/* Called to rename a user playlist. */
bool pl_user_rename(struct playlist *, const gchar *);
struct database *pl_user_db_get(); struct database *pl_user_db_get();
#endif /* OCARINA_CORE_PLAYLISTS_USER_H */ #endif /* OCARINA_CORE_PLAYLISTS_USER_H */

View File

@ -1,196 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* Queues are lists of tracks that the user has requested to play next.
* Users of queues are expected to implement their own save and load functions,
* and to provide a filled out queue_ops structure during initialization.
*/
#ifndef OCARINA_CORE_QUEUE_H
#define OCARINA_CORE_QUEUE_H
#include <core/file.h>
#include <core/tags/track.h>
struct queue;
enum queue_flags {
Q_ENABLED = (1 << 0), /* Queue is enabled. */
Q_RANDOM = (1 << 1), /* Queue will pick songs randomly. */
Q_REPEAT = (1 << 2), /* Queue will not remove songs when picked. */
Q_NO_SORT = (1 << 3), /* Queue will not be sorted. */
Q_SAVE_FLAGS = (1 << 4), /* Queue will be saved when flags change. */
Q_SAVE_SORT = (1 << 5), /* Queue will be saved when sorted. */
Q_ADD_FRONT = (1 << 6), /* Queue will add new tracks at the front. */
};
struct queue_ops {
/* Called to tell a higher layer that a queue has been initialized. */
void *(*qop_init)(struct queue *, void *);
/* Called to tell a higher layer that a queue is deinitializing. */
void (*qop_deinit)(struct queue *);
/* Called to tell a higher layer that a track has been added. */
void (*qop_added)(struct queue *, unsigned int);
/* Called to ask a higher layer if a track can be erased. */
bool (*qop_erase)(struct queue *, struct track *);
/* Called to tell a higher layer that a track has been removed. */
void (*qop_removed)(struct queue *, unsigned int);
/* Called to tell a higher layer that the queue has been cleared. */
void (*qop_cleared)(struct queue *, unsigned int);
/* Called to have a higher layer save the queue. */
void (*qop_save)(struct queue *, enum queue_flags);
/* Called to tell a higher layer that a track has been updated. */
void (*qop_updated)(struct queue *, unsigned int);
};
struct queue_iter {
GList *it_iter; /* The current link in the GQueue. */
guint it_pos; /* The current index into the queue. */
};
struct queue {
unsigned int q_flags; /* The queue's set of flags. */
unsigned int q_length; /* The queue's total runtime (in seconds). */
GQueue q_tracks; /* The queue's list of tracks. */
GSList *q_sort; /* The queue's sort order. */
void *q_private; /* The queue's private data. */
struct queue_iter q_cur; /* The queue's last-played position. */
const struct queue_ops *q_ops; /* The queue's operations vector. */
};
/* Called to initialize a queue iterator. */
static inline void queue_iter_init(struct queue *queue, struct queue_iter *it)
{
it->it_iter = g_queue_peek_head_link(&queue->q_tracks);
it->it_pos = g_queue_link_index(&queue->q_tracks, it->it_iter);
}
/* Called to advance a queue iterator by one step. */
static inline void queue_iter_next(struct queue_iter *it)
{
it->it_iter = g_list_next(it->it_iter);
it->it_pos++;
}
/* Called to rewind a queue iterator by one step. */
static inline void queue_iter_prev(struct queue_iter *it)
{
it->it_iter = g_list_previous(it->it_iter);
it->it_pos--;
}
/* Called to set a queue iterator to a specific position. */
static inline void queue_iter_set(struct queue *queue, struct queue_iter *it,
unsigned int pos)
{
it->it_iter = g_queue_peek_nth_link(&queue->q_tracks, pos);
it->it_pos = pos;
}
/* Called to access the value of a queue iterator. */
static inline struct track *queue_iter_val(struct queue_iter *it)
{
return (it->it_iter) ? it->it_iter->data : NULL;
}
#define queue_for_each(queue, it) \
for (queue_iter_init(queue, it); (it)->it_iter; queue_iter_next(it))
/* Called to initialize a queue. */
void queue_init(struct queue *, unsigned int, const struct queue_ops *, void *);
/* Called to deinitialize a queue. */
void queue_deinit(struct queue *);
/* Called to save queue flags, sort order, and (optionally) iterator pos. */
void queue_save_flags(struct queue *, struct file *, bool);
/* Called to save the list of queued tracks. */
void queue_save_tracks(struct queue *, struct file *);
/* Called to load flags, sort order, and (optionally) iterator pos from file. */
void queue_load_flags(struct queue *, struct file *, bool);
/* Called to load queued tracks from file. */
void queue_load_tracks(struct queue *, struct file *);
/* Called to set a queue flag. */
void queue_set_flag(struct queue *, enum queue_flags);
/* Called to clear a queue flag. */
void queue_unset_flag(struct queue *, enum queue_flags);
/* Called to check if the queue has a specific flag set. */
static inline bool queue_has_flag(struct queue *queue, enum queue_flags flag)
{
return (queue->q_flags & flag) == (unsigned int)flag;
}
/* Called to find the size of the queue. */
static inline unsigned int queue_size(struct queue *queue)
{
return g_queue_get_length(&queue->q_tracks);
}
/* Called to access the queued track at a given index. */
static inline struct track *queue_at(struct queue *queue, unsigned int index)
{
return (struct track *)g_queue_peek_nth(&queue->q_tracks, index);
}
/* Called to add a track to the queue. */
unsigned int queue_add(struct queue *, struct track *);
/*
* Called to erase a track from the queue by index.
* This can be prevented if qop_erase() returns "false".
*/
void queue_erase(struct queue *, unsigned int);
/* Called to remove a track from the queue by index. */
void queue_remove(struct queue *, unsigned int);
/* Called to remove all instances of the track from the queue. */
unsigned int queue_remove_all(struct queue *, struct track *);
/* Called to remove all tracks from the queue. */
void queue_clear(struct queue *);
/* Called to check if a queue has a track. */
bool queue_has(struct queue *, struct track *);
/* Called to tell the queue that a track has been updated. */
void queue_updated(struct queue *, struct track *);
/* Called to tell the queue that a specific index has been selected. */
struct track *queue_selected(struct queue *, unsigned int);
/* Called to pick the next track from the queue. */
struct track *queue_next(struct queue *);
/* Called to sort the queue without changing sort order. */
void queue_resort(struct queue *);
/* Called to change the sort order and resort the queue. */
void queue_sort(struct queue *, enum compare_t, bool);
#endif /* OCARINA_CORE_QUEUE_H */

View File

@ -39,6 +39,9 @@ static inline bool string_match(const gchar *a, const gchar *b)
/* Returns True if one of the tokens begins with the specified prefix. */ /* Returns True if one of the tokens begins with the specified prefix. */
bool string_match_token(const gchar *, gchar **); bool string_match_token(const gchar *, gchar **);
/* Returns True if string a is a subdirectory of string b. */
bool string_is_subdir(const gchar *, const gchar *);
/* Return the length of the string, with NULL checks */ /* Return the length of the string, with NULL checks */
static inline int string_length(const gchar *str) static inline int string_length(const gchar *str)
{ {

View File

@ -37,9 +37,11 @@ bool library_db_defrag();
const struct database *library_db_get(); const struct database *library_db_get();
/* /*
* Called to find a library tag by library path. The difference is that * Called to find a library tag by path. The difference is that
* library_find() will allocate a new library struct if the requested one * library_find() will allocate a new library struct if the requested one
* doesn't exist yet, but library_lookup() will return NULL in this situation. * doesn't exist yet, but library_lookup() will return NULL in this situation.
*
* Note that path may be a subdirectory of the returned library.
*/ */
struct library *library_find(const gchar *); struct library *library_find(const gchar *);
struct library *library_lookup(const gchar *); struct library *library_lookup(const gchar *);

View File

@ -58,6 +58,7 @@ struct track {
}; };
#define TRACK(dbe) ((struct track *)DBE_DATA(dbe)) #define TRACK(dbe) ((struct track *)DBE_DATA(dbe))
#define TRACK_IS_EXTERNAL(track) (track->tr_library == NULL)
/* Called to initialize the track database. */ /* Called to initialize the track database. */
@ -87,6 +88,12 @@ unsigned int track_db_count_plays();
/* Called to find the average play count of tracks in the database. */ /* Called to find the average play count of tracks in the database. */
unsigned int track_db_average_plays(); unsigned int track_db_average_plays();
/* Called to allocate a track without adding it to the database. */
struct track *track_alloc_external(const gchar *);
/* Called to free an external track. */
void track_free_external(struct track *);
/* Called to add a track tag to the database. */ /* Called to add a track tag to the database. */
struct track *track_add(struct library *, const gchar *); struct track *track_add(struct library *, const gchar *);
@ -99,6 +106,9 @@ void track_remove_all(struct library *);
/* Called to get a track tag with a specific index. */ /* Called to get a track tag with a specific index. */
struct track *track_get(const unsigned int); struct track *track_get(const unsigned int);
/* Called to look up a track tag using only a filepath. */
struct track *track_lookup(const gchar *);
/* Called to compare two track tags */ /* Called to compare two track tags */
int track_compare(struct track *, struct track *, enum compare_t); int track_compare(struct track *, struct track *, enum compare_t);

View File

@ -3,8 +3,19 @@
*/ */
#ifndef OCARINA_GUI_ARTWORK_H #ifndef OCARINA_GUI_ARTWORK_H
#define OCARINA_GUI_ARTWORK_H #define OCARINA_GUI_ARTWORK_H
#include <core/tags/track.h>
#include <gui/builder.h>
/* Called to set artwork for a track. */ /* Called to set artwork for a track. */
void gui_artwork_set_cover(void); void gui_artwork_set_cover(void);
/* Called to import artwork for a track. */
void gui_artwork_import(struct track *, gchar *);
/* Called to get the cover image. */
static inline GtkImage *gui_artwork(void)
{
return GTK_IMAGE(gui_builder_widget("artwork"));
}
#endif /* OCARINA_GUI_ARTWORK_H */ #endif /* OCARINA_GUI_ARTWORK_H */

View File

@ -3,9 +3,11 @@
*/ */
#ifndef OCARINA_GUI_AUDIO_H #ifndef OCARINA_GUI_AUDIO_H
#define OCARINA_GUI_AUDIO_H #define OCARINA_GUI_AUDIO_H
#include <core/audio.h>
#include <gui/builder.h>
/* Audio callback functions. */ /* Audio callback functions. */
extern struct audio_ops audio_ops; extern struct audio_callbacks audio_cb;
/* Called to initialize the GUI audio controls. */ /* Called to initialize the GUI audio controls. */
void gui_audio_init(); void gui_audio_init();
@ -13,7 +15,95 @@ void gui_audio_init();
/* Called to stop the GUI audio timeout function. */ /* Called to stop the GUI audio timeout function. */
void gui_audio_deinit(); void gui_audio_deinit();
#ifdef CONFIG_TESTING /* Called to update the current track position. */
void test_gui_audio_timeout(); int gui_audio_timeout();
#endif /* CONFIG_TESTING */ int gui_audio_popover_timeout();
/* Called to get the label displaying the album tag. */
static inline GtkLabel *gui_album_tag(void)
{
return GTK_LABEL(gui_builder_widget("album_tag"));
}
/* Called to get the label displaying the artist tag. */
static inline GtkLabel *gui_artist_tag(void)
{
return GTK_LABEL(gui_builder_widget("artist_tag"));
}
/* Called to get the label displaying the title tag. */
static inline GtkLabel *gui_title_tag(void)
{
return GTK_LABEL(gui_builder_widget("title_tag"));
}
/* Called to get the label displaying the track's position. */
static inline GtkLabel *gui_position(void)
{
return GTK_LABEL(gui_builder_widget("position"));
}
/* Called to get the label displaying the track's duration. */
static inline GtkLabel *gui_duration(void)
{
return GTK_LABEL(gui_builder_widget("duration"));
}
/* Called to get the play button. */
static inline GtkButton *gui_play_button(void)
{
return GTK_BUTTON(gui_builder_widget("play_button"));
}
/* Called to get the pause button. */
static inline GtkButton *gui_pause_button(void)
{
return GTK_BUTTON(gui_builder_widget("pause_button"));
}
/* Called to get the previous button. */
static inline GtkButton *gui_prev_button(void)
{
return GTK_BUTTON(gui_builder_widget("prev_button"));
}
/* Called to get the next button. */
static inline GtkButton *gui_next_button(void)
{
return GTK_BUTTON(gui_builder_widget("next_button"));
}
/* Called to get the pause-after widgets. */
static inline GtkEntry *gui_pause_entry(void)
{
return GTK_ENTRY(gui_builder_widget("pause_entry"));
}
static inline GtkButton *gui_pause_down(void)
{
return GTK_BUTTON(gui_builder_widget("pause_down"));
}
static inline GtkButton *gui_pause_up(void)
{
return GTK_BUTTON(gui_builder_widget("pause_up"));
}
static inline GtkPopover *gui_pause_popover(void)
{
return GTK_POPOVER(gui_builder_widget("pause_popover"));
}
/* Called to get the seeking GtkAdjustment. */
static inline GtkAdjustment *gui_seek(void)
{
return GTK_ADJUSTMENT(gui_builder_object("seek"));
}
/* Called to get the volume button. */
static inline GtkScaleButton *gui_volume_button(void)
{
return GTK_SCALE_BUTTON(gui_builder_widget("volume_button"));
}
#endif /* OCARINA_GUI_AUDIO_H */ #endif /* OCARINA_GUI_AUDIO_H */

View File

@ -20,9 +20,6 @@ void gui_filter_init();
/* Called to deinitialize the filter model. */ /* Called to deinitialize the filter model. */
void gui_filter_deinit(); void gui_filter_deinit();
/* Called to clear any saved search text. */
void gui_filter_clear_search(struct playlist *);
/* Called to set the current playlist. */ /* Called to set the current playlist. */
void gui_filter_set_playlist(struct playlist *); void gui_filter_set_playlist(struct playlist *);
@ -41,6 +38,9 @@ unsigned int gui_filter_path_get_index(GtkTreePath *);
/* Called to convert a playlist iterator index into a path. */ /* Called to convert a playlist iterator index into a path. */
GtkTreePath *gui_filter_path_from_index(unsigned int); GtkTreePath *gui_filter_path_from_index(unsigned int);
/* Called to refilter a playlist. Pass NULL to refilter the current playlist */
void gui_filter_refilter(struct playlist *);
/* Called to access the filter search-entry. */ /* Called to access the filter search-entry. */
static inline GtkSearchEntry *gui_filter_search(void) static inline GtkSearchEntry *gui_filter_search(void)
{ {

View File

@ -38,6 +38,15 @@ struct gui_model_class {
}; };
typedef struct gui_model_class GuiModelClass; typedef struct gui_model_class GuiModelClass;
struct gui_model_drag_data {
unsigned int drag_row;
struct track *drag_track;
};
#define GUI_DRAG_DATA "GUI_DRAG_DATA"
extern const GtkTargetEntry gui_model_drag_targets[];
extern const unsigned int gui_model_n_targets;
/* Called to initialize the GuiModel */ /* Called to initialize the GuiModel */
void gui_model_init(void); void gui_model_init(void);
@ -52,16 +61,16 @@ GuiModel *gui_model_get(void);
GType gui_model_get_type(); GType gui_model_get_type();
/* Called to add a row to the model */ /* Called to add a row to the model */
void gui_model_add(struct playlist *, unsigned int); void gui_model_add(struct playlist *, struct track *);
/* Called to remove a row from the model */ /* Called to remove a row from the model */
void gui_model_remove(struct playlist *, unsigned int); void gui_model_remove(struct playlist *, struct track *, unsigned int);
/* Called to remove all rows from the model */ /*
void gui_model_clear(struct playlist *, unsigned int); * Called to update a row in the model
* If track is NULL, then all rows will be updated
/* Called to update a row in the model */ */
void gui_model_update(struct playlist *, unsigned int); void gui_model_update(struct playlist *, struct track *);
/* Called to change the queue represented by the model. */ /* Called to change the queue represented by the model. */
void gui_model_set_playlist(struct playlist *); void gui_model_set_playlist(struct playlist *);
@ -74,8 +83,8 @@ struct playlist *gui_model_get_playlist(void);
static inline struct track *gui_model_iter_get_track(GtkTreeIter *iter) static inline struct track *gui_model_iter_get_track(GtkTreeIter *iter)
{ {
g_return_val_if_fail(iter != NULL, NULL); g_return_val_if_fail(iter != NULL, NULL);
g_return_val_if_fail(iter->user_data2 != NULL, NULL); g_return_val_if_fail(iter->user_data != NULL, NULL);
return (struct track *)iter->user_data2; return playlist_iter_track(iter->user_data);
} }
/* Called to convert a GtkTreePath into a struct track */ /* Called to convert a GtkTreePath into a struct track */

View File

@ -4,6 +4,7 @@
#ifndef OCARINA_GUI_PLAYLIST_H #ifndef OCARINA_GUI_PLAYLIST_H
#define OCARINA_GUI_PLAYLIST_H #define OCARINA_GUI_PLAYLIST_H
#include <core/playlist.h> #include <core/playlist.h>
#include <gui/builder.h>
#include <gui/playlists/artist.h> #include <gui/playlists/artist.h>
#include <gui/playlists/library.h> #include <gui/playlists/library.h>
#include <gui/playlists/system.h> #include <gui/playlists/system.h>
@ -12,10 +13,19 @@
/* Called to initialize the GUI playlist code. */ /* Called to initialize the GUI playlist code. */
void gui_playlist_init(); void gui_playlist_init();
/* Called to get the currently selected playlist. */ /* Called to access the right-click menu. */
const gchar *gui_playlist_cur(); static inline GtkMenu *gui_rc_menu()
{
return GTK_MENU(gui_builder_widget("rc_menu"));
}
/* Playlist operations passed to core_init() */ /* Called to acces the "Add to Other Playlist" menu item. */
extern struct queue_ops playlist_ops; static inline GtkMenuItem *gui_rc_add_to_other()
{
return GTK_MENU_ITEM(gui_builder_widget("rc_add_to_other"));
}
/* Playlist callbacks passed to core_init() */
extern struct playlist_callbacks playlist_cb;
#endif /* OCARINA_GUI_PLAYLIST_H */ #endif /* OCARINA_GUI_PLAYLIST_H */

View File

@ -13,4 +13,7 @@ bool gui_pl_artist_add(struct playlist *);
/* Called to update an artist playlist. */ /* Called to update an artist playlist. */
void gui_pl_artist_update(struct playlist *); void gui_pl_artist_update(struct playlist *);
/* Called to select an artist playlist. */
void gui_pl_artist_select(struct playlist *);
#endif /* OCARINA_GUI_PLAYLISTS_ARTIST_H */ #endif /* OCARINA_GUI_PLAYLISTS_ARTIST_H */

View File

@ -8,9 +8,12 @@
void gui_pl_library_init(); void gui_pl_library_init();
/* Called to add a library path. */ /* Called to add a library path. */
bool gui_pl_library_add(const gchar *); struct playlist *gui_pl_library_add(const gchar *);
/* Called to update a library path. */ /* Called to update a library path. */
void gui_pl_library_update(struct playlist *); void gui_pl_library_update(struct playlist *);
/* Called to select a library playlist. */
void gui_pl_library_select(struct playlist *);
#endif /* OCARINA_GUI_PLAYLISTS_LIBRARY_H */ #endif /* OCARINA_GUI_PLAYLISTS_LIBRARY_H */

View File

@ -11,6 +11,9 @@ void gui_pl_system_init();
/* Called to update a system playlist. */ /* Called to update a system playlist. */
void gui_pl_system_update(struct playlist *); void gui_pl_system_update(struct playlist *);
/* Called to select a system playlist. */
void gui_pl_system_select(struct playlist *);
/* Called to set favorites and hidden button states. */ /* Called to set favorites and hidden button states. */
void gui_pl_system_track_loaded(struct track *); void gui_pl_system_track_loaded(struct track *);

View File

@ -16,6 +16,9 @@ struct playlist *gui_pl_user_add_dialog(void);
/* Called to update a user playlist. */ /* Called to update a user playlist. */
void gui_pl_user_update(struct playlist *); void gui_pl_user_update(struct playlist *);
/* Called to select a user playlist. */
void gui_pl_user_select(struct playlist *);
/* /*
* Called to get a (sorted) list of user playlists. * Called to get a (sorted) list of user playlists.
* Note: The caller is responsible for freeing the list with g_list_free(). * Note: The caller is responsible for freeing the list with g_list_free().

View File

@ -9,6 +9,9 @@
/* Called to initialize the sidebar. */ /* Called to initialize the sidebar. */
void gui_sidebar_init(); void gui_sidebar_init();
/* Called to set an iterator to the currently displayed playlist. */
gboolean gui_sidebar_iter_current(GtkTreeIter *);
/* Called to set an iterator to the first playlist. */ /* Called to set an iterator to the first playlist. */
gboolean gui_sidebar_iter_first(GtkTreeIter *); gboolean gui_sidebar_iter_first(GtkTreeIter *);
@ -27,6 +30,12 @@ gchar *gui_sidebar_iter_name(GtkTreeIter *);
/* Called to find the type of the playlist at the given iterator. */ /* Called to find the type of the playlist at the given iterator. */
enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *); enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *);
/* Called to find the editable state of the playlist at the given iterator. */
bool gui_sidebar_iter_editable(GtkTreeIter *);
/* Called to find the playlist at the given iterator. */
struct playlist *gui_sidebar_iter_playlist(GtkTreeIter *);
/* Called to add a playlist at the current iterator. */ /* Called to add a playlist at the current iterator. */
void gui_sidebar_iter_add(GtkTreeIter *, struct playlist *, const gchar *); void gui_sidebar_iter_add(GtkTreeIter *, struct playlist *, const gchar *);
@ -39,11 +48,24 @@ void gui_sidebar_iter_append_child(GtkTreeIter *, struct playlist *,
const gchar *); const gchar *);
/* Called to update the playlist at the current iterator. */ /* Called to update the playlist at the current iterator. */
void gui_sidebar_iter_update_playlist(GtkTreeIter *, struct playlist *);
void gui_sidebar_iter_update(GtkTreeIter *); void gui_sidebar_iter_update(GtkTreeIter *);
/* Called to select the row at the current iterator. */
void gui_sidebar_iter_select(GtkTreeIter *);
/* Called to set the current iterator as editable. */
bool gui_sidebar_iter_set_editable(GtkTreeIter *, bool);
/* Called to set the playlist at the given iterator as the default. */ /* Called to set the playlist at the given iterator as the default. */
void gui_sidebar_filter_path_select(GtkTreePath *); void gui_sidebar_filter_path_select(GtkTreePath *);
/* Called to expand or collapse sidebar rows from the user's settings. */
void gui_sidebar_filter_set_expand(GtkTreeIter *);
/* Called when a playlist treeview row is expanded or collapsed. */
void gui_sidebar_filter_row_expanded(GtkTreeIter *, bool);
/* /*
* Called to find a playlist with the given name and type, * Called to find a playlist with the given name and type,
* starting from the current iterator position. * starting from the current iterator position.
@ -51,6 +73,12 @@ void gui_sidebar_filter_path_select(GtkTreePath *);
gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *, gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *,
enum playlist_type_t); enum playlist_type_t);
/* Called to set the a GtkTreeIter to the row at path string */
gboolean gui_sidebar_iter_from_string(const gchar *, GtkTreeIter *);
/* Called to set the GtkTreeIter to the row at (x, y) */
gboolean gui_sidebar_iter_from_xy(gint, gint, GtkTreeIter *);
/* Called to get the sidebar widget. */ /* Called to get the sidebar widget. */
static inline GtkPaned *gui_sidebar() static inline GtkPaned *gui_sidebar()
{ {
@ -81,6 +109,12 @@ static inline GtkTreeView *gui_sidebar_treeview()
return GTK_TREE_VIEW(gui_builder_widget("sidebar_treeview")); return GTK_TREE_VIEW(gui_builder_widget("sidebar_treeview"));
} }
/* Called to get the sidebar right-click menu. */
static inline GtkMenu *gui_sidebar_menu()
{
return GTK_MENU(gui_builder_widget("rc_sidebar"));
}
/* Called to get the random button. */ /* Called to get the random button. */
static inline GtkToggleButton *gui_random_button() static inline GtkToggleButton *gui_random_button()
{ {

View File

@ -17,12 +17,27 @@ void gui_treeview_set_playlist(struct playlist *);
/* Called to scroll the treeview to the current track. */ /* Called to scroll the treeview to the current track. */
void gui_treeview_scroll(); void gui_treeview_scroll();
/* Called to select a path from (x, y) screen coodinates. */
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y);
/*
* Called to get a list of selected tracks.
* NOTE: The caller is responsible for freeing the list with g_list_free().
*/
GList *gui_treeview_list_selected_tracks(void);
/* Called to access the treeview widget. */ /* Called to access the treeview widget. */
static inline GtkTreeView *gui_treeview() static inline GtkTreeView *gui_treeview()
{ {
return GTK_TREE_VIEW(gui_builder_widget("treeview")); return GTK_TREE_VIEW(gui_builder_widget("treeview"));
} }
/* Called to access the treview selection. */
static inline GtkTreeSelection *gui_treeview_selection()
{
return gtk_tree_view_get_selection(gui_treeview());
}
/* Called to access the sorting display widget. */ /* Called to access the sorting display widget. */
static inline GtkLabel *gui_sorting() static inline GtkLabel *gui_sorting()
{ {

View File

@ -1,14 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_VIEW_H
#define OCARINA_GUI_VIEW_H
#include <gui/builder.h>
/* Called to initialize structures needed by the treeview. */
void gui_view_init();
/* Called to set the currently displayed model. */
void gui_view_set_playlist(struct playlist *);
#endif /* OCARINA_GUI_VIEW_H */

View File

@ -1,30 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_TESTS_GUI_H
#define OCARINA_TESTS_GUI_H
static GMainLoop *__gui_test_main_loop;
static int __gui_test_on_idle(gpointer data)
{
g_main_loop_quit(__gui_test_main_loop);
return G_SOURCE_CONTINUE;
}
static void gui_test_init()
{
__gui_test_main_loop = g_main_loop_new(NULL, FALSE);
g_idle_add(__gui_test_on_idle, NULL);
}
static void gui_test_deinit()
{
g_main_loop_unref(__gui_test_main_loop);
}
static void gui_test_main_loop()
{
g_main_loop_run(__gui_test_main_loop);
}
#endif /* OCARINA_TESTS_GUI_H */

30
include/tests/loop.h Normal file
View File

@ -0,0 +1,30 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_TESTS_LOOP_H
#define OCARINA_TESTS_LOOP_H
static GMainLoop *__test_main_loop;
static int __test_loop_on_idle(gpointer data)
{
g_main_loop_quit(__test_main_loop);
return G_SOURCE_CONTINUE;
}
static void test_loop_init()
{
__test_main_loop = g_main_loop_new(NULL, FALSE);
g_idle_add(__test_loop_on_idle, NULL);
}
static void test_loop_deinit()
{
g_main_loop_unref(__test_main_loop);
}
static void test_main_loop()
{
g_main_loop_run(__test_main_loop);
}
#endif /* OCARINA_TESTS_LOOP_H */

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 --> <!-- Generated with glade 3.20.2 -->
<interface> <interface>
<requires lib="gtk+" version="3.16"/> <requires lib="gtk+" version="3.16"/>
<object class="GtkAdjustment" id="adjustment1"> <object class="GtkAdjustment" id="adjustment1">
@ -30,9 +30,44 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">document-new</property> <property name="icon_name">document-new</property>
</object> </object>
<object class="GtkMenu" id="o_menu"> <object class="GtkImage" id="image24">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">audio-x-generic</property>
</object>
<object class="GtkMenu" id="rc_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Add to Queued Tracks</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image24</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_playlist_add_queued" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Add to Favorites</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image20</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_playlist_add_favorites" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Add to Hidden</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image19</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_playlist_add_hidden" swapped="no"/>
</object>
</child>
<child> <child>
<object class="GtkImageMenuItem"> <object class="GtkImageMenuItem">
<property name="label" translatable="yes">Add to New Playlist</property> <property name="label" translatable="yes">Add to New Playlist</property>
@ -40,31 +75,11 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="image">image23</property> <property name="image">image23</property>
<property name="use_stock">False</property> <property name="use_stock">False</property>
<signal name="activate" handler="__view_rc_add_new" swapped="no"/> <signal name="activate" handler="__gui_playlist_add_user" swapped="no"/>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkImageMenuItem" id="o_add_to_favorites"> <object class="GtkImageMenuItem" id="rc_add_to_other">
<property name="label" translatable="yes">Add to Favorites</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image20</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__view_rc_add_favorites" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="o_add_to_hidden">
<property name="label" translatable="yes">Add to Hidden</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image19</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__view_rc_add_hidden" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="o_add_to_other">
<property name="label" translatable="yes">Add to Other Playlist</property> <property name="label" translatable="yes">Add to Other Playlist</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -73,12 +88,64 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkImage" id="image25">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">dialog-ok</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="image26">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-delete</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="image27">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit</property>
<property name="icon_size">1</property>
</object>
<object class="GtkMenu" id="rc_sidebar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Select Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image25</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_select" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rc_sidebar_rename">
<property name="label" translatable="yes">Rename Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image27</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_rename" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Delete Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image26</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_delete" swapped="no"/>
</object>
</child>
</object>
<object class="GtkImage" id="image3"> <object class="GtkImage" id="image3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">folder-new</property> <property name="icon_name">folder-new</property>
</object> </object>
<object class="GtkAdjustment" id="o_progress"> <object class="GtkAdjustment" id="seek">
<property name="upper">100000000000</property> <property name="upper">100000000000</property>
<property name="step_increment">1000000000</property> <property name="step_increment">1000000000</property>
<property name="page_increment">10000000000</property> <property name="page_increment">10000000000</property>
@ -91,6 +158,8 @@
<column type="gchararray"/> <column type="gchararray"/>
<!-- column-name Type --> <!-- column-name Type -->
<column type="guint"/> <column type="guint"/>
<!-- column-name Editable -->
<column type="gboolean"/>
</columns> </columns>
</object> </object>
<object class="GtkTreeModelFilter" id="sidebar_filter"> <object class="GtkTreeModelFilter" id="sidebar_filter">
@ -120,7 +189,7 @@
<property name="valign">center</property> <property name="valign">center</property>
<property name="layout_style">start</property> <property name="layout_style">start</property>
<child> <child>
<object class="GtkButton" id="o_prev"> <object class="GtkButton" id="prev_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="focus_on_click">False</property> <property name="focus_on_click">False</property>
@ -128,7 +197,7 @@
<property name="tooltip_text" translatable="yes">Previous track</property> <property name="tooltip_text" translatable="yes">Previous track</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="can-activate-accel" handler="__audio_can_accel" swapped="no"/> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="audio_prev" swapped="no"/> <signal name="clicked" handler="audio_prev" swapped="no"/>
<child> <child>
<object class="GtkImage" id="image4"> <object class="GtkImage" id="image4">
@ -143,8 +212,8 @@
<property name="icon_name">media-skip-backward</property> <property name="icon_name">media-skip-backward</property>
</object> </object>
</child> </child>
<accelerator key="n" signal="clicked" modifiers="GDK_SHIFT_MASK"/>
<accelerator key="AudioPrev" signal="clicked"/> <accelerator key="AudioPrev" signal="clicked"/>
<accelerator key="n" signal="clicked" modifiers="GDK_SHIFT_MASK"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -154,7 +223,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="o_play"> <object class="GtkButton" id="play_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="focus_on_click">False</property> <property name="focus_on_click">False</property>
@ -162,7 +231,7 @@
<property name="tooltip_text" translatable="yes">Start playback</property> <property name="tooltip_text" translatable="yes">Start playback</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="can-activate-accel" handler="__audio_can_accel" swapped="no"/> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="audio_play" swapped="no"/> <signal name="clicked" handler="audio_play" swapped="no"/>
<child> <child>
<object class="GtkImage" id="image1"> <object class="GtkImage" id="image1">
@ -178,8 +247,8 @@
<property name="icon_size">5</property> <property name="icon_size">5</property>
</object> </object>
</child> </child>
<accelerator key="space" signal="clicked"/>
<accelerator key="AudioPlay" signal="clicked"/> <accelerator key="AudioPlay" signal="clicked"/>
<accelerator key="space" signal="clicked"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -189,15 +258,15 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="o_pause"> <object class="GtkButton" id="pause_button">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="focus_on_click">False</property> <property name="focus_on_click">False</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Pause playback</property> <property name="tooltip_text" translatable="yes">Pause playback</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="can-activate-accel" handler="__audio_can_accel" swapped="no"/> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="audio_pause" swapped="no"/> <signal name="clicked" handler="__gui_audio_pause" swapped="no"/>
<child> <child>
<object class="GtkImage" id="image2"> <object class="GtkImage" id="image2">
<property name="visible">True</property> <property name="visible">True</property>
@ -212,8 +281,8 @@
<property name="icon_size">5</property> <property name="icon_size">5</property>
</object> </object>
</child> </child>
<accelerator key="space" signal="clicked"/>
<accelerator key="AudioPlay" signal="clicked"/> <accelerator key="AudioPlay" signal="clicked"/>
<accelerator key="space" signal="clicked"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -223,7 +292,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkButton" id="o_next"> <object class="GtkButton" id="next_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="focus_on_click">False</property> <property name="focus_on_click">False</property>
@ -231,7 +300,7 @@
<property name="tooltip_text" translatable="yes">Next track</property> <property name="tooltip_text" translatable="yes">Next track</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="can-activate-accel" handler="__audio_can_accel" swapped="no"/> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="audio_next" swapped="no"/> <signal name="clicked" handler="audio_next" swapped="no"/>
<child> <child>
<object class="GtkImage" id="image5"> <object class="GtkImage" id="image5">
@ -246,8 +315,8 @@
<property name="icon_name">media-skip-forward</property> <property name="icon_name">media-skip-forward</property>
</object> </object>
</child> </child>
<accelerator key="n" signal="clicked"/>
<accelerator key="AudioNext" signal="clicked"/> <accelerator key="AudioNext" signal="clicked"/>
<accelerator key="n" signal="clicked"/>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -269,11 +338,12 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="relief">none</property> <property name="relief">none</property>
<signal name="clicked" handler="__artwork_select_cover" swapped="no"/> <signal name="clicked" handler="__gui_artwork_select_cover" swapped="no"/>
<child> <child>
<object class="GtkImage" id="o_cover"> <object class="GtkImage" id="artwork">
<property name="height_request">100</property> <property name="height_request">100</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="icon_name">image-missing</property> <property name="icon_name">image-missing</property>
<property name="icon_size">6</property> <property name="icon_size">6</property>
@ -287,7 +357,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkScrolledWindow" id="o_tags"> <object class="GtkScrolledWindow">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
@ -306,7 +376,7 @@
<property name="column_spacing">5</property> <property name="column_spacing">5</property>
<property name="row_homogeneous">True</property> <property name="row_homogeneous">True</property>
<child> <child>
<object class="GtkLabel" id="o_title"> <object class="GtkLabel" id="title_tag">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -321,7 +391,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="o_artist"> <object class="GtkLabel" id="artist_tag">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -335,7 +405,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="o_album"> <object class="GtkLabel" id="album_tag">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -416,7 +486,7 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkVolumeButton" id="o_volume"> <object class="GtkVolumeButton" id="volume_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="focus_on_click">False</property> <property name="focus_on_click">False</property>
@ -431,7 +501,7 @@ audio-volume-high
audio-volume-low audio-volume-low
audio-volume-medium</property> audio-volume-medium</property>
<property name="use_symbolic">False</property> <property name="use_symbolic">False</property>
<signal name="value-changed" handler="__audio_volume_changed" swapped="no"/> <signal name="value-changed" handler="__gui_audio_volume_changed" swapped="no"/>
<child internal-child="plus_button"> <child internal-child="plus_button">
<object class="GtkButton"> <object class="GtkButton">
<property name="can_focus">True</property> <property name="can_focus">True</property>
@ -462,7 +532,6 @@ audio-volume-medium</property>
<packing> <packing>
<property name="left_attach">3</property> <property name="left_attach">3</property>
<property name="top_attach">0</property> <property name="top_attach">0</property>
<property name="width">2</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -472,7 +541,7 @@ audio-volume-medium</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="spacing">3</property> <property name="spacing">3</property>
<child> <child>
<object class="GtkLabel" id="o_position"> <object class="GtkLabel" id="position">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="has_tooltip">True</property> <property name="has_tooltip">True</property>
@ -488,16 +557,16 @@ audio-volume-medium</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkScale" id="o_seek"> <object class="GtkScale">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="adjustment">o_progress</property> <property name="adjustment">seek</property>
<property name="round_digits">0</property> <property name="round_digits">0</property>
<property name="draw_value">False</property> <property name="draw_value">False</property>
<property name="value_pos">left</property> <property name="value_pos">left</property>
<signal name="change-value" handler="__audio_seek" swapped="no"/> <signal name="change-value" handler="__gui_audio_seek" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@ -506,7 +575,7 @@ audio-volume-medium</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel" id="o_duration"> <object class="GtkLabel" id="duration">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">start</property> <property name="halign">start</property>
@ -528,132 +597,83 @@ audio-volume-medium</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Pause after</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
<property name="height">2</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="o_pause_after">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">end</property> <property name="halign">end</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="active">0</property> <child>
<items> <object class="GtkEntry" id="pause_entry">
<item translatable="yes">(disabled)</item> <property name="visible">True</property>
<item translatable="yes">this track</item> <property name="can_focus">True</property>
<item translatable="yes">next track</item> <property name="text" translatable="yes">Paused</property>
<item translatable="yes">2 tracks</item> <signal name="activate" handler="__gui_audio_pause_change_text" swapped="no"/>
<item translatable="yes">3 tracks</item> </object>
<item translatable="yes">4 tracks</item> <packing>
<item translatable="yes">5 tracks</item> <property name="expand">False</property>
<item translatable="yes">6 tracks</item> <property name="fill">True</property>
<item translatable="yes">7 tracks</item> <property name="position">0</property>
<item translatable="yes">8 tracks</item> </packing>
<item translatable="yes">9 tracks</item> </child>
<item translatable="yes">10 tracks</item> <child>
<item translatable="yes">11 tracks</item> <object class="GtkButton" id="pause_down">
<item translatable="yes">12 tracks</item> <property name="visible">True</property>
<item translatable="yes">13 tracks</item> <property name="sensitive">False</property>
<item translatable="yes">14 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">15 tracks</item> <property name="receives_default">True</property>
<item translatable="yes">16 tracks</item> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<item translatable="yes">17 tracks</item> <signal name="clicked" handler="__gui_audio_pause_dec" swapped="no"/>
<item translatable="yes">18 tracks</item> <child>
<item translatable="yes">19 tracks</item> <object class="GtkImage">
<item translatable="yes">20 tracks</item> <property name="visible">True</property>
<item translatable="yes">21 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">22 tracks</item> <property name="icon_name">list-remove-symbolic</property>
<item translatable="yes">23 tracks</item> </object>
<item translatable="yes">24 tracks</item> </child>
<item translatable="yes">25 tracks</item> <accelerator key="minus" signal="clicked"/>
<item translatable="yes">26 tracks</item> <accelerator key="KP_Subtract" signal="clicked"/>
<item translatable="yes">27 tracks</item> <style>
<item translatable="yes">28 tracks</item> <class name="down"/>
<item translatable="yes">29 tracks</item> </style>
<item translatable="yes">30 tracks</item> </object>
<item translatable="yes">31 tracks</item> <packing>
<item translatable="yes">32 tracks</item> <property name="expand">False</property>
<item translatable="yes">33 tracks</item> <property name="fill">True</property>
<item translatable="yes">34 tracks</item> <property name="position">1</property>
<item translatable="yes">35 tracks</item> </packing>
<item translatable="yes">36 tracks</item> </child>
<item translatable="yes">37 tracks</item> <child>
<item translatable="yes">38 tracks</item> <object class="GtkButton" id="pause_up">
<item translatable="yes">39 tracks</item> <property name="visible">True</property>
<item translatable="yes">40 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">41 tracks</item> <property name="receives_default">True</property>
<item translatable="yes">42 tracks</item> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<item translatable="yes">43 tracks</item> <signal name="clicked" handler="__gui_audio_pause_inc" swapped="no"/>
<item translatable="yes">44 tracks</item> <child>
<item translatable="yes">45 tracks</item> <object class="GtkImage">
<item translatable="yes">46 tracks</item> <property name="visible">True</property>
<item translatable="yes">47 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">48 tracks</item> <property name="icon_name">list-add-symbolic</property>
<item translatable="yes">49 tracks</item> </object>
<item translatable="yes">50 tracks</item> </child>
<item translatable="yes">51 tracks</item> <accelerator key="plus" signal="clicked"/>
<item translatable="yes">52 tracks</item> <accelerator key="KP_Add" signal="clicked"/>
<item translatable="yes">53 tracks</item> <style>
<item translatable="yes">54 tracks</item> <class name="up"/>
<item translatable="yes">55 tracks</item> </style>
<item translatable="yes">56 tracks</item> </object>
<item translatable="yes">57 tracks</item> <packing>
<item translatable="yes">58 tracks</item> <property name="expand">False</property>
<item translatable="yes">59 tracks</item> <property name="fill">True</property>
<item translatable="yes">60 tracks</item> <property name="position">2</property>
<item translatable="yes">61 tracks</item> </packing>
<item translatable="yes">62 tracks</item> </child>
<item translatable="yes">63 tracks</item> <style>
<item translatable="yes">64 tracks</item> <class name="linked"/>
<item translatable="yes">65 tracks</item> </style>
<item translatable="yes">66 tracks</item>
<item translatable="yes">67 tracks</item>
<item translatable="yes">68 tracks</item>
<item translatable="yes">69 tracks</item>
<item translatable="yes">70 tracks</item>
<item translatable="yes">71 tracks</item>
<item translatable="yes">72 tracks</item>
<item translatable="yes">73 tracks</item>
<item translatable="yes">74 tracks</item>
<item translatable="yes">75 tracks</item>
<item translatable="yes">76 tracks</item>
<item translatable="yes">77 tracks</item>
<item translatable="yes">78 tracks</item>
<item translatable="yes">79 tracks</item>
<item translatable="yes">80 tracks</item>
<item translatable="yes">81 tracks</item>
<item translatable="yes">82 tracks</item>
<item translatable="yes">83 tracks</item>
<item translatable="yes">84 tracks</item>
<item translatable="yes">85 tracks</item>
<item translatable="yes">86 tracks</item>
<item translatable="yes">87 tracks</item>
<item translatable="yes">88 tracks</item>
<item translatable="yes">89 tracks</item>
<item translatable="yes">90 tracks</item>
<item translatable="yes">91 tracks</item>
<item translatable="yes">92 tracks</item>
<item translatable="yes">93 tracks</item>
<item translatable="yes">94 tracks</item>
<item translatable="yes">95 tracks</item>
<item translatable="yes">96 tracks</item>
<item translatable="yes">97 tracks</item>
<item translatable="yes">98 tracks</item>
<item translatable="yes">99 tracks</item>
</items>
<signal name="changed" handler="__audio_pause_changed" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="left_attach">4</property> <property name="left_attach">3</property>
<property name="top_attach">1</property> <property name="top_attach">1</property>
<property name="height">2</property> <property name="height">2</property>
</packing> </packing>
@ -747,8 +767,13 @@ audio-volume-medium</property>
<property name="headers_visible">False</property> <property name="headers_visible">False</property>
<property name="search_column">1</property> <property name="search_column">1</property>
<property name="enable_tree_lines">True</property> <property name="enable_tree_lines">True</property>
<signal name="button-press-event" handler="__gui_sidebar_button_press" swapped="no"/>
<signal name="drag-data-received" handler="__gui_playlist_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="__gui_treeview_drag_drop" swapped="no"/>
<signal name="key-press-event" handler="__gui_sidebar_keypress" swapped="no"/> <signal name="key-press-event" handler="__gui_sidebar_keypress" swapped="no"/>
<signal name="row-activated" handler="__playlist_row_activated" swapped="no"/> <signal name="row-activated" handler="__gui_playlist_row_activated" swapped="no"/>
<signal name="row-collapsed" handler="__gui_playlist_row_collapsed" swapped="no"/>
<signal name="row-expanded" handler="__gui_playlist_row_expanded" swapped="no"/>
<child internal-child="selection"> <child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection3"> <object class="GtkTreeSelection" id="treeview-selection3">
<signal name="changed" handler="__gui_sidebar_selection_changed" swapped="no"/> <signal name="changed" handler="__gui_sidebar_selection_changed" swapped="no"/>
@ -771,8 +796,12 @@ audio-volume-medium</property>
<object class="GtkTreeViewColumn" id="treeviewcolumn3"> <object class="GtkTreeViewColumn" id="treeviewcolumn3">
<property name="title" translatable="yes">column</property> <property name="title" translatable="yes">column</property>
<child> <child>
<object class="GtkCellRendererText" id="cellrenderertext1"/> <object class="GtkCellRendererText" id="cellrenderertext1">
<signal name="edited" handler="__gui_pl_user_edited" swapped="no"/>
<signal name="editing-started" handler="__gui_pl_user_editing_started" swapped="no"/>
</object>
<attributes> <attributes>
<attribute name="editable">3</attribute>
<attribute name="markup">1</attribute> <attribute name="markup">1</attribute>
</attributes> </attributes>
</child> </child>
@ -805,7 +834,7 @@ audio-volume-medium</property>
</object> </object>
<packing> <packing>
<property name="resize">False</property> <property name="resize">False</property>
<property name="shrink">False</property> <property name="shrink">True</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -884,8 +913,10 @@ audio-volume-medium</property>
<property name="fixed_height_mode">True</property> <property name="fixed_height_mode">True</property>
<property name="rubber_banding">True</property> <property name="rubber_banding">True</property>
<property name="tooltip_column">9</property> <property name="tooltip_column">9</property>
<signal name="button-press-event" handler="__view_button_press" swapped="no"/> <signal name="button-press-event" handler="__gui_playlist_button_press" swapped="no"/>
<signal name="key-press-event" handler="__view_keypress" swapped="no"/> <signal name="drag-data-received" handler="__gui_treeview_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="__gui_treeview_drag_drop" swapped="no"/>
<signal name="key-press-event" handler="__gui_playlist_keypress" swapped="no"/>
<signal name="row-activated" handler="__gui_treeview_row_activated" swapped="no"/> <signal name="row-activated" handler="__gui_treeview_row_activated" swapped="no"/>
<child internal-child="selection"> <child internal-child="selection">
<object class="GtkTreeSelection" id="treeview-selection5"> <object class="GtkTreeSelection" id="treeview-selection5">
@ -1090,7 +1121,7 @@ audio-volume-medium</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">3</property> <property name="top_attach">3</property>
<property name="width">5</property> <property name="width">4</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -1106,7 +1137,7 @@ audio-volume-medium</property>
<object class="GtkSizeGroup"> <object class="GtkSizeGroup">
<widgets> <widgets>
<widget name="random_button"/> <widget name="random_button"/>
<widget name="o_volume"/> <widget name="volume_button"/>
</widgets> </widgets>
</object> </object>
<object class="GtkSizeGroup"> <object class="GtkSizeGroup">
@ -1116,4 +1147,55 @@ audio-volume-medium</property>
<widget name="filter_how"/> <widget name="filter_how"/>
</widgets> </widgets>
</object> </object>
<object class="GtkPopover" id="pause_popover">
<property name="can_focus">False</property>
<property name="relative_to">play_button</property>
<property name="position">bottom</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Cancel "pause after" configuration?</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_popover_no">
<property name="label" translatable="yes">No</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="__gui_audio_pause_popover_popdown" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_popover_yes">
<property name="label" translatable="yes">Yes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="__gui_audio_pause_popover_clear" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface> </interface>

View File

@ -5,5 +5,5 @@ date
idle idle
settings settings
database database
queue playlist
audio audio

View File

@ -13,7 +13,6 @@ core_unit_test(Settings)
core_unit_test(Database) core_unit_test(Database)
add_subdirectory(tags/) add_subdirectory(tags/)
core_unit_test(Queue) core_unit_test(Playlist)
add_subdirectory(playlists/) add_subdirectory(playlists/)
core_unit_test(Audio) core_unit_test(Audio)

View File

@ -1,65 +1,54 @@
/* /*
* Copyright 2013 (c) Anna Schumaker. * Copyright 2013 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/core.h> #include <core/core.h>
#include <tests/test.h> #include <tests/test.h>
#include <tests/loop.h>
static unsigned int load_count = 0; static unsigned int load_count = 0;
static unsigned int state_count = 0; static unsigned int state_count = 0;
static int pause_count = 0; static int pause_count = 0;
static bool test_audio_seek(gint64 pos) static unsigned int test_wait_state(void)
{ {
bool ret = audio_seek(pos); GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(test_audio_pipeline()));
GstState state = audio_cur_state();
while (state != GST_STATE_PAUSED && state != GST_STATE_PLAYING) g_usleep(G_USEC_PER_SEC / 15);
state = audio_cur_state(); while (gst_bus_have_pending(bus))
return ret; test_main_loop();
gst_object_unref(bus);
return state_count;
} }
static void test_send_error() static void test_send_error()
{ {
GstMessage *message; GError *error = g_error_new(1, G_FILE_ERROR_BADF, "Simulated Error");
GError *error; test_audio_error(error, "Fake error for testing");
error = g_error_new(1, G_FILE_ERROR_BADF, "Simulated Error");
message = gst_message_new_error(GST_OBJECT(test_audio_player()),
error, "Fake error for testing");
audio_error(message);
gst_message_unref(message);
g_error_free(error); g_error_free(error);
} }
static void test_audio_load(struct track *track) { load_count++; } static void test_audio_load(struct track *track) { load_count++; }
static void test_change_state(GstState state) { state_count++; } static void test_change_state(GstState state) { state_count++; }
static void test_config_pause(int n) { pause_count = n; } static void test_config_pause(int n) { pause_count = n; }
static struct audio_ops test_audio_ops = { static struct audio_callbacks test_audio_cb = {
test_audio_load, .audio_cb_load = test_audio_load,
test_change_state, .audio_cb_state_change = test_change_state,
test_config_pause, .audio_cb_config_pause = test_config_pause,
};
static struct core_init_data test_init_data = {
.audio_ops = &test_audio_ops,
}; };
static void test_init() static void test_init()
{ {
g_assert_null(test_audio_player()); g_assert_null(test_audio_pipeline());
g_assert_null(audio_cur_track()); g_assert_null(audio_cur_track());
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL);
g_assert_null(audio_next()); g_assert_null(audio_next());
core_init(NULL, NULL, &test_init_data); core_init(NULL, NULL, NULL, &test_audio_cb, IDLE_SYNC);
test_loop_init();
g_assert_false(audio_load(NULL)); g_assert_false(audio_load(NULL));
g_assert_null(audio_next()); g_assert_null(audio_next());
@ -73,6 +62,7 @@ static void test_init()
g_assert_cmpuint(audio_get_volume(), ==, 100); g_assert_cmpuint(audio_get_volume(), ==, 100);
g_assert_null(audio_cur_track()); g_assert_null(audio_cur_track());
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpuint(load_count, ==, 0); g_assert_cmpuint(load_count, ==, 0);
g_assert_cmpuint(state_count, ==, 0); g_assert_cmpuint(state_count, ==, 0);
@ -80,7 +70,7 @@ static void test_init()
while (idle_run_task()) {}; while (idle_run_task()) {};
g_assert_null(audio_cur_track()); g_assert_null(audio_cur_track());
g_assert_nonnull(test_audio_player()); g_assert_nonnull(test_audio_pipeline());
} }
static void test_playback() static void test_playback()
@ -93,9 +83,9 @@ static void test_playback()
g_assert_true(audio_load(tracks[i])); g_assert_true(audio_load(tracks[i]));
else else
g_assert_false(audio_load(tracks[i])); g_assert_false(audio_load(tracks[i]));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "History"), ==, 1); g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "History")), ==, 1);
g_assert_cmpuint(load_count, ==, 1); g_assert_cmpuint(load_count, ==, 1);
g_assert_cmpuint(state_count, ==, 1); g_assert_cmpuint(test_wait_state(), ==, 1);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(audio_cur_track() == tracks[0]); g_assert(audio_cur_track() == tracks[0]);
g_assert_cmpuint(audio_duration(), ==, tracks[0]->tr_length * GST_SECOND); g_assert_cmpuint(audio_duration(), ==, tracks[0]->tr_length * GST_SECOND);
@ -110,19 +100,21 @@ static void test_playback()
g_assert_true(audio_pause()); g_assert_true(audio_pause());
g_assert_false(audio_pause()); g_assert_false(audio_pause());
g_assert_cmpuint(state_count, ==, 2); g_assert_cmpuint(test_wait_state(), ==, 2);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_true(test_audio_seek(5 * GST_SECOND)); g_assert_true(audio_seek(5 * GST_SECOND));
g_assert_cmpuint(audio_position(), ==, 5 * GST_SECOND); g_assert_cmpuint(test_wait_state(), ==, 3);
g_assert_true(test_audio_seek(42 * GST_SECOND)); g_assert_cmpuint(audio_position(), ==, 5 * GST_SECOND);
g_assert_cmpuint(audio_position(), ==, 42 * GST_SECOND); g_assert_true(audio_seek(42 * GST_SECOND));
g_assert_cmpuint(test_wait_state(), ==, 4);
g_assert_cmpuint(audio_position(), ==, 42 * GST_SECOND);
g_assert_true(audio_play()); g_assert_true(audio_play());
g_assert_false(audio_play()); g_assert_false(audio_play());
g_assert_cmpuint(state_count, ==, 3); g_assert_cmpuint(test_wait_state(), ==, 5);
g_assert_true(audio_pause()); g_assert_true(audio_pause());
g_assert_cmpuint(state_count, ==, 4); g_assert_cmpuint(test_wait_state(), ==, 6);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
/* Check duration again now that track is fully loaded. */ /* Check duration again now that track is fully loaded. */
@ -131,119 +123,168 @@ static void test_playback()
static void test_next() static void test_next()
{ {
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History"); struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
int i; int i;
state_count = 0; state_count = 0;
/* First, let's test getting tracks from a temporary queue. */ /* First, let's test getting tracks from a temporary queue. */
playlist_add(PL_SYSTEM, "Queued Tracks", track_get(2)); playlist_add(playlist_lookup(PL_SYSTEM, "Queued Tracks"), track_get(2));
playlist_add(PL_SYSTEM, "Queued Tracks", track_get(1)); playlist_add(playlist_lookup(PL_SYSTEM, "Queued Tracks"), track_get(1));
playlist_add(PL_SYSTEM, "Queued Tracks", track_get(0)); playlist_add(playlist_lookup(PL_SYSTEM, "Queued Tracks"), track_get(0));
for (i = 2; i >= 0; i--) { for (i = 2; i >= 0; i--) {
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, i + 1); g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Queued Tracks")), ==, i + 1);
if (i > 0) if (i > 0)
g_assert_cmpuint(audio_next()->tr_track, ==, track_get(i)->tr_track); g_assert_cmpuint(audio_next()->tr_track, ==, track_get(i)->tr_track);
else /* Simulate an error. */ else /* Simulate an error. */
test_send_error(); test_send_error();
g_assert(queue_at(history_q, 0) == track_get(i)); g_assert(playlist_at(history, 0) == track_get(i));
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(audio_cur_track() == track_get(i)); g_assert(audio_cur_track() == track_get(i));
} }
g_assert_cmpuint(state_count, ==, 3); g_assert_cmpuint(test_wait_state(), ==, 3);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 0); g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Queued Tracks")), ==, 0);
/* Tracks should now be picked from the collection. */ /* Tracks should now be picked from the collection. */
playlist_select(PL_SYSTEM, "Collection"); playlist_select(playlist_lookup(PL_SYSTEM, "Collection"));
for (i = 1; i <= 3; i++) { for (i = 1; i <= 3; i++) {
if (i < 3) if (i < 3)
g_assert_cmpuint(audio_next()->tr_track, ==, i); g_assert_cmpuint(audio_next()->tr_track, ==, i);
else /* Simulate an error. */ else /* Simulate an error. */
test_send_error(); test_send_error();
g_assert_cmpuint(queue_at(history_q, 0)->tr_track, ==, i); g_assert_cmpuint(playlist_at(history, 0)->tr_track, ==, i);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, i); g_assert_cmpuint(audio_cur_track()->tr_track, ==, i);
} }
g_assert_cmpuint(state_count, ==, 6); g_assert_cmpuint(test_wait_state(), ==, 6);
} }
static void test_prev() static void test_prev()
{ {
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History"); struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
struct track *track = queue_at(history_q, 0); struct track *track = playlist_at(history, 0);
state_count = 0; state_count = 0;
g_assert_cmpuint(audio_prev()->tr_track, ==, 2); g_assert_cmpuint(audio_prev()->tr_track, ==, 2);
g_assert(queue_at(history_q, 0) == track); g_assert(playlist_at(history, 0) == track);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 2); g_assert_cmpuint(audio_cur_track()->tr_track, ==, 2);
g_assert_cmpuint(state_count, ==, 1); g_assert_cmpuint(test_wait_state(), ==, 1);
g_assert_cmpuint(audio_prev()->tr_track, ==, 1); g_assert_cmpuint(audio_prev()->tr_track, ==, 1);
g_assert(queue_at(history_q, 0) == track); g_assert(playlist_at(history, 0) == track);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 1); g_assert_cmpuint(audio_cur_track()->tr_track, ==, 1);
g_assert_cmpuint(state_count, ==, 2); g_assert_cmpuint(test_wait_state(), ==, 2);
g_assert_true(audio_pause()); g_assert_true(audio_pause());
g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(0)->tr_track); g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(0)->tr_track);
g_assert(queue_at(history_q, 0) == track); g_assert(playlist_at(history, 0) == track);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(0)->tr_track); g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(0)->tr_track);
g_assert_cmpuint(state_count, ==, 4); g_assert_cmpuint(test_wait_state(), ==, 4);
g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(1)->tr_track); g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(1)->tr_track);
g_assert(queue_at(history_q, 0) == track); g_assert(playlist_at(history, 0) == track);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(1)->tr_track); g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(1)->tr_track);
g_assert_cmpuint(state_count, ==, 5); g_assert_cmpuint(test_wait_state(), ==, 5);
g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(2)->tr_track); g_assert_cmpuint(audio_prev()->tr_track, ==, track_get(2)->tr_track);
g_assert(queue_at(history_q, 0) == track); g_assert(playlist_at(history, 0) == track);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(2)->tr_track); g_assert_cmpuint(audio_cur_track()->tr_track, ==, track_get(2)->tr_track);
g_assert_cmpuint(state_count, ==, 6); g_assert_cmpuint(test_wait_state(), ==, 6);
} }
void test_autopause() void test_autopause()
{ {
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History"); struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
int i; int i;
audio_pause_after(3); g_assert_true(audio_pause_after(3));
g_assert_cmpuint(pause_count, ==, 3); g_assert_cmpint(pause_count, ==, 3);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
g_assert_false(audio_pause_after(-2));
g_assert_cmpint(pause_count, ==, 3);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
pause_count = 0; pause_count = 0;
audio_pause_after(3); g_assert_false(audio_pause_after(3));
g_assert_cmpuint(pause_count, ==, 0); g_assert_cmpint(pause_count, ==, 0);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
audio_pause_after(5); g_assert_true(audio_pause_after(-1));
g_assert_cmpuint(pause_count, ==, 5); g_assert_cmpint(pause_count, ==, -1);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_true(audio_pause_after(5));
g_assert_cmpint(pause_count, ==, 5);
g_assert_cmpint(audio_get_pause_count(), ==, 5);
state_count = 0; state_count = 0;
for (i = 4; i > -1; i--) { for (i = 4; i > -1; i--) {
audio_eos(); test_audio_eos();
g_assert_cmpuint(pause_count, ==, i); g_assert_cmpint(pause_count, ==, i);
g_assert_cmpint(audio_get_pause_count(), ==, i);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(queue_at(history_q, 0) == audio_cur_track()); g_assert(playlist_at(history, 0) == audio_cur_track());
} }
g_assert_cmpuint(state_count, ==, 5); g_assert_cmpuint(test_wait_state(), ==, 5);
audio_eos(); test_audio_eos();
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpint(pause_count, ==, -1); g_assert_cmpint(pause_count, ==, -1);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpuint(state_count, ==, 6); g_assert_cmpuint(test_wait_state(), ==, 6);
test_send_error(); test_send_error();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
} }
void test_filepath()
{
struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
const gchar *path = "tests/Music/Hyrule Symphony/01 - Title Theme.ogg";
const gchar *path2 = "tests/Music/Ocarina of Time/01 - Title Theme.ogg";
struct track *track = track_lookup(path);
load_count = 0;
playlist_generic_clear(history);
g_assert_false(audio_load_filepath(NULL));
g_assert_false(audio_load_filepath("tests/Music/00 - No Track.ogg"));
g_assert_cmpuint(playlist_size(history), ==, 0);
g_assert_cmpuint(load_count, ==, 0);
g_assert_true(audio_load_filepath(path));
g_assert_cmpuint(load_count, ==, 1);
g_assert(audio_cur_track() == track);
g_assert_cmpuint(playlist_size(history), ==, 1);
g_assert_true(playlist_has(history, audio_cur_track()));
g_assert_true(audio_load_filepath(path2));
g_assert_cmpuint(load_count, ==, 2);
g_assert(audio_cur_track() != track);
g_assert_null(audio_cur_track()->tr_library);
g_assert_cmpuint(playlist_size(history), ==, 1);
g_assert_false(playlist_has(history, audio_cur_track()));
g_assert_true(audio_load_filepath(path));
g_assert_cmpuint(load_count, ==, 3);
g_assert(audio_cur_track() == track);
g_assert_cmpuint(playlist_size(history), ==, 2);
g_assert_true(playlist_has(history, audio_cur_track()));
}
static void test_deinit() static void test_deinit()
{ {
core_deinit(); core_deinit();
test_loop_deinit();
g_assert_null(audio_cur_track()); g_assert_null(audio_cur_track());
g_assert_null(test_audio_player()); g_assert_null(test_audio_pipeline());
} }
@ -255,6 +296,7 @@ int main(int argc, char **argv)
g_test_add_func("/Core/Audio/Next", test_next); g_test_add_func("/Core/Audio/Next", test_next);
g_test_add_func("/Core/Audio/Previous", test_prev); g_test_add_func("/Core/Audio/Previous", test_prev);
g_test_add_func("/Core/Audio/Automatic Pausing", test_autopause); g_test_add_func("/Core/Audio/Automatic Pausing", test_autopause);
g_test_add_func("/Core/Audio/Filepath", test_filepath);
g_test_add_func("/Core/Audio/Deinitialization", test_deinit); g_test_add_func("/Core/Audio/Deinitialization", test_deinit);
return g_test_run(); return g_test_run();
} }

View File

@ -26,7 +26,7 @@ static struct int_entry *__int_alloc(unsigned int val)
return ent; return ent;
} }
static struct db_entry *int_alloc(const gchar *key) static struct db_entry *int_alloc(const gchar *key, unsigned int index)
{ {
unsigned int val; unsigned int val;
sscanf(key, "%u", &val); sscanf(key, "%u", &val);
@ -44,11 +44,9 @@ static gchar *int_key(struct db_entry *dbe)
return g_strdup_printf("%u", INT_ENTRY(dbe)->ie_val); return g_strdup_printf("%u", INT_ENTRY(dbe)->ie_val);
} }
static struct db_entry *int_read(struct file *f) static struct db_entry *int_read(struct file *f, unsigned int index)
{ {
unsigned int val; return &__int_alloc(file_readu(f))->ie_dbe;
file_readf(f, "%u", &val);
return &__int_alloc(val)->ie_dbe;
} }
static void int_write(struct file *file, struct db_entry *dbe) static void int_write(struct file *file, struct db_entry *dbe)
@ -67,16 +65,15 @@ static const struct db_ops int_ops = {
static void test_db_entry() static void test_db_entry()
{ {
struct file f = FILE_INIT_DATA("", "test_db_entry", 0);
struct int_entry *ent; struct int_entry *ent;
struct file f;
ent = INT_ENTRY(int_ops.dbe_alloc("1")); ent = INT_ENTRY(int_ops.dbe_alloc("1", 0));
g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0); g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0);
g_assert(ent->ie_dbe.dbe_data == ent); g_assert(ent->ie_dbe.dbe_data == ent);
g_assert_cmpuint(ent->ie_val, ==, 1); g_assert_cmpuint(ent->ie_val, ==, 1);
g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1"); g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1");
file_init(&f, "test_db_entry", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
int_ops.dbe_write(&f, &ent->ie_dbe); int_ops.dbe_write(&f, &ent->ie_dbe);
file_close(&f); file_close(&f);
@ -85,7 +82,7 @@ static void test_db_entry()
g_assert_cmpuint(test_free_count, ==, 1); g_assert_cmpuint(test_free_count, ==, 1);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
ent = INT_ENTRY(int_ops.dbe_read(&f)); ent = INT_ENTRY(int_ops.dbe_read(&f, 0));
file_close(&f); file_close(&f);
g_assert(ent->ie_dbe.dbe_data == ent); g_assert(ent->ie_dbe.dbe_data == ent);

View File

@ -16,7 +16,7 @@ void test_date()
.d_month = 0, .d_month = 0,
.d_day = 0, .d_day = 0,
}; };
struct file f = FILE_INIT("date", 0); struct file f = FILE_INIT_DATA("", "date", 0);
date_today(NULL); date_today(NULL);
date_set(NULL, 0, 0, 0); date_set(NULL, 0, 0, 0);

View File

@ -11,7 +11,7 @@ static void test_verify_constructor(struct file *file, gchar *fpath, gchar *ftmp
{ {
g_assert_null(file->f_file); g_assert_null(file->f_file);
g_assert_cmpuint(file_version(file), ==, OCARINA_MINOR_VERSION); g_assert_cmpuint(file_version(file), ==, OCARINA_MINOR_VERSION);
g_assert_cmpuint(file->f_mode, ==, OPEN_READ); g_assert_cmpuint(file->f_mode, ==, CLOSED);
g_assert_cmpstr_free(file_path(file), ==, fpath); g_assert_cmpstr_free(file_path(file), ==, fpath);
g_assert_cmpstr_free(file_write_path(file), ==, ftmp); g_assert_cmpstr_free(file_write_path(file), ==, ftmp);
} }
@ -20,21 +20,21 @@ static void test_invalid_file(gconstpointer path)
{ {
struct file file; struct file file;
file_init(&file, (gchar *)path, 0); file_init_data(&file, "", (gchar *)path, 0);
test_verify_constructor(&file, "", ""); test_verify_constructor(&file, "", "");
g_assert_false(file_open(&file, OPEN_READ)); g_assert_false(file_open(&file, OPEN_READ));
g_assert_null(file.f_file); g_assert_null(file.f_file);
g_assert_false(file_open(&file, OPEN_WRITE)); g_assert_false(file_open(&file, OPEN_WRITE));
g_assert_null(file.f_file); g_assert_null(file.f_file);
g_assert_cmpuint(file.f_mode, ==, OPEN_READ); g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_false(file_exists(&file)); g_assert_false(file_exists(&file));
} }
static void __test_file_subprocess() static void __test_file_subprocess()
{ {
struct file file = FILE_INIT("file.txt", 0); struct file file = FILE_INIT_DATA("", "file.txt", 0);
gchar *basepath, *filepath, *realpath; gchar *basepath, *filepath, *realpath;
basepath = g_strjoin("/", g_get_user_data_dir(), OCARINA_NAME, NULL); basepath = g_strjoin("/", g_get_user_data_dir(), OCARINA_NAME, NULL);
@ -45,7 +45,8 @@ static void __test_file_subprocess()
g_assert_false(file_exists(&file)); g_assert_false(file_exists(&file));
g_assert_false(file_open(&file, OPEN_READ)); g_assert_false(file_open(&file, OPEN_READ));
g_assert_true(file_open(&file, OPEN_WRITE)); g_assert_false(file_open(&file, CLOSED));
g_assert_true( file_open(&file, OPEN_WRITE));
g_assert_nonnull(file.f_file); g_assert_nonnull(file.f_file);
g_assert_cmpuint(file.f_mode, ==, OPEN_WRITE); g_assert_cmpuint(file.f_mode, ==, OPEN_WRITE);
g_assert_false(file_open(&file, OPEN_WRITE)); g_assert_false(file_open(&file, OPEN_WRITE));
@ -53,7 +54,7 @@ static void __test_file_subprocess()
g_assert_false(file_exists(&file)); g_assert_false(file_exists(&file));
file_close(&file); file_close(&file);
g_assert_null(file.f_file); g_assert_null(file.f_file);
g_assert_cmpuint(file.f_mode, ==, OPEN_WRITE); g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_true(file_exists(&file)); g_assert_true(file_exists(&file));
g_chmod(filepath, 0444); g_chmod(filepath, 0444);
@ -61,6 +62,7 @@ static void __test_file_subprocess()
g_chmod(filepath, 0200); g_chmod(filepath, 0200);
g_assert_false(file_open(&file, OPEN_READ)); g_assert_false(file_open(&file, OPEN_READ));
g_chmod(filepath, 0644); g_chmod(filepath, 0644);
g_assert_false(file_open(&file, CLOSED));
g_assert_true(file_open(&file, OPEN_READ)); g_assert_true(file_open(&file, OPEN_READ));
g_assert_nonnull(file.f_file); g_assert_nonnull(file.f_file);
@ -70,6 +72,7 @@ static void __test_file_subprocess()
g_assert_false(file_remove(&file)); g_assert_false(file_remove(&file));
g_assert_true(file_exists(&file)); g_assert_true(file_exists(&file));
file_close(&file); file_close(&file);
g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_true(file_remove(&file)); g_assert_true(file_remove(&file));
g_assert_false(file_exists(&file)); g_assert_false(file_exists(&file));
@ -90,10 +93,8 @@ static void test_file()
static void test_io() static void test_io()
{ {
struct file fout = FILE_INIT("file.txt", 0); struct file fout = FILE_INIT_DATA("", "file.txt", 0);
struct file fin = FILE_INIT("file.txt", 1); struct file fin = FILE_INIT_DATA("", "file.txt", 1);
char *res = NULL;
unsigned int i;
fout.f_version = 1; fout.f_version = 1;
g_assert_true(file_open(&fout, OPEN_WRITE)); g_assert_true(file_open(&fout, OPEN_WRITE));
@ -101,28 +102,28 @@ static void test_io()
file_writef(&fout, "2 FGHIJ KLMNO\n"); file_writef(&fout, "2 FGHIJ KLMNO\n");
file_writef(&fout, "3 \n"); file_writef(&fout, "3 \n");
file_writef(&fout, "4 5 PQRST\n"); file_writef(&fout, "4 5 PQRST\n");
file_writef(&fout, "-6 UV WX YZ\n");
file_close(&fout); file_close(&fout);
g_assert_true(file_exists(&fout)); g_assert_true(file_exists(&fout));
g_assert_true(file_open(&fin, OPEN_READ)); g_assert_true(file_open(&fin, OPEN_READ));
g_assert_cmpuint(file_version(&fin), ==, 1); g_assert_cmpuint(file_version(&fin), ==, 1);
g_assert_cmpuint(file_readf(&fin, "%u %ms\n", &i, &res), ==, 2); g_assert_cmpuint( file_readu(&fin), ==, 1);
g_assert_cmpuint(i, ==, 1); g_assert_cmpstr_free(file_readl(&fin), ==, "ABCDE");
g_assert_cmpstr_free(res, ==, "ABCDE");
g_assert_cmpuint(file_readf(&fin, "%u %m[^\n]\n", &i, &res), ==, 2); g_assert_cmpuint( file_readu(&fin), ==, 2);
g_assert_cmpuint(i, ==, 2); g_assert_cmpstr_free(file_readl(&fin), ==, "FGHIJ KLMNO");
g_assert_cmpstr_free(res, ==, "FGHIJ KLMNO");
g_assert_cmpuint(file_readf(&fin, "%u", &i), ==, 1); g_assert_cmpuint( file_readu(&fin), ==, 3);
res = file_readl(&fin); g_assert_cmpstr_free(file_readl(&fin), ==, "");
g_assert_cmpuint(i, ==, 3);
g_assert_cmpstr_free(res, ==, "");
g_assert_cmpuint(file_readf(&fin, "%u %m[^\n]", &i, &res), ==, 2); g_assert_cmpuint( file_readu(&fin), ==, 4);
g_assert_cmpuint(i, ==, 4); g_assert_cmpstr_free(file_readl(&fin), ==, "5 PQRST");
g_assert_cmpstr_free(res, ==, "5 PQRST");
g_assert_cmpint( file_readd(&fin), ==, -6);
g_assert_cmpstr_free(file_readw(&fin), ==, "UV");
g_assert_cmpstr_free(file_readl(&fin), ==, "WX YZ");
file_close(&fin); file_close(&fin);
g_assert_cmpuint(file_version(&fin), ==, OCARINA_MINOR_VERSION); g_assert_cmpuint(file_version(&fin), ==, OCARINA_MINOR_VERSION);
@ -130,8 +131,8 @@ static void test_io()
static void __test_versioning_subprocess(unsigned int out, unsigned int in) static void __test_versioning_subprocess(unsigned int out, unsigned int in)
{ {
struct file fout = FILE_INIT("file.txt", out); struct file fout = FILE_INIT_DATA("", "file.txt", out);
struct file fin = FILE_INIT("file.txt", in); struct file fin = FILE_INIT_DATA("", "file.txt", in);
fout.f_version = out; fout.f_version = out;
fin.f_version = in; fin.f_version = in;
@ -171,44 +172,67 @@ static void test_versioning_new()
static void test_cache() static void test_cache()
{ {
struct cache_file file = CACHE_FILE_INIT("dir", "file.txt");
struct cache_file copy = CACHE_FILE_INIT("dir", "copy.txt");
gchar *basepath, *filepath, *writepath; gchar *basepath, *filepath, *writepath;
struct file file, copy;
basepath = g_strjoin("/", g_get_user_cache_dir(), OCARINA_NAME, NULL); file_init_cache(&file, "dir", "file.txt");
filepath = g_strjoin("/", basepath, "dir", "file.txt", NULL); file_init_cache(&copy, "dir", "copy.txt");
writepath = g_strjoin("/", basepath, "dir", ".file.txt.tmp", NULL);
g_assert_null(file.cf_file); basepath = g_strjoin("/", g_get_user_cache_dir(), OCARINA_NAME, "dir", NULL);
g_assert_cmpstr(file.cf_name, ==, "file.txt"); filepath = g_strjoin("/", basepath, "file.txt", NULL);
g_assert_cmpstr(file.cf_subdir, ==, "dir"); writepath = g_strjoin("/", basepath, ".file.txt.tmp", NULL);
g_assert_cmpstr_free(cache_file_path(&file), ==, filepath); g_assert_null(file.f_file);
g_assert_cmpstr_free(cache_file_write_path(&file), ==, writepath); g_assert_cmpstr(file.f_name, ==, "file.txt");
g_assert_cmpstr(file.f_subdir, ==, "dir");
g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_cmpstr_free(file_path(&file), ==, filepath);
g_assert_cmpstr_free(file_write_path(&file), ==, writepath);
/* Test writing data to a cache file. */ /* Test writing data to a cache file. */
g_assert_false(cache_file_exists(&file)); g_assert_false(file_exists(&file));
g_assert_false(cache_file_open(&file, OPEN_READ)); g_assert_false(file_open(&file, OPEN_READ_BINARY));
g_assert_true(cache_file_open(&file, OPEN_WRITE)); g_assert_false(file_open(&file, CLOSED));
g_assert_nonnull(file.cf_file); g_assert_true( file_open(&file, OPEN_WRITE_BINARY));
g_assert_false(cache_file_open(&file, OPEN_WRITE)); g_assert_nonnull(file.f_file);
g_assert_cmpuint(file.f_mode, ==, OPEN_WRITE_BINARY);
g_assert_false(file_open(&file, OPEN_WRITE_BINARY));
g_assert_false(cache_file_exists(&file)); g_assert_false(file_exists(&file));
g_assert_cmpuint(cache_file_write(&file, "abcde", 5), ==, 5); g_assert_cmpuint(file_write(&file, "abcde", 5), ==, 5);
cache_file_close(&file); file_close(&file);
g_assert_null(file.cf_file); g_assert_null(file.f_file);
g_assert_true(cache_file_exists(&file)); g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_true(file_exists(&file));
g_assert_true(file_open(&file, OPEN_READ_BINARY));
g_assert_cmpuint(file.f_mode, ==, OPEN_READ_BINARY);
g_assert_cmpuint(file.f_version, ==, OCARINA_MINOR_VERSION);
g_assert_cmpuint(file.f_prev, ==, 0);
g_assert_cmpstr_free(file_read(&file), ==, "abcde");
file_close(&file);
/* Test importing a file into the cache. */ /* Test importing a file into the cache. */
g_assert_false(cache_file_exists(&copy)); g_assert_false(file_exists(&copy));
g_assert_false(cache_file_import(&copy, filepath)); g_assert_false(file_import(&copy, filepath));
g_assert_false(cache_file_exists(&copy)); g_assert_false(file_exists(&copy));
g_assert_true(cache_file_open(&copy, OPEN_WRITE)); g_assert_true( file_open(&copy, OPEN_WRITE_BINARY));
g_assert_false(cache_file_import(&copy, NULL)); g_assert_false(file_import(&copy, NULL));
g_assert_true(cache_file_import(&copy, filepath)); g_assert_true( file_import(&copy, filepath));
g_assert_false(cache_file_exists(&copy)); g_assert_false(file_exists(&copy));
cache_file_close(&copy); file_close(&copy);
g_assert_true(cache_file_exists(&copy)); g_assert_true(file_exists(&copy));
/* Test removing cache files. */
g_assert_true(file_remove(&copy));
g_assert_false(file_exists(&copy));
g_assert_true(file_exists(&file));
g_assert_true(g_file_test(basepath, G_FILE_TEST_EXISTS));
g_assert_true(file_remove(&file));
g_assert_false(file_exists(&file));
g_assert_false(g_file_test(basepath, G_FILE_TEST_EXISTS));
} }
int main(int argc, char **argv) int main(int argc, char **argv)

View File

@ -19,7 +19,7 @@ static void test_idle(gconstpointer arg)
unsigned int n = GPOINTER_TO_UINT(arg); unsigned int n = GPOINTER_TO_UINT(arg);
cur = -1; cur = -1;
idle_init(); idle_init(IDLE_ASYNC);
g_assert_cmpfloat(idle_progress(), ==, 1.0); g_assert_cmpfloat(idle_progress(), ==, 1.0);
g_assert_false(idle_run_task()); g_assert_false(idle_run_task());

456
tests/core/playlist.c Normal file
View File

@ -0,0 +1,456 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
#include <core/tags/tags.h>
static struct playlist *cb_playlist = NULL;
static struct track *cb_track = NULL;
static void test_pl_alloc(struct playlist *playlist)
{
cb_playlist = NULL;
}
static void test_pl_removed(struct playlist *playlist, struct track *track,
unsigned int n)
{
cb_playlist = playlist;
cb_track = track;
}
static void test_pl_callback(struct playlist *playlist, struct track *track)
{
cb_playlist = playlist;
cb_track = track;
}
static struct playlist_ops test_noop;
static struct playlist_ops test_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static struct playlist_callbacks test_cb = {
.pl_cb_alloc = test_pl_alloc,
.pl_cb_added = test_pl_callback,
.pl_cb_removed = test_pl_removed,
.pl_cb_updated = test_pl_callback,
};
static void test_null()
{
g_assert_null(playlist_new(PL_MAX_TYPE, "NULL"));
g_assert_null(playlist_new(PL_MAX_TYPE, NULL));
g_assert_false(playlist_delete(NULL));
playlist_generic_free(NULL);
playlist_generic_init(NULL, 0);
playlist_generic_deinit(NULL);
g_assert_null(playlist_lookup(PL_MAX_TYPE, "NULL"));
g_assert_null(playlist_lookup(PL_MAX_TYPE, NULL));
g_assert_null(playlist_get(PL_MAX_TYPE, 0));
g_assert(playlist_current() == playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_false(playlist_select(NULL));
g_assert(playlist_current() == playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_null(playlist_next());
playlist_selected(NULL);
playlist_played(NULL);
g_assert_false(playlist_add(NULL, NULL));
g_assert_false(playlist_add(NULL, track_get(0)));
g_assert_false(playlist_has(NULL, NULL));
g_assert_false(playlist_has(NULL, track_get(0)));
g_assert_cmpuint(playlist_size(NULL), ==, 0);
g_assert_false(playlist_remove(NULL, NULL));
g_assert_false(playlist_remove(NULL, track_get(0)));
playlist_set_random(NULL, true);
g_assert_false(playlist_sort(NULL, COMPARE_TRACK));
g_assert_false(playlist_sort(NULL, COMPARE_TRACK));
playlist_generic_resort(NULL);
playlist_clear_sort(NULL);
g_assert_false(playlist_rearrange(NULL, 0, 0));
playlist_set_search(NULL, NULL);
g_assert_false(playlist_generic_add(NULL, NULL));
g_assert_false(playlist_generic_add_front(NULL, NULL));
g_assert_false(playlist_generic_remove(NULL, NULL));
g_assert_null(playlist_at(NULL, 0));
playlist_generic_update(NULL, NULL);
playlist_generic_clear(NULL);
playlist_generic_save(NULL, NULL, PL_SAVE_ALL);
playlist_generic_load(NULL, NULL, PL_SAVE_ALL);
g_assert_null(playlist_iter_get(NULL, 0));
g_assert_false(playlist_iter_next(NULL));
g_assert_null(playlist_iter_track(NULL));
g_assert_cmpint(playlist_iter_index(NULL, NULL), ==, -1);
g_assert_false(playlist_current_set(NULL, 0));
g_assert_false(playlist_current_next(NULL));
g_assert_false(playlist_current_previous(NULL));
g_assert_null(playlist_current_track(NULL));
g_assert_cmpint(playlist_current_index(NULL), ==, -1);
}
static void test_playlist()
{
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
unsigned int ex_length = 0;
playlist_iter it;
int i;
g_assert_cmpuint(playlist_size(&p), ==, 0);
playlist_generic_init(&p, 0);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
for (i = 0; i < 13; i++) {
ex_length += track_get(i)->tr_length;
playlist_generic_add_front(&p, track_get(i));
g_assert_true(playlist_has(&p, track_get(i)));
g_assert_cmpuint(p.pl_length, ==, ex_length);
g_assert_cmpuint(playlist_size(&p), ==, i + 1);
g_assert(cb_playlist == &p);
g_assert(cb_track == track_get(i));
}
g_assert_false(playlist_generic_add_front(&p, NULL));
/* Trigger an update for each track. */
i = 13;
playlist_for_each(&p, it) {
g_assert_cmpuint(playlist_iter_track(it)->tr_track, ==, i--);
playlist_generic_update(&p, playlist_iter_track(it));
g_assert(cb_playlist == &p);
g_assert(cb_track == playlist_iter_track(it));
}
playlist_generic_update(&p, NULL);
g_assert(cb_playlist == &p);
g_assert_null(cb_track);
/* Remove all tracks. */
playlist_current_set(&p, 12);
for (i = 0; i < 13; i++) {
ex_length -= track_get(i)->tr_length;
g_assert_true(playlist_remove(&p, track_get(i)));
g_assert_false(playlist_has(&p, track_get(i)));
g_assert(cb_playlist == &p);
g_assert(cb_track == track_get(i));
g_assert_cmpuint(p.pl_length, ==, ex_length);
g_assert_cmpint(playlist_current_index(&p), ==, (11 - i));
g_assert_cmpuint(playlist_size(&p), ==, (12 - i));
if (i < 12)
g_assert_nonnull(p.pl_current);
}
g_assert_false(playlist_generic_remove(&p, NULL));
g_assert_false(playlist_has(&p, track_get(i)));
g_assert(cb_playlist == &p);
g_assert(cb_track == track_get(12));
g_assert_cmpuint(p.pl_length, ==, ex_length);
g_assert_null(p.pl_current);
/* Re-add the tracks! */
for (i = 0; i < 13; i++) {
ex_length += track_get(i)->tr_length;
g_assert_true( playlist_add(&p, track_get(i)));
g_assert_false(playlist_add(&p, track_get(i)));
g_assert_true(playlist_has(&p, track_get(i)));
g_assert(playlist_at(&p, i) == track_get(i));
g_assert_cmpuint(p.pl_length, ==, ex_length);
g_assert_cmpuint(playlist_size(&p), ==, i + 1);
g_assert(cb_playlist == &p);
g_assert(cb_track == track_get(i));
}
g_assert_false(playlist_generic_add(&p, NULL));
g_assert_cmpuint(p.pl_length, ==, ex_length);
/* Now clear the playlist. */
playlist_current_set(&p, 12);
playlist_generic_clear(&p);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_null(p.pl_current);
/* Set search text. */
playlist_set_search(&p, "test");
g_assert_cmpstr(p.pl_search[0], ==, "test");
playlist_set_search(&p, "Test Text");
g_assert_cmpstr(p.pl_search[0], ==, "test");
g_assert_cmpstr(p.pl_search[1], ==, "text");
/* Deinitialize the playlist. */
playlist_generic_deinit(&p);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_null(p.pl_current);
g_assert_null(p.pl_search);
g_assert_null(p.pl_sort);
}
static void test_sorting()
{
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
struct track *track;
unsigned int i;
playlist_generic_init(&p, 4, COMPARE_ARTIST, COMPARE_YEAR,
COMPARE_ALBUM, COMPARE_TRACK);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
playlist_clear_sort(&p);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
for (i = 0; i < 13; i++) {
track = track_get(i);
track->tr_count = (track->tr_track % 2) ? 4 : 2;
playlist_generic_add_front(&p, track);
}
cb_playlist = NULL;
cb_track = NULL;
p.pl_ops = &test_noop;
g_assert_false(playlist_sort(&p, COMPARE_TRACK));
g_assert_null(cb_playlist);
g_assert_null(cb_track);
p.pl_ops = &test_ops;
g_assert_true(playlist_sort(&p, COMPARE_TRACK));
g_assert(cb_playlist == &p);
g_assert_null(cb_track);
for (i = 0; i < 13; i++) {
track = playlist_at(&p, i);
g_assert_cmpuint(track->tr_track, ==, i + 1);
}
playlist_clear_sort(&p);
playlist_generic_resort(&p);
g_assert_true(playlist_sort(&p, COMPARE_COUNT));
for (i = 0; i < 13; i++) {
track = playlist_at(&p, i);
g_assert_cmpuint(track->tr_count, ==, (i < 6) ? 2 : 4);
}
g_assert_true(playlist_sort(&p, COMPARE_TRACK));
for (i = 0; i < 13; i++) {
track = playlist_at(&p, i);
g_assert_cmpuint(track->tr_count, ==, (i < 6) ? 2 : 4);
if (i < 6)
g_assert_cmpuint(track->tr_track, ==, (i + 1) * 2);
else
g_assert_cmpuint(track->tr_track, ==, (2 * i) - 11);
}
g_assert_true(playlist_sort(&p, COMPARE_COUNT));
for (i = 0; i < 13; i++) {
track = playlist_at(&p, i);
g_assert_cmpuint(track->tr_count, ==, (i < 7) ? 4 : 2);
if (i < 7)
g_assert_cmpuint(track->tr_track, ==, (2 * i) + 1);
else
g_assert_cmpuint(track->tr_track, ==, (2 * i) - 12);
}
/* Test inserting at a sorted position. */
playlist_generic_clear(&p);
playlist_clear_sort(&p);
playlist_sort(&p, COMPARE_TRACK);
playlist_sort(&p, COMPARE_TRACK);
for (i = 0; i < 13; i++) {
g_assert_false(playlist_has(&p, track_get(i)));
g_assert_true( playlist_add(&p, track_get(i)));
g_assert_false(playlist_add(&p, track_get(i)));
}
for (i = 0; i < 13; i++)
g_assert_cmpuint(playlist_at(&p, i)->tr_track, ==, 13 - i);
/* Deinitialize the playlist. */
playlist_generic_deinit(&p);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_null(p.pl_current);
g_assert_null(p.pl_sort);
}
static void test_rearranging()
{
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
struct track *track;
unsigned int i;
playlist_generic_init(&p, 4, COMPARE_ARTIST, COMPARE_YEAR,
COMPARE_ALBUM, COMPARE_TRACK);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
for (i = 0; i < 13; i++)
playlist_add(&p, track_get(i));
g_assert_false(playlist_rearrange(&p, 42, 4));
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
g_assert_false(playlist_rearrange(&p, 4, 42));
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
g_assert_false(playlist_rearrange(&p, 4, 4));
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
g_assert_true(playlist_rearrange(&p, 12, 0));
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
g_assert_true(playlist_rearrange(&p, 1, 12));
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
for (i = 0; i < 13; i++) {
track = playlist_at(&p, i);
if (i == 0)
g_assert_cmpuint(track->tr_track, ==, 13);
else if (i == 12)
g_assert_cmpuint(track->tr_track, ==, 1);
else
g_assert_cmpuint(track->tr_track, ==, i + 1);
}
playlist_generic_deinit(&p);
}
static void test_next()
{
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
struct track *track;
unsigned int i;
g_random_set_seed(0);
playlist_generic_init(&p, 0);
for (i = 0; i < 13; i++)
playlist_generic_add(&p, track_get(i));
g_assert_true(playlist_select(&p));
g_assert(playlist_current() == &p);
/* Test playlist_next() with wraparound, but no random. */
for (i = 0; i < (13 * 2); i++) {
track = playlist_next();
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, (i % 13) + 1);
g_assert_cmpuint(playlist_size(&p), ==, 13);
}
playlist_set_random(&p, true);
g_assert_true(p.pl_random);
/* rand() = { 10, 4, 6, 1, 8, 4, 1 } */
g_assert_cmpuint(playlist_next()->tr_track, ==, 9);
g_assert_cmpuint(playlist_next()->tr_track, ==, 13);
g_assert_cmpuint(playlist_next()->tr_track, ==, 6);
g_assert_cmpuint(playlist_next()->tr_track, ==, 7);
g_assert_cmpuint(playlist_next()->tr_track, ==, 2);
g_assert_cmpuint(playlist_next()->tr_track, ==, 6);
g_assert_cmpuint(playlist_next()->tr_track, ==, 1);
g_assert_cmpuint(playlist_size(&p), ==, 13);
/* Deinitialize the playlist. */
playlist_generic_deinit(&p);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_null(p.pl_current);
g_assert_null(p.pl_sort);
}
static void test_save_load()
{
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
struct playlist q = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test 2", 0, NULL);
struct playlist r = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
struct playlist s = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test 2", 0, NULL);
struct file f = FILE_INIT_DATA("", "test.playlist", 0);
unsigned int i;
for (i = 0; i < 13; i++) {
playlist_generic_add(&p, track_get(i));
playlist_generic_add(&q, track_get(i));
playlist_generic_add(&r, track_get(i));
}
playlist_set_random(&p, true);
playlist_clear_sort(&p);
playlist_sort(&p, COMPARE_TRACK);
playlist_current_set(&p, 3);
playlist_current_set(&q, 4);
g_assert_false(file_exists(&f));
g_assert_true( file_open(&f, OPEN_WRITE));
playlist_generic_save(&p, &f, PL_SAVE_METADATA);
playlist_generic_save(&q, &f, PL_SAVE_ALL);
file_close(&f);
g_assert_true(file_open(&f, OPEN_READ));
playlist_generic_load(&r, &f, PL_SAVE_METADATA);
playlist_generic_load(&s, &f, PL_SAVE_ALL);
file_close(&f);
g_assert_true(r.pl_random);
g_assert_cmpuint(playlist_current_index(&r), ==, 3);
g_assert_cmpuint(g_slist_length(r.pl_sort), ==, 1);
g_assert_cmpuint(GPOINTER_TO_UINT(r.pl_sort->data), ==, COMPARE_TRACK);
g_assert_false(s.pl_random);
g_assert_cmpuint(playlist_current_index(&s), ==, 4);
g_assert(playlist_current_track(&s) == playlist_at(&q, 4));
g_assert_cmpuint(g_slist_length(s.pl_sort), ==, 0);
g_assert_cmpuint(playlist_size(&s), ==, 13);
/* Deinitialize the playlist. */
playlist_generic_deinit(&p);
g_assert_cmpuint(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_null(p.pl_current);
}
int main(int argc, char **argv)
{
struct library *library;
int ret;
idle_init(IDLE_SYNC);
settings_init();
tags_init();
playlist_init(&test_cb);
while (idle_run_task()) {};
library = library_find("tests/Music");
track_add(library, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
track_add(library, "tests/Music/Hyrule Symphony/02 - Kokiri Forest.ogg");
track_add(library, "tests/Music/Hyrule Symphony/03 - Hyrule Field.ogg");
track_add(library, "tests/Music/Hyrule Symphony/04 - Hyrule Castle.ogg");
track_add(library, "tests/Music/Hyrule Symphony/05 - Lon Lon Ranch.ogg");
track_add(library, "tests/Music/Hyrule Symphony/06 - Kakariko Village.ogg");
track_add(library, "tests/Music/Hyrule Symphony/07 - Death Mountain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/08 - Zora's Domain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/09 - Gerudo Valley.ogg");
track_add(library, "tests/Music/Hyrule Symphony/10 - Ganondorf.ogg");
track_add(library, "tests/Music/Hyrule Symphony/11 - Princess Zelda.ogg");
track_add(library, "tests/Music/Hyrule Symphony/12 - Ocarina Medley.ogg");
track_add(library,
"tests/Music/Hyrule Symphony/13 - The Legend of Zelda Medley.ogg");
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Playlist/NULL", test_null);
g_test_add_func("/Core/Playlists/General", test_playlist);
g_test_add_func("/Core/Playlists/Sorting", test_sorting);
g_test_add_func("/Core/Playlists/Rearranging", test_rearranging);
g_test_add_func("/Core/Playlists/Next Track", test_next);
g_test_add_func("/Core/Playlist/Save and Load", test_save_load);
ret = g_test_run();
playlist_deinit();
tags_deinit();
settings_deinit();
idle_deinit();
return ret;
}

View File

@ -12,36 +12,47 @@
void test_artist() void test_artist()
{ {
struct artist *artist; struct playlist *playlist;
struct artist *artist;
g_assert_false(playlist_new(PL_ARTIST, "Koji Kondo")); g_assert_null(playlist_new(PL_ARTIST, "Koji Kondo"));
g_assert_null(playlist_get_queue(PL_ARTIST, "Koji Kondo")); g_assert_null(playlist_lookup(PL_ARTIST, "Koji Kondo"));
artist = artist_find("Koji Kondo"); artist = artist_find("Koji Kondo");
g_assert_null(artist->ar_playlist); g_assert_null(artist->ar_playlist);
g_assert_false(playlist_add(PL_ARTIST, "Koji Kondo", track_get(0))); g_assert_false(playlist_add(NULL, track_get(0)));
g_assert_false(playlist_select(PL_ARTIST, "Koji Kondo")); g_assert_false(playlist_select(NULL));
g_assert_false(playlist_select(PL_ARTIST, "Hajime Wakai"));
pl_artist_deinit(); pl_artist_deinit();
pl_artist_init(NULL); pl_artist_init();
g_assert_cmpuint(playlist_get_id(PL_ARTIST, "Koji Kondo"), ==, 0);
g_assert_cmpstr_free(playlist_get_name(PL_ARTIST, 0), ==, "Koji Kondo");
while (idle_run_task()) {}; while (idle_run_task()) {};
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 2); playlist = playlist_lookup(PL_ARTIST, "Koji Kondo");
g_assert_nonnull(artist->ar_playlist); g_assert_nonnull(playlist);
g_assert_false(playlist_remove(PL_ARTIST, "Koji Kondo", track_get(0)));
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 2);
g_assert(playlist_cur() != playlist_get(PL_ARTIST, "Koji Kondo")); g_assert_cmpuint(playlist->pl_id, ==, artist_index(artist));
g_assert_true(playlist_select(PL_ARTIST, "Koji Kondo")); g_assert_cmpuint(playlist_size(playlist), ==, 2);
g_assert_nonnull(artist->ar_playlist);
g_assert_false(playlist_remove(playlist, track_get(0)));
g_assert_cmpuint(playlist_size(playlist), ==, 2);
g_assert(playlist_current() != playlist);
g_assert_true(playlist_select(playlist));
g_assert_cmpuint(settings_get("core.playlist.cur.type"), ==, PL_ARTIST); g_assert_cmpuint(settings_get("core.playlist.cur.type"), ==, PL_ARTIST);
g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, 0); g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, 0);
g_assert(playlist_cur() == playlist_get(PL_ARTIST, "Koji Kondo")); g_assert(playlist_current() == playlist);
g_assert_false(playlist_select(playlist));
g_assert_false(playlist_delete(PL_ARTIST, "Koji Kondo")); playlist_selected(track_get(0));
playlist_played(track_get(0));
g_assert_cmpuint(playlist_next()->tr_track, ==, 1);
g_assert_cmpuint(playlist_size(playlist), ==, 2);
g_assert_true(playlist_has(playlist, track_get(0)));
g_assert_true(playlist_has(playlist, track_get(1)));
g_assert_false(playlist_delete(playlist));
pl_artist_deinit(); pl_artist_deinit();
g_assert_null(artist->ar_playlist); g_assert_null(artist->ar_playlist);
} }
@ -51,7 +62,7 @@ int main(int argc, char **argv)
struct library *library; struct library *library;
int ret; int ret;
idle_init_sync(); idle_init(IDLE_SYNC);
settings_init(); settings_init();
tags_init(); tags_init();
playlist_init(NULL); playlist_init(NULL);

View File

@ -13,90 +13,98 @@ void test_library()
struct playlist *playlist; struct playlist *playlist;
struct library *library; struct library *library;
g_assert_false(playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg")); g_assert_null(playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg"));
g_assert_null(playlist_lookup(PL_LIBRARY, "tests/Music"));
g_assert_null(playlist_get_queue(PL_LIBRARY, "tests/Music")); g_assert_false(playlist_select(NULL));
g_assert_false(playlist_select(PL_LIBRARY, "tests/Music"));
g_assert_true(playlist_new(PL_LIBRARY, "tests/Music"));
g_assert_false(playlist_new(PL_LIBRARY, "tests/Music"));
g_assert_nonnull(playlist_get_queue(PL_LIBRARY, "tests/Music"));
g_assert_cmpuint(playlist_get_id(PL_LIBRARY, "tests/Music"), ==, 0); playlist = playlist_new(PL_LIBRARY, "tests/Music");
g_assert_cmpstr_free(playlist_get_name(PL_LIBRARY, 0), ==, "tests/Music"); g_assert_nonnull(playlist);
g_assert_false(playlist_select(PL_LIBRARY, "tests/Music")); g_assert_null(playlist_new(PL_LIBRARY, "tests/Music"));
g_assert_nonnull(playlist_lookup(PL_LIBRARY, "tests/Music"));
library = library_get(0); library = library_get(0);
playlist = library->li_playlist;
g_assert_nonnull(library); g_assert_nonnull(library);
g_assert_nonnull(library->li_playlist); g_assert_nonnull(playlist);
g_assert_cmpuint(playlist->pl_id, ==, library_index(library));
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 0); g_assert_cmpuint(playlist_size(playlist), ==, 0);
while (idle_run_task()) {}; while (idle_run_task()) {};
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 48); g_assert_cmpuint(playlist_size(playlist), ==, 48);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Unplayed"), ==, 48); g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Unplayed")),
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Collection"), ==, 48); ==, 48);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Collection")),
==, 48);
g_assert(playlist_cur() != playlist_get(PL_LIBRARY, "tests/Music")); playlist = playlist_lookup(PL_LIBRARY, "tests/Music");
g_assert_true(playlist_select(PL_LIBRARY, "tests/Music")); g_assert(playlist_current() != playlist);
g_assert_true(playlist_select(playlist));
g_assert_cmpuint(settings_get("core.playlist.cur.type"), ==, PL_LIBRARY); g_assert_cmpuint(settings_get("core.playlist.cur.type"), ==, PL_LIBRARY);
g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, 0); g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, 0);
g_assert(playlist_cur() == playlist_get(PL_LIBRARY, "tests/Music")); g_assert(playlist_current() == playlist);
g_assert_false(playlist_select(playlist));
g_assert_false(playlist_add(PL_LIBRARY, "tests/Music", track_get(0))); g_assert_false(playlist_add(playlist, track_get(0)));
g_assert_false(playlist_add(PL_LIBRARY, "tests/Music", track_get(1))); g_assert_false(playlist_add(playlist, track_get(1)));
g_assert_false(playlist_remove(PL_LIBRARY, "tests/Music", track_get(0))); g_assert_false(playlist_remove(playlist, track_get(0)));
g_assert_false(playlist_remove(PL_LIBRARY, "tests/Music", track_get(1))); g_assert_false(playlist_remove(playlist, track_get(1)));
pl_library_deinit(); pl_library_deinit();
g_assert_null(playlist_get_queue(PL_LIBRARY, "tests/Music")); g_assert_null(playlist_lookup(PL_LIBRARY, "tests/Music"));
g_assert_null(library->li_playlist); g_assert_null(library->li_playlist);
pl_library_init(NULL); pl_library_init();
while (idle_run_task()) {}; while (idle_run_task()) {};
g_assert_nonnull(library->li_playlist);
g_assert_nonnull(playlist_get_queue(PL_LIBRARY, "tests/Music"));
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 48);
g_assert(playlist_get_queue(PL_LIBRARY, "tests/Music"));
g_assert_false(playlist_add(PL_LIBRARY, "tests/Music", track_get(0)));
g_assert_false(playlist_add(PL_LIBRARY, "tests/Music", track_get(1)));
g_assert_false(playlist_remove(PL_LIBRARY, "tests/Music", track_get(0)));
g_assert_false(playlist_remove(PL_LIBRARY, "tests/Music", track_get(1)));
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 48);
g_assert_false(playlist_get_random(PL_LIBRARY, "tests/Music"));
playlist_set_random(PL_LIBRARY, "tests/Music", true);
g_assert_true(playlist_get_random(PL_LIBRARY, "tests/Music"));
playlist_set_random(PL_LIBRARY, "tests/Music", false);
g_assert_false(playlist_get_random(PL_LIBRARY, "tests/Music"));
playlist = library->li_playlist; playlist = library->li_playlist;
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 3); g_assert_nonnull(library->li_playlist);
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_ARTIST, true); g_assert_cmpuint(playlist_size(playlist), ==, 48);
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 1);
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_YEAR, false); g_assert_false(playlist_add(playlist, track_get(0)));
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 2); g_assert_false(playlist_add(playlist, track_get(1)));
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_TRACK, false); g_assert_false(playlist_remove(playlist, track_get(0)));
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 3); g_assert_false(playlist_remove(playlist, track_get(1)));
g_assert_cmpuint(playlist_size(playlist), ==, 48);
playlist_selected(track_get(1));
playlist_played(track_get(1));
g_assert_false(playlist->pl_random);
playlist_set_random(playlist, true);
g_assert_true(playlist->pl_random);
playlist_set_random(playlist, false);
g_assert_false(playlist->pl_random);
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 4);
g_assert_true(playlist_rearrange(playlist, 15, 20));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
g_assert_true(playlist_sort(playlist, COMPARE_ARTIST));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 1);
g_assert_true(playlist_sort(playlist, COMPARE_YEAR));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 2);
g_assert_true(playlist_sort(playlist, COMPARE_TRACK));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 3);
g_rename("tests/Music/Hyrule Symphony/", "tests/Hyrule Symphony/"); g_rename("tests/Music/Hyrule Symphony/", "tests/Hyrule Symphony/");
playlist_update(PL_LIBRARY, "tests/Music"); pl_library_update(playlist);
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpuint(db_actual_size(track_db_get()), ==, 48); g_assert_cmpuint(db_actual_size(track_db_get()), ==, 48);
g_assert_cmpuint(track_db_get()->db_size, ==, 35); g_assert_cmpuint(track_db_get()->db_size, ==, 35);
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 35); g_assert_cmpuint(playlist_size(playlist), ==, 35);
g_rename("tests/Hyrule Symphony", "tests/Music/Hyrule Symphony/"); g_rename("tests/Hyrule Symphony", "tests/Music/Hyrule Symphony/");
playlist_update(PL_LIBRARY, "tests/Music"); pl_library_update(playlist);
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpuint(db_actual_size(track_db_get()), ==, 61); g_assert_cmpuint(db_actual_size(track_db_get()), ==, 61);
g_assert_cmpuint(track_db_get()->db_size, ==, 48); g_assert_cmpuint(track_db_get()->db_size, ==, 48);
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 48); g_assert_cmpuint(playlist_size(playlist), ==, 48);
g_assert_true( playlist_delete(PL_LIBRARY, "tests/Music")); g_assert_true(playlist_delete(playlist));
g_assert_false(playlist_delete(PL_LIBRARY, "tests/Music"));
g_assert_null(library_get(0)); g_assert_null(library_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Unplayed"), ==, 0); g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Unplayed")),
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Collection"), ==, 0); ==, 0);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Collection")),
==, 0);
g_assert_cmpuint(track_db_get()->db_size, ==, 0); g_assert_cmpuint(track_db_get()->db_size, ==, 0);
} }
@ -104,7 +112,7 @@ int main(int argc, char **argv)
{ {
int ret; int ret;
idle_init_sync(); idle_init(IDLE_SYNC);
settings_init(); settings_init();
tags_init(); tags_init();
playlist_init(NULL); playlist_init(NULL);

View File

@ -8,419 +8,387 @@
#include <tests/test.h> #include <tests/test.h>
#define __test_playlist_id(name, id) \ static inline struct playlist *__test_pl_favorites(void)
g_assert_cmpuint(playlist_get_id(PL_SYSTEM, name), ==, id); \ { return playlist_lookup(PL_SYSTEM, "Favorites"); }
g_assert_cmpstr_free(playlist_get_name(PL_SYSTEM, id), ==, name) static inline struct playlist *__test_pl_hidden(void)
{ return playlist_lookup(PL_SYSTEM, "Hidden"); }
#define __test_playlist_has(name, track, expected) \ static inline struct playlist *__test_pl_queued(void)
if (expected) \ { return playlist_lookup(PL_SYSTEM, "Queued Tracks"); }
g_assert_true(playlist_has(PL_SYSTEM, name, track)); \ static inline struct playlist *__test_pl_collection(void)
else \ { return playlist_lookup(PL_SYSTEM, "Collection"); }
g_assert_false(playlist_has(PL_SYSTEM, name, track)) static inline struct playlist *__test_pl_history(void)
{ return playlist_lookup(PL_SYSTEM, "History"); }
#define __test_playlist_state(name, ex_size, ex_track0, ex_track1) \ static inline struct playlist *__test_pl_unplayed(void)
g_assert_cmpuint(playlist_size(PL_SYSTEM, name), ==, ex_size); \ { return playlist_lookup(PL_SYSTEM, "Unplayed"); }
__test_playlist_has(name, track_get(0), ex_track0); \ static inline struct playlist *__test_pl_most_played(void)
__test_playlist_has(name, track_get(1), ex_track1) { return playlist_lookup(PL_SYSTEM, "Most Played"); }
static inline struct playlist *__test_pl_least_played(void)
#define __test_playlist_random(name) \ { return playlist_lookup(PL_SYSTEM, "Least Played"); }
g_assert_false(playlist_get_random(PL_SYSTEM, name)); \
playlist_set_random(PL_SYSTEM, name, true); \
g_assert_true(playlist_get_random(PL_SYSTEM, name)); \
playlist_set_random(PL_SYSTEM, name, false); \
g_assert_false(playlist_get_random(PL_SYSTEM, name))
#define __test_playlist_add(name) \
__test_playlist_state(name, 0, false, false); \
g_assert_true( playlist_add(PL_SYSTEM, name, track_get(0))); \
g_assert_false(playlist_add(PL_SYSTEM, name, track_get(0))); \
g_assert_true( playlist_add(PL_SYSTEM, name, track_get(1))); \
g_assert_false(playlist_add(PL_SYSTEM, name, track_get(1))); \
__test_playlist_state(name, 2, true, true)
#define __test_playlist_remove(name) \
g_assert_true( playlist_remove(PL_SYSTEM, name, track_get(0))); \
g_assert_false(playlist_remove(PL_SYSTEM, name, track_get(0))); \
g_assert_true( playlist_remove(PL_SYSTEM, name, track_get(1))); \
g_assert_false(playlist_remove(PL_SYSTEM, name, track_get(1))); \
__test_playlist_state(name, 0, false, false)
#define __test_playlist_clear(name, ex_size, ex_track0, ex_track1) \
g_assert_false(playlist_delete(PL_SYSTEM, name)); \
__test_playlist_state(name, ex_size, ex_track0, ex_track1)
#define __test_playlist_update(name, ex_size, ex_track0, ex_track1) \
playlist_update(PL_SYSTEM, name); \
while (idle_run_task()) {}; \
__test_playlist_state(name, ex_size, ex_track0, ex_track1)
#define __test_playlist_hide_track(name, ex_size, ex_track0, ex_track1) \
g_assert_true(playlist_add(PL_SYSTEM, "Hidden", track_get(0))); \
__test_playlist_state(name, ex_size, ex_track0, ex_track1); \
g_assert_false(playlist_add(PL_SYSTEM, name, track_get(0)))
#define __test_playlist_unhide_track(name, ex_size, ex_track0, ex_track1) \
g_assert_true(playlist_remove(PL_SYSTEM, "Hidden", track_get(0))); \
__test_playlist_state(name, ex_size, ex_track0, ex_track1)
#define __test_playlist_clear_hidden(name, ex_size, ex_track0, ex_track1) \
__test_playlist_clear("Hidden", 0, false, false); \
__test_playlist_state(name, ex_size, ex_track0, ex_track1);
#define __test_playlist_select(name, id) \
g_assert_true(playlist_select(PL_SYSTEM, name)); \
g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, id); \
g_assert(playlist_cur() == playlist_get(PL_SYSTEM, name))
#define __test_playlist_noselect(name) \
do { \
unsigned int id = settings_get("core.playlist.cur.id"); \
struct playlist *cur = playlist_cur(); \
g_assert_false(playlist_select(PL_SYSTEM, name)); \
g_assert_cmpuint(settings_get("core.playlist.cur.id"), ==, id); \
g_assert(playlist_cur() == cur); \
} while (0)
#define __test_playlist_reinit(name, ex_size, ex_track0, ex_track1) \
do { \
struct queue *queue; \
pl_system_deinit(); \
pl_system_init(NULL); \
queue = playlist_get_queue(PL_SYSTEM, name); \
g_assert_nonnull(queue); \
__test_playlist_state(name, 0, false, false); \
while (idle_run_task()) {}; \
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT)); \
__test_playlist_state(name, ex_size, ex_track0, ex_track1); \
} while (0)
static void test_invalid() static void test_init()
{ {
g_assert_null(playlist_get_queue(PL_SYSTEM, NULL)); struct library *library = library_find("tests/Music");
g_assert_null(playlist_get_queue(PL_SYSTEM, "Invalid")); struct playlist *playlist;
unsigned int i;
g_assert_cmpuint(playlist_get_id(PL_SYSTEM, NULL), ==, g_assert(playlist_current() == __test_pl_collection());
SYS_PL_NUM_PLAYLISTS);
g_assert_cmpuint(playlist_get_id(PL_SYSTEM, "Invalid"), ==,
SYS_PL_NUM_PLAYLISTS);
g_assert_null(playlist_get_name(PL_SYSTEM, SYS_PL_NUM_PLAYLISTS));
g_assert_false(playlist_new(PL_SYSTEM, "New Playlist")); for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
g_assert_false(playlist_delete(PL_SYSTEM, "Favorites")); playlist = playlist_get(PL_SYSTEM, i);
g_assert_nonnull(playlist);
g_assert(playlist_lookup(PL_SYSTEM, playlist->pl_name) == playlist);
g_assert_cmpuint(playlist->pl_id, ==, i);
g_assert_false(playlist_select(playlist));
__test_playlist_noselect(NULL); if (i == SYS_PL_QUEUED || i == SYS_PL_HISTORY) {
__test_playlist_noselect("Invalid"); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
} else
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 4);
}
playlist_update(PL_SYSTEM, NULL); /* Add tracks to the collection. */
g_assert_false(playlist_add(PL_SYSTEM, NULL, track_get(0))); track_add(library, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_false(playlist_remove(PL_SYSTEM, NULL, track_get(0))); track_add(library, "tests/Music/Hyrule Symphony/02 - Kokiri Forest.ogg");
g_assert_false(playlist_delete(PL_SYSTEM, NULL)); track_add(library, "tests/Music/Hyrule Symphony/03 - Hyrule Field.ogg");
g_assert_false(playlist_has(PL_SYSTEM, NULL, track_get(0))); pl_system_new_track(track_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, NULL), ==, 0); pl_system_new_track(track_get(1));
pl_system_new_track(track_get(2));
g_assert_false(playlist_get_random(PL_SYSTEM, NULL)); g_assert_null(playlist_new(PL_SYSTEM, "New Playlist"));
playlist_set_random(PL_SYSTEM, NULL, true); for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
g_assert_false(playlist_get_random(PL_SYSTEM, NULL)); playlist = playlist_get(PL_SYSTEM, i);
if (i == SYS_PL_COLLECTION || i == SYS_PL_UNPLAYED)
g_assert_cmpuint(playlist_size(playlist), ==, 3);
else
g_assert_cmpuint(playlist_size(playlist), ==, 0);
}
} }
static void test_favorites() static void test_random()
{ {
struct queue *queue = playlist_get_queue(PL_SYSTEM, "Favorites"); struct playlist *playlist;
unsigned int i;
g_assert_nonnull(queue); for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT)); playlist = playlist_get(PL_SYSTEM, i);
g_assert_true(queue_has_flag(queue, Q_REPEAT));
__test_playlist_id("Favorites", SYS_PL_FAVORITES); g_assert_false(playlist->pl_random);
__test_playlist_noselect("Favorites"); playlist_set_random(playlist, true);
__test_playlist_random("Favorites"); if (i == SYS_PL_HISTORY)
__test_playlist_add("Favorites"); g_assert_false(playlist->pl_random);
__test_playlist_select("Favorites", SYS_PL_FAVORITES); else
__test_playlist_reinit("Favorites", 2, true, true); g_assert_true(playlist->pl_random);
__test_playlist_update("Favorites", 2, true, true);
__test_playlist_clear("Favorites", 0, false, false); playlist_set_random(playlist, false);
__test_playlist_reinit("Favorites", 0, false, false); g_assert_false(playlist->pl_random);
__test_playlist_add("Favorites"); }
__test_playlist_remove("Favorites");
} }
static void test_hidden() static void test_sort()
{ {
struct queue *queue = playlist_get_queue(PL_SYSTEM, "Hidden"); struct playlist *playlist;
unsigned int i;
g_assert_nonnull(queue); for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
g_assert_null(playlist_get_queue(PL_SYSTEM, "Banned")); playlist = playlist_get(PL_SYSTEM, i);
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT)); playlist_clear_sort(playlist);
g_assert_true(queue_has_flag(queue, Q_REPEAT));
__test_playlist_id("Hidden", SYS_PL_HIDDEN); if (i == SYS_PL_HISTORY) {
__test_playlist_noselect("Hidden"); g_assert_false(playlist_sort(playlist, COMPARE_TRACK));
__test_playlist_random("Hidden"); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
__test_playlist_add("Hidden"); g_assert_false(playlist_sort(playlist, COMPARE_YEAR));
__test_playlist_select("Hidden", SYS_PL_HIDDEN); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
__test_playlist_reinit("Hidden", 2, true, true); } else {
__test_playlist_update("Hidden", 2, true, true); g_assert_true(playlist_sort(playlist, COMPARE_TRACK));
__test_playlist_clear("Hidden", 0, false, false); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 1);
__test_playlist_reinit("Hidden", 0, false, false); g_assert_true(playlist_sort(playlist, COMPARE_YEAR));
__test_playlist_add("Hidden"); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 2);
__test_playlist_remove("Hidden"); }
}
} }
static void test_queued() static void test_played()
{ {
struct queue *queue = playlist_get_queue(PL_SYSTEM, "Queued Tracks"); g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 0);
g_assert_nonnull(queue); track_played(track_get(0));
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT)); playlist_played(track_get(0));
g_assert_false(queue_has_flag(queue, Q_NO_SORT)); while (idle_run_task()) {}
g_assert_false(queue_has_flag(queue, Q_REPEAT));
g_assert_cmpuint(g_slist_length(queue->q_sort), ==, 0);
__test_playlist_id("Queued Tracks", SYS_PL_QUEUED); g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 2);
__test_playlist_noselect("Queued Tracks"); g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 0);
__test_playlist_random("Queued Tracks"); g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 1);
__test_playlist_add("Queued Tracks");
__test_playlist_select("Queued Tracks", SYS_PL_QUEUED);
__test_playlist_reinit("Queued Tracks", 2, true, true);
g_assert(playlist_next() == track_get(0)); g_assert_true(playlist_has(__test_pl_least_played(), track_get(0)));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 1); g_assert_true(playlist_has(__test_pl_unplayed(), track_get(1)));
playlist_select(PL_SYSTEM, "Collection"); g_assert_true(playlist_has(__test_pl_unplayed(), track_get(2)));
g_assert(playlist_next() == track_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 1);
playlist_select(PL_SYSTEM, "Queued Tracks");
g_assert(playlist_next() == track_get(1));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 0);
g_assert(playlist_next() == track_get(1));
__test_playlist_add("Queued Tracks"); track_played(track_get(0));
__test_playlist_remove("Queued Tracks");
__test_playlist_update("Queued Tracks", 0, false, false);
__test_playlist_add("Queued Tracks");
__test_playlist_clear("Queued Tracks", 0, false, false);
__test_playlist_reinit("Queued Tracks", 0, false, false);
}
static void test_collection()
{
struct queue *queue = playlist_get_queue(PL_SYSTEM, "Collection");
pl_system_deinit();
pl_system_init(NULL);
g_assert_nonnull(queue);
g_assert_true(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_true(queue_has_flag(queue, Q_REPEAT));
__test_playlist_id("Collection", SYS_PL_COLLECTION);
__test_playlist_select("Collection", SYS_PL_COLLECTION);
__test_playlist_random("Collection");
__test_playlist_add("Collection");
__test_playlist_hide_track("Collection", 1, false, true);
__test_playlist_reinit("Collection", 1, false, true);
__test_playlist_update("Collection", 1, false, true);
__test_playlist_unhide_track("Collection", 2, true, true);
__test_playlist_hide_track("Collection", 1, false, true);
__test_playlist_clear_hidden("Collection", 2, true, true);
__test_playlist_clear("Collection", 2, true, true);
}
static void test_history()
{
struct queue *queue = playlist_get_queue(PL_SYSTEM, "History");
g_assert_nonnull(queue);
g_assert_true(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_true(queue_has_flag(queue, Q_NO_SORT));
g_assert_true(queue_has_flag(queue, Q_REPEAT));
__test_playlist_id("History", SYS_PL_HISTORY);
__test_playlist_noselect("History");
g_assert_cmpuint(g_slist_length(queue->q_sort), ==, 0);
playlist_sort(PL_SYSTEM, "History", COMPARE_TRACK, true);
g_assert_cmpuint(g_slist_length(queue->q_sort), ==, 0);
g_assert_false(playlist_get_random(PL_SYSTEM, "History"));
playlist_set_random(PL_SYSTEM, "History", true);
g_assert_false(playlist_get_random(PL_SYSTEM, "History"));
__test_playlist_state("History", 0, false, false);
g_assert_true(playlist_add(PL_SYSTEM, "History", track_get(0)));
g_assert_true(playlist_add(PL_SYSTEM, "History", track_get(0)));
g_assert_true(playlist_add(PL_SYSTEM, "History", track_get(1)));
g_assert_true(playlist_add(PL_SYSTEM, "History", track_get(1)));
__test_playlist_state("History", 4, true, true);
__test_playlist_noselect("History");
g_assert(playlist_prev() == track_get(1));
g_assert(playlist_prev() == track_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "History"), ==, 4);
g_assert_true(playlist_add(PL_SYSTEM, "History", track_get(1)));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "History"), ==, 5);
g_assert(playlist_prev() == track_get(1));
__test_playlist_clear("History", 5, true, true);
__test_playlist_remove("History");
__test_playlist_update("History", 0, false, false);
}
static void test_unplayed()
{
struct queue *queue = playlist_get_queue(PL_SYSTEM, "Unplayed");
pl_system_deinit();
pl_system_init(NULL);
g_assert_nonnull(queue);
g_assert_true(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_true(queue_has_flag(queue, Q_REPEAT));
__test_playlist_id("Unplayed", SYS_PL_UNPLAYED);
__test_playlist_noselect("Unplayed");
__test_playlist_random("Unplayed");
__test_playlist_reinit("Unplayed", 2, true, true);
__test_playlist_remove("Unplayed");
track_get(1)->tr_count = 1;
g_assert_true( playlist_add(PL_SYSTEM, "Unplayed", track_get(0)));
g_assert_false(playlist_add(PL_SYSTEM, "Unplayed", track_get(1)));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Unplayed"), ==, 1);
__test_playlist_select("Unplayed", SYS_PL_UNPLAYED);
track_get(0)->tr_count = 1;
track_get(1)->tr_count = 0;
__test_playlist_update("Unplayed", 1, false, true);
track_get(0)->tr_count = 0;
__test_playlist_update("Unplayed", 2, true, true);
__test_playlist_hide_track("Unplayed", 1, false, true);
__test_playlist_reinit("Unplayed", 1, false, true);
__test_playlist_update("Unplayed", 1, false, true);
__test_playlist_unhide_track("Unplayed", 2, true, true);
__test_playlist_hide_track("Unplayed", 1, false, true);
__test_playlist_clear_hidden("Unplayed", 2, true, true);
__test_playlist_clear("Unplayed", 2, true, true);
}
static void test_most_played()
{
struct queue *most = playlist_get_queue(PL_SYSTEM, "Most Played");
/* Set average = (4 / 2) = 2 */
track_played(track_get(0)); track_played(track_get(0));
track_played(track_get(1)); track_played(track_get(1));
track_played(track_get(1)); playlist_played(track_get(0));
track_played(track_get(1)); playlist_played(track_get(1));
while (idle_run_task()) {}
pl_system_deinit(); g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 1);
pl_system_init(NULL); g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 1);
g_assert_nonnull(most); g_assert_true(playlist_has(__test_pl_most_played(), track_get(0)));
g_assert_true(queue_has_flag(most, Q_ADD_FRONT)); g_assert_true(playlist_has(__test_pl_least_played(), track_get(1)));
g_assert_true(queue_has_flag(most, Q_REPEAT)); g_assert_true(playlist_has(__test_pl_unplayed(), track_get(2)));
__test_playlist_id("Most Played", SYS_PL_MOST_PLAYED);
__test_playlist_noselect("Most Played");
__test_playlist_random("Most Played");
__test_playlist_reinit("Most Played", 1, false, true);
g_assert_false(playlist_remove(PL_SYSTEM, "Most Played", track_get(0)));
g_assert_true( playlist_remove(PL_SYSTEM, "Most Played", track_get(1)));
g_assert_false(playlist_remove(PL_SYSTEM, "Most Played", track_get(1)));
__test_playlist_state("Most Played", 0, false, false);
g_assert_false(playlist_add(PL_SYSTEM, "Most Played", track_get(0)));
g_assert_true( playlist_add(PL_SYSTEM, "Most Played", track_get(1)));
g_assert_false(playlist_add(PL_SYSTEM, "Most Played", track_get(1)));
__test_playlist_state("Most Played", 1, false, true);
__test_playlist_select("Most Played", SYS_PL_MOST_PLAYED);
track_get(0)->tr_count = 3;
track_get(1)->tr_count = 1;
__test_playlist_update("Most Played", 1, true, false);
__test_playlist_hide_track("Most Played", 0, false, false);
__test_playlist_reinit("Most Played", 0, false, false);
__test_playlist_update("Most Played", 0, false, false);
__test_playlist_unhide_track("Most Played", 1, true, false);
__test_playlist_hide_track("Most Played", 0, false, false);
__test_playlist_clear_hidden("Most Played", 1, true, false);
__test_playlist_clear("Most Played", 1, true, false);
} }
static void test_least_played() static void test_add()
{ {
struct queue *least = playlist_get_queue(PL_SYSTEM, "Least Played"); struct playlist *playlist;
unsigned int i;
/* Reset playcounts so track 1 is "least played" */ g_assert_true( playlist_add(__test_pl_favorites(), track_get(0)));
track_get(0)->tr_count = 3; g_assert_false(playlist_add(__test_pl_favorites(), track_get(0)));
track_get(1)->tr_count = 1; g_assert_true( playlist_add(__test_pl_queued(), track_get(0)));
g_assert_false(playlist_add(__test_pl_queued(), track_get(0)));
g_assert_true( playlist_add(__test_pl_history(), track_get(0)));
g_assert_true( playlist_add(__test_pl_history(), track_get(0)));
pl_system_deinit(); g_assert_cmpuint(playlist_size(__test_pl_favorites()), ==, 1);
pl_system_init(NULL); g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 2);
g_assert_nonnull(least); g_assert_true(playlist_has(__test_pl_favorites(), track_get(0)));
g_assert_true(queue_has_flag(least, Q_ADD_FRONT)); g_assert_true(playlist_has(__test_pl_queued(), track_get(0)));
g_assert_true(queue_has_flag(least, Q_REPEAT)); g_assert_true(playlist_has(__test_pl_history(), track_get(0)));
__test_playlist_id("Least Played", SYS_PL_LEAST_PLAYED); g_assert_true( playlist_add(__test_pl_hidden(), track_get(0)));
__test_playlist_noselect("Least Played"); g_assert_true( playlist_add(__test_pl_hidden(), track_get(1)));
__test_playlist_random("Least Played"); g_assert_true( playlist_add(__test_pl_hidden(), track_get(2)));
__test_playlist_reinit("Least Played", 1, false, true); g_assert_false(playlist_add(__test_pl_collection(), track_get(0)));
g_assert_false(playlist_add(__test_pl_collection(), track_get(1)));
g_assert_false(playlist_add(__test_pl_collection(), track_get(2)));
g_assert_false(playlist_add(__test_pl_most_played(), track_get(0)));
g_assert_false(playlist_add(__test_pl_least_played(), track_get(1)));
g_assert_false(playlist_add(__test_pl_unplayed(), track_get(2)));
g_assert_false(playlist_remove(PL_SYSTEM, "Least Played", track_get(0))); g_assert_cmpuint(playlist_size(__test_pl_hidden()), ==, 3);
g_assert_true( playlist_remove(PL_SYSTEM, "Least Played", track_get(1))); g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 0);
g_assert_false(playlist_remove(PL_SYSTEM, "Least Played", track_get(1))); g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 0);
__test_playlist_state("Least Played", 0, false, false); g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 0);
g_assert_false(playlist_add(PL_SYSTEM, "Least Played", track_get(0))); g_assert_true( playlist_has(__test_pl_hidden(), track_get(0)));
g_assert_true( playlist_add(PL_SYSTEM, "Least Played", track_get(1))); g_assert_true( playlist_has(__test_pl_hidden(), track_get(1)));
g_assert_false(playlist_add(PL_SYSTEM, "Least Played", track_get(1))); g_assert_true( playlist_has(__test_pl_hidden(), track_get(2)));
__test_playlist_state("Least Played", 1, false, true); g_assert_false(playlist_has(__test_pl_collection(), track_get(0)));
__test_playlist_select("Least Played", SYS_PL_LEAST_PLAYED); g_assert_false(playlist_has(__test_pl_collection(), track_get(1)));
g_assert_false(playlist_has(__test_pl_collection(), track_get(2)));
g_assert_false(playlist_has(__test_pl_most_played(), track_get(0)));
g_assert_false(playlist_has(__test_pl_least_played(), track_get(1)));
g_assert_false(playlist_has(__test_pl_unplayed(), track_get(2)));
track_get(0)->tr_count = 1; for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
track_get(1)->tr_count = 3; playlist = playlist_get(PL_SYSTEM, i);
__test_playlist_update("Least Played", 1, true, false);
__test_playlist_hide_track("Least Played", 0, false, false); switch (i) {
__test_playlist_reinit("Least Played", 0, false, false); case SYS_PL_FAVORITES:
__test_playlist_update("Least Played", 0, false, false); case SYS_PL_HIDDEN:
__test_playlist_unhide_track("Least Played", 1, true, false); case SYS_PL_QUEUED:
__test_playlist_hide_track("Least Played", 0, false, false); g_assert_true( playlist_select(playlist));
__test_playlist_clear_hidden("Least Played", 1, true, false); g_assert_false(playlist_select(playlist));
__test_playlist_clear("Least Played", 1, true, false); g_assert(playlist_current() == playlist);
break;
default:
g_assert_false(playlist_select(playlist));
g_assert(playlist_current() != playlist);
}
}
}
static void test_remove()
{
struct playlist *playlist;
unsigned int i;
playlist_set_random(__test_pl_queued(), true);
g_assert_true(playlist_sort(__test_pl_queued(), COMPARE_TRACK));
g_assert_true( playlist_remove(__test_pl_favorites(), track_get(0)));
g_assert_false(playlist_remove(__test_pl_favorites(), track_get(0)));
g_assert_true( playlist_remove(__test_pl_queued(), track_get(0)));
g_assert_false(playlist_remove(__test_pl_queued(), track_get(0)));
g_assert_false(playlist_remove(__test_pl_history(), track_get(0)));
g_assert_cmpuint(playlist_size(__test_pl_favorites()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 2);
g_assert_false(playlist_has(__test_pl_favorites(), track_get(0)));
g_assert_false(playlist_has(__test_pl_queued(), track_get(0)));
g_assert_true( playlist_has(__test_pl_history(), track_get(0)));
g_assert_true( playlist_remove(__test_pl_hidden(), track_get(0)));
g_assert_true( playlist_remove(__test_pl_hidden(), track_get(1)));
g_assert_true( playlist_remove(__test_pl_hidden(), track_get(2)));
g_assert_false(playlist_remove(__test_pl_most_played(), track_get(0)));
g_assert_false(playlist_remove(__test_pl_least_played(), track_get(1)));
g_assert_false(playlist_remove(__test_pl_unplayed(), track_get(2)));
g_assert_cmpuint(playlist_size(__test_pl_hidden()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 1);
g_assert_false(playlist_has(__test_pl_hidden(), track_get(0)));
g_assert_false(playlist_has(__test_pl_hidden(), track_get(1)));
g_assert_false(playlist_has(__test_pl_hidden(), track_get(2)));
g_assert_true( playlist_has(__test_pl_collection(), track_get(0)));
g_assert_true( playlist_has(__test_pl_collection(), track_get(1)));
g_assert_true( playlist_has(__test_pl_collection(), track_get(2)));
g_assert_true( playlist_has(__test_pl_most_played(), track_get(0)));
g_assert_true( playlist_has(__test_pl_least_played(), track_get(1)));
g_assert_true( playlist_has(__test_pl_unplayed(), track_get(2)));
g_assert_true(playlist_remove(__test_pl_collection(), track_get(0)));
g_assert_cmpuint(playlist_size(__test_pl_hidden()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 2);
g_assert_true (playlist_has(__test_pl_hidden(), track_get(0)));
g_assert_false(playlist_has(__test_pl_collection(), track_get(0)));
g_assert_true(playlist_remove(__test_pl_hidden(), track_get(0)));
g_assert_true(playlist_select(__test_pl_least_played()));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
playlist = playlist_get(PL_SYSTEM, i);
switch (i) {
case SYS_PL_QUEUED:
g_assert_false(playlist->pl_random);
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_HISTORY:
g_assert_false(playlist_select(playlist));
g_assert(playlist_current() != playlist);
break;
default:
g_assert_true( playlist_select(playlist));
g_assert_false(playlist_select(playlist));
g_assert(playlist_current() == playlist);
}
}
}
static void test_delete()
{
unsigned int i;
g_assert_true(playlist_add(__test_pl_favorites(), track_get(0)));
g_assert_true(playlist_add(__test_pl_hidden(), track_get(1)));
g_assert_true(playlist_add(__test_pl_hidden(), track_get(2)));
g_assert_true(playlist_add(__test_pl_queued(), track_get(0)));
g_assert_true(playlist_add(__test_pl_history(), track_get(0)));
playlist_set_random(__test_pl_queued(), true);
g_assert_true(playlist_sort(__test_pl_queued(), COMPARE_TRACK));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
g_assert_false(playlist_delete(playlist_get(PL_SYSTEM, i)));
g_assert_cmpuint(playlist_size(__test_pl_favorites()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_hidden()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 1);
g_assert_false(__test_pl_queued()->pl_random);
g_assert_cmpuint(g_slist_length(__test_pl_queued()->pl_sort), ==, 0);
}
static void test_next()
{
playlist_select(__test_pl_collection());
g_assert_cmpuint(playlist_next()->tr_track, ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
g_assert_cmpuint(playlist_next()->tr_track, ==, 2);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
g_assert_cmpuint(playlist_next()->tr_track, ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
g_assert_cmpuint(playlist_next()->tr_track, ==, 1);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 3);
playlist_add(__test_pl_queued(), track_get(0));
playlist_add(__test_pl_queued(), track_get(1));
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 2);
g_assert(playlist_current() == __test_pl_queued());
g_assert(playlist_next() == track_get(0));
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 2);
playlist_selected(track_get(0));
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 1);
g_assert(playlist_current() == __test_pl_queued());
g_assert(playlist_next() == track_get(1));
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 1);
playlist_selected(track_get(1));
g_assert_cmpuint(playlist_size(__test_pl_queued()), ==, 0);
g_assert(playlist_current() == __test_pl_collection());
playlist_generic_clear(__test_pl_history());
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 0);
g_assert_true(playlist_add(__test_pl_history(), track_get(0)));
g_assert_true(playlist_add(__test_pl_history(), track_get(1)));
g_assert_true(playlist_add(__test_pl_history(), track_get(2)));
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 3);
g_assert(playlist_prev() == track_get(1));
g_assert(playlist_prev() == track_get(0));
g_assert(playlist_prev() == track_get(2));
g_assert_cmpuint(playlist_size(__test_pl_history()), ==, 3);
}
static void test_delete_tracks()
{
unsigned int i;
g_assert_true(playlist_add(__test_pl_favorites(), track_get(0)));
g_assert_true(playlist_add(__test_pl_hidden(), track_get(1)));
g_assert_true(playlist_add(__test_pl_queued(), track_get(0)));
pl_system_delete_track(track_get(0));
pl_system_delete_track(track_get(1));
pl_system_delete_track(track_get(2));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
g_assert_cmpuint(playlist_size(playlist_get(PL_SYSTEM, i)), ==, 0);
} }
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
struct library *library;
int ret; int ret;
idle_init_sync(); idle_init(IDLE_SYNC);
settings_init(); settings_init();
tags_init(); tags_init();
playlist_init(NULL); playlist_init(NULL);
while (idle_run_task()) {}; while (idle_run_task()) {};
/* Add tracks to the collection. */
library = library_find("tests/Music");
track_add(library, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
track_add(library, "tests/Music/Hyrule Symphony/02 - Kokiri Forest.ogg");
g_test_init(&argc, &argv, NULL); g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Playlists/System/Invalid", test_invalid); g_test_add_func("/Core/Playlists/System/Init", test_init);
g_test_add_func("/Core/Playlists/System/Favorites", test_favorites); g_test_add_func("/Core/Playlists/System/Random", test_random);
g_test_add_func("/Core/Playlists/System/Hidden", test_hidden); g_test_add_func("/Core/Playlists/System/Sort", test_sort);
g_test_add_func("/Core/Playlists/System/Queued", test_queued); g_test_add_func("/Core/Playlists/System/Played", test_played);
g_test_add_func("/Core/Playlists/System/Collection", test_collection); g_test_add_func("/Core/Playlists/System/Add Tracks", test_add);
g_test_add_func("/Core/Playlists/System/History", test_history); g_test_add_func("/Core/Playlists/System/Remove Tracks", test_remove);
g_test_add_func("/Core/Playlists/System/Unplayed Tracks", test_unplayed); g_test_add_func("/Core/Playlists/System/Delete", test_delete);
g_test_add_func("/Core/Playlists/System/Most Played Tracks", test_most_played); g_test_add_func("/Core/Playlists/System/Next", test_next);
g_test_add_func("/Core/Playlists/System/Least Played Tracks", test_least_played); g_test_add_func("/Core/PLaylists/System/Delete Tracks", test_delete_tracks);
ret = g_test_run(); ret = g_test_run();
playlist_deinit(); playlist_deinit();

View File

@ -12,53 +12,74 @@
void test_user() void test_user()
{ {
struct database *db = pl_user_db_get(); struct database *db = pl_user_db_get();
struct playlist *playlist;
g_assert_cmpuint(db->db_size, ==, 0); g_assert_cmpuint(db->db_size, ==, 0);
g_assert_true( playlist_new(PL_USER, "Test Playlist")); playlist = playlist_new(PL_USER, "Test Playlist");
g_assert_false(playlist_new(PL_USER, "Test Playlist")); g_assert_nonnull(playlist);
g_assert_cmpuint(playlist->pl_id, ==, 0);
g_assert_cmpuint(playlist->pl_length, ==, 0);
g_assert_false(playlist->pl_random);
g_assert_null(playlist_new(PL_USER, "Test Playlist"));
g_assert_cmpuint(db->db_size, ==, 1); g_assert_cmpuint(db->db_size, ==, 1);
g_assert_false(playlist_get_random(PL_USER, "Test Playlist")); g_assert_false(playlist->pl_random);
playlist_set_random(PL_USER, "Test Playlist", true); playlist_set_random(playlist, true);
g_assert_true(playlist_get_random(PL_USER, "Test Playlist")); g_assert_true(playlist->pl_random);
playlist_set_random(PL_USER, "Test Playlist", false); playlist_set_random(playlist, false);
g_assert_false(playlist_get_random(PL_USER, "Test Playlist")); g_assert_false(playlist->pl_random);
g_assert_cmpuint(playlist_get_id(PL_USER, "Test Playlist"), ==, 0); g_assert(playlist_current() != playlist);
g_assert_cmpuint(playlist_get_id(PL_USER, "No Playlist"), ==, g_assert_false(playlist_select(playlist));
(unsigned int)-1); g_assert(playlist_current() != playlist);
g_assert_cmpstr_free(playlist_get_name(PL_USER, 0), ==, "Test Playlist"); g_assert_cmpuint(playlist_size(playlist), ==, 0);
g_assert_null(playlist_get_name(PL_USER, 1)); g_assert_false(playlist_has(playlist, track_get(0)));
g_assert_true( playlist_add(playlist, track_get(0)));
g_assert_false(playlist_add(playlist, track_get(0)));
g_assert_true( playlist_has(playlist, track_get(0)));
g_assert_cmpuint(playlist_size(playlist), ==, 1);
g_assert(playlist_cur() != playlist_get(PL_USER, "Test Playlist")); g_assert_true(playlist_select(playlist));
g_assert_true( playlist_select(PL_USER, "Test Playlist")); g_assert(playlist_current() == playlist);
g_assert(playlist_cur() == playlist_get(PL_USER, "Test Playlist")); g_assert_false(playlist_select(playlist));
g_assert_false(playlist_select(PL_USER, "No Playlist")); g_assert_false(playlist_select(NULL));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 0); g_assert(playlist_next() == track_get(0));
g_assert_false(playlist_has( PL_USER, "Test Playlist", track_get(0))); g_assert_true(playlist_has(playlist, track_get(0)));
g_assert_true( playlist_add( PL_USER, "Test Playlist", track_get(0))); g_assert_cmpuint(playlist_size(playlist), ==, 1);
g_assert_false(playlist_add( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0))); playlist_selected(track_get(0));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1); playlist_played(track_get(0));
playlist_update(PL_USER, "Test Playlist");
pl_user_deinit(); pl_user_deinit();
g_assert_cmpuint(db->db_size, ==, 0); g_assert_cmpuint(db->db_size, ==, 0);
pl_user_init(NULL); pl_user_init();
while (idle_run_task()) {}; while (idle_run_task()) {};
g_assert_cmpuint(db->db_size, ==, 1); g_assert_cmpuint(db->db_size, ==, 1);
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1); playlist = playlist_lookup(PL_USER, "Test Playlist");
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0))); g_assert_nonnull(playlist);
g_assert_true( playlist_remove(PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_remove(PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 0);
g_assert_true( playlist_delete(PL_USER, "Test Playlist")); g_assert_false(pl_user_rename(NULL, "Null Playlist"));
g_assert_false(playlist_delete(PL_USER, "Test Playlist")); g_assert_true( pl_user_rename(playlist, "New Playlist Name"));
g_assert_null(playlist_lookup(PL_USER, "Test Playlist"));
g_assert_cmpstr(playlist->pl_name, ==, "New Playlist Name");
g_assert_nonnull(playlist_new(PL_USER, "Test Playlist"));
g_assert_false(pl_user_rename(playlist, "Test Playlist"));
g_assert_cmpuint(playlist_size(playlist), ==, 1);
g_assert_true( playlist_has( playlist, track_get(0)));
g_assert_true( playlist_remove(playlist, track_get(0)));
g_assert_false(playlist_remove(playlist, track_get(0)));
g_assert_false(playlist_has( playlist, track_get(0)));
g_assert_cmpuint(playlist_size(playlist), ==, 0);
g_assert_true(playlist_delete(playlist));
playlist = playlist_lookup(PL_USER, "Test Playlist");
g_assert_true(playlist_delete(playlist));
g_assert_cmpuint(db->db_size, ==, 0); g_assert_cmpuint(db->db_size, ==, 0);
g_assert_cmpuint(db_actual_size(db), ==, 0); g_assert_cmpuint(db_actual_size(db), ==, 0);
} }
@ -67,7 +88,7 @@ int main(int argc, char **argv)
{ {
int ret; int ret;
idle_init_sync(); idle_init(IDLE_SYNC);
settings_init(); settings_init();
tags_init(); tags_init();
playlist_init(NULL); playlist_init(NULL);

View File

@ -1,525 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/queue.h>
#include <core/tags/tags.h>
#include <tests/test.h>
unsigned int count_init = 0;
unsigned int count_deinit = 0;
unsigned int count_added = 0;
unsigned int count_erase = 0;
unsigned int count_deleted = 0;
unsigned int count_cleared = 0;
unsigned int count_flags = 0;
unsigned int count_sort = 0;
unsigned int count_updated = 0;
bool can_erase = true;
static void *queue_op_init(struct queue *queue, void *data)
{
count_init++;
return GUINT_TO_POINTER(count_init);
}
static void queue_op_deinit(struct queue *queue)
{
count_deinit++;
}
static void queue_op_added(struct queue *queue, unsigned int pos)
{
count_added++;
}
static bool queue_op_erase(struct queue *queue, struct track *track)
{
count_erase++;
return can_erase;
}
static void queue_op_removed(struct queue *queue, unsigned int pos)
{
count_deleted++;
}
static void queue_op_cleared(struct queue *queue, unsigned int n)
{
count_cleared++;
}
static void queue_op_save(struct queue *queue, enum queue_flags flag)
{
if (flag == Q_SAVE_FLAGS)
count_flags++;
else if (flag == Q_SAVE_SORT)
count_sort++;
}
static void queue_op_updated(struct queue *queue, unsigned int pos)
{
count_updated++;
}
static const struct queue_ops test_ops = {
.qop_init = queue_op_init,
.qop_deinit = queue_op_deinit,
.qop_added = queue_op_added,
.qop_erase = queue_op_erase,
.qop_removed = queue_op_removed,
.qop_cleared = queue_op_cleared,
.qop_save = queue_op_save,
.qop_updated = queue_op_updated,
};
static void test_init()
{
struct queue q;
struct queue_iter it;
queue_init(&q, 0, NULL, NULL);
g_assert_cmpuint(count_init, ==, 0);
g_assert_null(q.q_private);
g_assert_cmpuint(q.q_cur.it_pos, ==, (unsigned int)-1);
g_assert_cmpuint(q.q_flags, ==, 0);
g_assert_cmpuint(q.q_length, ==, 0);
g_assert_null(q.q_sort);
g_assert_null(q.q_ops);
g_assert_null(queue_next(&q));
queue_iter_init(&q, &it);
g_assert_null(it.it_iter);
g_assert_cmpuint(it.it_pos, ==, (unsigned int)-1);
g_assert_null(queue_iter_val(&it));
queue_deinit(&q);
g_assert_cmpuint(count_deinit, ==, 0);
queue_init(&q, Q_ENABLED | Q_RANDOM, &test_ops, NULL);
g_assert_cmpuint(count_init, ==, 1);
g_assert_cmpuint(GPOINTER_TO_UINT(q.q_private), ==, 1);
g_assert_cmpuint(q.q_cur.it_pos, ==, (unsigned int)-1);
g_assert_cmpuint(q.q_flags, ==, Q_ENABLED | Q_RANDOM);
g_assert_cmpuint(q.q_length, ==, 0);
g_assert_null(q.q_sort);
g_assert(q.q_ops == &test_ops);
g_assert_null(queue_next(&q));
queue_deinit(&q);
g_assert_cmpuint(count_deinit, ==, 1);
}
static void test_flags()
{
struct queue q;
queue_init(&q, 0, &test_ops, NULL);
g_assert_cmpuint(q.q_flags, ==, 0);
g_assert_false(queue_has_flag(&q, Q_ENABLED));
g_assert_false(queue_has_flag(&q, Q_RANDOM));
g_assert_false(queue_has_flag(&q, Q_REPEAT));
g_assert_false(queue_has_flag(&q, Q_NO_SORT));
g_assert_false(queue_has_flag(&q, Q_SAVE_FLAGS));
g_assert_false(queue_has_flag(&q, Q_SAVE_SORT));
g_assert_false(queue_has_flag(&q, Q_ADD_FRONT));
queue_set_flag(&q, Q_ENABLED);
g_assert_cmpuint(q.q_flags, ==, Q_ENABLED);
g_assert_cmpuint(count_flags, ==, 0);
queue_unset_flag(&q, Q_ENABLED);
g_assert_cmpuint(q.q_flags, ==, 0);
g_assert_cmpuint(count_flags, ==, 0);
queue_set_flag(&q, Q_SAVE_FLAGS);
queue_set_flag(&q, Q_ENABLED);
queue_set_flag(&q, Q_RANDOM);
queue_set_flag(&q, Q_REPEAT);
queue_set_flag(&q, Q_NO_SORT);
queue_set_flag(&q, Q_ADD_FRONT);
g_assert_true(queue_has_flag(&q, Q_ENABLED));
g_assert_true(queue_has_flag(&q, Q_RANDOM));
g_assert_true(queue_has_flag(&q, Q_REPEAT));
g_assert_true(queue_has_flag(&q, Q_NO_SORT));
g_assert_true(queue_has_flag(&q, Q_SAVE_FLAGS));
g_assert_true(queue_has_flag(&q, Q_ADD_FRONT));
g_assert_cmpuint(count_flags, ==, 6);
queue_unset_flag(&q, Q_ENABLED);
queue_unset_flag(&q, Q_RANDOM);
queue_unset_flag(&q, Q_REPEAT);
queue_unset_flag(&q, Q_NO_SORT);
queue_unset_flag(&q, Q_ADD_FRONT);
queue_unset_flag(&q, Q_SAVE_FLAGS);
g_assert_cmpuint(q.q_flags, ==, 0);
g_assert_cmpuint(count_flags, ==, 11);
}
static void test_queue(gconstpointer arg)
{
unsigned int N = GPOINTER_TO_UINT(arg);
unsigned int ex_length = 0;
unsigned int ex_size = N;
struct queue_iter it;
struct track *track;
unsigned int i;
struct queue q;
count_added = 0;
count_deleted = 0;
count_cleared = 0;
count_updated = 0;
queue_init(&q, 0, &test_ops, NULL);
/* queue_add() */
for (i = 0; i < N; i++) {
track = track_get(i % 13);
ex_length += track->tr_length;
g_assert_cmpuint(queue_add(&q, track), ==, i);
g_assert_cmpuint(count_added, ==, i + 1);
}
g_assert_cmpuint(q.q_length, ==, ex_length);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
/* queue_iter_init() */
if (N > 0) {
queue_iter_init(&q, &it);
g_assert_nonnull(it.it_iter);
g_assert_cmpuint(it.it_pos, ==, 0);
}
/* queue_for_each() */
i = 0;
queue_for_each(&q, &it) {
g_assert_cmpuint(it.it_pos, ==, i);
g_assert(queue_iter_val(&it) == track_get(i % 13));
i++;
}
g_assert_cmpuint(i, ==, N);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
/* queue_remove_all() and queue_has() */
track = track_get(0);
ex_length -= track->tr_length * (N / 13);
ex_size -= (N / 13);
if (N > 0)
g_assert_true(queue_has(&q, track));
else
g_assert_false(queue_has(&q, track));
g_assert_cmpuint(queue_remove_all(&q, track), ==, N / 13);
g_assert_cmpuint(q.q_length, ==, ex_length);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
g_assert_false(queue_has(&q, track));
/* queue_erase() = false */
can_erase = false;
for (i = 0; i < ex_size; i += 11) {
queue_erase(&q, i);
g_assert_cmpuint(q.q_length, ==, ex_length);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
}
/* queue_remove() and queue_erase() == true */
can_erase = true;
track = track_get(1);
ex_length -= track->tr_length * (N / 13);
ex_size -= (N / 13);
for (i = 0; i < ex_size; i += 11) {
g_assert(queue_at(&q, i) == track);
if (i % 2 == 0)
queue_remove(&q, i);
else
queue_erase(&q, i);
}
g_assert_cmpuint(q.q_length, ==, ex_length);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
/* queue_updated() */
track = track_get(2);
queue_updated(&q, track);
g_assert_cmpuint(count_updated, ==, N / 13);
g_assert_null(queue_next(&q));
g_assert_cmpint(queue_size(&q), ==, ex_size);
/* Tracks should not be removed. */
queue_set_flag(&q, Q_ENABLED);
queue_set_flag(&q, Q_REPEAT);
for (i = 0; i < ex_size; i++) {
g_assert(queue_next(&q) == track_get((i % 11) + 2));
g_assert_cmpuint(count_updated, ==, (N / 13) + (2 * i) + 1);
queue_selected(&q, i);
g_assert_cmpuint(count_updated, ==, (N / 13) + (2 * i) + 2);
g_assert_cmpuint(queue_size(&q), ==, ex_size);
}
/* Tracks should be removed. */
queue_unset_flag(&q, Q_REPEAT);
for (i = 0; i < ex_size / 2; i++) {
track = queue_next(&q);
ex_length -= track->tr_length;
ex_size--;
g_assert(track == track_get((i % 11) + 2));
g_assert_cmpuint(queue_size(&q), ==, ex_size);
g_assert_cmpuint(q.q_length, ==, ex_length);
}
queue_clear(&q);
g_assert_cmpuint(count_cleared, ==, 1);
g_assert_cmpuint(queue_size(&q), ==, 0);
g_assert_cmpuint(q.q_length, ==, 0);
queue_deinit(&q);
g_assert_cmpuint(count_cleared, ==, 2);
g_assert_null(q.q_sort);
}
static void test_rand_select()
{
unsigned int i;
struct queue q;
g_random_set_seed(0);
queue_init(&q, Q_ENABLED | Q_RANDOM, &test_ops, NULL);
/* Call next() on an empty queue. */
for (i = 0; i < 13; i++) {
g_assert_null(queue_next(&q));
g_assert_cmpuint(queue_size(&q), ==, 0);
}
for (i = 0; i < 13; i++)
queue_add(&q, track_get(i));
queue_sort(&q, COMPARE_TRACK, true);
/*
* The comments below use the following notation:
* <val>: The value pointed to by q._cur.
* (val): The value selected by q.track_selected().
* [val]: The value picked by q.next().
*/
/* rand() = 9, q = { <>, 1, 2, 3, 4, 5, 6, 7, 8, [9], 10, 11, 12, 13 } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 9);
g_assert_cmpuint(queue_size(&q), ==, 12);
/* select = 6, q = { 1, 2, 3, 4, 5, 6, (7), <8>, 10, 11, 12, 13 } */
g_assert_cmpuint(queue_selected(&q, 6)->tr_track, ==, 7);
g_assert_cmpuint(queue_size(&q), ==, 11);
/* rand() = 10, q = { 1, 2, 3, 4, [5], <6>, 8, 10, 11, 12, 13 } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 5);
g_assert_cmpuint(queue_size(&q), ==, 10);
/* select = 7, q = { 1, 2, 3, <4>, 6, 8, 10, (11), 12, 13 } */
g_assert_cmpuint(queue_selected(&q, 7)->tr_track, ==, 11);
g_assert_cmpuint(queue_size(&q), ==, 9);
/* rand() = 6, q = { 1, 2, 3, [4], 6, 8, <10>, 12, 13 } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 4);
g_assert_cmpuint(queue_size(&q), ==, 8);
/* select = 2, q = { 1, 2, (<3>), 6, 8, 10, 12, 13 } */
g_assert_cmpuint(queue_selected(&q, 2)->tr_track, ==, 3);
g_assert_cmpuint(queue_size(&q), ==, 7);
/* rand() = 1, q = { 1, <2>, [6], 8, 10, 12, 13 } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 6);
g_assert_cmpuint(queue_size(&q), ==, 6);
/* select = 1, q = { 1, (<2>), 8, 10, 12, 13 } */
g_assert_cmpuint(queue_selected(&q, 1)->tr_track, ==, 2);
g_assert_cmpuint(queue_size(&q), ==, 5);
/* rand() = 4, q = { <1>, 8, 10, 12, [13] } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 13);
g_assert_cmpuint(queue_size(&q), ==, 4);
/* rand() = 1, q = { [1], 8, 10, <12> } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 1);
g_assert_cmpuint(queue_size(&q), ==, 3);
/* select = 1, q = { <>, 8, (10), 12 } */
g_assert_cmpuint(queue_selected(&q, 1)->tr_track, ==, 10);
g_assert_cmpuint(queue_size(&q), ==, 2);
/* rand() = 1, q = { <8>, [12] } */
g_assert_cmpuint(queue_next(&q)->tr_track, ==, 12);
g_assert_cmpuint(queue_size(&q), ==, 1);
/* select = 0, q = { (<8>) } */
g_assert_cmpuint(queue_selected(&q, 0)->tr_track, ==, 8);
g_assert_cmpuint(queue_size(&q), ==, 0);
/* q = { } */
g_assert_null(queue_next(&q));
g_assert_null(queue_selected(&q, 3));
queue_deinit(&q);
}
static void test_sorting()
{
unsigned int ex_count[] = { 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6 };
unsigned int ex_title[] = { 7, 10, 9, 4, 3, 6, 2, 5, 12, 11, 13, 1, 8 };
unsigned int ex_co_ti[] = { 4, 3, 6, 2, 5, 1, 7, 10, 9, 12, 11, 13, 8 };
struct track *track;
unsigned int i;
struct queue q;
queue_init(&q, Q_SAVE_SORT | Q_ADD_FRONT, &test_ops, NULL);
for (i = 0; i < 13; i++) {
track = track_get(i);
track->tr_count = (track->tr_track <= 6) ? 4 : 2;
queue_add(&q, track);
}
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_dbe.dbe_index, ==, 12 - i);
}
g_assert_cmpuint(count_sort, ==, 0);
queue_sort(&q, COMPARE_TRACK, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, i + 1);
}
g_assert_cmpuint(count_sort, ==, 1);
queue_sort(&q, COMPARE_COUNT, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, ex_count[i]);
}
g_assert_cmpuint(count_sort, ==, 2);
queue_set_flag(&q, Q_NO_SORT);
queue_sort(&q, COMPARE_TITLE, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, ex_count[i]);
}
g_assert_cmpuint(count_sort, ==, 2);
queue_unset_flag(&q, Q_NO_SORT);
queue_sort(&q, COMPARE_TITLE, true);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, ex_title[i]);
}
g_assert_cmpuint(count_sort, ==, 3);
queue_sort(&q, COMPARE_COUNT, true);
queue_sort(&q, COMPARE_TITLE, false);
queue_sort(&q, COMPARE_COUNT, false);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, ex_co_ti[i]);
}
g_assert_cmpuint(count_sort, ==, 6);
queue_unset_flag(&q, Q_SAVE_SORT);
queue_sort(&q, COMPARE_ARTIST, true);
queue_sort(&q, COMPARE_ALBUM, false);
queue_sort(&q, COMPARE_TRACK, false);
queue_sort(&q, COMPARE_TRACK, false);
for (i = 0; i < 13; i++) {
track = queue_at(&q, i);
g_assert_nonnull(track);
g_assert_cmpuint(track->tr_track, ==, 13 - i);
}
g_assert_cmpuint(count_sort, ==, 6);
queue_deinit(&q);
g_assert_cmpuint(q.q_length, ==, 0);
g_assert_cmpuint(queue_size(&q),==, 0);
g_assert_null(q.q_sort);
}
static void test_save_load()
{
struct file f = FILE_INIT("queue.q", 0);
struct queue q, r;
unsigned int i;
queue_init(&q, 0, &test_ops, NULL);
queue_init(&r, 0, &test_ops, NULL);
for (i = 0; i < 13; i++)
queue_add(&q, track_get(i));
g_assert_false(file_exists(&f));
g_assert_true(file_open(&f, OPEN_WRITE));
queue_save_tracks(&q, &f);
file_close(&f);
g_assert_true(file_exists(&f));
g_assert_true(file_open(&f, OPEN_READ));
queue_load_tracks(&r, &f);
file_close(&f);
g_assert_cmpuint(queue_size(&r), ==, 13);
for (i = 0; i < 13; i++)
g_assert_true(queue_has(&r, track_get(i)));
queue_deinit(&q);
queue_deinit(&r);
}
int main(int argc, char **argv)
{
struct library *library;
int ret;
idle_init_sync();
tags_init();
while (idle_run_task()) {}
library = library_find("tests/Music");
track_add(library, "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
track_add(library, "tests/Music/Hyrule Symphony/02 - Kokiri Forest.ogg");
track_add(library, "tests/Music/Hyrule Symphony/03 - Hyrule Field.ogg");
track_add(library, "tests/Music/Hyrule Symphony/04 - Hyrule Castle.ogg");
track_add(library, "tests/Music/Hyrule Symphony/05 - Lon Lon Ranch.ogg");
track_add(library, "tests/Music/Hyrule Symphony/06 - Kakariko Village.ogg");
track_add(library, "tests/Music/Hyrule Symphony/07 - Death Mountain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/08 - Zora's Domain.ogg");
track_add(library, "tests/Music/Hyrule Symphony/09 - Gerudo Valley.ogg");
track_add(library, "tests/Music/Hyrule Symphony/10 - Ganondorf.ogg");
track_add(library, "tests/Music/Hyrule Symphony/11 - Princess Zelda.ogg");
track_add(library, "tests/Music/Hyrule Symphony/12 - Ocarina Medley.ogg");
track_add(library,
"tests/Music/Hyrule Symphony/13 - The Legend of Zelda Medley.ogg");
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Queue/Initialization", test_init);
g_test_add_func("/Core/Queue/Flags", test_flags);
g_test_add_data_func("/Core/Queue/n = 0", GUINT_TO_POINTER( 0), test_queue);
g_test_add_data_func("/Core/Queue/n = 13", GUINT_TO_POINTER( 13), test_queue);
g_test_add_data_func("/Core/Queue/n = 100,009)", GUINT_TO_POINTER(100009), test_queue);
g_test_add_func("/Core/Queue/Random Next and Selection", test_rand_select);
g_test_add_func("/Core/Queue/Sorting", test_sorting);
g_test_add_func("/Core/Queue/Save and Load", test_save_load);
ret = g_test_run();
tags_deinit();
idle_deinit();
return ret;
}

View File

@ -7,7 +7,7 @@
static void test_settings() static void test_settings()
{ {
struct file f = FILE_INIT("settings", 0); struct file f = FILE_INIT_DATA("", "settings", 0);
settings_set(NULL, 0); settings_set(NULL, 0);
g_assert_cmpuint(settings_get(NULL), ==, 0); g_assert_cmpuint(settings_get(NULL), ==, 0);

View File

@ -108,6 +108,18 @@ void test_match_tokens()
g_assert_false(string_match_token("rule", tokens)); g_assert_false(string_match_token("rule", tokens));
} }
void test_subdirectory()
{
g_assert_false(string_is_subdir(NULL, NULL));
g_assert_false(string_is_subdir(NULL, "/a/b"));
g_assert_false(string_is_subdir("/a/b", NULL));
g_assert_false(string_is_subdir("/a", "/a/b"));
g_assert_false(string_is_subdir("/a", "/ab"));
g_assert_true( string_is_subdir("/a/b", "/a"));
g_assert_true( string_is_subdir("/a/b", "/a/b"));
g_assert_true( string_is_subdir("/a/b/c/d", "/a/b"));
}
void test_length() void test_length()
{ {
g_assert_cmpint(string_length(NULL), ==, 0); g_assert_cmpint(string_length(NULL), ==, 0);
@ -125,6 +137,7 @@ int main(int argc, char **argv)
g_test_add_func("/Core/String/Comparison/Tokens", test_compare_tokens); g_test_add_func("/Core/String/Comparison/Tokens", test_compare_tokens);
g_test_add_func("/Core/String/Matching", test_match); g_test_add_func("/Core/String/Matching", test_match);
g_test_add_func("/Core/String/Matching/Tokens", test_match_tokens); g_test_add_func("/Core/String/Matching/Tokens", test_match_tokens);
g_test_add_func("/Core/String/Subdirectory", test_subdirectory);
g_test_add_func("/Core/String/Length", test_length); g_test_add_func("/Core/String/Length", test_length);
return g_test_run(); return g_test_run();
} }

View File

@ -46,19 +46,18 @@ static void test_album()
struct album *album; struct album *album;
struct artist *koji; struct artist *koji;
struct genre *genre; struct genre *genre;
struct file f; struct file f = FILE_INIT_DATA("", "album_tag", 1);
idle_init(); idle_init(IDLE_SYNC);
koji = artist_find("Koji Kondo"); koji = artist_find("Koji Kondo");
genre = genre_find("Video Game Music"); genre = genre_find("Video Game Music");
album = ALBUM(album_ops->dbe_alloc("0/0/1998/Hyrule Symphony")); album = ALBUM(album_ops->dbe_alloc("0/0/1998/Hyrule Symphony", 0));
test_verify_hyrule(album, koji, genre); test_verify_hyrule(album, koji, genre);
g_assert_true( album_match_token(album, "hyrule")); g_assert_true( album_match_token(album, "hyrule"));
g_assert_true( album_match_token(album, "symphony")); g_assert_true( album_match_token(album, "symphony"));
g_assert_false(album_match_token(album, "skyward")); g_assert_false(album_match_token(album, "skyward"));
file_init(&f, "album_tag", 1);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "0 0 0 \n"); file_writef(&f, "0 0 0 \n");
album_ops->dbe_write(&f, &album->al_dbe); album_ops->dbe_write(&f, &album->al_dbe);
@ -66,11 +65,11 @@ static void test_album()
album_ops->dbe_free(&album->al_dbe); album_ops->dbe_free(&album->al_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
album = ALBUM(album_ops->dbe_read(&f)); album = ALBUM(album_ops->dbe_read(&f, 0));
test_verify_empty(album); test_verify_empty(album);
album_ops->dbe_free(&album->al_dbe); album_ops->dbe_free(&album->al_dbe);
album = ALBUM(album_ops->dbe_read(&f)); album = ALBUM(album_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_hyrule(album, koji, genre); test_verify_hyrule(album, koji, genre);
album_ops->dbe_free(&album->al_dbe); album_ops->dbe_free(&album->al_dbe);
@ -81,8 +80,8 @@ static void test_album_compare()
const struct db_ops *album_ops = test_album_ops(); const struct db_ops *album_ops = test_album_ops();
struct album *twilight, *skyward; struct album *twilight, *skyward;
twilight = ALBUM(album_ops->dbe_alloc("2006/Twilight Princess")); twilight = ALBUM(album_ops->dbe_alloc("2006/Twilight Princess", 0));
skyward = ALBUM(album_ops->dbe_alloc("2011/skyward sword")); skyward = ALBUM(album_ops->dbe_alloc("2011/skyward sword", 0));
g_assert_cmpint(album_compare(twilight, twilight), ==, 0); g_assert_cmpint(album_compare(twilight, twilight), ==, 0);
g_assert_cmpint(album_compare(twilight, skyward), ==, 1); g_assert_cmpint(album_compare(twilight, skyward), ==, 1);
@ -134,7 +133,7 @@ static void test_album_artwork()
const gchar *cache; const gchar *cache;
idle_deinit(); idle_deinit();
idle_init(); idle_init(IDLE_ASYNC);
cache = g_get_user_cache_dir(); cache = g_get_user_cache_dir();
o_path = g_build_filename(cache, "ocarina-test", "core", "tags", "album", "1998", "Ocarina of Time.jpg", NULL); o_path = g_build_filename(cache, "ocarina-test", "core", "tags", "album", "1998", "Ocarina of Time.jpg", NULL);
@ -182,7 +181,7 @@ int main(int argc, char **argv)
{ {
int ret; int ret;
idle_init_sync(); idle_init(IDLE_SYNC);
artist_db_init(); artist_db_init();
genre_db_init(); genre_db_init();
album_db_init(); album_db_init();

View File

@ -28,19 +28,17 @@ static void test_verify_koji(struct artist *artist)
static void test_artist() static void test_artist()
{ {
struct file f = FILE_INIT_DATA("", "artist_tag", 0);
const struct db_ops *artist_ops = test_artist_ops(); const struct db_ops *artist_ops = test_artist_ops();
struct artist *artist; struct artist *artist;
unsigned int i;
struct file f;
artist = ARTIST(artist_ops->dbe_alloc("Koji Kondo")); artist = ARTIST(artist_ops->dbe_alloc("Koji Kondo", 0));
test_verify_koji(artist); test_verify_koji(artist);
g_assert_true( artist_match_token(artist, "koji")); g_assert_true( artist_match_token(artist, "koji"));
g_assert_true( artist_match_token(artist, "kondo")); g_assert_true( artist_match_token(artist, "kondo"));
g_assert_false(artist_match_token(artist, "hajime")); g_assert_false(artist_match_token(artist, "hajime"));
file_init(&f, "artist_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n2 "); file_writef(&f, "1 \n2 ");
artist_ops->dbe_write(&f, &artist->ar_dbe); artist_ops->dbe_write(&f, &artist->ar_dbe);
@ -49,14 +47,14 @@ static void test_artist()
artist_ops->dbe_free(&artist->ar_dbe); artist_ops->dbe_free(&artist->ar_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i); file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f)); artist = ARTIST(artist_ops->dbe_read(&f, 0));
test_verify_empty(artist); test_verify_empty(artist);
g_free(artist->ar_name); g_free(artist->ar_name);
artist_ops->dbe_free(&artist->ar_dbe); artist_ops->dbe_free(&artist->ar_dbe);
file_readf(&f, "%u", &i); file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f)); artist = ARTIST(artist_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_koji(artist); test_verify_koji(artist);
g_free(artist->ar_name); g_free(artist->ar_name);
@ -68,8 +66,8 @@ static void test_artist_compare()
const struct db_ops *artist_ops = test_artist_ops(); const struct db_ops *artist_ops = test_artist_ops();
struct artist *koji, *hajime; struct artist *koji, *hajime;
koji = ARTIST(artist_ops->dbe_alloc("Koji Kondo")); koji = ARTIST(artist_ops->dbe_alloc("Koji Kondo", 0));
hajime = ARTIST(artist_ops->dbe_alloc("hajime wakai")); hajime = ARTIST(artist_ops->dbe_alloc("hajime wakai", 0));
g_assert_cmpint(artist_compare(koji, koji), ==, 0); g_assert_cmpint(artist_compare(koji, koji), ==, 0);
g_assert_cmpint(artist_compare(koji, hajime), ==, 1); g_assert_cmpint(artist_compare(koji, hajime), ==, 1);

View File

@ -27,18 +27,16 @@ static void test_verify_vg(struct genre *genre)
static void test_genre() static void test_genre()
{ {
struct file f = FILE_INIT_DATA("", "genre_tag", 0);
const struct db_ops *genre_ops = test_genre_ops(); const struct db_ops *genre_ops = test_genre_ops();
struct genre *genre; struct genre *genre;
unsigned int i;
struct file f;
genre = GENRE(genre_ops->dbe_alloc("Video Game Music")); genre = GENRE(genre_ops->dbe_alloc("Video Game Music", 0));
test_verify_vg(genre); test_verify_vg(genre);
g_assert_true( genre_match_token(genre, "video")); g_assert_true( genre_match_token(genre, "video"));
g_assert_true( genre_match_token(genre, "music")); g_assert_true( genre_match_token(genre, "music"));
g_assert_false(genre_match_token(genre, "rock")); g_assert_false(genre_match_token(genre, "rock"));
file_init(&f, "genre_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n1 "); file_writef(&f, "1 \n1 ");
genre_ops->dbe_write(&f, &genre->ge_dbe); genre_ops->dbe_write(&f, &genre->ge_dbe);
@ -47,14 +45,14 @@ static void test_genre()
genre_ops->dbe_free(&genre->ge_dbe); genre_ops->dbe_free(&genre->ge_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i); file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f)); genre = GENRE(genre_ops->dbe_read(&f, 0));
test_verify_empty(genre); test_verify_empty(genre);
g_free(genre->ge_name); g_free(genre->ge_name);
genre_ops->dbe_free(&genre->ge_dbe); genre_ops->dbe_free(&genre->ge_dbe);
file_readf(&f, "%u", &i); file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f)); genre = GENRE(genre_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_vg(genre); test_verify_vg(genre);
g_free(genre->ge_name); g_free(genre->ge_name);
@ -66,8 +64,8 @@ static void test_genre_compare()
const struct db_ops *genre_ops = test_genre_ops(); const struct db_ops *genre_ops = test_genre_ops();
struct genre *video, *game; struct genre *video, *game;
video = GENRE(genre_ops->dbe_alloc("Video Game Music")); video = GENRE(genre_ops->dbe_alloc("Video Game Music", 0));
game = GENRE(genre_ops->dbe_alloc("game music")); game = GENRE(genre_ops->dbe_alloc("game music", 0));
g_assert_cmpint(genre_compare(video, video), ==, 0); g_assert_cmpint(genre_compare(video, video), ==, 0);
g_assert_cmpint(genre_compare(video, game), ==, 1); g_assert_cmpint(genre_compare(video, game), ==, 1);

View File

@ -20,17 +20,16 @@ static void test_verify_link(struct library *library)
static void test_library() static void test_library()
{ {
struct file f = FILE_INIT_DATA("", "library_tag", 0);
const struct db_ops *library_ops = test_library_ops(); const struct db_ops *library_ops = test_library_ops();
struct library *link, *zelda, *library; struct library *link, *zelda, *library;
struct file f;
link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music")); link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music", 0));
zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/Music")); zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/Music", 0));
test_verify_link(link); test_verify_link(link);
test_verify_zelda(zelda); test_verify_zelda(zelda);
file_init(&f, "library_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
library_ops->dbe_write(&f, &link->li_dbe); library_ops->dbe_write(&f, &link->li_dbe);
file_writef(&f, "\n"); file_writef(&f, "\n");
@ -38,14 +37,14 @@ static void test_library()
file_close(&f); file_close(&f);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
library = LIBRARY(library_ops->dbe_read(&f)); library = LIBRARY(library_ops->dbe_read(&f, 0));
test_verify_link(library); test_verify_link(library);
g_assert_cmpstr_free(library_file(library, "navi.mp3"), ==, g_assert_cmpstr_free(library_file(library, "navi.mp3"), ==,
"/home/Link/Music/navi.mp3"); "/home/Link/Music/navi.mp3");
g_free(library->li_path); g_free(library->li_path);
library_ops->dbe_free(&library->li_dbe); library_ops->dbe_free(&library->li_dbe);
library = LIBRARY(library_ops->dbe_read(&f)); library = LIBRARY(library_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_zelda(library); test_verify_zelda(library);
g_assert_cmpstr_free(library_file(library, "impa.ogg"), ==, g_assert_cmpstr_free(library_file(library, "impa.ogg"), ==,
@ -69,14 +68,15 @@ static void test_library_db()
g_assert_cmpuint(library_db_get()->db_size, ==, 0); g_assert_cmpuint(library_db_get()->db_size, ==, 0);
library_db_init(); library_db_init();
library = library_lookup("/home/Zelda/Music"); g_assert_null(library_lookup("/home/Zelda/Music"));
g_assert_null(library);
library = library_find("/home/Zelda/Music"); library = library_find("/home/Zelda/Music");
test_verify_zelda(library); test_verify_zelda(library);
g_assert_cmpuint(library_db_get()->db_size, ==, 1); g_assert_cmpuint(library_db_get()->db_size, ==, 1);
g_assert(library_lookup("/home/Zelda/Music") == library); g_assert(library_lookup("/home/Zelda/Music") == library);
g_assert(library_lookup("/home/Zelda/Music/Ocarina") == library);
g_assert(library_find("/home/Zelda/Music") == library); g_assert(library_find("/home/Zelda/Music") == library);
g_assert(library_find("/home/Zelda/Music/Ocarina") == library);
g_assert(library_get(0) == library); g_assert(library_get(0) == library);
g_assert_null(library_get(1)); g_assert_null(library_get(1));

View File

@ -12,32 +12,43 @@
static struct track *test_alloc(const gchar *key) static struct track *test_alloc(const gchar *key)
{ {
const struct db_ops *track_ops = test_track_ops(); const struct db_ops *track_ops = test_track_ops();
struct db_entry *dbe = track_ops->dbe_alloc(key); struct db_entry *dbe = track_ops->dbe_alloc(key, 0);
g_assert_nonnull(dbe); g_assert_nonnull(dbe);
return TRACK(dbe); return TRACK(dbe);
} }
static void test_verify_tags(struct track *track) static void test_verify_tags(struct track *track, bool external)
{ {
g_assert_cmpstr(track->tr_album->al_name, ==, "Hyrule Symphony"); g_assert_cmpstr(track->tr_album->al_name, ==, "Hyrule Symphony");
g_assert_cmpuint(track->tr_album->al_year, ==, 1998); g_assert_cmpuint(track->tr_album->al_year, ==, 1998);
g_assert_cmpstr(track->tr_album->al_artist->ar_name, ==, "Koji Kondo"); g_assert_cmpstr(track->tr_album->al_artist->ar_name, ==, "Koji Kondo");
g_assert_cmpstr(track->tr_album->al_genre->ge_name, ==, "Game"); g_assert_cmpstr(track->tr_album->al_genre->ge_name, ==, "Game");
g_assert_cmpstr(track->tr_library->li_path, ==, "tests/Music");
if (external) {
g_assert_true(TRACK_IS_EXTERNAL(track));
g_assert_null(track->tr_library);
} else {
g_assert_false(TRACK_IS_EXTERNAL(track));
g_assert_cmpstr(track->tr_library->li_path, ==, "tests/Music");
}
} }
static void test_verify_track(struct track *track) static void test_verify_track(struct track *track, bool external)
{ {
const struct db_ops *track_ops = test_track_ops(); const struct db_ops *track_ops = test_track_ops();
test_verify_tags(track);
if (!external)
track_free_external(track);
test_verify_tags(track, external);
g_assert_cmpstr(track->tr_title, ==, "Title Theme"); g_assert_cmpstr(track->tr_title, ==, "Title Theme");
g_assert_cmpstr(track->tr_tokens[0], ==, "title"); g_assert_cmpstr(track->tr_tokens[0], ==, "title");
g_assert_cmpstr(track->tr_tokens[1], ==, "theme"); g_assert_cmpstr(track->tr_tokens[1], ==, "theme");
g_assert_null(track->tr_tokens[2]); g_assert_null(track->tr_tokens[2]);
g_assert_null(track->tr_alts[0]); g_assert_null(track->tr_alts[0]);
g_assert_cmpstr(track_ops->dbe_key(&track->tr_dbe), ==, if (!external)
"0/Hyrule Symphony/01 - Title Theme.ogg"); g_assert_cmpstr(track_ops->dbe_key(&track->tr_dbe), ==,
"0/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_cmpstr_free(track_path(track), ==, g_assert_cmpstr_free(track_path(track), ==,
"tests/Music/Hyrule Symphony/01 - Title Theme.ogg"); "tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_cmpstr_free(track_last_play(track), ==, "Never"); g_assert_cmpstr_free(track_last_play(track), ==, "Never");
@ -50,7 +61,7 @@ static void test_verify_track(struct track *track)
static void test_verify_notrack(struct track *track) static void test_verify_notrack(struct track *track)
{ {
const struct db_ops *track_ops = test_track_ops(); const struct db_ops *track_ops = test_track_ops();
test_verify_tags(track); test_verify_tags(track, false);
g_assert_cmpstr(track->tr_title, ==, ""); g_assert_cmpstr(track->tr_title, ==, "");
g_assert_null(track->tr_tokens[0]); g_assert_null(track->tr_tokens[0]);
@ -72,17 +83,16 @@ static void test_track()
time_t rawtime = time(NULL); time_t rawtime = time(NULL);
struct tm *now = localtime(&rawtime); struct tm *now = localtime(&rawtime);
struct track *track; struct track *track;
struct file f; struct file f = FILE_INIT_DATA("", "track_tag", 1);
gchar *date; gchar *date;
file_init(&f, "track_tag", 1);
g_assert_nonnull(library_find("tests/Music")); g_assert_nonnull(library_find("tests/Music"));
date = string_tm2str(now); date = string_tm2str(now);
track = test_alloc("0/Hyrule Symphony/01 - Title Theme.ogg"); track = test_alloc("0/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_nonnull(track); g_assert_nonnull(track);
track->tr_dbe.dbe_index = 0; track->tr_dbe.dbe_index = 0;
test_verify_track(track); test_verify_track(track, false);
g_assert_true( track_match_token(track, "title")); g_assert_true( track_match_token(track, "title"));
g_assert_true( track_match_token(track, "theme")); g_assert_true( track_match_token(track, "theme"));
@ -98,16 +108,16 @@ static void test_track()
track_ops->dbe_free(&track->tr_dbe); track_ops->dbe_free(&track->tr_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
track = TRACK(track_ops->dbe_read(&f)); track = TRACK(track_ops->dbe_read(&f, 0));
track->tr_dbe.dbe_index = 0; track->tr_dbe.dbe_index = 0;
test_verify_notrack(track); test_verify_notrack(track);
g_free(track->tr_path); g_free(track->tr_path);
track_ops->dbe_free(&track->tr_dbe); track_ops->dbe_free(&track->tr_dbe);
track = TRACK(track_ops->dbe_read(&f)); track = TRACK(track_ops->dbe_read(&f, 0));
track->tr_dbe.dbe_index = 0; track->tr_dbe.dbe_index = 0;
test_verify_track(track); test_verify_track(track, false);
file_close(&f); file_close(&f);
track_played(track); track_played(track);
@ -119,6 +129,27 @@ static void test_track()
g_free(date); g_free(date);
} }
static void test_track_external()
{
struct track *track = track_alloc_external(
"tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_nonnull(track);
test_verify_track(track, true);
g_assert_cmpuint(track_db_get()->db_size, ==, 0);
g_assert_cmpuint(track_db_count_unplayed(), ==, 0);
track_played(track);
g_assert_cmpuint(track_db_count_unplayed(), ==, 0);
g_assert_cmpuint(track_db_count_plays(), ==, 0);
track_free_external(track);
g_assert_cmpuint(track_db_count_unplayed(), ==, 0);
g_assert_cmpuint(track_db_count_plays(), ==, 0);
g_assert_null(track_alloc_external("/home/Zelda/Music/ocarina.ogg"));
}
static void test_track_compare() static void test_track_compare()
{ {
const struct db_ops *track_ops = test_track_ops(); const struct db_ops *track_ops = test_track_ops();
@ -209,8 +240,14 @@ static void __test_track_db_subprocess()
g_assert_cmpuint(track_db_count_unplayed(), ==, 0); g_assert_cmpuint(track_db_count_unplayed(), ==, 0);
g_assert_cmpuint(track_db_count_plays(), ==, 0); g_assert_cmpuint(track_db_count_plays(), ==, 0);
g_assert_cmpuint(track_db_average_plays(), ==, 0); g_assert_cmpuint(track_db_average_plays(), ==, 0);
g_assert_null(track_lookup(NULL));
g_assert_null(track_lookup("/home/Zelda/Music/ocarina.ogg"));
g_assert_null(track_lookup(path));
track = track_add(library, path); track = track_add(library, path);
test_verify_track(track); test_verify_track(track, false);
g_assert(track_lookup(path) == track);
g_assert_cmpuint(track_db_count_unplayed(), ==, 1); g_assert_cmpuint(track_db_count_unplayed(), ==, 1);
g_assert_null(track_add(library, path)); g_assert_null(track_add(library, path));
@ -226,7 +263,7 @@ static void __test_track_db_subprocess()
track_db_commit(); track_db_commit();
db_load(&track_db); db_load(&track_db);
g_assert_cmpuint(track_db.db_size, ==, 1); g_assert_cmpuint(track_db.db_size, ==, 1);
test_verify_track(TRACK(db_first(&track_db))); test_verify_track(TRACK(db_first(&track_db)), false);
/* Make sure our unplayed count isn't skewed */ /* Make sure our unplayed count isn't skewed */
db_deinit(&track_db); db_deinit(&track_db);
@ -302,12 +339,13 @@ int main(int argc, char **argv)
int ret; int ret;
setlocale(LC_TIME, "C"); setlocale(LC_TIME, "C");
idle_init_sync(); idle_init(IDLE_SYNC);
tags_init(); tags_init();
while (idle_run_task()) {} while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL); g_test_init(&argc, &argv, NULL);
g_test_add_func("/Core/Tags/Track", test_track); g_test_add_func("/Core/Tags/Track", test_track);
g_test_add_func("/Core/Tags/Track/External", test_track_external);
g_test_add_func("/Core/Tags/Track/Comparison", test_track_compare); g_test_add_func("/Core/Tags/Track/Comparison", test_track_compare);
g_test_add_func("/Core/Tags/Track/Database", test_track_db); g_test_add_func("/Core/Tags/Track/Database", test_track_db);
ret = g_test_run(); ret = g_test_run();

View File

@ -5,6 +5,6 @@ model
filter filter
treeview treeview
sidebar sidebar
view
playlist playlist
artwork
audio audio

View File

@ -13,6 +13,6 @@ gui_unit_test(Treeview)
gui_unit_test(Sidebar) gui_unit_test(Sidebar)
add_subdirectory(playlists/) add_subdirectory(playlists/)
gui_unit_test(View)
gui_unit_test(Playlist) gui_unit_test(Playlist)
gui_unit_test(Artwork)
gui_unit_test(Audio) gui_unit_test(Audio)

96
tests/gui/artwork.c Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <gui/artwork.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/playlist.h>
#include <gui/sidebar.h>
#include <gui/treeview.h>
static void test_audio_load(struct track *track)
{ gui_artwork_set_cover(); }
static void test_change_state(GstState state) { }
static void test_config_pause(int n) { }
static struct audio_callbacks test_audio_cb = {
.audio_cb_load = test_audio_load,
.audio_cb_state_change = test_change_state,
.audio_cb_config_pause = test_config_pause,
};
#ifdef CONFIG_ALBUM_ART_TEST
enum idle_sync_t test_sync = IDLE_ASYNC;
#else /* CONFIG_ALBUM_ART_TEST */
enum idle_sync_t test_sync = IDLE_SYNC;
#endif /* CONFIG_ALBUM_ART_TEST */
static void test_artwork(void)
{
const gchar *name;
GtkIconSize size;
gchar *path;
g_assert_true(GTK_IS_IMAGE(gui_artwork()));
gtk_image_get_icon_name(gui_artwork(), &name, &size);
g_assert_cmpuint(gtk_image_get_storage_type(gui_artwork()), ==,
GTK_IMAGE_ICON_NAME);
g_assert_cmpstr(name, ==, "image-missing");
g_assert_cmpuint(size, ==, GTK_ICON_SIZE_DIALOG);
g_assert_false(gtk_widget_is_sensitive(GTK_WIDGET(gui_artwork())));
#ifdef CONFIG_ALBUM_ART_TEST
audio_load(track_get(0));
g_assert_cmpuint(gtk_image_get_storage_type(gui_artwork()), ==,
GTK_IMAGE_SURFACE);
#endif /* CONFIG_ALBUM_ART_TEST */
track_get(1)->tr_album = album_find(artist_find("Koji Kondo"),
genre_find("Video Game Music"),
"Hyrule Symphony 2", 1999);
audio_load(track_get(1));
gtk_image_get_icon_name(gui_artwork(), &name, &size);
g_assert_cmpuint(gtk_image_get_storage_type(gui_artwork()), ==,
GTK_IMAGE_ICON_NAME);
g_assert_cmpstr(name, ==, "image-missing");
g_assert_cmpuint(size, ==, GTK_ICON_SIZE_DIALOG);
g_assert_false(gtk_widget_is_sensitive(GTK_WIDGET(gui_artwork())));
path = album_artwork_path(track_get(0)->tr_album);
#ifdef CONFIG_ALBUM_ART_TEST
gui_artwork_import(track_get(1), path);
g_assert_cmpuint(gtk_image_get_storage_type(gui_artwork()), ==,
GTK_IMAGE_SURFACE);
#endif /* CONFIG_ALBUM_ART_TEST */
g_free(path);
}
int main(int argc, char **argv)
{
int ret;
gtk_init(&argc, NULL);
core_init(&argc, NULL, NULL, &test_audio_cb, test_sync);
gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init();
gui_filter_init();
gui_treeview_init();
gui_sidebar_init();
gui_playlist_init();
gui_pl_library_add("tests/Music/Hyrule Symphony");
while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Artwork", test_artwork);
ret = g_test_run();
core_deinit();
gui_treeview_deinit();
gui_filter_deinit();
gui_model_deinit();
gui_builder_deinit();
return ret;
}

View File

@ -1,131 +1,179 @@
/* /*
* Copyright 2015 (c) Anna Schumaker. * Copyright 2015 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/string.h> #include <core/string.h>
#include <gui/audio.h> #include <gui/audio.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
#include <gui/playlist.h>
#include <gui/sidebar.h>
#include <gui/treeview.h>
#include <gui/window.h> #include <gui/window.h>
#include <tests/test.h> #include <tests/test.h>
#include <tests/loop.h>
struct core_init_data init_data = { static void test_audio_init()
.audio_ops = &audio_ops,
};
static inline const gchar *test_get_label_text(const gchar *name)
{ {
return gtk_label_get_text(GTK_LABEL(gui_builder_widget(name))); g_assert_cmpstr(gtk_label_get_text(gui_album_tag()), ==, " ");
g_assert_cmpstr(gtk_label_get_text(gui_artist_tag()), ==, " ");
g_assert_cmpstr(gtk_label_get_text(gui_title_tag()), ==, " ");
g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:00");
g_assert_cmpstr(gtk_label_get_text(gui_duration()), ==, "0:00");
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_up())));
g_assert_false(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
g_assert_cmpfloat(gtk_scale_button_get_value(gui_volume_button()),
==, 100);
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_play_button())));
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_button())));
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
} }
static inline bool test_get_toggle_state(const gchar *name) static void test_audio_load()
{ {
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gui_builder_widget(name))); struct track *track = track_get(0);
gchar *length = string_sec2str(track->tr_length);
audio_load(track);
g_assert_cmpstr(gtk_label_get_text(gui_album_tag()), ==,
track->tr_album->al_name);
g_assert_cmpstr(gtk_label_get_text(gui_artist_tag()), ==,
track->tr_album->al_artist->ar_name);
g_assert_cmpstr(gtk_label_get_text(gui_title_tag()), ==,
track->tr_title);
g_assert_cmpstr(gtk_label_get_text(gui_duration()), ==, length);
g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:00");
test_main_loop();
test_main_loop();
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_play_button())));
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_pause_button())));
g_assert_cmpuint(gtk_adjustment_get_upper(gui_seek()), ==,
track->tr_length);
g_free(length);
} }
static inline gchar *test_get_pause_text() static void test_audio_buttons()
{ {
return gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(gui_builder_widget("o_pause_after"))); audio_load(track_get(0));
gtk_button_clicked(gui_pause_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_play_button())));
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_button())));
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
g_assert_false(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_play_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_play_button())));
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_pause_button())));
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Keep playing");
gtk_button_clicked(gui_next_button());
if (track_get(0)->tr_track == 1)
gtk_button_clicked(gui_next_button());
g_assert(audio_cur_track() != track_get(0));
gtk_button_clicked(gui_prev_button());
g_assert(audio_cur_track() == track_get(0));
g_assert_cmpint(audio_get_pause_count(), ==, -1);
gtk_button_clicked(gui_pause_up());
g_assert_cmpint(audio_get_pause_count(), ==, 0);
gtk_button_clicked(gui_pause_up());
g_assert_cmpint(audio_get_pause_count(), ==, 1);
gtk_button_clicked(gui_pause_down());
g_assert_cmpint(audio_get_pause_count(), ==, 0);
gtk_button_clicked(gui_pause_down());
g_assert_cmpint(audio_get_pause_count(), ==, -1);
gtk_entry_set_text(gui_pause_entry(), "2 tracks");
gtk_widget_activate(GTK_WIDGET(gui_pause_entry()));
g_assert_cmpint(audio_get_pause_count(), ==, 2);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after 2 tracks");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
gtk_button_clicked(gui_pause_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(GTK_BUTTON(gui_builder_widget("pause_popover_yes")));
test_main_loop();
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_play_button());
gtk_button_clicked(gui_pause_up());
gtk_button_clicked(gui_pause_up());
gtk_button_clicked(gui_pause_button());
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(GTK_BUTTON(gui_builder_widget("pause_popover_no")));
g_assert_cmpint(audio_get_pause_count(), ==, 1);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_pause_button());
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
g_assert_cmpint(gui_audio_popover_timeout(), ==, G_SOURCE_REMOVE);
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_play_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after this track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_false(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_main_loop(); /* Give the text entry time to update */
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
gtk_scale_button_set_value(gui_volume_button(), 50);
g_assert_cmpuint(audio_get_volume(), ==, 50);
gtk_scale_button_set_value(gui_volume_button(), 0);
g_assert_cmpuint(audio_get_volume(), ==, 0);
gtk_scale_button_set_value(gui_volume_button(), 100);
g_assert_cmpuint(audio_get_volume(), ==, 100);
} }
static inline void test_set_pause_after(unsigned int n) static void test_audio_seek()
{
GtkComboBox *combo = GTK_COMBO_BOX(gui_builder_widget("o_pause_after"));
gtk_combo_box_set_active(combo, n + 1);
}
static inline void test_click_button(const gchar *name)
{
gtk_button_clicked(GTK_BUTTON(gui_builder_widget(name)));
}
static void test_audio_seek(gint64 pos)
{ {
GstState state; GstState state;
audio_seek(pos); audio_load(track_get(0));
state = audio_cur_state(); gtk_button_clicked(gui_pause_button());
while (state != GST_STATE_PAUSED && state != GST_STATE_PLAYING) audio_seek(10 * GST_SECOND);
do {
state = audio_cur_state(); state = audio_cur_state();
} } while (state != GST_STATE_PAUSED && state != GST_STATE_PLAYING);
static void test_audio() g_assert_cmpint(gui_audio_timeout(), ==, G_SOURCE_CONTINUE);
{ g_assert_cmpint(gtk_adjustment_get_value(gui_seek()), ==, 10);
struct db_entry *dbe, *next; g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:10");
struct track *track;
gchar *duration;
g_assert_true(gtk_widget_is_visible(gui_builder_widget("o_play")));
g_assert_false(gtk_widget_is_visible(gui_builder_widget("o_pause")));
g_assert_cmpstr_free(test_get_pause_text(), ==, "(disabled)");
g_assert_false(test_get_toggle_state("favorite_button"));
g_assert_false(test_get_toggle_state("hide_button"));
db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_track == 1) {
track = TRACK(dbe);
break;
}
}
playlist_add(PL_SYSTEM, "Favorites", track);
playlist_add(PL_SYSTEM, "Hidden", track);
g_assert_false(playlist_has(PL_SYSTEM, "Collection", track));
audio_load(track);
g_assert_cmpuint(audio_cur_track()->tr_track, ==, track->tr_track);
duration = string_sec2str(track->tr_length);
g_assert_cmpstr(test_get_label_text("o_title"), ==, track->tr_title);
g_assert_cmpstr(test_get_label_text("o_album"), ==, track->tr_album->al_name);
g_assert_cmpstr(test_get_label_text("o_artist"), ==, track->tr_album->al_artist->ar_name);
g_assert_cmpstr(test_get_label_text("o_position"), ==, "0:00");
g_assert_cmpstr(test_get_label_text("o_duration"), ==, duration);
g_assert_true(test_get_toggle_state("favorite_button"));
g_assert_true(test_get_toggle_state("hide_button"));
g_free(duration);
test_click_button("o_play");
g_assert_false(gtk_widget_is_visible(gui_builder_widget("o_play")));
g_assert_true(gtk_widget_is_visible(gui_builder_widget("o_pause")));
test_click_button("o_pause");
g_assert_true(gtk_widget_is_visible(gui_builder_widget("o_play")));
g_assert_false(gtk_widget_is_visible(gui_builder_widget("o_pause")));
test_set_pause_after(0);
audio_eos();
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 2);
g_assert_cmpstr_free(test_get_pause_text(), ==, "(disabled)");
test_click_button("o_next");
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 3);
g_assert_false(test_get_toggle_state("favorite_button"));
g_assert_false(test_get_toggle_state("hide_button"));
test_set_pause_after(1);
g_assert_cmpstr_free(test_get_pause_text(), ==, "next track");
audio_eos();
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 4);
g_assert_cmpstr_free(test_get_pause_text(), ==, "this track");
audio_eos();
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 5);
g_assert_cmpstr_free(test_get_pause_text(), ==, "(disabled)");
test_click_button("o_prev");
g_assert_cmpuint(audio_cur_track()->tr_track, ==, 4);
test_click_button("o_pause");
test_audio_seek(71 * GST_SECOND);
test_gui_audio_timeout();
g_assert_cmpstr(test_get_label_text("o_position"), ==, "1:11");
g_assert_cmpuint(gtk_adjustment_get_upper(GTK_ADJUSTMENT(gui_builder_object("o_progress"))),
==, audio_cur_track()->tr_length);
} }
int main(int argc, char **argv) int main(int argc, char **argv)
@ -133,21 +181,32 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, &argv, NULL, &audio_cb, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
core_init(&argc, NULL, &init_data); gui_filter_init();
gui_treeview_init();
gui_sidebar_init();
gui_playlist_init();
gui_audio_init(); gui_audio_init();
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony"); test_loop_init();
gui_pl_library_add("tests/Music/Hyrule Symphony");
while (idle_run_task()) {}; while (idle_run_task()) {};
g_test_init(&argc, &argv, NULL); g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Audio", test_audio); g_test_add_func("/Gui/Audio/Init", test_audio_init);
g_test_add_func("/Gui/Audio/Load", test_audio_load);
g_test_add_func("/Gui/Audio/Buttons", test_audio_buttons);
g_test_add_func("/Gui/Audio/Seek", test_audio_seek);
ret = g_test_run(); ret = g_test_run();
gui_audio_deinit();
gui_window_deinit();
gui_model_deinit();
gui_builder_deinit();
core_deinit(); core_deinit();
test_loop_deinit();
gui_audio_deinit();
gui_filter_deinit();
gui_treeview_deinit();
gui_model_deinit();
gui_window_deinit();
gui_builder_deinit();
return ret; return ret;
} }

View File

@ -1,52 +1,11 @@
/* /*
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
#include <gui/window.h> #include <gui/window.h>
void *test_queue_init(struct queue *queue, void *data)
{ return NULL; }
void test_queue_deinit(struct queue *queue)
{ gui_filter_clear_search(queue->q_private); }
void test_queue_add(struct queue *queue, unsigned int n)
{ gui_model_add(queue->q_private, n); }
void test_queue_remove(struct queue *queue, unsigned int n)
{ gui_model_remove(queue->q_private, n); }
void test_queue_clear(struct queue *queue, unsigned int n)
{ gui_model_clear(queue->q_private, n); }
void test_queue_save(struct queue *queue, enum queue_flags flag) {}
void test_queue_update(struct queue *queue, unsigned int n)
{ gui_model_update(queue->q_private, n); }
struct queue_ops test_ops = {
.qop_init = test_queue_init,
.qop_deinit = test_queue_deinit,
.qop_added = test_queue_add,
.qop_removed = test_queue_remove,
.qop_cleared = test_queue_clear,
.qop_save = test_queue_save,
.qop_updated = test_queue_update,
};
void test_on_load(struct track *track) {}
void test_on_state_change(GstState state) {}
void test_on_config_pause(int count) {}
struct audio_ops test_audio_ops = {
.on_load = test_on_load,
.on_state_change = test_on_state_change,
.on_config_pause = test_on_config_pause,
};
struct core_init_data init_data = {
.playlist_ops = &test_ops,
.audio_ops = &test_audio_ops,
};
void test_filter() void test_filter()
{ {
struct track *track; struct track *track;
@ -63,11 +22,9 @@ void test_filter()
entry = GTK_ENTRY(gui_filter_search()); entry = GTK_ENTRY(gui_filter_search());
model = GTK_TREE_MODEL(gui_filter_get()); model = GTK_TREE_MODEL(gui_filter_get());
playlist_get_queue(PL_SYSTEM, "Collection")->q_private =
playlist_get(PL_SYSTEM, "Collection");
g_assert_false(gtk_tree_model_get_iter_first(model, &iter)); g_assert_false(gtk_tree_model_get_iter_first(model, &iter));
gui_filter_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_filter_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_false(gtk_tree_model_get_iter_first(model, &iter)); g_assert_false(gtk_tree_model_get_iter_first(model, &iter));
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony"); playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
@ -101,11 +58,11 @@ void test_filter()
gtk_combo_box_set_active(gui_filter_how(), GUI_FILTER_TITLE); gtk_combo_box_set_active(gui_filter_how(), GUI_FILTER_TITLE);
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 2); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 2);
gui_filter_set_playlist(playlist_get(PL_SYSTEM, "Unplayed")); gui_filter_set_playlist(playlist_lookup(PL_SYSTEM, "Unplayed"));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 13); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 13);
g_assert_cmpstr(gtk_entry_get_text(entry), ==, ""); g_assert_cmpstr(gtk_entry_get_text(entry), ==, "");
gui_filter_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_filter_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 2); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 2);
g_assert_cmpstr(gtk_entry_get_text(entry), ==, "hyrule"); g_assert_cmpstr(gtk_entry_get_text(entry), ==, "hyrule");
@ -118,7 +75,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, NULL, &init_data); core_init(&argc, NULL, NULL, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_window_init("share/ocarina/ocarina.png"); gui_window_init("share/ocarina/ocarina.png");
gui_model_init(); gui_model_init();

View File

@ -4,11 +4,10 @@
#include <core/core.h> #include <core/core.h>
#include <gui/idle.h> #include <gui/idle.h>
#include <tests/test.h> #include <tests/test.h>
#include <tests/gui.h> #include <tests/loop.h>
static const unsigned int N = 100; static const unsigned int N = 100;
static unsigned int cur = -1; static unsigned int cur = -1;
struct core_init_data init_data;
static bool inc_cur(void *data) static bool inc_cur(void *data)
{ {
@ -29,14 +28,14 @@ static void test_idle()
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(progress))); g_assert_false(gtk_widget_is_visible(GTK_WIDGET(progress)));
for (i = 0; i < (N - 1); i++) { for (i = 0; i < (N - 1); i++) {
gui_test_main_loop(); test_main_loop();
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(progress))); g_assert_true(gtk_widget_is_visible(GTK_WIDGET(progress)));
g_assert_cmpfloat(idle_progress(), ==, (float)(i + 1) / N); g_assert_cmpfloat(idle_progress(), ==, (float)(i + 1) / N);
g_assert_cmpfloat(gtk_progress_bar_get_fraction(progress), g_assert_cmpfloat(gtk_progress_bar_get_fraction(progress),
==, (float)(i + 1) / N); ==, (float)(i + 1) / N);
} }
gui_test_main_loop(); test_main_loop();
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(progress))); g_assert_false(gtk_widget_is_visible(GTK_WIDGET(progress)));
} }
@ -45,16 +44,16 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, NULL, &init_data); core_init(&argc, NULL, NULL, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_test_init(); test_loop_init();
while (idle_run_task()) {} while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL); g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Idle", test_idle); g_test_add_func("/Gui/Idle", test_idle);
ret = g_test_run(); ret = g_test_run();
gui_test_deinit(); test_loop_deinit();
gui_builder_deinit(); gui_builder_deinit();
core_deinit(); core_deinit();
return ret; return ret;

View File

@ -1,10 +1,7 @@
/* /*
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/idle.h>
#include <core/core.h> #include <core/core.h>
#include <core/playlist.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/model.h> #include <gui/model.h>
#include <tests/test.h> #include <tests/test.h>
@ -23,42 +20,13 @@ void on_row_changed(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data) GtkTreeIter *iter, gpointer data)
{ count_update++; } { count_update++; }
void *test_queue_init(struct queue *queue, void *data) void test_cb_alloc(struct playlist *playlist) {}
{ return NULL; }
void test_queue_deinit(struct queue *queue)
{ }
void test_queue_add(struct queue *queue, unsigned int n)
{ gui_model_add(queue->q_private, n); }
void test_queue_remove(struct queue *queue, unsigned int n)
{ gui_model_remove(queue->q_private, n); }
void test_queue_clear(struct queue *queue, unsigned int n)
{ gui_model_clear(queue->q_private, n); }
void test_queue_save(struct queue *queue, enum queue_flags flag) {}
void test_queue_update(struct queue *queue, unsigned int n)
{ gui_model_update(queue->q_private, n); }
void test_on_load(struct track *track) {}
void test_on_state_change(GstState state) {}
void test_on_config_pause(int count) {}
struct queue_ops test_ops = { struct playlist_callbacks test_cb = {
.qop_init = test_queue_init, .pl_cb_alloc = test_cb_alloc,
.qop_deinit = test_queue_deinit, .pl_cb_added = gui_model_add,
.qop_added = test_queue_add, .pl_cb_removed = gui_model_remove,
.qop_removed = test_queue_remove, .pl_cb_updated = gui_model_update,
.qop_cleared = test_queue_clear,
.qop_save = test_queue_save,
.qop_updated = test_queue_update,
};
struct audio_ops test_audio_ops = {
.on_load = test_on_load,
.on_state_change = test_on_state_change,
.on_config_pause = test_on_config_pause,
};
struct core_init_data init_data = {
.playlist_ops = &test_ops,
.audio_ops = &test_audio_ops,
}; };
static void test_init() static void test_init()
@ -68,10 +36,10 @@ static void test_init()
GType type; GType type;
g_assert_null(gui_model_get_playlist()); g_assert_null(gui_model_get_playlist());
gui_model_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_nonnull(model); g_assert_nonnull(model);
g_assert_true(GTK_IS_TREE_MODEL(model)); g_assert_true(GTK_IS_TREE_MODEL(model));
g_assert(gui_model_get_playlist() == playlist_get(PL_SYSTEM, "Collection")); g_assert(gui_model_get_playlist() == playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(gtk_tree_model_get_flags(treemodel), ==, g_assert_cmpuint(gtk_tree_model_get_flags(treemodel), ==,
GTK_TREE_MODEL_LIST_ONLY); GTK_TREE_MODEL_LIST_ONLY);
@ -108,7 +76,7 @@ static void __test_empty_subprocess()
GValue value; GValue value;
GType type; GType type;
gui_model_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
memset(&value, 0, sizeof(GValue)); memset(&value, 0, sizeof(GValue));
g_assert_false(gtk_tree_model_get_iter_first(model, &iter)); g_assert_false(gtk_tree_model_get_iter_first(model, &iter));
@ -165,13 +133,13 @@ static void test_empty()
static void test_model() static void test_model()
{ {
GtkTreeModel *model = GTK_TREE_MODEL(gui_model_get()); GtkTreeModel *model = GTK_TREE_MODEL(gui_model_get());
struct db_entry *dbe, *next; struct playlist *collection, *favorites;
struct track *track; struct track *track;
GtkTreePath *path; GtkTreePath *path;
GtkTreeIter iter; GtkTreeIter iter;
GValue value; GValue value;
gui_model_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, ""); g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, "");
memset(&value, 0, sizeof(GValue)); memset(&value, 0, sizeof(GValue));
@ -180,18 +148,18 @@ static void test_model()
g_signal_connect(model, "row-changed", (GCallback)on_row_changed, NULL); g_signal_connect(model, "row-changed", (GCallback)on_row_changed, NULL);
/* Okay, now scan a directory ... */ /* Okay, now scan a directory ... */
playlist_get_queue(PL_SYSTEM, "Collection")->q_private = collection = playlist_lookup(PL_SYSTEM, "Collection");
playlist_get(PL_SYSTEM, "Collection"); favorites = playlist_lookup(PL_SYSTEM, "Favorites");
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony"); playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
while (idle_run_task() == true) {} while (idle_run_task() == true) {}
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Collection"), ==, 13); g_assert_cmpuint(playlist_size(collection), ==, 13);
g_assert_cmpuint(count_insert, ==, 13); g_assert_cmpuint(count_insert, ==, 13);
queue_resort(playlist_get_queue(PL_SYSTEM, "Collection")); playlist_generic_resort(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(count_update, ==, 13); g_assert_cmpuint(count_update, ==, 13);
playlist_add(PL_SYSTEM, "Favorites", track_get(0)); playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
playlist_add(PL_SYSTEM, "Favorites", track_get(1)); playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(1));
playlist_add(PL_SYSTEM, "Favorites", track_get(2)); playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(2));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Favorites"), ==, 3); g_assert_cmpuint(playlist_size(favorites), ==, 3);
g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==,
"42 minutes, 45 seconds"); "42 minutes, 45 seconds");
@ -257,7 +225,7 @@ static void test_model()
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 0); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 0);
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 13); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 13);
gui_model_set_playlist(playlist_get(PL_SYSTEM, "Favorites")); gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Favorites"));
g_assert_cmpuint(count_delete, ==, 13); g_assert_cmpuint(count_delete, ==, 13);
g_assert_cmpuint(count_insert, ==, 14); g_assert_cmpuint(count_insert, ==, 14);
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 3); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 3);
@ -270,7 +238,7 @@ static void test_model()
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 0); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, NULL), ==, 0);
g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, ""); g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, "");
gui_model_set_playlist(playlist_get(PL_SYSTEM, "Collection")); gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(count_delete, ==, 16); g_assert_cmpuint(count_delete, ==, 16);
g_assert_cmpuint(count_insert, ==, 15); g_assert_cmpuint(count_insert, ==, 15);
g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==, g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==,
@ -282,9 +250,13 @@ static void test_model()
g_assert_false(gtk_tree_model_iter_parent(model, &iter, &iter)); g_assert_false(gtk_tree_model_iter_parent(model, &iter, &iter));
db_for_each(dbe, next, track_db_get()) gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Favorites"));
playlist_remove(PL_SYSTEM, "Collection", TRACK(dbe));
g_assert_cmpuint(count_delete, ==, 29); g_assert_cmpuint(count_delete, ==, 29);
g_assert_cmpuint(count_insert, ==, 16);
playlist_remove(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
playlist_remove(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(1));
playlist_remove(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(2));
g_assert_cmpuint(count_delete, ==, 32);
} }
int main(int argc, char **argv) int main(int argc, char **argv)
@ -292,7 +264,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, NULL, &init_data); core_init(&argc, NULL, &test_cb, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
while (idle_run_task()) {}; while (idle_run_task()) {};

View File

@ -2,105 +2,51 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
#include <gui/playlist.h> #include <gui/playlist.h>
#include <gui/sidebar.h> #include <gui/sidebar.h>
#include <gui/treeview.h> #include <gui/treeview.h>
#include <gui/view.h>
#include <gui/window.h>
#include <tests/test.h> #include <tests/test.h>
struct core_init_data init_data = { static void test_playlist()
.playlist_ops = &playlist_ops,
};
static void test_playlist_sidebar()
{ {
GtkTreeSelection *selection; GtkTreeIter iter, child;
GtkTreeView *treeview;
GtkTreeModel *model;
GtkTreePath *path; GtkTreePath *path;
GtkTreeIter iter;
g_assert_true(GTK_IS_TREE_VIEW(gui_sidebar_treeview())); gui_pl_library_add("tests/Music/Hyrule Symphony");
while (idle_run_task()) {}
treeview = gui_sidebar_treeview(); playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
selection = gtk_tree_view_get_selection(treeview);
model = gui_sidebar_model();
playlist_add(PL_SYSTEM, "Queued Tracks", track_get(0));
playlist_add(PL_SYSTEM, "History", track_get(0));
playlist_add(PL_SYSTEM, "Favorites", track_get(0));
playlist_add(PL_SYSTEM, "Hidden", track_get(1));
gui_pl_user_add("Test");
playlist_add(PL_USER, "Test", track_get(0));
gtk_tree_model_filter_refilter(gui_sidebar_filter()); gtk_tree_model_filter_refilter(gui_sidebar_filter());
gtk_tree_view_expand_all(treeview);
g_assert_true(gtk_tree_model_get_iter_first(model, &iter)); g_assert_true(gui_sidebar_iter_first(&iter));
path = gtk_tree_model_get_path(model, &iter); path = gtk_tree_model_get_path(gui_sidebar_model(), &iter);
gtk_tree_selection_select_path(selection, path); gtk_tree_view_row_activated(gui_sidebar_treeview(), path, NULL);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1); g_assert_true(playlist_current() == playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpstr(gui_playlist_cur(), ==, "Queued Tracks"); gtk_tree_path_free(path);
gtk_tree_path_next(path); g_assert_false(settings_has("gui.sidebar.expand.Playlists"));
gtk_tree_selection_select_path(selection, path); g_assert_true(gui_sidebar_iter_find(&iter, "Playlists", PL_MAX_TYPE));
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1); g_assert_true(gtk_tree_model_filter_convert_child_iter_to_iter(
g_assert_cmpstr(gui_playlist_cur(), ==, "Collection"); gui_sidebar_filter(), &child, &iter));
path = gtk_tree_model_get_path(GTK_TREE_MODEL(gui_sidebar_filter()),
gtk_tree_path_next(path); &child);
gtk_tree_selection_select_path(selection, path); g_assert_nonnull(path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1); g_assert_true(gtk_tree_view_expand_row(gui_sidebar_treeview(), path, false));
g_assert_cmpstr(gui_playlist_cur(), ==, "History"); g_assert_true(settings_has("gui.sidebar.expand.Playlists"));
g_assert_cmpuint(settings_get("gui.sidebar.expand.Playlists"), ==, 1);
gtk_tree_path_next(path); g_assert_true(gtk_tree_view_collapse_row(gui_sidebar_treeview(), path));
gtk_tree_selection_unselect_all(selection); g_assert_cmpuint(settings_get("gui.sidebar.expand.Playlists"), ==, 0);
gtk_tree_selection_select_path(selection, path); gtk_tree_path_free(path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 0);
g_assert_null(gui_playlist_cur());
gtk_tree_path_next(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 0);
g_assert_null(gui_playlist_cur());
gtk_tree_path_down(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1);
g_assert_cmpstr(gui_playlist_cur(), ==, "Favorites");
gtk_tree_path_next(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1);
g_assert_cmpstr(gui_playlist_cur(), ==, "Hidden");
gtk_tree_path_next(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1);
g_assert_cmpstr(gui_playlist_cur(), ==, "Test");
gtk_tree_path_up(path);
gtk_tree_path_next(path);
gtk_tree_selection_unselect_all(selection);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 0);
g_assert_null(gui_playlist_cur());
gtk_tree_path_next(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 0);
g_assert_null(gui_playlist_cur());
/* Most played and least played are both filtered out */
gtk_tree_path_down(path);
gtk_tree_selection_select_path(selection, path);
g_assert_cmpuint(gtk_tree_selection_count_selected_rows(selection), ==, 1);
g_assert_cmpstr(gui_playlist_cur(), ==, "Unplayed");
g_assert_true(gui_sidebar_iter_down(&iter, &child));
g_assert_true(gtk_tree_model_filter_convert_child_iter_to_iter(
gui_sidebar_filter(), &iter, &child));
path = gtk_tree_model_get_path(GTK_TREE_MODEL(gui_sidebar_filter()),
&iter);
g_assert_nonnull(path);
gtk_tree_view_row_activated(gui_sidebar_treeview(), path, NULL);
g_assert_true(playlist_current() == playlist_lookup(PL_SYSTEM, "Favorites"));
gtk_tree_path_free(path); gtk_tree_path_free(path);
} }
@ -109,23 +55,23 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, NULL, NULL, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
core_init(&argc, NULL, &init_data);
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();
gui_treeview_init(); gui_treeview_init();
gui_sidebar_init(); gui_sidebar_init();
gui_view_init();
gui_playlist_init(); gui_playlist_init();
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
while (idle_run_task()) {} while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL); g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Playlist/Sidebar", test_playlist_sidebar); g_test_add_func("/Gui/Playlist", test_playlist);
ret = g_test_run(); ret = g_test_run();
core_deinit(); core_deinit();
gui_treeview_deinit();
gui_filter_deinit(); gui_filter_deinit();
gui_model_deinit();
gui_builder_deinit(); gui_builder_deinit();
return ret; return ret;
} }

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h> #include <gui/treeview.h>
#include <tests/test.h> #include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_artist() static void test_artist()
{ {
GtkTreeModel *model = gui_sidebar_model(); GtkTreeModel *model = gui_sidebar_model();
@ -24,14 +19,15 @@ static void test_artist()
gui_sidebar_iter_find(&iter, "Collection", PL_SYSTEM); gui_sidebar_iter_find(&iter, "Collection", PL_SYSTEM);
g_assert_false(gtk_tree_model_iter_has_child(model, &iter)); g_assert_false(gtk_tree_model_iter_has_child(model, &iter));
g_assert_true(gui_pl_library_add("tests/Music/Hyrule Symphony")); g_assert_nonnull(gui_pl_library_add("tests/Music/Hyrule Symphony"));
g_assert_cmpuint(artist_db_get()->db_size, ==, 0); g_assert_cmpuint(artist_db_get()->db_size, ==, 0);
g_assert_null(playlist_get(PL_ARTIST, "Koji Kondo")); g_assert_null(playlist_lookup(PL_ARTIST, "Koji Kondo"));
g_assert_false(gtk_tree_model_iter_has_child(model, &iter)); g_assert_false(gtk_tree_model_iter_has_child(model, &iter));
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpuint(artist_db_get()->db_size, ==, 1); g_assert_cmpuint(artist_db_get()->db_size, ==, 1);
g_assert_nonnull(playlist_get(PL_ARTIST, "Koji Kondo")); g_assert_nonnull(playlist_lookup(PL_ARTIST, "Koji Kondo"));
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 13); g_assert_cmpuint(playlist_size(playlist_lookup(PL_ARTIST, "Koji Kondo")),
==, 13);
g_assert_true(gtk_tree_model_iter_has_child(model, &iter)); g_assert_true(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 1); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 1);
@ -45,7 +41,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, &argv, &init_data); core_init(&argc, &argv, &playlist_cb, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h> #include <gui/treeview.h>
#include <tests/test.h> #include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_library() static void test_library()
{ {
GtkTreeModel *model = gui_sidebar_model(); GtkTreeModel *model = gui_sidebar_model();
@ -26,23 +21,24 @@ static void test_library()
g_assert_false(gtk_tree_model_iter_has_child(model, &iter)); g_assert_false(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(library_db_get()->db_size, ==, 0); g_assert_cmpuint(library_db_get()->db_size, ==, 0);
g_assert_null(playlist_get(PL_LIBRARY, "tests/Music/Ocarina of Time")); g_assert_null(playlist_lookup(PL_LIBRARY, "tests/Music/Ocarina of Time"));
g_assert_null(playlist_get(PL_LIBRARY, "tests/Music/Hyrule Symphony")); g_assert_null(playlist_lookup(PL_LIBRARY, "tests/Music/Hyrule Symphony"));
ocarina = gui_pl_library_add("tests/Music/Ocarina of Time");
g_assert_nonnull(ocarina);
hyrule = gui_pl_library_add("tests/Music/Hyrule Symphony");
g_assert_nonnull(hyrule);
g_assert_true(gui_pl_library_add("tests/Music/Ocarina of Time"));
g_assert_true(gui_pl_library_add("tests/Music/Hyrule Symphony"));
g_assert_cmpuint(library_db_get()->db_size, ==, 2); g_assert_cmpuint(library_db_get()->db_size, ==, 2);
g_assert_true(gtk_tree_model_iter_has_child(model, &iter)); g_assert_true(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2);
ocarina = playlist_get(PL_LIBRARY, "tests/Music/Ocarina of Time"); g_assert_cmpuint(playlist_size(ocarina), ==, 0);
hyrule = playlist_get(PL_LIBRARY, "tests/Music/Hyrule Symphony"); g_assert_cmpuint(playlist_size(hyrule), ==, 0);
g_assert_cmpuint(playlist_size(ocarina->pl_type, ocarina->pl_name), ==, 0);
g_assert_cmpuint(playlist_size(hyrule->pl_type, hyrule->pl_name), ==, 0);
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpuint(playlist_size(hyrule->pl_type, hyrule->pl_name), ==, 13); g_assert_cmpuint(playlist_size(hyrule), ==, 13);
g_assert_cmpuint(playlist_size(ocarina->pl_type, ocarina->pl_name), ==, 35); g_assert_cmpuint(playlist_size(ocarina), ==, 35);
g_assert_true(gtk_tree_model_iter_has_child(model, &iter)); g_assert_true(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2); g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2);
@ -59,7 +55,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, &argv, &init_data); core_init(&argc, &argv, NULL, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();

View File

@ -1,9 +1,7 @@
/* /*
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/audio.h>
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
#include <gui/playlist.h> #include <gui/playlist.h>
@ -16,15 +14,10 @@ static void test_audio_load(struct track *track)
static void test_change_state(GstState state) { } static void test_change_state(GstState state) { }
static void test_config_pause(int n) { } static void test_config_pause(int n) { }
static struct audio_ops test_audio_ops = { static struct audio_callbacks test_audio_cb = {
.on_load = test_audio_load, .audio_cb_load = test_audio_load,
.on_state_change = test_change_state, .audio_cb_state_change = test_change_state,
.on_config_pause = test_config_pause, .audio_cb_config_pause = test_config_pause,
};
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
.audio_ops = &test_audio_ops,
}; };
static const gchar *toplevel[3] = { "Queued Tracks", "Collection", "History" }; static const gchar *toplevel[3] = { "Queued Tracks", "Collection", "History" };
@ -65,32 +58,35 @@ static void test_system()
static void test_buttons() static void test_buttons()
{ {
g_assert_true(gui_pl_library_add("tests/Music/Hyrule Symphony")); struct playlist *favorites = playlist_lookup(PL_SYSTEM, "Favorites");
struct playlist *hidden = playlist_lookup(PL_SYSTEM, "Hidden");
g_assert_nonnull(gui_pl_library_add("tests/Music/Hyrule Symphony"));
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_false(gtk_toggle_button_get_active(gui_favorite_button())); g_assert_false(gtk_toggle_button_get_active(gui_favorite_button()));
g_assert_false(gtk_toggle_button_get_active(gui_hide_button())); g_assert_false(gtk_toggle_button_get_active(gui_hide_button()));
playlist_add(PL_SYSTEM, "Favorites", track_get(0)); playlist_add(favorites, track_get(0));
audio_load(track_get(0)); audio_load(track_get(0));
g_assert_true( gtk_toggle_button_get_active(gui_favorite_button())); g_assert_true( gtk_toggle_button_get_active(gui_favorite_button()));
g_assert_false(gtk_toggle_button_get_active(gui_hide_button())); g_assert_false(gtk_toggle_button_get_active(gui_hide_button()));
gtk_toggle_button_set_active(gui_favorite_button(), false); gtk_toggle_button_set_active(gui_favorite_button(), false);
g_assert_false(playlist_has(PL_SYSTEM, "Favorites", track_get(0))); g_assert_false(playlist_has(favorites, track_get(0)));
gtk_toggle_button_set_active(gui_favorite_button(), true); gtk_toggle_button_set_active(gui_favorite_button(), true);
g_assert_true(playlist_has(PL_SYSTEM, "Favorites", track_get(0))); g_assert_true(playlist_has(favorites, track_get(0)));
playlist_add(PL_SYSTEM, "Hidden", track_get(1)); playlist_add(hidden, track_get(1));
audio_load(track_get(1)); audio_load(track_get(1));
g_assert_false(gtk_toggle_button_get_active(gui_favorite_button())); g_assert_false(gtk_toggle_button_get_active(gui_favorite_button()));
g_assert_true(gtk_toggle_button_get_active(gui_hide_button())); g_assert_true(gtk_toggle_button_get_active(gui_hide_button()));
gtk_toggle_button_set_active(gui_hide_button(), false); gtk_toggle_button_set_active(gui_hide_button(), false);
g_assert_false(playlist_has(PL_SYSTEM, "Hidden", track_get(1))); g_assert_false(playlist_has(hidden, track_get(1)));
g_assert(audio_cur_track() == track_get(1)); g_assert(audio_cur_track() == track_get(1));
gtk_toggle_button_set_active(gui_hide_button(), true); gtk_toggle_button_set_active(gui_hide_button(), true);
g_assert_true(playlist_has(PL_SYSTEM, "Hidden", track_get(1))); g_assert_true(playlist_has(hidden, track_get(1)));
g_assert(audio_cur_track() != track_get(1)); g_assert(audio_cur_track() != track_get(1));
} }
@ -99,7 +95,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, &argv, &init_data); core_init(&argc, &argv, &playlist_cb, &test_audio_cb, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker. * Copyright 2016 (c) Anna Schumaker.
*/ */
#include <core/core.h> #include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h> #include <gui/builder.h>
#include <gui/filter.h> #include <gui/filter.h>
#include <gui/model.h> #include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h> #include <gui/treeview.h>
#include <tests/test.h> #include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_user() static void test_user()
{ {
GtkTreeModel *model = gui_sidebar_model(); GtkTreeModel *model = gui_sidebar_model();
@ -37,18 +32,30 @@ static void test_user()
g_assert_true(gui_sidebar_iter_down(&iter, &child)); g_assert_true(gui_sidebar_iter_down(&iter, &child));
g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Favorites"); g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Favorites");
g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_SYSTEM); g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_SYSTEM);
g_assert_false(gui_sidebar_iter_editable(&child));
g_assert_true(gui_sidebar_iter_next(&child)); g_assert_true(gui_sidebar_iter_next(&child));
g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Hidden"); g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Hidden");
g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_SYSTEM); g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_SYSTEM);
g_assert_false(gui_sidebar_iter_editable(&child));
g_assert_true(gui_sidebar_iter_next(&child)); g_assert_true(gui_sidebar_iter_next(&child));
g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 1"); g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 1");
g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_USER); g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_USER);
g_assert_false(gui_sidebar_iter_editable(&child));
g_assert_true( gui_sidebar_iter_set_editable(&child, true));
g_assert_true( gui_sidebar_iter_editable(&child));
g_assert_true( gui_sidebar_iter_set_editable(&child, false));
g_assert_false(gui_sidebar_iter_editable(&child));
g_assert_true(gui_sidebar_iter_next(&child)); g_assert_true(gui_sidebar_iter_next(&child));
g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 2"); g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 2");
g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_USER); g_assert_cmpuint(gui_sidebar_iter_type(&child), ==, PL_USER);
g_assert_false(gui_sidebar_iter_editable(&child));
g_assert_true( gui_sidebar_iter_set_editable(&child, true));
g_assert_true( gui_sidebar_iter_editable(&child));
g_assert_true( gui_sidebar_iter_set_editable(&child, false));
g_assert_false(gui_sidebar_iter_editable(&child));
list = gui_pl_user_list(); list = gui_pl_user_list();
g_assert_nonnull(list); g_assert_nonnull(list);
@ -72,7 +79,7 @@ int main(int argc, char **argv)
int ret; int ret;
gtk_init(&argc, NULL); gtk_init(&argc, NULL);
core_init(&argc, &argv, &init_data); core_init(&argc, &argv, NULL, NULL, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui"); gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init(); gui_model_init();
gui_filter_init(); gui_filter_init();

Some files were not shown because too many files have changed in this diff Show More