Compare commits

...

168 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
94 changed files with 4155 additions and 4140 deletions

View File

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

View File

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

View File

@ -6,77 +6,117 @@
#include <core/playlist.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_VOLUME = "core.audio.volume";
static struct file audio_file = FILE_INIT("cur_track", 0);
static struct track *audio_track = NULL;
static GstElement *audio_player = NULL;
static struct audio_ops *audio_ops = NULL;
static int audio_pause_count = -1;
static guint audio_bus = 0;
static struct file audio_file = FILE_INIT_DATA("", "cur_track", 0);
static struct track *audio_track = NULL;
static int audio_pause_count = -1;
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)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
if (audio_cur_state() != state)
ret = gst_element_set_state(audio_player, state);
if (ret == GST_STATE_CHANGE_FAILURE)
if (audio_cur_state() == state)
return false;
audio_ops->on_state_change(state);
return true;
return gst_element_set_state(audio_pipeline, state) != GST_STATE_CHANGE_FAILURE;
}
/* Load a track, but don't add it to the history. */
static struct track *__audio_load_basic(struct track *track, GstState state)
static struct track *__audio_load(struct track *track, unsigned int flags)
{
struct track *prev = audio_track;
gchar *path, *uri;
gchar *path;
if (!track)
return NULL;
path = track_path(track);
uri = gst_filename_to_uri(path, NULL);
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);
g_object_set(G_OBJECT(audio_player), "uri", uri, NULL);
audio_ops->on_load(track);
__audio_change_state(state);
playlist_played(prev);
if (prev && TRACK_IS_EXTERNAL(prev))
track_free_external(prev);
queue_updated(playlist_get_queue(PL_SYSTEM, "Queued Tracks"), prev);
queue_updated(playlist_get_queue(PL_SYSTEM, "Collection"), prev);
queue_updated(playlist_get_queue(PL_SYSTEM, "Collection"), audio_track);
playlist_selected(track);
if (flags & LOAD_HISTORY && !TRACK_IS_EXTERNAL(track))
playlist_add(playlist_lookup(PL_SYSTEM, "History"), track);
if (audio_cb)
audio_cb->audio_cb_load(track);
audio_save();
g_free(uri);
g_free(path);
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))
playlist_add(PL_SYSTEM, "History", track);
return track;
}
GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink");
static struct track *__audio_next(GstState state)
{
return __audio_load(playlist_next(), state);
gst_element_link(element, audio_converter);
gst_pad_link(pad, sink);
gst_object_unref(sink);
}
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)) {
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;
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:
break;
}
@ -90,29 +130,40 @@ static bool __audio_init_idle(void *data)
if (settings_has(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)) {
file_readf(&audio_file, "%u", &track);
track = file_readu(&audio_file);
file_close(&audio_file);
file_remove(&audio_file);
__audio_load(track_get(track), GST_STATE_PAUSED);
__audio_load(track_get(track), LOAD_HISTORY);
}
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;
GstBus *bus;
gst_init(argc, argv);
audio_player = gst_element_factory_make("playbin", "ocarina_player");
audio_ops = ops;
audio_cb = callbacks;
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));
audio_bus = gst_bus_add_watch(bus, __audio_message, NULL);
gst_bin_add_many(GST_BIN(audio_pipeline), audio_source, audio_decoder,
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);
if (settings_has(SETTINGS_VOLUME))
@ -124,19 +175,24 @@ void audio_init(int *argc, char ***argv, struct audio_ops *ops)
void audio_deinit()
{
gst_element_set_state(audio_player, GST_STATE_NULL);
gst_object_unref(GST_ELEMENT(audio_player));
g_source_remove(audio_bus);
gst_element_set_state(audio_pipeline, GST_STATE_NULL);
gst_object_unref(GST_ELEMENT(audio_pipeline));
g_source_remove(audio_bus_id);
audio_player = NULL;
audio_track = NULL;
audio_pipeline = NULL;
audio_source = NULL;
audio_decoder = NULL;
audio_converter = NULL;
audio_volume = NULL;
audio_sink = NULL;
audio_track = NULL;
gst_deinit();
}
void audio_save()
{
if (audio_track)
if (audio_track && !TRACK_IS_EXTERNAL(audio_track))
settings_set(SETTINGS_TRACK, track_index(audio_track));
}
@ -144,7 +200,19 @@ bool audio_load(struct track *track)
{
if (track == audio_track)
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()
@ -155,8 +223,8 @@ struct track *audio_cur_track()
GstState audio_cur_state()
{
GstState cur = GST_STATE_NULL;
if (audio_player)
gst_element_get_state(audio_player,
if (audio_pipeline)
gst_element_get_state(audio_pipeline,
&cur, NULL,
GST_CLOCK_TIME_NONE);
return cur;
@ -171,13 +239,13 @@ void audio_set_volume(unsigned int volume)
vol = (gdouble)volume / 100;
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()
{
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;
}
@ -199,7 +267,7 @@ bool audio_seek(gint64 offset)
{
if (!audio_track)
return false;
return gst_element_seek_simple(audio_player,
return gst_element_seek_simple(audio_pipeline,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH,
offset);
@ -208,17 +276,17 @@ bool audio_seek(gint64 offset)
gint64 audio_position()
{
gint64 position;
if (gst_element_query_position(audio_player,
if (gst_element_query_position(audio_pipeline,
GST_FORMAT_TIME,
&position))
return position;
return 0;
}
int64_t audio_duration()
gint64 audio_duration()
{
gint64 duration;
if (gst_element_query_duration(audio_player,
if (gst_element_query_duration(audio_pipeline,
GST_FORMAT_TIME,
&duration))
return duration;
@ -229,65 +297,48 @@ int64_t audio_duration()
struct track *audio_next()
{
return __audio_next(GST_STATE_PLAYING);
return __audio_load(playlist_next(), LOAD_DEFAULT);
}
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 (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) {
if (n >= -1 && n != audio_pause_count) {
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
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 */

View File

@ -1,13 +1,7 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/audio.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)
{
@ -18,18 +12,14 @@ static bool core_defragment(void *data)
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
if (init->idle_async == false)
idle_init_sync();
else
#endif /* CONFIG_TESTING */
idle_init();
idle_init(idle_sync);
settings_init();
tags_init();
playlist_init(init->playlist_ops);
audio_init(argc, argv, init->audio_ops);
playlist_init(playlist_cb);
audio_init(argc, argv, audio_cb);
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)
{
struct db_entry *dbe = NULL;
int valid;
file_readf(&db->db_file, "%d", &valid);
if (valid)
dbe = db->db_ops->dbe_read(&db->db_file);
if (file_readd(&db->db_file))
dbe = db->db_ops->dbe_read(&db->db_file, index);
g_ptr_array_index(db->db_entries, index) = dbe;
return dbe;
@ -76,7 +74,7 @@ void db_init(struct database *db, const char *filepath, bool autosave,
db->db_autosave = autosave;
db->db_entries = g_ptr_array_new();
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)
@ -118,7 +116,7 @@ void db_load(struct database *db)
if (file_open(&db->db_file, OPEN_READ) == false)
return;
file_readf(&db->db_file, "%u", &size);
size = file_readu(&db->db_file);
g_ptr_array_set_size(db->db_entries, size);
for (unsigned int i = 0; i < size; 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;
if (key)
item = db->db_ops->dbe_alloc(key);
item = db->db_ops->dbe_alloc(key, db_actual_size(db));
if (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)
{
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)
{
uint32_t stamp;
file_readf(f, "%u", &stamp);
date->d_stamp = be32toh(stamp);
date->d_stamp = be32toh(file_readu(f));
}
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)
#define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno))
static gchar *__file_path(const gchar *base, const gchar *dir,
const gchar *name)
static void __file_init_common(struct file *file, const gchar *subdir,
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,
const gchar *name)
static bool __file_open(struct file *file, enum open_mode mode)
{
if (string_length(name) == 0)
return g_strdup("");
return __file_path(base, dir, name);
}
gchar *cmode, *path;
static gchar *__file_build_tmp(const gchar *base, const gchar *dir,
const gchar *name)
{
gchar *tmp, *res;
if (string_length(name) == 0)
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);
if (mode == OPEN_READ || mode == OPEN_READ_BINARY) {
cmode = "r";
path = file_path(file);
} else {
cmode = "w";
path = file_write_path(file);
}
file->f_file = g_fopen(path, cmode);
if (!file->f_file)
REPORT_ERRNO(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);
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->f_version = OCARINA_MINOR_VERSION;
file->f_prev = 0;
file->f_min = min;
file->f_file = NULL;
file->f_name = name;
__file_init_common(file, subdir, name, min, g_get_user_data_dir);
}
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->cf_name = name;
file->cf_subdir = subdir;
__file_init_common(file, subdir, name, 0, g_get_user_cache_dir);
}
gchar *file_path(struct file *file)
{
return __file_build_path(g_get_user_data_dir(), NULL, file->f_name);
}
gchar *cache_file_path(struct cache_file *file)
{
return __file_build_path(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
if (string_length(file->f_name) == 0)
return g_strdup("");
return g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, file->f_name, NULL);
}
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)
{
return __file_build_tmp(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
if (string_length(file->f_name) == 0)
return g_strdup("");
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)
@ -142,26 +113,24 @@ const unsigned int file_version(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)
{
return __file_exists(cache_file_path(file));
}
static bool __file_open_read(struct file *file)
static bool __file_open_read(struct file *file, enum open_mode mode)
{
if (!file_exists(file))
return false;
file->f_file = __file_open(file_path(file), "r");
if (!file->f_file)
if (!__file_open(file, mode))
return false;
file->f_mode = OPEN_READ;
if (file_readf(file, "%u\n", &file->f_prev) != 1)
return false;
file->f_mode = mode;
if (mode == OPEN_READ_BINARY)
return true;
file->f_prev = file_readu(file);
if (file->f_prev < file->f_min) {
REPORT_ERROR(file->f_name, "File too old to be upgraded.");
file_close(file);
@ -175,18 +144,18 @@ static bool __file_open_read(struct file *file)
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;
if (!__file_can_write(file))
return false;
file->f_file = __file_open(file_write_path(file), "w");
if (!file->f_file)
if (!__file_open(file, OPEN_WRITE))
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;
}
@ -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))
return false;
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))
if (mode == CLOSED)
return false;
file->cf_file = __file_open(cache_file_write_path(file), "wb");
return file->cf_file != NULL;
if (mode == OPEN_READ || mode == OPEN_READ_BINARY)
return __file_open_read(file, mode);
return __file_open_write(file, mode);
}
void file_close(struct file *file)
{
__file_close(file->f_file,
file->f_mode == OPEN_WRITE ? file_path(file) : NULL,
file->f_mode == OPEN_WRITE ? file_write_path(file) : NULL);
gchar *path = file_path(file);
gchar *tmp = file_write_path(file);
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_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,
cache_file_path(file),
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 *s;
return fscanf(file->f_file, "%ms%*c", &s) ? s : g_strdup("");
}
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)
return g_strdup("");
g_strstrip(res);
return res;
unsigned int file_readu(struct file *file)
{
unsigned int u;
return fscanf(file->f_file, "%u%*c", &u) ? u : 0;
}
int file_writef(struct file *file, const char *fmt, ...)
@ -266,36 +222,57 @@ int file_writef(struct file *file, const char *fmt, ...)
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 -1;
}
bool cache_file_import(struct cache_file *file, const gchar *srcpath)
bool file_import(struct file *file, const gchar *srcpath)
{
gchar *contents = NULL;
gsize length = 0;
if (!file->cf_file || !srcpath)
if (!file->f_file || !srcpath)
return false;
if (!g_file_get_contents(srcpath, &contents, &length, NULL))
return false;
cache_file_write(file, contents, length);
file_write(file, contents, length);
return true;
}
bool file_remove(struct file *file)
{
gchar *path, *dir;
int ret = -1;
gchar *path;
if (!file->f_file) {
path = file_path(file);
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(dir);
}
return ret == 0;

View File

@ -8,11 +8,11 @@
struct idle_task {
bool (*idle_func)(void *);
void *idle_data;
enum idle_sync_t idle_sync;
};
static GThreadPool *idle_pool = NULL;
static GQueue idle_queue = G_QUEUE_INIT;
static GThreadPool *idle_pool = NULL;
static GQueue idle_queue = G_QUEUE_INIT;
static enum idle_sync_t idle_mode = IDLE_SYNC;
static unsigned int queued = 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()
{
struct idle_task *task;
@ -58,8 +52,10 @@ void idle_deinit()
g_free(task);
}
if (idle_pool)
g_thread_pool_free(idle_pool, true, false);
if (idle_pool) {
g_thread_pool_free(idle_pool, true, true);
idle_pool = NULL;
}
queued = 0;
serviced = 0;
@ -67,12 +63,24 @@ void idle_deinit()
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_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);
}
@ -82,12 +90,7 @@ bool idle_run_task()
if (!g_queue_is_empty(&idle_queue)) {
task = g_queue_pop_head(&idle_queue);
if (task->idle_sync == IDLE_ASYNC) {
if (idle_pool)
g_thread_pool_push(idle_pool, task, NULL);
else
__idle_free_task(task);
} else if (!__idle_run_task(task))
if (!__idle_run_task(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_ID = "core.playlist.prev.id";
static struct playlist *current = NULL;
static struct playlist *previous = NULL;
struct playlist_type *playlist_types[] = {
[PL_SYSTEM] = &pl_system,
[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);
pl_artist_init(ops);
pl_user_init(ops);
pl_library_init(ops);
unsigned int type, id;
if (!settings_has(SETTINGS_CUR_TYPE) ||
!settings_has(SETTINGS_CUR_ID)) {
playlist_select(PL_SYSTEM, "Collection");
if (playlist_size(PL_SYSTEM, "Queued Tracks") > 0)
playlist_select(PL_SYSTEM, "Queued Tracks");
}
if (!settings_has(s_type) || !settings_has(s_id))
return NULL;
type = settings_get(s_type);
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()
@ -48,140 +62,175 @@ void playlist_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))
return false;
if ((settings_get(SETTINGS_CUR_TYPE) == type) &&
settings_get(SETTINGS_CUR_ID) == playlist_get_id(type, name))
return true;
unsigned int i;
settings_set(SETTINGS_PREV_TYPE, settings_get(SETTINGS_CUR_TYPE));
settings_set(SETTINGS_PREV_ID, settings_get(SETTINGS_CUR_ID));
settings_set(SETTINGS_CUR_TYPE, type);
settings_set(SETTINGS_CUR_ID, playlist_get_id(type, name));
return true;
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_played(track);
}
}
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,
struct track *track)
bool playlist_delete(struct playlist *playlist)
{
bool ret;
enum playlist_type_t type;
bool ret;
if (!track)
if (!playlist || !playlist->pl_ops->pl_delete)
return false;
ret = playlist_types[type]->pl_add_track(name, track);
if (type == PL_SYSTEM && string_match(name, "Queued Tracks"))
playlist_select(PL_SYSTEM, "Queued Tracks");
type = playlist->pl_type;
ret = playlist->pl_ops->pl_delete(playlist);
if (ret)
playlist_types[type]->pl_save();
return ret;
}
bool playlist_remove(enum playlist_type_t type, const gchar *name,
struct track *track)
struct playlist *playlist_lookup(enum playlist_type_t type, const gchar *name)
{
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 playlist_types[type]->pl_remove_track(name, track);
}
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)
if (!playlist->pl_ops->pl_can_select)
return false;
if (!playlist->pl_ops->pl_can_select(playlist))
return false;
return queue_has(queue, track);
}
unsigned int playlist_size(enum playlist_type_t type, const gchar *name)
{
struct queue *queue = playlist_get_queue(type, name);
return queue ? queue_size(queue) : 0;
}
previous = current;
current = playlist;
void playlist_set_random(enum playlist_type_t type, const gchar *name,
bool enabled)
{
playlist_types[type]->pl_set_flag(name, Q_RANDOM, enabled);
}
settings_set(SETTINGS_CUR_TYPE, current->pl_type);
settings_set(SETTINGS_CUR_ID, current->pl_id);
bool playlist_get_random(enum playlist_type_t type, const gchar *name)
{
struct queue *queue = playlist_get_queue(type, name);
return queue ? queue_has_flag(queue, Q_RANDOM) : false;
}
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);
if (previous) {
settings_set(SETTINGS_PREV_TYPE, previous->pl_type);
settings_set(SETTINGS_PREV_ID, previous->pl_id);
}
return true;
}
struct track *playlist_next(void)
{
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 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);
struct track *track = playlist_generic_next(current);
if (track && current->pl_type < PL_MAX_TYPE)
playlist_types[current->pl_type]->pl_save();
return track;
}
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)
{
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);
if (!track || !playlist || !playlist->pl_ops->pl_add)
return false;
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;
}
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);
return playlist ? &playlist->pl_queue : NULL;
bool ret;
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/string.h>
static struct queue_ops *artist_ops = NULL;
static struct file artist_file = FILE_INIT("playlist.artist", 0);
static struct file artist_file = FILE_INIT_DATA("", "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));
playlist->pl_name = name;
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);
}
return playlist_generic_alloc(artist->ar_name, PL_ARTIST,
artist_index(artist), &pl_artist_ops,
3, COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
}
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 db_entry *dbe, *next;
queue_set_flag(&playlist->pl_queue, Q_ADD_FRONT);
db_for_each(dbe, next, track_db_get()) {
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;
}
@ -59,17 +52,14 @@ static bool __artist_pl_load(void *data)
if (!file_open(&artist_file, OPEN_READ))
return true;
file_readf(&artist_file, "%u\n", &n);
n = file_readu(&artist_file);
for (i = 0; i < n; i++) {
name = file_readl(&artist_file);
playlist = __artist_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &artist_file,
PL_SAVE_METADATA);
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);
@ -89,99 +79,46 @@ static void pl_artist_save(void)
db_for_each(dbe, next, artist_db_get()) {
playlist = ARTIST(dbe)->ar_playlist;
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);
}
static struct playlist *pl_artist_get_playlist(const gchar *name)
static struct playlist *pl_artist_lookup(const gchar *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);
return artist ? artist->ar_dbe.dbe_index : -1;
struct artist *artist = artist_get(id);
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);
return playlist ? playlist_generic_can_select(playlist) : false;
}
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 artist *artist = track->tr_album->al_artist;
playlist_generic_update(artist->ar_playlist, track);
}
struct playlist_type pl_artist = {
.pl_save = pl_artist_save,
.pl_get_playlist = pl_artist_get_playlist,
.pl_get_id = pl_artist_get_id,
.pl_get_name = pl_artist_get_name,
.pl_can_select = pl_artist_can_select,
.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,
.pl_save = pl_artist_save,
.pl_lookup = pl_artist_lookup,
.pl_get = pl_artist_get,
.pl_played = pl_artist_played,
.pl_selected = pl_artist_played,
};
void pl_artist_init(struct queue_ops *ops)
void pl_artist_init(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
artist_ops = ops;
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;
idle_schedule(IDLE_SYNC, __artist_pl_add, playlist);
@ -193,13 +130,10 @@ void pl_artist_init(struct queue_ops *ops)
void pl_artist_deinit()
{
struct db_entry *dbe, *next;
struct playlist *playlist;
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_pl_free(playlist);
}
}
@ -209,18 +143,15 @@ void pl_artist_new_track(struct track *track)
struct playlist *playlist = (struct playlist *)artist->ar_playlist;
if (!playlist) {
playlist = __artist_pl_alloc(artist->ar_name);
playlist = __artist_pl_alloc(artist);
artist->ar_playlist = playlist;
}
playlist_generic_add_track(playlist, track);
playlist_generic_add(playlist, track);
}
void pl_artist_delete_track(struct track *track)
{
struct artist *artist = track->tr_album->al_artist;
struct playlist *playlist = (struct playlist *)artist->ar_playlist;
if (playlist)
playlist_generic_remove_track(playlist, track);
struct artist *artist = track->tr_album->al_artist;
playlist_generic_remove(artist->ar_playlist, track);
}

View File

@ -2,118 +2,312 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/type.h>
#include <core/playlists/generic.h>
#include <stdlib.h>
static struct playlist_callbacks *callbacks = NULL;
/*
* Noop playlist operations.
*/
bool playlist_noop_can_select(struct playlist *playlist)
static int __playlist_generic_find_sort(gconstpointer a, gconstpointer b)
{
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,
enum queue_flags flag, bool enabled)
{
}
while (cur) {
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,
enum compare_t sort, bool reset)
{
return (field > 0) ? res : -res;
}
/*
* Generic playlist operations.
*/
void playlist_generic_init(struct playlist *playlist, unsigned int flags,
struct queue_ops *ops)
void playlist_generic_set_callbacks(struct playlist_callbacks *cb)
{
queue_init(&playlist->pl_queue, flags | Q_ENABLED, ops, playlist);
queue_sort(&playlist->pl_queue, COMPARE_ARTIST, true);
queue_sort(&playlist->pl_queue, COMPARE_YEAR, false);
queue_sort(&playlist->pl_queue, COMPARE_TRACK, false);
playlist->pl_private = NULL;
callbacks = cb;
}
static void __playlist_generic_vinit(struct playlist *playlist,
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)
{
return queue_size(&playlist->pl_queue) > 0;
return playlist_size(playlist) > 0;
}
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 (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)
if (!playlist)
return;
data = g_malloc(sizeof(struct pl_update_data));
data->pud_playlist = playlist;
data->pud_func = func;
idle_schedule(IDLE_SYNC, __playlist_generic_update, data);
n = playlist_size(playlist);
g_queue_clear(&playlist->pl_tracks);
playlist->pl_length = 0;
playlist->pl_current = NULL;
if (callbacks)
callbacks->pl_cb_removed(playlist, NULL, n);
}
void playlist_generic_set_flag(struct playlist *playlist,
enum queue_flags flag, bool enabled)
bool playlist_generic_add(struct playlist *playlist, struct track *track)
{
if (enabled)
return queue_set_flag(&playlist->pl_queue, flag);
return queue_unset_flag(&playlist->pl_queue, flag);
if (!playlist || !track || playlist_has(playlist, track))
return false;
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,
enum compare_t sort, bool reset)
bool playlist_generic_add_front(struct playlist *playlist, struct track *track)
{
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)
{
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/library.h>
#include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <unistd.h>
struct scan_data {
@ -13,27 +14,17 @@ struct scan_data {
};
static bool __lib_pl_scan_dir(void *);
static struct queue_ops *lib_ops = NULL;
static struct file lib_file = FILE_INIT("playlist.library", 0);
static struct file lib_file = FILE_INIT_DATA("", "playlist.library", 0);
static struct playlist_ops pl_library_ops;
static struct playlist *__lib_pl_alloc(struct library *library)
{
struct playlist *playlist = g_malloc(sizeof(struct playlist));
playlist->pl_name = library->li_path;
playlist->pl_type = PL_LIBRARY;
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);
}
return playlist_generic_alloc(library->li_path, PL_LIBRARY,
library_index(library), &pl_library_ops,
4, COMPARE_ARTIST, COMPARE_YEAR,
COMPARE_ALBUM, COMPARE_TRACK);
}
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 db_entry *dbe, *next;
queue_set_flag(&playlist->pl_queue, Q_ADD_FRONT);
db_for_each(dbe, next, track_db_get()) {
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;
}
@ -67,17 +57,14 @@ static bool __lib_pl_load(void *data)
if (!file_open(&lib_file, OPEN_READ))
return true;
file_readf(&lib_file, "%u\n", &n);
n = file_readu(&lib_file);
for (i = 0; i < n; i++) {
name = file_readl(&lib_file);
playlist = __lib_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &lib_file,
PL_SAVE_METADATA);
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);
@ -106,7 +93,7 @@ static void __lib_pl_read_path(struct scan_data *scan, const gchar *name)
else {
track = track_add(scan->sd_library, path);
if (track) {
queue_add(&playlist->pl_queue, track);
playlist_generic_add(playlist, track);
pl_system_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) {
pl_system_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));
}
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)
{
struct db_entry *dbe, *next;
@ -180,136 +197,65 @@ static void pl_library_save(void)
db_for_each(dbe, next, library_db_get()) {
playlist = LIBRARY(dbe)->li_playlist;
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);
}
static struct playlist *pl_library_get_playlist(const gchar *name)
static struct playlist *pl_library_lookup(const gchar *name)
{
return __lib_pl_lookup(name);
}
static unsigned int pl_library_get_id(const gchar *name)
{
struct library *library = library_find(name);
return library ? library->li_dbe.dbe_index : -1;
}
static gchar *pl_library_get_name(unsigned int id)
static struct playlist *pl_library_get(unsigned int 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)
{
struct playlist *playlist = __lib_pl_lookup(name);
return playlist ? playlist_generic_can_select(playlist) : false;
}
static bool pl_library_new(const gchar *name)
static struct playlist *pl_library_new(const gchar *name)
{
struct library *library;
if (__lib_pl_lookup(name) || !g_file_test(name, G_FILE_TEST_IS_DIR))
return false;
return NULL;
library = library_find(name);
library->li_playlist = __lib_pl_alloc(library);
__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 = library_lookup(name);
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 library *library = track->tr_library;
playlist_generic_update(library->li_playlist, track);
}
struct playlist_type pl_library = {
.pl_save = pl_library_save,
.pl_get_playlist = pl_library_get_playlist,
.pl_get_id = pl_library_get_id,
.pl_get_name = pl_library_get_name,
.pl_can_select = pl_library_can_select,
.pl_new = pl_library_new,
.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,
.pl_save = pl_library_save,
.pl_lookup = pl_library_lookup,
.pl_get = pl_library_get,
.pl_new = pl_library_new,
.pl_played = pl_library_played,
.pl_selected = pl_library_played,
};
void pl_library_init(struct queue_ops *ops)
void pl_library_init(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
lib_ops = ops;
db_for_each(dbe, next, library_db_get()) {
playlist = __lib_pl_alloc(LIBRARY(dbe));
LIBRARY(dbe)->li_playlist = playlist;
idle_schedule(IDLE_SYNC, __lib_pl_add, playlist);
pl_library_update(playlist->pl_name);
idle_schedule(IDLE_SYNC, __lib_pl_add, playlist);
idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
}
idle_schedule(IDLE_SYNC, __lib_pl_load, NULL);
@ -318,12 +264,14 @@ void pl_library_init(struct queue_ops *ops)
void pl_library_deinit()
{
struct db_entry *dbe, *next;
struct playlist *playlist;
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;
__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/string.h>
static bool pl_system_add_track(const gchar *, struct track *);
static bool pl_system_remove_track(const gchar *, struct track *);
static void pl_system_update(const gchar *);
static inline struct queue *__sys_pl_queue(enum sys_playlist_t);
static void __sys_pl_save();
static bool __sys_pl_load();
static struct playlist *pl_system_lookup(const gchar *);
static struct playlist *pl_system_get(unsigned int);
static void pl_system_save();
static struct file sys_file = FILE_INIT("playlist.db", 0);
static struct file sys_deck_f = FILE_INIT("deck", 1);
static struct file sys_collection_f = FILE_INIT("library.q", 0);
static struct file sys_pl_file = FILE_INIT("playlist.system", 0);
static struct sys_playlist *sys_playlists[SYS_PL_NUM_PLAYLISTS];
static struct file sys_file = FILE_INIT_DATA("", "playlist.db", 0);
static struct file sys_deck_f = FILE_INIT_DATA("", "deck", 1);
static struct file sys_collection_f = FILE_INIT_DATA("", "library.q", 0);
static struct file sys_pl_file = FILE_INIT_DATA("", "playlist.system", 0);
/*
* Generic system playlist operations.
*/
static void sys_pl_generic_init(struct playlist *playlist, unsigned int flags,
struct queue_ops *ops)
static bool sys_pl_delete_clear(struct playlist *playlist)
{
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,
struct queue_ops *ops)
static bool sys_pl_update_check(struct playlist *playlist, struct track *track)
{
sys_pl_generic_init(playlist, flags, ops);
pl_system_update(playlist->pl_name);
switch (playlist->pl_id) {
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);
queue_save_flags(&playlist->pl_queue, file, true);
struct playlist *playlist = (struct playlist *)data;
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);
queue_save_tracks(&playlist->pl_queue, file);
switch (playlist->pl_id) {
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.
*/
static struct sys_playlist sys_favorites = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Favorites"),
.spl_init = playlist_generic_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
.spl_can_select = playlist_generic_can_select,
.spl_add = playlist_generic_add_track,
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_generic_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
static struct playlist_ops favorites_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_delete_clear,
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -90,46 +91,49 @@ static struct sys_playlist sys_favorites = {
*/
static bool sys_pl_hidden_add(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_add_track(playlist, track);
pl_system_remove_track("Collection", track);
pl_system_remove_track("Unplayed", track);
pl_system_remove_track("Most Played", track);
pl_system_remove_track("Least Played", track);
bool ret = playlist_generic_add(pl_system_get(SYS_PL_HIDDEN), track);
playlist_generic_remove(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_remove(pl_system_get(SYS_PL_UNPLAYED), track);
playlist_generic_remove(pl_system_get(SYS_PL_MOST_PLAYED), track);
playlist_generic_remove(pl_system_get(SYS_PL_LEAST_PLAYED), track);
return ret;
}
static bool sys_pl_hidden_remove(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_remove_track(playlist, track);
pl_system_add_track("Collection", track);
pl_system_add_track("Unplayed", track);
pl_system_add_track("Most Played", track);
pl_system_add_track("Least Played", track);
bool ret = playlist_generic_remove(playlist, track);
unsigned int average = track_db_average_plays();
unsigned int add_id = SYS_PL_LEAST_PLAYED;
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;
}
static void sys_pl_hidden_clear(struct playlist *playlist)
static bool sys_pl_hidden_clear(struct playlist *playlist)
{
struct track *track;
while (queue_size(&playlist->pl_queue) > 0) {
track = queue_at(&playlist->pl_queue, 0);
while (playlist_size(playlist) > 0) {
track = playlist_at(playlist, 0);
sys_pl_hidden_remove(playlist, track);
}
pl_system_save();
return false;
}
static struct sys_playlist sys_hidden = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Hidden"),
.spl_init = playlist_generic_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
.spl_can_select = playlist_generic_can_select,
.spl_add = sys_pl_hidden_add,
.spl_remove = sys_pl_hidden_remove,
.spl_clear = sys_pl_hidden_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
static struct playlist_ops hidden_ops = {
.pl_add = sys_pl_hidden_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_hidden_clear,
.pl_remove = sys_pl_hidden_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -138,20 +142,20 @@ static struct sys_playlist sys_hidden = {
*/
static bool sys_pl_queued_load()
{
struct playlist *playlist = &sys_playlists[SYS_PL_QUEUED]->spl_playlist;
struct queue *queue = &playlist->pl_queue;
struct playlist *playlist = pl_system_get(SYS_PL_QUEUED);
unsigned int num, i, flags = 0;
if (!file_open(&sys_deck_f, OPEN_READ))
return true;
file_readf(&sys_deck_f, "%u", &num);
num = file_readu(&sys_deck_f);
for (i = 0; i < num; i++) {
file_readf(&sys_deck_f, "%u", &flags);
flags &= ~(Q_SAVE_SORT | Q_SAVE_FLAGS);
flags = file_readu(&sys_deck_f);
flags &= PL_RANDOM;
if (i == 0)
queue->q_flags |= flags;
queue_load_tracks(queue, &sys_deck_f);
playlist_generic_set_random(playlist,
flags == PL_RANDOM);
playlist_generic_load(playlist, &sys_deck_f, PL_SAVE_TRACKS);
}
file_close(&sys_deck_f);
@ -159,24 +163,29 @@ static bool sys_pl_queued_load()
return true;
}
static void sys_pl_queued_init(struct playlist *playlist,
unsigned int flags, struct queue_ops *ops)
static bool sys_pl_queued_delete(struct playlist *playlist)
{
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 = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "Queued Tracks"),
.spl_init = sys_pl_queued_init,
.spl_save = sys_pl_save_full,
.spl_load = sys_pl_load_full,
.spl_can_select = playlist_generic_can_select,
.spl_add = playlist_generic_add_track,
.spl_remove = playlist_generic_remove_track,
.spl_clear = playlist_generic_clear,
.spl_set_flag = playlist_generic_set_flag,
.spl_sort = playlist_generic_sort,
.spl_next = playlist_generic_next,
static bool sys_pl_queued_remove(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_remove(playlist, track);
if (playlist_size(playlist) == 0)
sys_pl_queued_delete(playlist);
return ret;
}
static struct playlist_ops queued_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.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()
{
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)) {
queue_load_flags(&playlist->pl_queue, &sys_collection_f, false);
queue_unset_flag(&playlist->pl_queue, Q_SAVE_FLAGS);
queue_unset_flag(&playlist->pl_queue, Q_SAVE_SORT);
playlist_generic_load(playlist, &sys_collection_f, PL_SAVE_FLAGS);
file_close(&sys_collection_f);
file_remove(&sys_collection_f);
}
@ -198,227 +205,94 @@ static bool sys_pl_collection_load()
return true;
}
static bool sys_pl_collection_can_select(struct playlist *playlist)
{
return true;
}
static bool sys_pl_collection_update(struct playlist *playlist,
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,
static struct playlist_ops collection_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_remove = sys_pl_hidden_add,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
/*
* 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)
{
queue_add(&playlist->pl_queue, track);
queue_iter_set(&playlist->pl_queue, &playlist->pl_queue.q_cur, 0);
playlist_generic_add_front(playlist, track);
playlist_current_set(playlist, 0);
return true;
}
static struct sys_playlist sys_history = {
.spl_playlist = DEFINE_PLAYLIST(PL_SYSTEM, "History"),
.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,
static struct playlist_ops history_ops = {
.pl_add = sys_pl_history_add,
};
/*
* 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)
{
if (track->tr_count > 0)
return false;
return sys_pl_generic_add(playlist, track);
}
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,
static struct playlist_ops dynamic_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,
};
/*
* Most played tracks playlist operations.
*/
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);
}
#define SYS_PLAYLIST(id, name, ops) \
[id] = DEFINE_PLAYLIST(PL_SYSTEM, name, id, ops)
static bool sys_pl_most_played_update(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) || true;
}
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,
static struct playlist sys_playlists[SYS_PL_NUM_PLAYLISTS] = {
SYS_PLAYLIST(SYS_PL_FAVORITES, "Favorites", &favorites_ops),
SYS_PLAYLIST(SYS_PL_HIDDEN, "Hidden", &hidden_ops),
SYS_PLAYLIST(SYS_PL_QUEUED, "Queued Tracks", &queued_ops),
SYS_PLAYLIST(SYS_PL_COLLECTION, "Collection", &collection_ops),
SYS_PLAYLIST(SYS_PL_HISTORY, "History", &history_ops),
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),
};
/*
* 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()
{
__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;
}
static bool __sys_pl_load_new()
{
struct sys_playlist *sys_pl;
unsigned int i, n;
struct playlist *playlist;
unsigned int i, n, load;
gchar *name;
if (!file_open(&sys_pl_file, OPEN_READ)) {
@ -429,213 +303,152 @@ static bool __sys_pl_load_new()
return true;
}
file_readf(&sys_pl_file, "%u\n", &n);
n = file_readu(&sys_pl_file);
for (i = 0; i < n; i++) {
name = file_readl(&sys_pl_file);
sys_pl = __sys_pl_lookup(name);
if (sys_pl)
sys_pl->spl_load(&sys_pl->spl_playlist, &sys_pl_file);
load = PL_SAVE_METADATA;
name = file_readl(&sys_pl_file);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
playlist = pl_system_lookup(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);
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)
{
__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)
{
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)
static struct playlist *pl_system_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 i;
if (string_match(name, pl_system_get(i)->pl_name))
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;
}
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 sys_pl ? sys_pl->spl_can_select(&sys_pl->spl_playlist) : false;
return (id < SYS_PL_NUM_PLAYLISTS) ? &sys_playlists[id] : NULL;
}
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);
if (sys_pl) {
sys_pl->spl_clear(&sys_pl->spl_playlist);
__sys_pl_save();
}
return false; /* Don't remove the playlist from the sidebar. */
}
unsigned int i;
static bool pl_system_add_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_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;
sys_pl_queued_remove(pl_system_get(SYS_PL_QUEUED), track);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_update(pl_system_get(i), track);
}
struct playlist_type pl_system = {
.pl_save = pl_system_save,
.pl_get_playlist = pl_system_get_playlist,
.pl_get_id = pl_system_get_id,
.pl_get_name = pl_system_get_name,
.pl_can_select = pl_system_can_select,
.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,
.pl_save = pl_system_save,
.pl_lookup = pl_system_lookup,
.pl_get = pl_system_get,
.pl_played = pl_system_played,
.pl_selected = pl_system_selected,
};
void pl_system_init(struct queue_ops *ops)
void pl_system_init(void)
{
struct sys_playlist *sys_pl;
struct playlist *playlist;
unsigned int i;
idle_schedule(IDLE_SYNC, __sys_pl_load_new, NULL);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_init(&sys_pl->spl_playlist, Q_REPEAT, ops);
playlist = pl_system_get(i);
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()
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
queue_deinit(__sys_pl_queue(i));
for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_deinit(pl_system_get(i));
}
void pl_system_new_track(struct track *track)
{
pl_system_add_track("Collection", track);
pl_system_add_track("Unplayed", track);
playlist_generic_add(pl_system_lookup("Collection"), track);
playlist_generic_add(pl_system_lookup("Unplayed"), track);
}
void pl_system_delete_track(struct track *track)
{
struct sys_playlist *sys_pl;
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
sys_pl = sys_playlists[i];
sys_pl->spl_remove(&sys_pl->spl_playlist, track);
}
for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_remove(pl_system_get(i), track);
}

View File

@ -3,29 +3,31 @@
*/
#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));
dbe_init(&playlist->pl_dbe, playlist);
playlist->pl_playlist.pl_name = name;
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;
}
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)
{
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));
}
@ -35,17 +37,12 @@ static gchar *user_db_key(struct db_entry *dbe)
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);
struct user_playlist *playlist = __user_db_alloc(name);
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);
struct user_playlist *playlist = __user_db_alloc(name, index);
playlist_generic_load(&playlist->pl_playlist, file, PL_SAVE_ALL);
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;
file_writef(file, "%s\n", playlist->pl_name);
queue_save_flags(&playlist->pl_queue, file, true);
queue_save_tracks(&playlist->pl_queue, file);
playlist_generic_save(playlist, file, PL_SAVE_ALL);
}
@ -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);
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);
struct db_entry *dbe = db_get(&user_db, playlist->pl_name);
if (dbe) {
db_remove(&user_db, dbe);
db_defrag(&user_db);
@ -119,82 +74,65 @@ static bool pl_user_delete(const gchar *name)
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);
bool ret = false;
if (playlist) {
ret = playlist_generic_add_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
db_save(&user_db);
}
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);
bool ret = false;
if (playlist) {
ret = playlist_generic_remove_track(playlist, track);
if (ret)
pl_user_save();
}
return ret;
struct db_entry *dbe = db_get(&user_db, name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
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,
bool enabled)
static struct playlist *pl_user_new(const gchar *name)
{
struct playlist *playlist = __user_pl_lookup(name);
playlist_generic_set_flag(playlist, flag, enabled);
pl_user_save();
struct db_entry *dbe;
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);
playlist_generic_sort(playlist, sort, reset);
pl_user_save();
}
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 db_entry *dbe, *next;
db_for_each(dbe, next, &user_db)
playlist_generic_update(&USER_PLAYLIST(dbe)->pl_playlist, track);
}
struct playlist_type pl_user = {
.pl_save = pl_user_save,
.pl_get_playlist = pl_user_get_playlist,
.pl_get_id = pl_user_get_id,
.pl_get_name = pl_user_get_name,
.pl_can_select = pl_user_can_select,
.pl_new = pl_user_new,
.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,
.pl_save = pl_user_save,
.pl_lookup = pl_user_lookup,
.pl_get = pl_user_get,
.pl_new = pl_user_new,
.pl_played = pl_user_played,
.pl_selected = pl_user_played,
};
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_load(&user_db);
}
@ -208,3 +146,33 @@ struct database *pl_user_db_get()
{
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,397 +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_erase_track(struct queue *queue, struct track *track)
{
struct queue_iter it;
queue_for_each(queue, &it) {
if (queue_iter_val(&it) == track) {
queue_erase(queue, it.it_pos);
return;
}
}
}
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>
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)
@ -29,9 +29,11 @@ static void __settings_read()
unsigned int num, i, value;
gchar *key;
file_readf(&gui_settings_file, "%u\n", &num);
num = file_readu(&gui_settings_file);
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));
}
}

View File

@ -81,3 +81,25 @@ bool string_match_token(const gchar *prefix, gchar **tokens)
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 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);
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);
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(f->cf_name);
g_free(acf->ac_subdir);
g_free(acf->ac_name);
g_free(acf);
}
static bool __album_fetch_cover(struct album *album, gchar *releaseid)
{
struct cache_file file;
struct album_cache_file *file;
CaaImageData image;
CaaCoverArt *caa;
gchar error[256];
@ -52,13 +63,13 @@ static bool __album_fetch_cover(struct album *album, gchar *releaseid)
goto out;
}
__album_init_file(album, &file);
if (cache_file_open(&file, OPEN_WRITE)) {
cache_file_write(&file, caa_imagedata_data(image),
caa_imagedata_size(image));
cache_file_close(&file);
file = __album_alloc_file(album);
if (file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
file_write(&file->ac_file, caa_imagedata_data(image),
caa_imagedata_size(image));
file_close(&file->ac_file);
}
__album_deinit_file(&file);
__album_free_file(file);
caa_imagedata_delete(image);
out:
@ -127,7 +138,8 @@ static bool __album_query_artist(struct album *album, struct artist *al_artist,
gchar *release, *artist, *year;
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;
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;
}
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;
gchar *name;
@ -241,7 +253,7 @@ static struct album *__album_parse_v0(gchar *line)
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;
struct album *album;
@ -351,40 +363,40 @@ bool album_match_token(struct album *album, const gchar *string)
bool album_artwork_exists(struct album *album)
{
struct cache_file file;
struct album_cache_file *file;
bool ret;
__album_init_file(album, &file);
ret = cache_file_exists(&file);
__album_deinit_file(&file);
file = __album_alloc_file(album);
ret = file_exists(&file->ac_file);
__album_free_file(file);
return ret;
}
gchar *album_artwork_path(struct album *album)
{
struct cache_file file;
struct album_cache_file *file;
gchar *ret = NULL;
__album_init_file(album, &file);
if (cache_file_exists(&file))
ret = cache_file_path(&file);
__album_deinit_file(&file);
file = __album_alloc_file(album);
if (file_exists(&file->ac_file))
ret = file_path(&file->ac_file);
__album_free_file(file);
return ret;
}
bool album_artwork_import(struct album *album, gchar *path)
{
struct cache_file file;
struct album_cache_file *file;
bool ret = false;
__album_init_file(album, &file);
if (path && cache_file_open(&file, OPEN_WRITE)) {
ret = cache_file_import(&file, path);
cache_file_close(&file);
file = __album_alloc_file(album);
if (path && file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
ret = file_import(&file->ac_file, path);
file_close(&file->ac_file);
}
__album_deinit_file(&file);
__album_free_file(file);
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;
}
@ -37,7 +37,7 @@ static gchar *artist_key(struct db_entry *dbe)
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;
}

View File

@ -18,7 +18,7 @@ static struct genre *__genre_alloc(gchar *name)
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;
}
@ -35,7 +35,7 @@ static gchar *genre_key(struct db_entry *dbe)
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;
}

View File

@ -1,6 +1,7 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/string.h>
#include <core/tags/library.h>
#define LIBRARY_DB_MIN 0 /* Ocarina 6.0 */
@ -17,7 +18,7 @@ static struct library *__library_alloc(gchar *path)
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;
}
@ -32,15 +33,15 @@ static gchar *library_key(struct db_entry *dbe)
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;
/* Old "enabled" flag */
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;
}
@ -82,12 +83,22 @@ const struct database *library_db_get()
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)
{
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)

View File

@ -41,26 +41,18 @@ static struct track *__track_alloc()
return track;
}
struct db_entry *track_alloc(const gchar *key)
static struct track *__track_alloc_filepath(const gchar *filepath)
{
TagLib_File *file = taglib_file_new(filepath);
const TagLib_AudioProperties *audio;
struct library *library;
struct track *track = NULL;
struct artist *artist;
struct genre *genre;
struct track *track = NULL;
unsigned int lib_id;
TagLib_File *file;
TagLib_Tag *tag;
char *fullpath, *path;
TagLib_Tag *tag;
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)) {
g_printerr("WARNING: Could not read tags for: %s\n", fullpath);
goto out;
g_printerr("WARNING: Could not read tags for: %s\n", filepath);
return NULL;
}
track = __track_alloc();
@ -69,24 +61,50 @@ struct db_entry *track_alloc(const gchar *key)
artist = artist_find(taglib_tag_artist(tag));
genre = genre_find(taglib_tag_genre(tag));
track->tr_album = album_find(artist, genre, taglib_tag_album(tag),
taglib_tag_year(tag));
track->tr_library = library;
track->tr_album = album_find(artist, genre, taglib_tag_album(tag),
taglib_tag_year(tag));
unplayed_count++;
track->tr_count = 0;
track->tr_length = taglib_audioproperties_length(audio);
track->tr_track = taglib_tag_track(tag);
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_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
&track->tr_alts);
taglib_tag_free_strings();
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(fullpath);
return track ? &track->tr_dbe : NULL;
@ -100,10 +118,7 @@ static void track_free(struct db_entry *dbe)
if (track->tr_count == 0)
unplayed_count--;
g_strfreev(track->tr_tokens);
g_strfreev(track->tr_alts);
g_free(track->tr_title);
g_free(track);
__track_free(track);
}
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)
{
unsigned int artist_id, album_id, genre_id;
struct artist *artist;
struct genre *genre;
struct album *album;
struct artist *artist = artist_get(file_readu(file));
struct album *album = album_get( file_readu(file));
struct genre *genre = genre_get( file_readu(file));
file_readf(file, "%u %u %u %hu", &artist_id, &album_id, &genre_id,
&track->tr_track);
track->tr_track = file_readhu(file);
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)
album = album_find(artist, genre, album->al_name, album->al_year);
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();
unsigned int library_id, album_id;
file_readf(file, "%u", &library_id);
track->tr_library = library_get(library_id);
track->tr_library = library_get(file_readu(file));
if (file_version(file) == 0)
track_read_v0(file, track);
else {
file_readf(file, "%u %hu", &album_id, &track->tr_track);
track->tr_album = album_get(album_id);
track->tr_album = album_get(file_readu(file));
track->tr_track = file_readhu(file);
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;
if (track->tr_count == 0)
@ -251,6 +259,24 @@ unsigned int track_db_average_plays()
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)
{
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));
}
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)
{
switch (compare) {
@ -329,11 +372,13 @@ gchar *track_path(struct track *track)
g_free(path);
return res;
}
return g_strdup("");
return g_strdup(track->tr_path);
}
void track_played(struct track *track)
{
if (TRACK_IS_EXTERNAL(track))
return;
if (track->tr_count == 0)
unplayed_count--;
track->tr_count++;

View File

@ -1,8 +1,6 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/playlist.h>
#include <core/string.h>
#include <gui/artwork.h>
#include <gui/audio.h>
@ -11,7 +9,8 @@
#include <gui/treeview.h>
#include <gui/window.h>
static guint audio_timeout = 0;
static guint audio_timeout = 0;
static guint popover_timeout = 0;
static inline void __gui_audio_set_label_markup(GtkLabel *label,
const gchar *size,
@ -44,29 +43,112 @@ static void __gui_audio_load(struct track *track)
g_free(duration);
}
static void __gui_audio_set_pause_text(int n, GstState state)
{
bool sensitive = true;
gchar *text;
if (n == -1) {
sensitive = false;
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)
{
gtk_combo_box_set_active(GTK_COMBO_BOX(gui_pause_after()), n + 1);
__gui_audio_set_pause_text(n, audio_cur_state());
}
struct audio_ops audio_ops = {
.on_load = __gui_audio_load,
.on_state_change = __gui_audio_change_state,
.on_config_pause = __gui_audio_config_pause,
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_changed(GtkComboBox *combo, gpointer data)
void __gui_audio_pause(GtkButton *button, gpointer data)
{
audio_pause_after(gtk_combo_box_get_active(combo) - 1);
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);
}
}
void __gui_audio_pause_change_text(GtkEntry *entry, gpointer data)
{
const gchar *text = gtk_entry_get_text(entry);
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 __gui_audio_pause_inc(GtkButton *button, gpointer data)
{
audio_pause_after(audio_get_pause_count() + 1);
}
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,
@ -100,6 +182,8 @@ void gui_audio_init()
void gui_audio_deinit()
{
g_source_remove(audio_timeout);
if (popover_timeout > 0)
g_source_remove(popover_timeout);
}
int gui_audio_timeout(gpointer data)
@ -113,3 +197,9 @@ int gui_audio_timeout(gpointer data)
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;
}

View File

@ -42,7 +42,7 @@ static gboolean __gui_filter_visible_func(GtkTreeModel *model,
gpointer data)
{
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;
if (!search)
@ -58,15 +58,8 @@ static gboolean __gui_filter_visible_func(GtkTreeModel *model,
void __gui_filter_search_changed(GtkSearchEntry *search, gpointer data)
{
const gchar *text = gtk_entry_get_text(GTK_ENTRY(search));
struct playlist *playlist = gui_model_get_playlist();
if (!playlist)
return;
gui_filter_clear_search(playlist);
if (strlen(text) > 0)
playlist->pl_private = g_str_tokenize_and_fold(text, NULL, NULL);
playlist_set_search(gui_model_get_playlist(),
gtk_entry_get_text(GTK_ENTRY(search)));
gtk_tree_model_filter_refilter(gui_filter_get());
}
@ -91,20 +84,9 @@ void gui_filter_deinit()
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)
{
gchar **search = playlist ? (gchar **)playlist->pl_private : NULL;
gchar **search = playlist ? (gchar **)playlist->pl_search : NULL;
gchar *text = search ? g_strjoinv(" ", search) : g_strdup("");
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];
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)
@ -152,3 +134,9 @@ GtkTreePath *gui_filter_path_from_index(unsigned int index)
gtk_tree_path_free(real);
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.
*/
#include <core/audio.h>
#include <core/playlist.h>
#include <core/string.h>
#include <gui/builder.h>
#include <gui/model.h>
@ -29,6 +28,12 @@ static GType gui_model_columns[GUI_MODEL_N_COLUMNS] = {
[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)
{
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)
{
GtkTreePath *path;
unsigned int pos;
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data2, FALSE);
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
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;
}
static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
gint column, GValue *value)
{
struct track *track = iter->user_data2;
struct track *track = playlist_iter_track(iter->user_data);
gchar *str;
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_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)
{
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);
g_return_val_if_fail(iter->user_data2, FALSE);
pos = GPOINTER_TO_UINT(iter->user_data) + 1;
return __gui_model_iter_nth_child(model, iter, NULL, pos);
iter->user_data = playlist_iter_next(iter->user_data);
return iter->user_data != NULL;
}
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)
return 0;
return queue_size(&cur_playlist->pl_queue);
return playlist_size(cur_playlist);
}
static gboolean __gui_model_iter_nth_child(GtkTreeModel *model,
@ -156,15 +161,12 @@ static gboolean __gui_model_iter_nth_child(GtkTreeModel *model,
GtkTreeIter *parent,
gint n)
{
struct queue *queue = &cur_playlist->pl_queue;
if (parent || !cur_playlist || n >= queue_size(queue))
if (parent || !cur_playlist || n >= playlist_size(cur_playlist))
return FALSE;
iter->stamp = gui_model->gm_stamp;
iter->user_data = GUINT_TO_POINTER(n);
iter->user_data2 = queue_at(queue, n);
return TRUE;
iter->stamp = gui_model->gm_stamp;
iter->user_data = playlist_iter_get(cur_playlist, n);
return iter->user_data != NULL;
}
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;
}
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)
{
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;
}
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 = {
.class_size = sizeof(GuiModelClass),
.base_init = NULL,
@ -225,6 +254,12 @@ static const GInterfaceInfo gui_tree_model = {
.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)
{
@ -233,6 +268,9 @@ void gui_model_init(void)
(GTypeFlags)0);
g_type_add_interface_static(gui_model_type, GTK_TYPE_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);
g_assert(gui_model != NULL);
@ -251,12 +289,20 @@ static void __gui_model_set_runtime(void)
gchar *len = NULL;
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);
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)
{
return gui_model;
@ -267,7 +313,7 @@ GType gui_model_get_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;
GtkTreeIter iter;
@ -275,32 +321,21 @@ void gui_model_add(struct playlist *playlist, unsigned int row)
if (cur_playlist != playlist)
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);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(gui_model), path, &iter);
__gui_model_set_runtime();
gtk_tree_path_free(path);
__gui_model_set_runtime();
}
void gui_model_remove(struct playlist *playlist, unsigned int row)
{
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)
void gui_model_remove(struct playlist *playlist, struct track *track,
unsigned int n)
{
GtkTreePath *path;
unsigned int i;
if (!gui_model || cur_playlist != playlist)
if (cur_playlist != playlist)
return;
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_path_prev(path);
}
gtk_tree_path_free(path);
__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;
GtkTreeIter iter;
if (cur_playlist == playlist)
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();
gtk_tree_path_free(path);
}
void gui_model_set_playlist(struct playlist *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;
__gui_model_set_runtime();
if (playlist && queue_size(&playlist->pl_queue) > 0)
if (playlist && playlist_size(playlist) > 0)
gui_model_add(playlist, 0);
}

View File

@ -2,7 +2,6 @@
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/audio.h>
#include <gui/audio.h>
#include <gui/builder.h>
#include <gui/filter.h>
@ -16,12 +15,13 @@
#define OCARINA_FLAGS (G_APPLICATION_HANDLES_COMMAND_LINE)
static const GOptionEntry ocarina_options[] = {
{ "next", 'n', 0, G_OPTION_ARG_NONE, NULL, "Play next track", NULL },
{ "pause", 'P', 0, G_OPTION_ARG_NONE, NULL, "Pause 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 },
{ "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 },
{ "next", 'n', 0, G_OPTION_ARG_NONE, NULL, "Play next track", NULL },
{ "pause", 'P', 0, G_OPTION_ARG_NONE, NULL, "Pause 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 },
{ "sync", 's', 0, G_OPTION_ARG_NONE, NULL, "Don't run background tasks", 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 },
};
@ -31,11 +31,7 @@ const static gchar *OCARINA_APP = "org.gtk.ocarina";
const static gchar *OCARINA_APP = "org.gtk.ocarina-debug";
#endif
struct core_init_data init_data = {
&playlist_ops,
&audio_ops,
};
static enum idle_sync_t idle_sync = IDLE_ASYNC;
static int startup_argc;
static char **startup_argv;
@ -58,6 +54,8 @@ static void __ocarina_activate(GApplication *application, gpointer data)
static int __ocarina_local_options(GApplication *application,
GVariantDict *options, gpointer data)
{
if (g_variant_dict_contains(options, "sync"))
idle_sync = IDLE_SYNC;
if (!g_variant_dict_contains(options, "version"))
return -1;
g_printf("Ocarina %s\n", get_version());
@ -72,9 +70,16 @@ static int __ocarina_command_line(GApplication *application,
gpointer data)
{
GVariantDict *options;
gchar **args;
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);
if (g_variant_dict_contains(options, "next"))
audio_next();
@ -99,7 +104,7 @@ static void __ocarina_startup(GApplication *application, gpointer data)
gchar *icon = find_file_path("ocarina.png");
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_model_init();
gui_filter_init();

View File

@ -28,76 +28,32 @@ static inline void __gui_playlist_update_size(struct playlist *playlist)
update_size[playlist->pl_type](playlist);
}
static void *__gui_playlist_init(struct queue *queue, void *data)
static void __gui_playlist_alloc(struct playlist *playlist)
{
struct playlist *playlist = (struct playlist *)data;
if (playlist->pl_type == PL_ARTIST)
gui_pl_artist_add(playlist);
return playlist;
}
static void __gui_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 __gui_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_playlist_update_size(queue->q_private);
}
static void __gui_playlist_removed(struct queue *queue, unsigned int row)
{
gui_model_remove(queue->q_private, row);
__gui_playlist_update_size(queue->q_private);
}
static void __gui_playlist_cleared(struct queue *queue, unsigned int n)
{
gui_model_clear(queue->q_private, n);
__gui_playlist_update_size(queue->q_private);
}
static void __gui_playlist_updated(struct queue *queue, unsigned int n)
{
gui_model_update(queue->q_private, n);
}
static bool __gui_playlist_erase(struct queue *queue, struct track *track)
{
struct playlist *playlist = queue->q_private;
enum playlist_type_t type = playlist->pl_type;
const gchar *name = playlist->pl_name;
switch (type) {
case PL_SYSTEM:
if (string_match(name, "Collection")) {
playlist_add(type, "Hidden", track);
break;
} else if (!string_match(name, "Favorites") &&
!string_match(name, "Hidden") &&
!string_match(name, "Queued Tracks"))
break;
case PL_USER:
playlist_remove(type, name, track);
default:
break;
};
return false;
gui_model_remove(playlist, track, n);
__gui_playlist_update_size(playlist);
}
struct queue_ops playlist_ops = {
.qop_init = __gui_playlist_init,
.qop_deinit = __gui_playlist_deinit,
.qop_added = __gui_playlist_added,
.qop_erase = __gui_playlist_erase,
.qop_removed = __gui_playlist_removed,
.qop_cleared = __gui_playlist_cleared,
.qop_updated = __gui_playlist_updated,
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,
};
@ -113,7 +69,7 @@ static void __gui_playlist_add_selected_to(struct playlist *playlist)
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
playlist_add(playlist->pl_type, playlist->pl_name, track);
playlist_add(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
@ -121,12 +77,12 @@ static void __gui_playlist_add_selected_to(struct playlist *playlist)
void __gui_playlist_add_favorites(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_get(PL_SYSTEM, "Favorites"));
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Favorites"));
}
void __gui_playlist_add_hidden(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_get(PL_SYSTEM, "Hidden"));
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Hidden"));
}
void __gui_playlist_add_user(GtkMenuItem *item, gpointer data)
@ -141,7 +97,7 @@ void __gui_playlist_add_other(GtkMenuItem *item, gpointer data)
void __gui_playlist_add_queued(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_get(PL_SYSTEM, "Queued Tracks"));
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Queued Tracks"));
}
void __gui_playlist_delete(GtkMenuItem *item, gpointer data)
@ -157,7 +113,7 @@ void __gui_playlist_delete(GtkMenuItem *item, gpointer data)
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
queue_erase_track(&playlist->pl_queue, track);
playlist_remove(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
@ -233,11 +189,11 @@ bool __gui_playlist_button_press(GtkTreeView *treeview, GdkEventButton *event,
void __gui_playlist_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
struct playlist *prev = playlist_cur();
struct playlist *prev = playlist_current();
gui_sidebar_filter_path_select(path);
__gui_playlist_update_size(prev);
__gui_playlist_update_size(playlist_cur());
__gui_playlist_update_size(playlist_current());
}
void __gui_playlist_row_collapsed(GtkTreeView *treeview, GtkTreeIter *iter,
@ -252,9 +208,34 @@ void __gui_playlist_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter,
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()
{
struct playlist *playlist = playlist_cur();
struct playlist *playlist = playlist_current();
GtkTreeModel *filter = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter;

View File

@ -77,20 +77,20 @@ void gui_pl_library_init()
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;
GtkTreeIter iter;
if (!__gui_pl_library_header(&iter))
return false;
if (!playlist_new(PL_LIBRARY, filename))
return false;
playlist = playlist_get(PL_LIBRARY, filename);
gui_sidebar_iter_sort_child(&iter, playlist, "folder");
gui_idle_enable();
return true;
playlist = playlist_new(PL_LIBRARY, filename);
if (playlist) {
gui_sidebar_iter_sort_child(&iter, playlist, "folder");
gui_idle_enable();
}
return playlist;
}
void gui_pl_library_update(struct playlist *playlist)

View File

@ -8,6 +8,9 @@
#include <gui/playlists/system.h>
#include <gui/sidebar.h>
static struct playlist *favorites;
static struct playlist *hidden;
static bool __gui_pl_system_is_playlist(struct playlist *playlist)
{
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)
{
if (gtk_toggle_button_get_active(toggle))
playlist_add(PL_SYSTEM, "Favorites", audio_cur_track());
playlist_add(favorites, audio_cur_track());
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)
{
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();
} else
playlist_remove(PL_SYSTEM, "Hidden", audio_cur_track());
playlist_remove(hidden, audio_cur_track());
}
static bool __gui_pl_system_init_idle()
@ -56,27 +59,27 @@ static bool __gui_pl_system_init_idle()
/* Add toplevel playlists. */
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");
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "Collection"),
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Collection"),
"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");
/* Add user-modifiable playlists. */
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");
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Hidden"),
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Hidden"),
"window-close");
/* Add dynamic playlists. */
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");
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");
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");
return true;
@ -84,6 +87,8 @@ static bool __gui_pl_system_init_idle()
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);
}
@ -128,7 +133,7 @@ void gui_pl_system_select(struct playlist *playlist)
void gui_pl_system_track_loaded(struct track *track)
{
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(),
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;
}
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()
{
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))
return NULL;
if (!playlist_new(PL_USER, name))
return NULL;
playlist = playlist_get(PL_USER, name);
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic");
playlist = playlist_new(PL_USER, name);
if (playlist)
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic");
return playlist;
}

View File

@ -11,6 +11,7 @@ enum sidebar_columns {
SB_IMAGE,
SB_NAME,
SB_TYPE,
SB_EDITABLE,
};
const gchar *SIDEBAR_SETTING = "gui.sidebar.pos";
@ -23,8 +24,8 @@ static gchar *__gui_sidebar_size_str(struct playlist *playlist)
if (!playlist)
return NULL;
size = playlist_size(playlist->pl_type, playlist->pl_name);
if (playlist_cur() == playlist)
size = playlist_size(playlist);
if (playlist_current() == playlist)
fmt = "<b>%s\n%d track%s</b>";
return g_markup_printf_escaped(fmt, playlist->pl_name, size,
(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,
const gchar *image, enum playlist_type_t type)
{
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, name,
SB_IMAGE, image,
SB_TYPE, type, -1);
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, name,
SB_IMAGE, image,
SB_TYPE, type,
SB_EDITABLE, false, -1);
}
static void __gui_sidebar_set_playlist(GtkTreeIter *iter,
@ -96,18 +98,10 @@ static gboolean __gui_sidebar_visible_func(GtkTreeModel *model,
gpointer data)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
gboolean ret = TRUE;
gchar *name;
if (type == PL_SYSTEM || type == PL_ARTIST) {
name = gui_sidebar_iter_name(iter);
if (name) {
ret = playlist_size(type, name) > 0;
g_free(name);
}
}
return ret;
if (type == PL_SYSTEM || type == PL_ARTIST)
return playlist_size(gui_sidebar_iter_playlist(iter)) > 0;
return TRUE;
}
static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection,
@ -123,49 +117,123 @@ static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection,
void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
bool active = false, sensitive = false;
struct playlist *playlist = NULL;
enum playlist_type_t type;
GtkTreeIter iter, child;
gchar *name;
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
__gui_sidebar_filter_iter_convert(&iter, &child);
name = gui_sidebar_iter_name(&child);
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);
if (gui_sidebar_iter_current(&iter)) {
playlist = gui_sidebar_iter_playlist(&iter);
active = playlist->pl_random;
sensitive = (playlist->pl_ops->pl_set_random != NULL);
}
gui_treeview_set_playlist(playlist);
gtk_toggle_button_set_active(gui_random_button(), active);
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,
gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct playlist *playlist = gui_model_get_playlist();
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter, child;
if (!playlist || event->keyval != GDK_KEY_Delete)
switch (event->keyval) {
case GDK_KEY_BackSpace:
return __gui_sidebar_rename(NULL, NULL);
case GDK_KEY_Return:
return __gui_sidebar_select(NULL, NULL);
case GDK_KEY_Delete:
return __gui_sidebar_delete(NULL, NULL);
default:
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;
__gui_sidebar_filter_iter_convert(&iter, &child);
if (playlist_delete(playlist->pl_type, playlist->pl_name))
gtk_tree_store_remove(gui_sidebar_store(), &child);
return true;
if (event->button == GDK_BUTTON_SECONDARY) {
gtk_tree_view_set_cursor(treeview, path, NULL, false);
if (gui_sidebar_iter_current(&iter))
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)
@ -178,10 +246,8 @@ void __gui_sidebar_random_toggled(GtkToggleButton *button, gpointer data)
struct playlist *playlist = gui_model_get_playlist();
bool active = gtk_toggle_button_get_active(button);
if (playlist) {
playlist_set_random(playlist->pl_type,
playlist->pl_name, active);
}
if (playlist)
playlist_set_random(playlist, active);
}
void gui_sidebar_init()
@ -190,6 +256,10 @@ void gui_sidebar_init()
GtkTreeSelection *selection;
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)) {
__gui_sidebar_add_header(&iter, "Playlists", "emblem-documents");
__gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic");
@ -208,6 +278,19 @@ void gui_sidebar_init()
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)
{
return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter);
@ -251,6 +334,23 @@ enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *iter)
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,
const gchar *image)
{
@ -289,22 +389,24 @@ void gui_sidebar_iter_append_child(GtkTreeIter *iter, struct playlist *playlist,
__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 *name, *text;
gchar *text;
if (type >= PL_MAX_TYPE)
if (!playlist)
return;
name = gui_sidebar_iter_name(iter);
text = __gui_sidebar_size_str(playlist_get(type, name));
text = __gui_sidebar_size_str(playlist);
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, text, -1);
g_free(name);
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;
@ -317,24 +419,25 @@ void gui_sidebar_iter_select(GtkTreeIter *iter)
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)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
enum playlist_type_t type;
GtkTreeIter iter, child;
gchar *name;
gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, &child);
type = gui_sidebar_iter_type(&child);
if (type >= PL_MAX_TYPE)
return;
name = gui_sidebar_iter_name(&child);
if (playlist_select(type, name))
if (playlist_select(gui_sidebar_iter_playlist(&child)))
gui_sidebar_iter_update(&child);
g_free(name);
}
void gui_sidebar_filter_set_expand(GtkTreeIter *iter)
@ -376,3 +479,30 @@ gboolean gui_sidebar_iter_find(GtkTreeIter *iter, const gchar *name,
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)
{
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) {
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();
enum compare_t compare = GPOINTER_TO_UINT(data);
bool reset = (sort_count == 0);
gchar *text;
if (!playlist)
return;
playlist_sort(playlist->pl_type, playlist->pl_name, compare, reset);
if (!playlist->pl_queue.q_sort)
if (sort_count == 0)
playlist_clear_sort(playlist);
if (!playlist_sort(playlist, compare))
return;
__gui_treeview_set_sort_indicators();
@ -160,6 +160,43 @@ void __gui_treeview_row_activated(GtkTreeView *treeview, GtkTreePath *path,
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()
{
GtkTreeViewColumn *col;
@ -167,6 +204,12 @@ void gui_treeview_init()
gtk_tree_view_set_model(gui_treeview(),
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++) {
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()
{
struct playlist *playlist = gui_model_get_playlist();
GtkTreePath *path;
int pos;
int pos = playlist_current_index(gui_model_get_playlist());
GtkTreePath *path;
pos = playlist ? playlist->pl_queue.q_cur.it_pos : -1;
if (!can_scroll || pos < 0)
return;
@ -220,26 +261,22 @@ void gui_treeview_scroll()
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y)
{
GtkTreeSelection *selection;
GtkTreePath *path;
GtkTreePath *path;
selection = gtk_tree_view_get_selection(gui_treeview());
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(selection, path);
gtk_tree_selection_select_path(gui_treeview_selection(), path);
gtk_tree_path_free(path);
}
}
GList *gui_treeview_list_selected_tracks(void)
{
GList *rows, *cur, *list = NULL;
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection(gui_treeview());
rows = gtk_tree_selection_get_selected_rows(selection, NULL);
cur = g_list_first(rows);
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));

View File

@ -11,20 +11,20 @@
#include <gst/gst.h>
struct audio_ops {
struct audio_callbacks {
/* Called when a track is loaded. */
void (*on_load)(struct track *);
void (*audio_cb_load)(struct track *);
/* Called when playback state changes. */
void (*on_state_change)(GstState);
void (*audio_cb_state_change)(GstState);
/* Called when the automatic pause state changes. */
void (*on_config_pause)(int);
void (*audio_cb_config_pause)(int);
};
/* 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. */
void audio_deinit();
@ -33,8 +33,9 @@ void audio_deinit();
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_filepath(const gchar *);
/* Called to get the current track. */
struct track *audio_cur_track();
@ -72,16 +73,16 @@ struct track *audio_next();
/* Called to load the previous track. */
struct track *audio_prev();
/* Called when playback has reached the end-of-stream position. */
struct track *audio_eos();
/* Called when gstreamer has received an error. */
void audio_error(GstMessage *);
/* Called to configure automatic pausing. */
void audio_pause_after(int);
/*
* Called to configure automatic pausing.
* Returns true if the value has been changed.
*/
bool audio_pause_after(int);
int audio_get_pause_count(void);
#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 /* OCARINA_CORE_AUDIO_H */

View File

@ -3,20 +3,16 @@
*/
#ifndef OCARINA_CORE_CORE_H
#define OCARINA_CORE_CORE_H
#include <core/queue.h>
struct core_init_data {
struct queue_ops *playlist_ops;
struct audio_ops *audio_ops;
#ifdef CONFIG_TESTING
bool idle_async;
#endif /* CONFIG_TESTING */
};
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
#include <core/tags/tags.h>
#include <stdbool.h>
/* 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. */
void core_deinit();

View File

@ -48,7 +48,7 @@ static inline void *DBE_DATA(struct db_entry *dbe)
struct db_ops {
/* 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. */
void (*dbe_free)(struct db_entry *);
@ -57,7 +57,7 @@ struct db_ops {
gchar *(*dbe_key)(struct db_entry *);
/* 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. */
void (*dbe_write)(struct file *, struct db_entry *);
@ -78,7 +78,7 @@ struct database {
{ \
.db_size = 0, \
.db_autosave = autosave, \
.db_file = FILE_INIT(fname, fmin), \
.db_file = FILE_INIT_DATA("", fname, fmin), \
.db_entries = g_ptr_array_new(), \
.db_keys = g_hash_table_new(g_str_hash, g_str_equal), \
.db_ops = ops, \

View File

@ -30,105 +30,100 @@
enum open_mode {
OPEN_READ,
OPEN_WRITE,
CLOSED, /* File is not open. */
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 {
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. */
unsigned int f_version; /* The file's current data version. */
unsigned int f_prev; /* The file's on-disk 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) \
{ \
.f_mode = OPEN_READ, \
.f_version = OCARINA_MINOR_VERSION, \
.f_prev = 0, \
.f_min = min, \
.f_file = NULL, \
.f_name = fname, \
#define FILE_INIT_DATA(fdir, fname, min) \
{ \
.f_file = NULL, \
.f_name = fname, \
.f_subdir = fdir, \
.f_mode = CLOSED, \
.f_version = OCARINA_MINOR_VERSION, \
.f_prev = 0, \
.f_min = min, \
.f_user_dir = g_get_user_data_dir, \
}
struct cache_file {
FILE *cf_file; /* The cache file's IO stream. */
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 *);
/* Initialize a file object. */
void file_init_data(struct file *, const gchar *, const gchar *, unsigned int);
void file_init_cache(struct file *, const gchar *, const gchar *);
/*
* 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 *cache_file_path(struct cache_file *);
/*
* 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 *cache_file_write_path(struct cache_file *);
/* Returns the version number of the file. */
const unsigned int file_version(struct file *);
/* Returns true if the file exists on disk and false otherwise. */
bool file_exists(struct file *);
bool cache_file_exists(struct cache_file *);
/*
* Call to open a file for either reading or writing. Callers
* are expected to call file_close() when IO is completed.
*
* When opening a file for reading (OPEN_READ):
* - Check if the file exists.
* - Read in file->_prev_version from the start of the file.
* When opening a file for reading (OPEN_READ / OPEN_READ_BINARY):
* - Check if the file exists
* - 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.
* - 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.
* Oening a cache file with OPEN_READ is currently unsupported.
*/
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
* with OPEN_WRITE, then rename the temporary file to file_path().
* Closes an open file, setting file->f_file to NULL and file->f_mode
* to CLOSED. If the file was opened for writing, then rename the
* temporary file to file_path().
*/
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.
* This function allocates a new string that MUST be freed with g_free().
* Called to read an unsigned int, signed int, single word, or entire
* 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 *);
/*
* Read from a file with an fscanf(3) style format string.
* Returns the number of items matched.
*/
int file_readf(struct file *, const char *, ...);
unsigned int file_readu(struct file *);
static inline int file_readd(struct file *file)
{ return (int)file_readu(file); }
static inline unsigned short int file_readhu(struct file *file)
{ return (unsigned short int)file_readu(file); }
/*
* 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 *, ...);
/*
* 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.
*/
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. */
bool cache_file_import(struct cache_file *, const gchar *);
bool file_import(struct file *, const gchar *);
/* Removes a closed file from disk. */
bool file_remove(struct file *);

View File

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

View File

@ -7,16 +7,15 @@
*/
#ifndef OCARINA_CORE_PLAYLIST_H
#define OCARINA_CORE_PLAYLIST_H
#include <core/playlists/artist.h>
#include <core/playlists/generic.h>
#include <core/playlists/library.h>
#include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <core/queue.h>
/* Called to initialize the playlist manager. */
void playlist_init(struct queue_ops *);
void playlist_init(struct playlist_callbacks *);
/* Called to deinitialize the playlist manager. */
void playlist_deinit();
@ -24,43 +23,30 @@ void playlist_deinit();
/* Called to force-save all playlists. */
void playlist_save();
/* Called to select the current playlist. */
bool playlist_select(enum playlist_type_t, const gchar *);
/* Called to notify all playlists that a track has been played. */
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. */
bool playlist_new(enum playlist_type_t, const gchar *);
struct playlist *playlist_new(enum playlist_type_t, const gchar *);
/* 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. */
bool playlist_add(enum playlist_type_t, const gchar *, struct track *);
/* Called to look up playlists either by name or id. */
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. */
bool playlist_remove(enum playlist_type_t, const gchar *, struct track *);
/* Called to update tracks on a playlist. */
void playlist_update(enum playlist_type_t, const gchar *);
/* Called to access the current playlist. */
struct playlist *playlist_current(void);
/* Called to check if a specific track is in the playlist. */
bool playlist_has(enum playlist_type_t, const gchar *, struct track *);
/* 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 select the current playlist. */
bool playlist_select(struct playlist *);
/* Called to get the next track from the default playlist. */
struct track *playlist_next(void);
@ -69,19 +55,27 @@ struct track *playlist_next(void);
struct track *playlist_prev(void);
/* Called to access the playlist. */
struct playlist *playlist_get(enum playlist_type_t, const gchar *);
/* Called to add a track to a playlist. */
bool playlist_add(struct playlist *, struct track *);
/* Called to access the current playlist. */
struct playlist *playlist_cur(void);
/* Called to remove a track from a playlist. */
bool playlist_remove(struct playlist *, struct track *);
/* Called to access the playlist queue. */
struct queue *playlist_get_queue(enum playlist_type_t, const gchar *);
/* Called to check if a specific track is in the playlist. */
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. */
gchar *playlist_get_name(enum playlist_type_t, unsigned int);
/* Called to set the playlist's random flag. */
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 */

View File

@ -3,14 +3,14 @@
*/
#ifndef OCARINA_CORE_PLAYLISTS_ARTIST_H
#define OCARINA_CORE_PLAYLISTS_ARTIST_H
#include <core/playlists/type.h>
#include <core/playlists/generic.h>
/* Artist playlist type. */
extern struct playlist_type pl_artist;
/* Called to initialize artist playlists. */
void pl_artist_init(struct queue_ops *ops);
void pl_artist_init(void);
/* Called to deinitialize library playlists. */
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
#define OCARINA_CORE_PLAYLISTS_LIBRARY_H
#include <core/playlists/type.h>
#include <core/playlists/generic.h>
/* Library playlist type. */
extern struct playlist_type pl_library;
/* Called to initialize library playlists. */
void pl_library_init(struct queue_ops *);
void pl_library_init(void);
/* Called to deinitialize system playlists. */
void pl_library_deinit();
/* Called to update a library path. */
void pl_library_update(struct playlist *);
#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
#define OCARINA_CORE_PLAYLISTS_SYSTEM_H
#include <core/playlists/type.h>
#include <core/playlists/generic.h>
enum sys_playlist_t {
SYS_PL_FAVORITES, /* Songs that the user likes. */
@ -18,29 +17,13 @@ enum sys_playlist_t {
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. */
extern struct playlist_type pl_system;
/* Called to initialize system playlists. */
void pl_system_init(struct queue_ops *);
void pl_system_init(void);
/* Called to deinitialize system playlists. */
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
#define OCARINA_CORE_PLAYLISTS_USER_H
#include <core/playlists/type.h>
#include <core/playlists/generic.h>
struct user_playlist {
struct playlist pl_playlist;
@ -19,10 +18,16 @@ extern struct playlist_type pl_user;
/* Called to initialize user playlists. */
void pl_user_init(struct queue_ops *ops);
void pl_user_init(void);
/* Called to deinitialize user playlists. */
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();
#endif /* OCARINA_CORE_PLAYLISTS_USER_H */

View File

@ -1,199 +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 erase a track from the queue */
void queue_erase_track(struct queue *, struct track *);
/* 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. */
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 */
static inline int string_length(const gchar *str)
{

View File

@ -37,9 +37,11 @@ bool library_db_defrag();
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
* 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_lookup(const gchar *);

View File

@ -58,6 +58,7 @@ struct track {
};
#define TRACK(dbe) ((struct track *)DBE_DATA(dbe))
#define TRACK_IS_EXTERNAL(track) (track->tr_library == NULL)
/* 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. */
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. */
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. */
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 */
int track_compare(struct track *, struct track *, enum compare_t);

View File

@ -3,6 +3,7 @@
*/
#ifndef 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. */

View File

@ -3,10 +3,11 @@
*/
#ifndef OCARINA_GUI_AUDIO_H
#define OCARINA_GUI_AUDIO_H
#include <core/audio.h>
#include <gui/builder.h>
/* Audio callback functions. */
extern struct audio_ops audio_ops;
extern struct audio_callbacks audio_cb;
/* Called to initialize the GUI audio controls. */
void gui_audio_init();
@ -16,6 +17,7 @@ void gui_audio_deinit();
/* Called to update the current track position. */
int gui_audio_timeout();
int gui_audio_popover_timeout();
/* Called to get the label displaying the album tag. */
static inline GtkLabel *gui_album_tag(void)
@ -71,10 +73,25 @@ static inline GtkButton *gui_next_button(void)
return GTK_BUTTON(gui_builder_widget("next_button"));
}
/* Called to get the pause-fater combobox. */
static inline GtkComboBoxText *gui_pause_after(void)
/* Called to get the pause-after widgets. */
static inline GtkEntry *gui_pause_entry(void)
{
return GTK_COMBO_BOX_TEXT(gui_builder_widget("pause_after"));
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. */

View File

@ -20,9 +20,6 @@ void gui_filter_init();
/* Called to deinitialize the filter model. */
void gui_filter_deinit();
/* Called to clear any saved search text. */
void gui_filter_clear_search(struct playlist *);
/* Called to set the current 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. */
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. */
static inline GtkSearchEntry *gui_filter_search(void)
{

View File

@ -38,6 +38,15 @@ struct gui_model_class {
};
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 */
void gui_model_init(void);
@ -52,16 +61,16 @@ GuiModel *gui_model_get(void);
GType gui_model_get_type();
/* 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 */
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 */
void gui_model_update(struct playlist *, unsigned int);
/*
* Called to update a row in the model
* If track is NULL, then all rows will be updated
*/
void gui_model_update(struct playlist *, struct track *);
/* Called to change the queue represented by the model. */
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)
{
g_return_val_if_fail(iter != NULL, NULL);
g_return_val_if_fail(iter->user_data2 != NULL, NULL);
return (struct track *)iter->user_data2;
g_return_val_if_fail(iter->user_data != NULL, NULL);
return playlist_iter_track(iter->user_data);
}
/* Called to convert a GtkTreePath into a struct track */

View File

@ -25,8 +25,7 @@ static inline GtkMenuItem *gui_rc_add_to_other()
return GTK_MENU_ITEM(gui_builder_widget("rc_add_to_other"));
}
/* Playlist operations passed to core_init() */
extern struct queue_ops playlist_ops;
/* Playlist callbacks passed to core_init() */
extern struct playlist_callbacks playlist_cb;
#endif /* OCARINA_GUI_PLAYLIST_H */

View File

@ -8,7 +8,7 @@
void gui_pl_library_init();
/* 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. */
void gui_pl_library_update(struct playlist *);

View File

@ -9,6 +9,9 @@
/* Called to initialize the sidebar. */
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. */
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. */
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. */
void gui_sidebar_iter_add(GtkTreeIter *, struct playlist *, const gchar *);
@ -39,11 +48,15 @@ void gui_sidebar_iter_append_child(GtkTreeIter *, struct playlist *,
const gchar *);
/* Called to update the playlist at the current iterator. */
void gui_sidebar_iter_update_playlist(GtkTreeIter *, struct playlist *);
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. */
void gui_sidebar_filter_path_select(GtkTreePath *);
@ -60,6 +73,12 @@ void gui_sidebar_filter_row_expanded(GtkTreeIter *, bool);
gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *,
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. */
static inline GtkPaned *gui_sidebar()
{
@ -90,6 +109,12 @@ static inline GtkTreeView *gui_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. */
static inline GtkToggleButton *gui_random_button()
{

View File

@ -32,6 +32,12 @@ static inline GtkTreeView *gui_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. */
static inline GtkLabel *gui_sorting()
{

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"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.20.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkAdjustment" id="adjustment1">
@ -88,6 +88,58 @@
</object>
</child>
</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">
<property name="visible">True</property>
<property name="can_focus">False</property>
@ -106,6 +158,8 @@
<column type="gchararray"/>
<!-- column-name Type -->
<column type="guint"/>
<!-- column-name Editable -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="sidebar_filter">
@ -212,7 +266,7 @@
<property name="halign">center</property>
<property name="valign">center</property>
<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>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
@ -478,7 +532,6 @@ audio-volume-medium</property>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
@ -544,132 +597,83 @@ audio-volume-medium</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<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="pause_after">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">center</property>
<property name="active">0</property>
<items>
<item translatable="yes">(disabled)</item>
<item translatable="yes">this track</item>
<item translatable="yes">next track</item>
<item translatable="yes">2 tracks</item>
<item translatable="yes">3 tracks</item>
<item translatable="yes">4 tracks</item>
<item translatable="yes">5 tracks</item>
<item translatable="yes">6 tracks</item>
<item translatable="yes">7 tracks</item>
<item translatable="yes">8 tracks</item>
<item translatable="yes">9 tracks</item>
<item translatable="yes">10 tracks</item>
<item translatable="yes">11 tracks</item>
<item translatable="yes">12 tracks</item>
<item translatable="yes">13 tracks</item>
<item translatable="yes">14 tracks</item>
<item translatable="yes">15 tracks</item>
<item translatable="yes">16 tracks</item>
<item translatable="yes">17 tracks</item>
<item translatable="yes">18 tracks</item>
<item translatable="yes">19 tracks</item>
<item translatable="yes">20 tracks</item>
<item translatable="yes">21 tracks</item>
<item translatable="yes">22 tracks</item>
<item translatable="yes">23 tracks</item>
<item translatable="yes">24 tracks</item>
<item translatable="yes">25 tracks</item>
<item translatable="yes">26 tracks</item>
<item translatable="yes">27 tracks</item>
<item translatable="yes">28 tracks</item>
<item translatable="yes">29 tracks</item>
<item translatable="yes">30 tracks</item>
<item translatable="yes">31 tracks</item>
<item translatable="yes">32 tracks</item>
<item translatable="yes">33 tracks</item>
<item translatable="yes">34 tracks</item>
<item translatable="yes">35 tracks</item>
<item translatable="yes">36 tracks</item>
<item translatable="yes">37 tracks</item>
<item translatable="yes">38 tracks</item>
<item translatable="yes">39 tracks</item>
<item translatable="yes">40 tracks</item>
<item translatable="yes">41 tracks</item>
<item translatable="yes">42 tracks</item>
<item translatable="yes">43 tracks</item>
<item translatable="yes">44 tracks</item>
<item translatable="yes">45 tracks</item>
<item translatable="yes">46 tracks</item>
<item translatable="yes">47 tracks</item>
<item translatable="yes">48 tracks</item>
<item translatable="yes">49 tracks</item>
<item translatable="yes">50 tracks</item>
<item translatable="yes">51 tracks</item>
<item translatable="yes">52 tracks</item>
<item translatable="yes">53 tracks</item>
<item translatable="yes">54 tracks</item>
<item translatable="yes">55 tracks</item>
<item translatable="yes">56 tracks</item>
<item translatable="yes">57 tracks</item>
<item translatable="yes">58 tracks</item>
<item translatable="yes">59 tracks</item>
<item translatable="yes">60 tracks</item>
<item translatable="yes">61 tracks</item>
<item translatable="yes">62 tracks</item>
<item translatable="yes">63 tracks</item>
<item translatable="yes">64 tracks</item>
<item translatable="yes">65 tracks</item>
<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="__gui_audio_pause_changed" swapped="no"/>
<child>
<object class="GtkEntry" id="pause_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="text" translatable="yes">Paused</property>
<signal name="activate" handler="__gui_audio_pause_change_text" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_down">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="__gui_audio_pause_dec" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-remove-symbolic</property>
</object>
</child>
<accelerator key="minus" signal="clicked"/>
<accelerator key="KP_Subtract" signal="clicked"/>
<style>
<class name="down"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_up">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="__gui_audio_pause_inc" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">list-add-symbolic</property>
</object>
</child>
<accelerator key="plus" signal="clicked"/>
<accelerator key="KP_Add" signal="clicked"/>
<style>
<class name="up"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<style>
<class name="linked"/>
</style>
</object>
<packing>
<property name="left_attach">4</property>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
<property name="height">2</property>
</packing>
@ -763,6 +767,9 @@ audio-volume-medium</property>
<property name="headers_visible">False</property>
<property name="search_column">1</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="row-activated" handler="__gui_playlist_row_activated" swapped="no"/>
<signal name="row-collapsed" handler="__gui_playlist_row_collapsed" swapped="no"/>
@ -789,8 +796,12 @@ audio-volume-medium</property>
<object class="GtkTreeViewColumn" id="treeviewcolumn3">
<property name="title" translatable="yes">column</property>
<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>
<attribute name="editable">3</attribute>
<attribute name="markup">1</attribute>
</attributes>
</child>
@ -823,7 +834,7 @@ audio-volume-medium</property>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
@ -903,6 +914,8 @@ audio-volume-medium</property>
<property name="rubber_banding">True</property>
<property name="tooltip_column">9</property>
<signal name="button-press-event" handler="__gui_playlist_button_press" 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"/>
<child internal-child="selection">
@ -1108,7 +1121,7 @@ audio-volume-medium</property>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
<property name="width">5</property>
<property name="width">4</property>
</packing>
</child>
</object>
@ -1134,4 +1147,55 @@ audio-volume-medium</property>
<widget name="filter_how"/>
</widgets>
</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>

View File

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

View File

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

View File

@ -1,65 +1,54 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/core.h>
#include <tests/test.h>
#include <tests/loop.h>
static unsigned int load_count = 0;
static unsigned int state_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);
GstState state = audio_cur_state();
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(test_audio_pipeline()));
while (state != GST_STATE_PAUSED && state != GST_STATE_PLAYING)
state = audio_cur_state();
return ret;
g_usleep(G_USEC_PER_SEC / 15);
while (gst_bus_have_pending(bus))
test_main_loop();
gst_object_unref(bus);
return state_count;
}
static void test_send_error()
{
GstMessage *message;
GError *error;
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);
GError *error = g_error_new(1, G_FILE_ERROR_BADF, "Simulated Error");
test_audio_error(error, "Fake error for testing");
g_error_free(error);
}
static void test_audio_load(struct track *track) { load_count++; }
static void test_change_state(GstState state) { state_count++; }
static void test_config_pause(int n) { pause_count = n; }
static struct audio_ops test_audio_ops = {
test_audio_load,
test_change_state,
test_config_pause,
};
static struct core_init_data test_init_data = {
.audio_ops = &test_audio_ops,
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,
};
static void test_init()
{
g_assert_null(test_audio_player());
g_assert_null(test_audio_pipeline());
g_assert_null(audio_cur_track());
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL);
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_null(audio_next());
@ -73,6 +62,7 @@ static void test_init()
g_assert_cmpuint(audio_get_volume(), ==, 100);
g_assert_null(audio_cur_track());
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(state_count, ==, 0);
@ -80,7 +70,7 @@ static void test_init()
while (idle_run_task()) {};
g_assert_null(audio_cur_track());
g_assert_nonnull(test_audio_player());
g_assert_nonnull(test_audio_pipeline());
}
static void test_playback()
@ -93,9 +83,9 @@ static void test_playback()
g_assert_true(audio_load(tracks[i]));
else
g_assert_false(audio_load(tracks[i]));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "History"), ==, 1);
g_assert_cmpuint(load_count, ==, 1);
g_assert_cmpuint(state_count, ==, 1);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "History")), ==, 1);
g_assert_cmpuint(load_count, ==, 1);
g_assert_cmpuint(test_wait_state(), ==, 1);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(audio_cur_track() == tracks[0]);
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_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_true(test_audio_seek(5 * GST_SECOND));
g_assert_cmpuint(audio_position(), ==, 5 * GST_SECOND);
g_assert_true(test_audio_seek(42 * GST_SECOND));
g_assert_cmpuint(audio_position(), ==, 42 * GST_SECOND);
g_assert_true(audio_seek(5 * GST_SECOND));
g_assert_cmpuint(test_wait_state(), ==, 3);
g_assert_cmpuint(audio_position(), ==, 5 * 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_false(audio_play());
g_assert_cmpuint(state_count, ==, 3);
g_assert_cmpuint(test_wait_state(), ==, 5);
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);
/* Check duration again now that track is fully loaded. */
@ -131,119 +123,168 @@ static void test_playback()
static void test_next()
{
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History");
struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
int i;
state_count = 0;
/* First, let's test getting tracks from a temporary queue. */
playlist_add(PL_SYSTEM, "Queued Tracks", track_get(2));
playlist_add(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(2));
playlist_add(playlist_lookup(PL_SYSTEM, "Queued Tracks"), track_get(1));
playlist_add(playlist_lookup(PL_SYSTEM, "Queued Tracks"), track_get(0));
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)
g_assert_cmpuint(audio_next()->tr_track, ==, track_get(i)->tr_track);
else /* Simulate an 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(audio_cur_track() == track_get(i));
}
g_assert_cmpuint(state_count, ==, 3);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 0);
g_assert_cmpuint(test_wait_state(), ==, 3);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Queued Tracks")), ==, 0);
/* 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++) {
if (i < 3)
g_assert_cmpuint(audio_next()->tr_track, ==, i);
else /* Simulate an 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_track()->tr_track, ==, i);
}
g_assert_cmpuint(state_count, ==, 6);
g_assert_cmpuint(test_wait_state(), ==, 6);
}
static void test_prev()
{
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History");
struct track *track = queue_at(history_q, 0);
struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
struct track *track = playlist_at(history, 0);
state_count = 0;
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_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(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_track()->tr_track, ==, 1);
g_assert_cmpuint(state_count, ==, 2);
g_assert_cmpuint(test_wait_state(), ==, 2);
g_assert_true(audio_pause());
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_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(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_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(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_track()->tr_track, ==, track_get(2)->tr_track);
g_assert_cmpuint(state_count, ==, 6);
g_assert_cmpuint(test_wait_state(), ==, 6);
}
void test_autopause()
{
struct queue *history_q = playlist_get_queue(PL_SYSTEM, "History");
struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
int i;
audio_pause_after(3);
g_assert_cmpuint(pause_count, ==, 3);
g_assert_true(audio_pause_after(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;
audio_pause_after(3);
g_assert_cmpuint(pause_count, ==, 0);
g_assert_false(audio_pause_after(3));
g_assert_cmpint(pause_count, ==, 0);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
audio_pause_after(5);
g_assert_cmpuint(pause_count, ==, 5);
g_assert_true(audio_pause_after(-1));
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;
for (i = 4; i > -1; i--) {
audio_eos();
g_assert_cmpuint(pause_count, ==, i);
test_audio_eos();
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(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()) {}
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(state_count, ==, 6);
g_assert_cmpuint(test_wait_state(), ==, 6);
test_send_error();
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()
{
core_deinit();
test_loop_deinit();
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/Previous", test_prev);
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);
return g_test_run();
}

View File

@ -26,7 +26,7 @@ static struct int_entry *__int_alloc(unsigned int val)
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;
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);
}
static struct db_entry *int_read(struct file *f)
static struct db_entry *int_read(struct file *f, unsigned int index)
{
unsigned int val;
file_readf(f, "%u", &val);
return &__int_alloc(val)->ie_dbe;
return &__int_alloc(file_readu(f))->ie_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()
{
struct file f = FILE_INIT_DATA("", "test_db_entry", 0);
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(ent->ie_dbe.dbe_data == ent);
g_assert_cmpuint(ent->ie_val, ==, 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);
int_ops.dbe_write(&f, &ent->ie_dbe);
file_close(&f);
@ -85,7 +82,7 @@ static void test_db_entry()
g_assert_cmpuint(test_free_count, ==, 1);
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);
g_assert(ent->ie_dbe.dbe_data == ent);

View File

@ -16,7 +16,7 @@ void test_date()
.d_month = 0,
.d_day = 0,
};
struct file f = FILE_INIT("date", 0);
struct file f = FILE_INIT_DATA("", "date", 0);
date_today(NULL);
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_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_write_path(file), ==, ftmp);
}
@ -20,21 +20,21 @@ static void test_invalid_file(gconstpointer path)
{
struct file file;
file_init(&file, (gchar *)path, 0);
file_init_data(&file, "", (gchar *)path, 0);
test_verify_constructor(&file, "", "");
g_assert_false(file_open(&file, OPEN_READ));
g_assert_null(file.f_file);
g_assert_false(file_open(&file, OPEN_WRITE));
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));
}
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;
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_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_cmpuint(file.f_mode, ==, 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));
file_close(&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_chmod(filepath, 0444);
@ -61,6 +62,7 @@ static void __test_file_subprocess()
g_chmod(filepath, 0200);
g_assert_false(file_open(&file, OPEN_READ));
g_chmod(filepath, 0644);
g_assert_false(file_open(&file, CLOSED));
g_assert_true(file_open(&file, OPEN_READ));
g_assert_nonnull(file.f_file);
@ -70,6 +72,7 @@ static void __test_file_subprocess()
g_assert_false(file_remove(&file));
g_assert_true(file_exists(&file));
file_close(&file);
g_assert_cmpuint(file.f_mode, ==, CLOSED);
g_assert_true(file_remove(&file));
g_assert_false(file_exists(&file));
@ -90,10 +93,8 @@ static void test_file()
static void test_io()
{
struct file fout = FILE_INIT("file.txt", 0);
struct file fin = FILE_INIT("file.txt", 1);
char *res = NULL;
unsigned int i;
struct file fout = FILE_INIT_DATA("", "file.txt", 0);
struct file fin = FILE_INIT_DATA("", "file.txt", 1);
fout.f_version = 1;
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, "3 \n");
file_writef(&fout, "4 5 PQRST\n");
file_writef(&fout, "-6 UV WX YZ\n");
file_close(&fout);
g_assert_true(file_exists(&fout));
g_assert_true(file_open(&fin, OPEN_READ));
g_assert_cmpuint(file_version(&fin), ==, 1);
g_assert_cmpuint(file_readf(&fin, "%u %ms\n", &i, &res), ==, 2);
g_assert_cmpuint(i, ==, 1);
g_assert_cmpstr_free(res, ==, "ABCDE");
g_assert_cmpuint( file_readu(&fin), ==, 1);
g_assert_cmpstr_free(file_readl(&fin), ==, "ABCDE");
g_assert_cmpuint(file_readf(&fin, "%u %m[^\n]\n", &i, &res), ==, 2);
g_assert_cmpuint(i, ==, 2);
g_assert_cmpstr_free(res, ==, "FGHIJ KLMNO");
g_assert_cmpuint( file_readu(&fin), ==, 2);
g_assert_cmpstr_free(file_readl(&fin), ==, "FGHIJ KLMNO");
g_assert_cmpuint(file_readf(&fin, "%u", &i), ==, 1);
res = file_readl(&fin);
g_assert_cmpuint(i, ==, 3);
g_assert_cmpstr_free(res, ==, "");
g_assert_cmpuint( file_readu(&fin), ==, 3);
g_assert_cmpstr_free(file_readl(&fin), ==, "");
g_assert_cmpuint(file_readf(&fin, "%u %m[^\n]", &i, &res), ==, 2);
g_assert_cmpuint(i, ==, 4);
g_assert_cmpstr_free(res, ==, "5 PQRST");
g_assert_cmpuint( file_readu(&fin), ==, 4);
g_assert_cmpstr_free(file_readl(&fin), ==, "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);
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)
{
struct file fout = FILE_INIT("file.txt", out);
struct file fin = FILE_INIT("file.txt", in);
struct file fout = FILE_INIT_DATA("", "file.txt", out);
struct file fin = FILE_INIT_DATA("", "file.txt", in);
fout.f_version = out;
fin.f_version = in;
@ -171,44 +172,67 @@ static void test_versioning_new()
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;
struct file file, copy;
basepath = g_strjoin("/", g_get_user_cache_dir(), OCARINA_NAME, NULL);
filepath = g_strjoin("/", basepath, "dir", "file.txt", NULL);
writepath = g_strjoin("/", basepath, "dir", ".file.txt.tmp", NULL);
file_init_cache(&file, "dir", "file.txt");
file_init_cache(&copy, "dir", "copy.txt");
g_assert_null(file.cf_file);
g_assert_cmpstr(file.cf_name, ==, "file.txt");
g_assert_cmpstr(file.cf_subdir, ==, "dir");
basepath = g_strjoin("/", g_get_user_cache_dir(), OCARINA_NAME, "dir", NULL);
filepath = g_strjoin("/", basepath, "file.txt", NULL);
writepath = g_strjoin("/", basepath, ".file.txt.tmp", NULL);
g_assert_cmpstr_free(cache_file_path(&file), ==, filepath);
g_assert_cmpstr_free(cache_file_write_path(&file), ==, writepath);
g_assert_null(file.f_file);
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. */
g_assert_false(cache_file_exists(&file));
g_assert_false(cache_file_open(&file, OPEN_READ));
g_assert_true(cache_file_open(&file, OPEN_WRITE));
g_assert_nonnull(file.cf_file);
g_assert_false(cache_file_open(&file, OPEN_WRITE));
g_assert_false(file_exists(&file));
g_assert_false(file_open(&file, OPEN_READ_BINARY));
g_assert_false(file_open(&file, CLOSED));
g_assert_true( file_open(&file, OPEN_WRITE_BINARY));
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_cmpuint(cache_file_write(&file, "abcde", 5), ==, 5);
cache_file_close(&file);
g_assert_null(file.cf_file);
g_assert_true(cache_file_exists(&file));
g_assert_false(file_exists(&file));
g_assert_cmpuint(file_write(&file, "abcde", 5), ==, 5);
file_close(&file);
g_assert_null(file.f_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. */
g_assert_false(cache_file_exists(&copy));
g_assert_false(cache_file_import(&copy, filepath));
g_assert_false(cache_file_exists(&copy));
g_assert_true(cache_file_open(&copy, OPEN_WRITE));
g_assert_false(cache_file_import(&copy, NULL));
g_assert_true(cache_file_import(&copy, filepath));
g_assert_false(cache_file_exists(&copy));
cache_file_close(&copy);
g_assert_true(cache_file_exists(&copy));
g_assert_false(file_exists(&copy));
g_assert_false(file_import(&copy, filepath));
g_assert_false(file_exists(&copy));
g_assert_true( file_open(&copy, OPEN_WRITE_BINARY));
g_assert_false(file_import(&copy, NULL));
g_assert_true( file_import(&copy, filepath));
g_assert_false(file_exists(&copy));
file_close(&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)

View File

@ -19,7 +19,7 @@ static void test_idle(gconstpointer arg)
unsigned int n = GPOINTER_TO_UINT(arg);
cur = -1;
idle_init();
idle_init(IDLE_ASYNC);
g_assert_cmpfloat(idle_progress(), ==, 1.0);
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()
{
struct artist *artist;
struct playlist *playlist;
struct artist *artist;
g_assert_false(playlist_new(PL_ARTIST, "Koji Kondo"));
g_assert_null(playlist_get_queue(PL_ARTIST, "Koji Kondo"));
g_assert_null(playlist_new(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_false(playlist_add(PL_ARTIST, "Koji Kondo", track_get(0)));
g_assert_false(playlist_select(PL_ARTIST, "Koji Kondo"));
g_assert_false(playlist_select(PL_ARTIST, "Hajime Wakai"));
g_assert_false(playlist_add(NULL, track_get(0)));
g_assert_false(playlist_select(NULL));
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()) {};
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 2);
g_assert_nonnull(artist->ar_playlist);
g_assert_false(playlist_remove(PL_ARTIST, "Koji Kondo", track_get(0)));
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 2);
playlist = playlist_lookup(PL_ARTIST, "Koji Kondo");
g_assert_nonnull(playlist);
g_assert(playlist_cur() != playlist_get(PL_ARTIST, "Koji Kondo"));
g_assert_true(playlist_select(PL_ARTIST, "Koji Kondo"));
g_assert_cmpuint(playlist->pl_id, ==, artist_index(artist));
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.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();
g_assert_null(artist->ar_playlist);
}
@ -51,7 +62,7 @@ int main(int argc, char **argv)
struct library *library;
int ret;
idle_init_sync();
idle_init(IDLE_SYNC);
settings_init();
tags_init();
playlist_init(NULL);

View File

@ -13,90 +13,98 @@ void test_library()
struct playlist *playlist;
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(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_false(playlist_select(NULL));
g_assert_cmpuint(playlist_get_id(PL_LIBRARY, "tests/Music"), ==, 0);
g_assert_cmpstr_free(playlist_get_name(PL_LIBRARY, 0), ==, "tests/Music");
g_assert_false(playlist_select(PL_LIBRARY, "tests/Music"));
playlist = playlist_new(PL_LIBRARY, "tests/Music");
g_assert_nonnull(playlist);
g_assert_null(playlist_new(PL_LIBRARY, "tests/Music"));
g_assert_nonnull(playlist_lookup(PL_LIBRARY, "tests/Music"));
library = library_get(0);
playlist = library->li_playlist;
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()) {};
g_assert_cmpuint(playlist_size(PL_LIBRARY, "tests/Music"), ==, 48);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Unplayed"), ==, 48);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Collection"), ==, 48);
g_assert_cmpuint(playlist_size(playlist), ==, 48);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Unplayed")),
==, 48);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Collection")),
==, 48);
g_assert(playlist_cur() != playlist_get(PL_LIBRARY, "tests/Music"));
g_assert_true(playlist_select(PL_LIBRARY, "tests/Music"));
playlist = playlist_lookup(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.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(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_false(playlist_add(playlist, track_get(0)));
g_assert_false(playlist_add(playlist, track_get(1)));
g_assert_false(playlist_remove(playlist, track_get(0)));
g_assert_false(playlist_remove(playlist, track_get(1)));
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);
pl_library_init(NULL);
pl_library_init();
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;
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 3);
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_ARTIST, true);
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 1);
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_YEAR, false);
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 2);
playlist_sort(PL_LIBRARY, "tests/Music", COMPARE_TRACK, false);
g_assert_cmpuint(g_slist_length(playlist->pl_queue.q_sort), ==, 3);
g_assert_nonnull(library->li_playlist);
g_assert_cmpuint(playlist_size(playlist), ==, 48);
g_assert_false(playlist_add(playlist, track_get(0)));
g_assert_false(playlist_add(playlist, track_get(1)));
g_assert_false(playlist_remove(playlist, track_get(0)));
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/");
playlist_update(PL_LIBRARY, "tests/Music");
pl_library_update(playlist);
while (idle_run_task()) {}
g_assert_cmpuint(db_actual_size(track_db_get()), ==, 48);
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/");
playlist_update(PL_LIBRARY, "tests/Music");
pl_library_update(playlist);
while (idle_run_task()) {}
g_assert_cmpuint(db_actual_size(track_db_get()), ==, 61);
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_false(playlist_delete(PL_LIBRARY, "tests/Music"));
g_assert_true(playlist_delete(playlist));
g_assert_null(library_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Unplayed"), ==, 0);
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Collection"), ==, 0);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Unplayed")),
==, 0);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Collection")),
==, 0);
g_assert_cmpuint(track_db_get()->db_size, ==, 0);
}
@ -104,7 +112,7 @@ int main(int argc, char **argv)
{
int ret;
idle_init_sync();
idle_init(IDLE_SYNC);
settings_init();
tags_init();
playlist_init(NULL);

View File

@ -8,419 +8,387 @@
#include <tests/test.h>
#define __test_playlist_id(name, id) \
g_assert_cmpuint(playlist_get_id(PL_SYSTEM, name), ==, id); \
g_assert_cmpstr_free(playlist_get_name(PL_SYSTEM, id), ==, name)
#define __test_playlist_has(name, track, expected) \
if (expected) \
g_assert_true(playlist_has(PL_SYSTEM, name, track)); \
else \
g_assert_false(playlist_has(PL_SYSTEM, name, track))
#define __test_playlist_state(name, ex_size, ex_track0, ex_track1) \
g_assert_cmpuint(playlist_size(PL_SYSTEM, name), ==, ex_size); \
__test_playlist_has(name, track_get(0), ex_track0); \
__test_playlist_has(name, track_get(1), ex_track1)
#define __test_playlist_random(name) \
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 inline struct playlist *__test_pl_favorites(void)
{ return playlist_lookup(PL_SYSTEM, "Favorites"); }
static inline struct playlist *__test_pl_hidden(void)
{ return playlist_lookup(PL_SYSTEM, "Hidden"); }
static inline struct playlist *__test_pl_queued(void)
{ return playlist_lookup(PL_SYSTEM, "Queued Tracks"); }
static inline struct playlist *__test_pl_collection(void)
{ return playlist_lookup(PL_SYSTEM, "Collection"); }
static inline struct playlist *__test_pl_history(void)
{ return playlist_lookup(PL_SYSTEM, "History"); }
static inline struct playlist *__test_pl_unplayed(void)
{ return playlist_lookup(PL_SYSTEM, "Unplayed"); }
static inline struct playlist *__test_pl_most_played(void)
{ return playlist_lookup(PL_SYSTEM, "Most Played"); }
static inline struct playlist *__test_pl_least_played(void)
{ return playlist_lookup(PL_SYSTEM, "Least Played"); }
static void test_invalid()
static void test_init()
{
g_assert_null(playlist_get_queue(PL_SYSTEM, NULL));
g_assert_null(playlist_get_queue(PL_SYSTEM, "Invalid"));
struct library *library = library_find("tests/Music");
struct playlist *playlist;
unsigned int i;
g_assert_cmpuint(playlist_get_id(PL_SYSTEM, NULL), ==,
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(playlist_current() == __test_pl_collection());
g_assert_false(playlist_new(PL_SYSTEM, "New Playlist"));
g_assert_false(playlist_delete(PL_SYSTEM, "Favorites"));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
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);
__test_playlist_noselect("Invalid");
if (i == SYS_PL_QUEUED || i == SYS_PL_HISTORY) {
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);
g_assert_false(playlist_add(PL_SYSTEM, NULL, track_get(0)));
g_assert_false(playlist_remove(PL_SYSTEM, NULL, track_get(0)));
g_assert_false(playlist_delete(PL_SYSTEM, NULL));
g_assert_false(playlist_has(PL_SYSTEM, NULL, track_get(0)));
g_assert_cmpuint(playlist_size(PL_SYSTEM, NULL), ==, 0);
/* Add tracks to the collection. */
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");
pl_system_new_track(track_get(0));
pl_system_new_track(track_get(1));
pl_system_new_track(track_get(2));
g_assert_false(playlist_get_random(PL_SYSTEM, NULL));
playlist_set_random(PL_SYSTEM, NULL, true);
g_assert_false(playlist_get_random(PL_SYSTEM, NULL));
g_assert_null(playlist_new(PL_SYSTEM, "New Playlist"));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
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);
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_true(queue_has_flag(queue, Q_REPEAT));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
playlist = playlist_get(PL_SYSTEM, i);
__test_playlist_id("Favorites", SYS_PL_FAVORITES);
__test_playlist_noselect("Favorites");
__test_playlist_random("Favorites");
__test_playlist_add("Favorites");
__test_playlist_select("Favorites", SYS_PL_FAVORITES);
__test_playlist_reinit("Favorites", 2, true, true);
__test_playlist_update("Favorites", 2, true, true);
__test_playlist_clear("Favorites", 0, false, false);
__test_playlist_reinit("Favorites", 0, false, false);
__test_playlist_add("Favorites");
__test_playlist_remove("Favorites");
g_assert_false(playlist->pl_random);
playlist_set_random(playlist, true);
if (i == SYS_PL_HISTORY)
g_assert_false(playlist->pl_random);
else
g_assert_true(playlist->pl_random);
playlist_set_random(playlist, false);
g_assert_false(playlist->pl_random);
}
}
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);
g_assert_null(playlist_get_queue(PL_SYSTEM, "Banned"));
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_true(queue_has_flag(queue, Q_REPEAT));
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
playlist = playlist_get(PL_SYSTEM, i);
playlist_clear_sort(playlist);
__test_playlist_id("Hidden", SYS_PL_HIDDEN);
__test_playlist_noselect("Hidden");
__test_playlist_random("Hidden");
__test_playlist_add("Hidden");
__test_playlist_select("Hidden", SYS_PL_HIDDEN);
__test_playlist_reinit("Hidden", 2, true, true);
__test_playlist_update("Hidden", 2, true, true);
__test_playlist_clear("Hidden", 0, false, false);
__test_playlist_reinit("Hidden", 0, false, false);
__test_playlist_add("Hidden");
__test_playlist_remove("Hidden");
if (i == SYS_PL_HISTORY) {
g_assert_false(playlist_sort(playlist, COMPARE_TRACK));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
g_assert_false(playlist_sort(playlist, COMPARE_YEAR));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
} else {
g_assert_true(playlist_sort(playlist, COMPARE_TRACK));
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);
}
}
}
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);
g_assert_false(queue_has_flag(queue, Q_ADD_FRONT));
g_assert_false(queue_has_flag(queue, Q_NO_SORT));
g_assert_false(queue_has_flag(queue, Q_REPEAT));
g_assert_cmpuint(g_slist_length(queue->q_sort), ==, 0);
track_played(track_get(0));
playlist_played(track_get(0));
while (idle_run_task()) {}
__test_playlist_id("Queued Tracks", SYS_PL_QUEUED);
__test_playlist_noselect("Queued Tracks");
__test_playlist_random("Queued Tracks");
__test_playlist_add("Queued Tracks");
__test_playlist_select("Queued Tracks", SYS_PL_QUEUED);
__test_playlist_reinit("Queued Tracks", 2, true, true);
g_assert_cmpuint(playlist_size(__test_pl_unplayed()), ==, 2);
g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_least_played()), ==, 1);
g_assert(playlist_next() == track_get(0));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Queued Tracks"), ==, 1);
playlist_select(PL_SYSTEM, "Collection");
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));
g_assert_true(playlist_has(__test_pl_least_played(), track_get(0)));
g_assert_true(playlist_has(__test_pl_unplayed(), track_get(1)));
g_assert_true(playlist_has(__test_pl_unplayed(), track_get(2)));
__test_playlist_add("Queued Tracks");
__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));
playlist_played(track_get(1));
while (idle_run_task()) {}
pl_system_deinit();
pl_system_init(NULL);
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_nonnull(most);
g_assert_true(queue_has_flag(most, Q_ADD_FRONT));
g_assert_true(queue_has_flag(most, Q_REPEAT));
__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);
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)));
}
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" */
track_get(0)->tr_count = 3;
track_get(1)->tr_count = 1;
g_assert_true( playlist_add(__test_pl_favorites(), track_get(0)));
g_assert_false(playlist_add(__test_pl_favorites(), track_get(0)));
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();
pl_system_init(NULL);
g_assert_cmpuint(playlist_size(__test_pl_favorites()), ==, 1);
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(queue_has_flag(least, Q_ADD_FRONT));
g_assert_true(queue_has_flag(least, Q_REPEAT));
g_assert_true(playlist_has(__test_pl_favorites(), track_get(0)));
g_assert_true(playlist_has(__test_pl_queued(), track_get(0)));
g_assert_true(playlist_has(__test_pl_history(), track_get(0)));
__test_playlist_id("Least Played", SYS_PL_LEAST_PLAYED);
__test_playlist_noselect("Least Played");
__test_playlist_random("Least Played");
__test_playlist_reinit("Least Played", 1, false, true);
g_assert_true( playlist_add(__test_pl_hidden(), 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_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_true( playlist_remove(PL_SYSTEM, "Least Played", track_get(1)));
g_assert_false(playlist_remove(PL_SYSTEM, "Least Played", track_get(1)));
__test_playlist_state("Least Played", 0, false, false);
g_assert_cmpuint(playlist_size(__test_pl_hidden()), ==, 3);
g_assert_cmpuint(playlist_size(__test_pl_collection()), ==, 0);
g_assert_cmpuint(playlist_size(__test_pl_most_played()), ==, 0);
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_add(PL_SYSTEM, "Least Played", track_get(1)));
g_assert_false(playlist_add(PL_SYSTEM, "Least Played", track_get(1)));
__test_playlist_state("Least Played", 1, false, true);
__test_playlist_select("Least Played", SYS_PL_LEAST_PLAYED);
g_assert_true( playlist_has(__test_pl_hidden(), track_get(0)));
g_assert_true( playlist_has(__test_pl_hidden(), track_get(1)));
g_assert_true( playlist_has(__test_pl_hidden(), track_get(2)));
g_assert_false(playlist_has(__test_pl_collection(), track_get(0)));
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;
track_get(1)->tr_count = 3;
__test_playlist_update("Least Played", 1, true, false);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
playlist = playlist_get(PL_SYSTEM, i);
__test_playlist_hide_track("Least Played", 0, false, false);
__test_playlist_reinit("Least Played", 0, false, false);
__test_playlist_update("Least Played", 0, false, false);
__test_playlist_unhide_track("Least Played", 1, true, false);
__test_playlist_hide_track("Least Played", 0, false, false);
__test_playlist_clear_hidden("Least Played", 1, true, false);
__test_playlist_clear("Least Played", 1, true, false);
switch (i) {
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_QUEUED:
g_assert_true( playlist_select(playlist));
g_assert_false(playlist_select(playlist));
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)
{
struct library *library;
int ret;
idle_init_sync();
idle_init(IDLE_SYNC);
settings_init();
tags_init();
playlist_init(NULL);
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_add_func("/Core/Playlists/System/Invalid", test_invalid);
g_test_add_func("/Core/Playlists/System/Favorites", test_favorites);
g_test_add_func("/Core/Playlists/System/Hidden", test_hidden);
g_test_add_func("/Core/Playlists/System/Queued", test_queued);
g_test_add_func("/Core/Playlists/System/Collection", test_collection);
g_test_add_func("/Core/Playlists/System/History", test_history);
g_test_add_func("/Core/Playlists/System/Unplayed Tracks", test_unplayed);
g_test_add_func("/Core/Playlists/System/Most Played Tracks", test_most_played);
g_test_add_func("/Core/Playlists/System/Least Played Tracks", test_least_played);
g_test_add_func("/Core/Playlists/System/Init", test_init);
g_test_add_func("/Core/Playlists/System/Random", test_random);
g_test_add_func("/Core/Playlists/System/Sort", test_sort);
g_test_add_func("/Core/Playlists/System/Played", test_played);
g_test_add_func("/Core/Playlists/System/Add Tracks", test_add);
g_test_add_func("/Core/Playlists/System/Remove Tracks", test_remove);
g_test_add_func("/Core/Playlists/System/Delete", test_delete);
g_test_add_func("/Core/Playlists/System/Next", test_next);
g_test_add_func("/Core/PLaylists/System/Delete Tracks", test_delete_tracks);
ret = g_test_run();
playlist_deinit();

View File

@ -12,53 +12,74 @@
void test_user()
{
struct database *db = pl_user_db_get();
struct playlist *playlist;
g_assert_cmpuint(db->db_size, ==, 0);
g_assert_true( playlist_new(PL_USER, "Test Playlist"));
g_assert_false(playlist_new(PL_USER, "Test Playlist"));
playlist = 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_false(playlist_get_random(PL_USER, "Test Playlist"));
playlist_set_random(PL_USER, "Test Playlist", true);
g_assert_true(playlist_get_random(PL_USER, "Test Playlist"));
playlist_set_random(PL_USER, "Test Playlist", false);
g_assert_false(playlist_get_random(PL_USER, "Test Playlist"));
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(playlist_get_id(PL_USER, "Test Playlist"), ==, 0);
g_assert_cmpuint(playlist_get_id(PL_USER, "No Playlist"), ==,
(unsigned int)-1);
g_assert(playlist_current() != playlist);
g_assert_false(playlist_select(playlist));
g_assert(playlist_current() != playlist);
g_assert_cmpstr_free(playlist_get_name(PL_USER, 0), ==, "Test Playlist");
g_assert_null(playlist_get_name(PL_USER, 1));
g_assert_cmpuint(playlist_size(playlist), ==, 0);
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(PL_USER, "Test Playlist"));
g_assert(playlist_cur() == playlist_get(PL_USER, "Test Playlist"));
g_assert_false(playlist_select(PL_USER, "No Playlist"));
g_assert_true(playlist_select(playlist));
g_assert(playlist_current() == playlist);
g_assert_false(playlist_select(playlist));
g_assert_false(playlist_select(NULL));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 0);
g_assert_false(playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_add( PL_USER, "Test Playlist", track_get(0)));
g_assert_false(playlist_add( PL_USER, "Test Playlist", track_get(0)));
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0)));
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1);
g_assert(playlist_next() == track_get(0));
g_assert_true(playlist_has(playlist, track_get(0)));
g_assert_cmpuint(playlist_size(playlist), ==, 1);
playlist_selected(track_get(0));
playlist_played(track_get(0));
playlist_update(PL_USER, "Test Playlist");
pl_user_deinit();
g_assert_cmpuint(db->db_size, ==, 0);
pl_user_init(NULL);
pl_user_init();
while (idle_run_task()) {};
g_assert_cmpuint(db->db_size, ==, 1);
g_assert_cmpuint(playlist_size(PL_USER, "Test Playlist"), ==, 1);
g_assert_true( playlist_has( PL_USER, "Test Playlist", track_get(0)));
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);
playlist = playlist_lookup(PL_USER, "Test Playlist");
g_assert_nonnull(playlist);
g_assert_true( playlist_delete(PL_USER, "Test Playlist"));
g_assert_false(playlist_delete(PL_USER, "Test Playlist"));
g_assert_false(pl_user_rename(NULL, "Null 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_actual_size(db), ==, 0);
}
@ -67,7 +88,7 @@ int main(int argc, char **argv)
{
int ret;
idle_init_sync();
idle_init(IDLE_SYNC);
settings_init();
tags_init();
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()
{
struct file f = FILE_INIT("settings", 0);
struct file f = FILE_INIT_DATA("", "settings", 0);
settings_set(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));
}
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()
{
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/Matching", test_match);
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);
return g_test_run();
}

View File

@ -46,19 +46,18 @@ static void test_album()
struct album *album;
struct artist *koji;
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");
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);
g_assert_true( album_match_token(album, "hyrule"));
g_assert_true( album_match_token(album, "symphony"));
g_assert_false(album_match_token(album, "skyward"));
file_init(&f, "album_tag", 1);
file_open(&f, OPEN_WRITE);
file_writef(&f, "0 0 0 \n");
album_ops->dbe_write(&f, &album->al_dbe);
@ -66,11 +65,11 @@ static void test_album()
album_ops->dbe_free(&album->al_dbe);
file_open(&f, OPEN_READ);
album = ALBUM(album_ops->dbe_read(&f));
album = ALBUM(album_ops->dbe_read(&f, 0));
test_verify_empty(album);
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);
test_verify_hyrule(album, koji, genre);
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();
struct album *twilight, *skyward;
twilight = ALBUM(album_ops->dbe_alloc("2006/Twilight Princess"));
skyward = ALBUM(album_ops->dbe_alloc("2011/skyward sword"));
twilight = ALBUM(album_ops->dbe_alloc("2006/Twilight Princess", 0));
skyward = ALBUM(album_ops->dbe_alloc("2011/skyward sword", 0));
g_assert_cmpint(album_compare(twilight, twilight), ==, 0);
g_assert_cmpint(album_compare(twilight, skyward), ==, 1);
@ -134,7 +133,7 @@ static void test_album_artwork()
const gchar *cache;
idle_deinit();
idle_init();
idle_init(IDLE_ASYNC);
cache = g_get_user_cache_dir();
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;
idle_init_sync();
idle_init(IDLE_SYNC);
artist_db_init();
genre_db_init();
album_db_init();

View File

@ -28,19 +28,17 @@ static void test_verify_koji(struct artist *artist)
static void test_artist()
{
struct file f = FILE_INIT_DATA("", "artist_tag", 0);
const struct db_ops *artist_ops = test_artist_ops();
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);
g_assert_true( artist_match_token(artist, "koji"));
g_assert_true( artist_match_token(artist, "kondo"));
g_assert_false(artist_match_token(artist, "hajime"));
file_init(&f, "artist_tag", 0);
file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n2 ");
artist_ops->dbe_write(&f, &artist->ar_dbe);
@ -49,14 +47,14 @@ static void test_artist()
artist_ops->dbe_free(&artist->ar_dbe);
file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i);
artist = ARTIST(artist_ops->dbe_read(&f));
file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f, 0));
test_verify_empty(artist);
g_free(artist->ar_name);
artist_ops->dbe_free(&artist->ar_dbe);
file_readf(&f, "%u", &i);
artist = ARTIST(artist_ops->dbe_read(&f));
file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f, 0));
file_close(&f);
test_verify_koji(artist);
g_free(artist->ar_name);
@ -68,8 +66,8 @@ static void test_artist_compare()
const struct db_ops *artist_ops = test_artist_ops();
struct artist *koji, *hajime;
koji = ARTIST(artist_ops->dbe_alloc("Koji Kondo"));
hajime = ARTIST(artist_ops->dbe_alloc("hajime wakai"));
koji = ARTIST(artist_ops->dbe_alloc("Koji Kondo", 0));
hajime = ARTIST(artist_ops->dbe_alloc("hajime wakai", 0));
g_assert_cmpint(artist_compare(koji, koji), ==, 0);
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()
{
struct file f = FILE_INIT_DATA("", "genre_tag", 0);
const struct db_ops *genre_ops = test_genre_ops();
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);
g_assert_true( genre_match_token(genre, "video"));
g_assert_true( genre_match_token(genre, "music"));
g_assert_false(genre_match_token(genre, "rock"));
file_init(&f, "genre_tag", 0);
file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n1 ");
genre_ops->dbe_write(&f, &genre->ge_dbe);
@ -47,14 +45,14 @@ static void test_genre()
genre_ops->dbe_free(&genre->ge_dbe);
file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i);
genre = GENRE(genre_ops->dbe_read(&f));
file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f, 0));
test_verify_empty(genre);
g_free(genre->ge_name);
genre_ops->dbe_free(&genre->ge_dbe);
file_readf(&f, "%u", &i);
genre = GENRE(genre_ops->dbe_read(&f));
file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f, 0));
file_close(&f);
test_verify_vg(genre);
g_free(genre->ge_name);
@ -66,8 +64,8 @@ static void test_genre_compare()
const struct db_ops *genre_ops = test_genre_ops();
struct genre *video, *game;
video = GENRE(genre_ops->dbe_alloc("Video Game Music"));
game = GENRE(genre_ops->dbe_alloc("game music"));
video = GENRE(genre_ops->dbe_alloc("Video Game Music", 0));
game = GENRE(genre_ops->dbe_alloc("game music", 0));
g_assert_cmpint(genre_compare(video, video), ==, 0);
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()
{
struct file f = FILE_INIT_DATA("", "library_tag", 0);
const struct db_ops *library_ops = test_library_ops();
struct library *link, *zelda, *library;
struct file f;
link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music"));
zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/Music"));
link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music", 0));
zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/Music", 0));
test_verify_link(link);
test_verify_zelda(zelda);
file_init(&f, "library_tag", 0);
file_open(&f, OPEN_WRITE);
library_ops->dbe_write(&f, &link->li_dbe);
file_writef(&f, "\n");
@ -38,14 +37,14 @@ static void test_library()
file_close(&f);
file_open(&f, OPEN_READ);
library = LIBRARY(library_ops->dbe_read(&f));
library = LIBRARY(library_ops->dbe_read(&f, 0));
test_verify_link(library);
g_assert_cmpstr_free(library_file(library, "navi.mp3"), ==,
"/home/Link/Music/navi.mp3");
g_free(library->li_path);
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);
test_verify_zelda(library);
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);
library_db_init();
library = library_lookup("/home/Zelda/Music");
g_assert_null(library);
g_assert_null(library_lookup("/home/Zelda/Music"));
library = library_find("/home/Zelda/Music");
test_verify_zelda(library);
g_assert_cmpuint(library_db_get()->db_size, ==, 1);
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/Ocarina") == library);
g_assert(library_get(0) == library);
g_assert_null(library_get(1));

View File

@ -12,32 +12,43 @@
static struct track *test_alloc(const gchar *key)
{
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);
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_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_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();
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_tokens[0], ==, "title");
g_assert_cmpstr(track->tr_tokens[1], ==, "theme");
g_assert_null(track->tr_tokens[2]);
g_assert_null(track->tr_alts[0]);
g_assert_cmpstr(track_ops->dbe_key(&track->tr_dbe), ==,
"0/Hyrule Symphony/01 - Title Theme.ogg");
if (!external)
g_assert_cmpstr(track_ops->dbe_key(&track->tr_dbe), ==,
"0/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_cmpstr_free(track_path(track), ==,
"tests/Music/Hyrule Symphony/01 - Title Theme.ogg");
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)
{
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_null(track->tr_tokens[0]);
@ -72,17 +83,16 @@ static void test_track()
time_t rawtime = time(NULL);
struct tm *now = localtime(&rawtime);
struct track *track;
struct file f;
struct file f = FILE_INIT_DATA("", "track_tag", 1);
gchar *date;
file_init(&f, "track_tag", 1);
g_assert_nonnull(library_find("tests/Music"));
date = string_tm2str(now);
track = test_alloc("0/Hyrule Symphony/01 - Title Theme.ogg");
g_assert_nonnull(track);
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, "theme"));
@ -98,16 +108,16 @@ static void test_track()
track_ops->dbe_free(&track->tr_dbe);
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;
test_verify_notrack(track);
g_free(track->tr_path);
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;
test_verify_track(track);
test_verify_track(track, false);
file_close(&f);
track_played(track);
@ -119,6 +129,27 @@ static void test_track()
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()
{
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_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);
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_null(track_add(library, path));
@ -226,7 +263,7 @@ static void __test_track_db_subprocess()
track_db_commit();
db_load(&track_db);
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 */
db_deinit(&track_db);
@ -302,12 +339,13 @@ int main(int argc, char **argv)
int ret;
setlocale(LC_TIME, "C");
idle_init_sync();
idle_init(IDLE_SYNC);
tags_init();
while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL);
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/Database", test_track_db);
ret = g_test_run();

View File

@ -1,9 +1,7 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/idle.h>
#include <gui/artwork.h>
#include <gui/filter.h>
#include <gui/model.h>
@ -16,19 +14,18 @@ static void test_audio_load(struct track *track)
static void test_change_state(GstState state) { }
static void test_config_pause(int n) { }
static struct audio_ops test_audio_ops = {
.on_load = test_audio_load,
.on_state_change = test_change_state,
.on_config_pause = test_config_pause,
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,
};
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
.audio_ops = &test_audio_ops,
#ifdef CONFIG_ALBUM_ART_TEST
.idle_async = true,
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)
{
@ -76,7 +73,7 @@ int main(int argc, char **argv)
int ret;
gtk_init(&argc, NULL);
core_init(&argc, NULL, &init_data);
core_init(&argc, NULL, NULL, &test_audio_cb, test_sync);
gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init();
gui_filter_init();

View File

@ -1,10 +1,7 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/string.h>
#include <gui/audio.h>
#include <gui/builder.h>
@ -15,10 +12,7 @@
#include <gui/treeview.h>
#include <gui/window.h>
#include <tests/test.h>
struct core_init_data init_data = {
.audio_ops = &audio_ops,
};
#include <tests/loop.h>
static void test_audio_init()
{
@ -27,14 +21,14 @@ static void test_audio_init()
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_combo_box_text_get_active_text(gui_pause_after()),
==, "(disabled)");
g_assert_cmpuint(gtk_combo_box_get_active(
GTK_COMBO_BOX(gui_pause_after())), ==, 0);
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 void test_audio_load()
@ -52,6 +46,8 @@ static void test_audio_load()
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())));
@ -65,14 +61,20 @@ static void test_audio_buttons()
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)
@ -82,16 +84,72 @@ static void test_audio_buttons()
gtk_button_clicked(gui_prev_button());
g_assert(audio_cur_track() == track_get(0));
gtk_combo_box_set_active(GTK_COMBO_BOX(gui_pause_after()), 2);
audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(gtk_combo_box_get_active(
GTK_COMBO_BOX(gui_pause_after())), ==, 1);
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);
audio_eos();
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_cmpuint(gtk_combo_box_get_active(
GTK_COMBO_BOX(gui_pause_after())), ==, 0);
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);
@ -123,7 +181,7 @@ int main(int argc, char **argv)
int ret;
gtk_init(&argc, NULL);
core_init(&argc, &argv, &init_data);
core_init(&argc, &argv, NULL, &audio_cb, IDLE_SYNC);
gui_builder_init("share/ocarina/ocarina.ui");
gui_model_init();
gui_filter_init();
@ -131,6 +189,7 @@ int main(int argc, char **argv)
gui_sidebar_init();
gui_playlist_init();
gui_audio_init();
test_loop_init();
gui_pl_library_add("tests/Music/Hyrule Symphony");
while (idle_run_task()) {};
@ -142,6 +201,7 @@ int main(int argc, char **argv)
ret = g_test_run();
core_deinit();
test_loop_deinit();
gui_audio_deinit();
gui_filter_deinit();
gui_treeview_deinit();

View File

@ -1,52 +1,11 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/idle.h>
#include <gui/filter.h>
#include <gui/model.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()
{
struct track *track;
@ -63,11 +22,9 @@ void test_filter()
entry = GTK_ENTRY(gui_filter_search());
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));
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));
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);
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_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_cmpstr(gtk_entry_get_text(entry), ==, "hyrule");
@ -118,7 +75,7 @@ int main(int argc, char **argv)
int ret;
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_window_init("share/ocarina/ocarina.png");
gui_model_init();

View File

@ -4,11 +4,10 @@
#include <core/core.h>
#include <gui/idle.h>
#include <tests/test.h>
#include <tests/gui.h>
#include <tests/loop.h>
static const unsigned int N = 100;
static unsigned int cur = -1;
struct core_init_data init_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)));
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_cmpfloat(idle_progress(), ==, (float)(i + 1) / N);
g_assert_cmpfloat(gtk_progress_bar_get_fraction(progress),
==, (float)(i + 1) / N);
}
gui_test_main_loop();
test_main_loop();
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(progress)));
}
@ -45,16 +44,16 @@ int main(int argc, char **argv)
int ret;
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_test_init();
test_loop_init();
while (idle_run_task()) {}
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Idle", test_idle);
ret = g_test_run();
gui_test_deinit();
test_loop_deinit();
gui_builder_deinit();
core_deinit();
return ret;

View File

@ -1,10 +1,7 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/idle.h>
#include <core/core.h>
#include <core/playlist.h>
#include <gui/builder.h>
#include <gui/model.h>
#include <tests/test.h>
@ -23,42 +20,13 @@ void on_row_changed(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{ count_update++; }
void *test_queue_init(struct queue *queue, void *data)
{ 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) {}
void test_cb_alloc(struct playlist *playlist) {}
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,
};
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,
struct playlist_callbacks test_cb = {
.pl_cb_alloc = test_cb_alloc,
.pl_cb_added = gui_model_add,
.pl_cb_removed = gui_model_remove,
.pl_cb_updated = gui_model_update,
};
static void test_init()
@ -68,10 +36,10 @@ static void test_init()
GType type;
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_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), ==,
GTK_TREE_MODEL_LIST_ONLY);
@ -108,7 +76,7 @@ static void __test_empty_subprocess()
GValue value;
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));
g_assert_false(gtk_tree_model_get_iter_first(model, &iter));
@ -165,13 +133,13 @@ static void test_empty()
static void test_model()
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_model_get());
struct db_entry *dbe, *next;
struct playlist *collection, *favorites;
struct track *track;
GtkTreePath *path;
GtkTreeIter iter;
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()), ==, "");
memset(&value, 0, sizeof(GValue));
@ -180,18 +148,18 @@ static void test_model()
g_signal_connect(model, "row-changed", (GCallback)on_row_changed, NULL);
/* Okay, now scan a directory ... */
playlist_get_queue(PL_SYSTEM, "Collection")->q_private =
playlist_get(PL_SYSTEM, "Collection");
collection = playlist_lookup(PL_SYSTEM, "Collection");
favorites = playlist_lookup(PL_SYSTEM, "Favorites");
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
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);
queue_resort(playlist_get_queue(PL_SYSTEM, "Collection"));
playlist_generic_resort(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(count_update, ==, 13);
playlist_add(PL_SYSTEM, "Favorites", track_get(0));
playlist_add(PL_SYSTEM, "Favorites", track_get(1));
playlist_add(PL_SYSTEM, "Favorites", track_get(2));
g_assert_cmpuint(playlist_size(PL_SYSTEM, "Favorites"), ==, 3);
playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(1));
playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(2));
g_assert_cmpuint(playlist_size(favorites), ==, 3);
g_assert_cmpstr(gtk_label_get_text(gui_model_runtime()), ==,
"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, 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_insert, ==, 14);
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_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_insert, ==, 15);
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));
db_for_each(dbe, next, track_db_get())
playlist_remove(PL_SYSTEM, "Collection", TRACK(dbe));
gui_model_set_playlist(playlist_lookup(PL_SYSTEM, "Favorites"));
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)
@ -292,7 +264,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
while (idle_run_task()) {};

View File

@ -2,8 +2,6 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/idle.h>
#include <core/settings.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/playlist.h>
@ -11,10 +9,6 @@
#include <gui/treeview.h>
#include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_playlist()
{
GtkTreeIter iter, child;
@ -22,13 +16,13 @@ static void test_playlist()
gui_pl_library_add("tests/Music/Hyrule Symphony");
while (idle_run_task()) {}
playlist_add(PL_SYSTEM, "Favorites", track_get(0));
playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
gtk_tree_model_filter_refilter(gui_sidebar_filter());
g_assert_true(gui_sidebar_iter_first(&iter));
path = gtk_tree_model_get_path(gui_sidebar_model(), &iter);
gtk_tree_view_row_activated(gui_sidebar_treeview(), path, NULL);
g_assert_true(playlist_cur() == playlist_get(PL_SYSTEM, "Collection"));
g_assert_true(playlist_current() == playlist_lookup(PL_SYSTEM, "Collection"));
gtk_tree_path_free(path);
g_assert_false(settings_has("gui.sidebar.expand.Playlists"));
@ -52,7 +46,7 @@ static void test_playlist()
&iter);
g_assert_nonnull(path);
gtk_tree_view_row_activated(gui_sidebar_treeview(), path, NULL);
g_assert_true(playlist_cur() == playlist_get(PL_SYSTEM, "Favorites"));
g_assert_true(playlist_current() == playlist_lookup(PL_SYSTEM, "Favorites"));
gtk_tree_path_free(path);
}
@ -61,7 +55,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h>
#include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_artist()
{
GtkTreeModel *model = gui_sidebar_model();
@ -24,14 +19,15 @@ static void test_artist()
gui_sidebar_iter_find(&iter, "Collection", PL_SYSTEM);
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_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));
while (idle_run_task()) {}
g_assert_cmpuint(artist_db_get()->db_size, ==, 1);
g_assert_nonnull(playlist_get(PL_ARTIST, "Koji Kondo"));
g_assert_cmpuint(playlist_size(PL_ARTIST, "Koji Kondo"), ==, 13);
g_assert_nonnull(playlist_lookup(PL_ARTIST, "Koji Kondo"));
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_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 1);
@ -45,7 +41,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h>
#include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_library()
{
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_cmpuint(library_db_get()->db_size, ==, 0);
g_assert_null(playlist_get(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/Ocarina of Time"));
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_true(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2);
ocarina = playlist_get(PL_LIBRARY, "tests/Music/Ocarina of Time");
hyrule = playlist_get(PL_LIBRARY, "tests/Music/Hyrule Symphony");
g_assert_cmpuint(playlist_size(ocarina->pl_type, ocarina->pl_name), ==, 0);
g_assert_cmpuint(playlist_size(hyrule->pl_type, hyrule->pl_name), ==, 0);
g_assert_cmpuint(playlist_size(ocarina), ==, 0);
g_assert_cmpuint(playlist_size(hyrule), ==, 0);
while (idle_run_task()) {}
g_assert_cmpuint(playlist_size(hyrule->pl_type, hyrule->pl_name), ==, 13);
g_assert_cmpuint(playlist_size(ocarina->pl_type, ocarina->pl_name), ==, 35);
g_assert_cmpuint(playlist_size(hyrule), ==, 13);
g_assert_cmpuint(playlist_size(ocarina), ==, 35);
g_assert_true(gtk_tree_model_iter_has_child(model, &iter));
g_assert_cmpuint(gtk_tree_model_iter_n_children(model, &iter), ==, 2);
@ -59,7 +55,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -1,9 +1,7 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/idle.h>
#include <gui/filter.h>
#include <gui/model.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_config_pause(int n) { }
static struct audio_ops test_audio_ops = {
.on_load = test_audio_load,
.on_state_change = test_change_state,
.on_config_pause = test_config_pause,
};
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
.audio_ops = &test_audio_ops,
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,
};
static const gchar *toplevel[3] = { "Queued Tracks", "Collection", "History" };
@ -65,32 +58,35 @@ static void test_system()
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()) {}
g_assert_false(gtk_toggle_button_get_active(gui_favorite_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));
g_assert_true( gtk_toggle_button_get_active(gui_favorite_button()));
g_assert_false(gtk_toggle_button_get_active(gui_hide_button()));
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);
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));
g_assert_false(gtk_toggle_button_get_active(gui_favorite_button()));
g_assert_true(gtk_toggle_button_get_active(gui_hide_button()));
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));
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));
}
@ -99,7 +95,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -2,7 +2,6 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/idle.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/model.h>
@ -11,10 +10,6 @@
#include <gui/treeview.h>
#include <tests/test.h>
struct core_init_data init_data = {
.playlist_ops = &playlist_ops,
};
static void test_user()
{
GtkTreeModel *model = gui_sidebar_model();
@ -37,18 +32,30 @@ static void test_user()
g_assert_true(gui_sidebar_iter_down(&iter, &child));
g_assert_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Favorites");
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_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Hidden");
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_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 1");
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_cmpstr_free(gui_sidebar_iter_name(&child), ==, "Test 2");
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();
g_assert_nonnull(list);
@ -72,7 +79,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -2,8 +2,6 @@
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <core/idle.h>
#include <core/settings.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/sidebar.h>
@ -16,7 +14,6 @@ const gchar *test_pl_names[8] = { "Collection",
"", "Dynamic",
"", "Library" };
const gchar *test_dyn_names[3] = { "Least Played", "Most Played", "Unplayed" };
struct core_init_data init_data;
static void test_sidebar()
{
@ -39,8 +36,8 @@ static void test_sidebar()
g_assert_true(GTK_IS_TOGGLE_BUTTON(gui_random_button()));
g_assert_true(gui_sidebar_iter_first(&iter));
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "Collection"), NULL);
gui_sidebar_iter_add(&iter, playlist_get(PL_SYSTEM, "History"), NULL);
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Collection"), NULL);
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "History"), NULL);
g_assert_true(gui_sidebar_iter_first(&iter));
for (i = 0; i < 8; i++) {
@ -60,9 +57,9 @@ static void test_sidebar()
g_assert_cmpuint(gui_sidebar_iter_type(&iter), ==, PL_MAX_TYPE);
g_assert_false(gui_sidebar_iter_down(&iter, &child));
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Favorites"),
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Favorites"),
NULL);
gui_sidebar_iter_append_child(&iter, playlist_get(PL_SYSTEM, "Hidden"),
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Hidden"),
NULL);
g_assert_true(gui_sidebar_iter_down(&iter, &child));
@ -78,14 +75,16 @@ static void test_sidebar()
g_assert_true(gui_sidebar_iter_find(&iter, "History", PL_SYSTEM));
g_assert_cmpstr_free(gui_sidebar_iter_name(&iter), ==, "History");
g_assert_cmpuint(gui_sidebar_iter_type(&iter), ==, PL_SYSTEM);
g_assert(gui_sidebar_iter_playlist(&iter) ==
playlist_lookup(PL_SYSTEM, "History"));
g_assert_true(gui_sidebar_iter_find(&iter, "Dynamic", PL_MAX_TYPE));
g_assert_false(gui_sidebar_iter_down(&iter, &child));
gui_sidebar_iter_sort_child(&iter, playlist_get(PL_SYSTEM, "Most Played"),
gui_sidebar_iter_sort_child(&iter, playlist_lookup(PL_SYSTEM, "Most Played"),
NULL);
gui_sidebar_iter_sort_child(&iter, playlist_get(PL_SYSTEM, "Least Played"),
gui_sidebar_iter_sort_child(&iter, playlist_lookup(PL_SYSTEM, "Least Played"),
NULL);
gui_sidebar_iter_sort_child(&iter, playlist_get(PL_SYSTEM, "Unplayed"),
gui_sidebar_iter_sort_child(&iter, playlist_lookup(PL_SYSTEM, "Unplayed"),
NULL);
g_assert_true(gui_sidebar_iter_down(&iter, &child));
@ -100,28 +99,30 @@ static void test_sidebar()
static void test_sidebar_selection()
{
struct playlist *collection;
GtkTreeSelection *selection;
GtkToggleButton *random;
GtkTreeIter iter, cur;
GtkTreeModel *filter;
GtkTreePath *path;
GtkTreeIter iter;
unsigned int i, n;
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
filter = GTK_TREE_MODEL(gui_sidebar_filter());
random = gui_random_button();
collection = playlist_lookup(PL_SYSTEM, "Collection");
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
filter = GTK_TREE_MODEL(gui_sidebar_filter());
random = gui_random_button();
g_assert_cmpuint(gtk_tree_model_iter_n_children(filter, NULL), ==, 6);
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
while (idle_run_task()) {}
playlist_add(PL_SYSTEM, "History", track_get(0));
playlist_add(PL_SYSTEM, "Favorites", track_get(0));
playlist_add(playlist_lookup(PL_SYSTEM, "History"), track_get(0));
playlist_add(playlist_lookup(PL_SYSTEM, "Favorites"), track_get(0));
gtk_tree_model_filter_refilter(gui_sidebar_filter());
g_assert_cmpuint(gtk_tree_model_iter_n_children(filter, NULL), ==, 8);
playlist_set_random(PL_SYSTEM, "Collection", true);
g_assert_true(playlist_select(PL_SYSTEM, "Favorites"));
g_assert(playlist_cur() == playlist_get(PL_SYSTEM, "Favorites"));
playlist_set_random(collection, true);
g_assert_true(playlist_select(playlist_lookup(PL_SYSTEM, "Favorites")));
g_assert(playlist_current() == playlist_lookup(PL_SYSTEM, "Favorites"));
g_assert_true(gui_sidebar_iter_first(&iter));
path = gtk_tree_model_get_path(gui_sidebar_model(), &iter);
@ -130,21 +131,27 @@ static void test_sidebar_selection()
n = gtk_tree_selection_count_selected_rows(selection);
g_assert_cmpuint(n, ==, (i < 2) ? 1 : 0);
if (i == 0) {
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() == collection);
g_assert_true(gtk_toggle_button_get_active(random));
g_assert_true(gtk_widget_get_sensitive(
GTK_WIDGET(random)));
g_assert_true(gui_sidebar_iter_current(&cur));
g_assert_true(gui_sidebar_iter_playlist(&cur) ==
collection);
gtk_toggle_button_set_active(random, false);
g_assert_false(
playlist_get_random(PL_SYSTEM, "Collection"));
g_assert_false(collection->pl_random);
gtk_toggle_button_set_active(random, true);
g_assert_true(
playlist_get_random(PL_SYSTEM, "Collection"));
g_assert_true(collection->pl_random);
} else if (i == 1) {
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "History"));
playlist_lookup(PL_SYSTEM, "History"));
g_assert_true(gui_sidebar_iter_current(&cur));
g_assert_true(gui_sidebar_iter_playlist(&cur) ==
playlist_lookup(PL_SYSTEM, "History"));
g_assert_false(gtk_toggle_button_get_active(random));
g_assert_false(gtk_widget_get_sensitive(
GTK_WIDGET(random)));
@ -156,7 +163,13 @@ static void test_sidebar_selection()
}
gui_sidebar_filter_path_select(path);
g_assert(playlist_cur() == playlist_get(PL_SYSTEM, "Collection"));
g_assert(playlist_current() == playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_false(gui_sidebar_iter_editable(&iter));
g_assert_false(gui_sidebar_iter_set_editable(&iter, true));
g_assert_false(gui_sidebar_iter_editable(&iter));
g_assert_false(gui_sidebar_iter_set_editable(&iter, false));
g_assert_false(gui_sidebar_iter_editable(&iter));
gtk_tree_selection_unselect_all(selection);
gui_sidebar_iter_next(&iter);
@ -230,7 +243,7 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();

View File

@ -1,15 +1,12 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/idle.h>
#include <core/settings.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/treeview.h>
#include <tests/test.h>
#include <tests/gui.h>
#include <tests/loop.h>
const gchar *GUI_COL_SETTINGS[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_TRACK_NR] = "gui.queue.track",
@ -25,20 +22,6 @@ const gchar *GUI_COL_SETTINGS[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_FONT] = "gui.queue.font",
};
static void test_load(struct track *track) { }
static void test_state_change(GstState state) {}
static void test_config_pause(int count) {}
struct audio_ops test_audio_ops = {
.on_load = test_load,
.on_state_change = test_state_change,
.on_config_pause = test_config_pause,
};
struct core_init_data init_data = {
.audio_ops = &test_audio_ops,
};
void test_treeview_init()
{
GtkTreePath *path;
@ -53,9 +36,9 @@ void test_treeview_init()
gtk_tree_view_get_cursor(gui_treeview(), &path, NULL);
g_assert_null(path);
gui_treeview_set_playlist(playlist_get(PL_SYSTEM, "Collection"));
gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "Collection"));
playlist_lookup(PL_SYSTEM, "Collection"));
/* No scrolling when playlist index is -1. */
gui_treeview_scroll();
@ -63,7 +46,7 @@ void test_treeview_init()
g_assert_null(path);
/* Okay, NOW we can scroll! */
playlist_get(PL_SYSTEM, "Collection")->pl_queue.q_cur.it_pos = 4;
playlist_current_set(playlist_lookup(PL_SYSTEM, "Collection"), 4);
gui_treeview_scroll();
gtk_tree_view_get_cursor(gui_treeview(), &path, NULL);
g_assert_nonnull(path);
@ -82,11 +65,11 @@ void test_treeview_select()
GList *list;
unsigned int i;
selection = gtk_tree_view_get_selection(gui_treeview());
selection = gui_treeview_selection();
gui_treeview_set_playlist(playlist_get(PL_SYSTEM, "Collection"));
gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "Collection"));
playlist_lookup(PL_SYSTEM, "Collection"));
g_assert_cmpuint(audio_cur_track()->tr_track, !=, 2);
path = gui_filter_path_from_index(1);
@ -116,9 +99,9 @@ void test_treeview_sort()
unsigned int i, sort;
/* Switch to collection playlist and check sort arrows. */
gui_treeview_set_playlist(playlist_get(PL_SYSTEM, "Collection"));
gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "Collection"));
playlist_lookup(PL_SYSTEM, "Collection"));
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i);
@ -129,6 +112,7 @@ void test_treeview_sort()
break;
case GUI_MODEL_ARTIST:
case GUI_MODEL_YEAR:
case GUI_MODEL_ALBUM:
case GUI_MODEL_TRACK_NR:
g_assert_true(
gtk_tree_view_column_get_sort_indicator(col));
@ -188,9 +172,9 @@ void test_treeview_sort()
}
/* History playlist should not have any sort arrows. */
gui_treeview_set_playlist(playlist_get(PL_SYSTEM, "History"));
gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "History"));
g_assert(gui_model_get_playlist() ==
playlist_get(PL_SYSTEM, "History"));
playlist_lookup(PL_SYSTEM, "History"));
g_assert_cmpstr(gtk_label_get_text(gui_sorting()), ==, "");
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
@ -216,7 +200,7 @@ void test_treeview_columns()
gtk_tree_view_column_set_fixed_width(col, (i + 2) * 10);
}
gui_test_main_loop();
test_main_loop();
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i);
if (!col || (i == GUI_MODEL_LAST_PLAY))
@ -233,12 +217,12 @@ int main(int argc, char **argv)
int ret;
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_model_init();
gui_filter_init();
gui_treeview_init();
gui_test_init();
test_loop_init();
playlist_new(PL_LIBRARY, "tests/Music/Hyrule Symphony");
while (idle_run_task()) {}
@ -251,7 +235,7 @@ int main(int argc, char **argv)
ret = g_test_run();
core_deinit();
gui_test_deinit();
test_loop_deinit();
gui_treeview_deinit();
gui_filter_deinit();
gui_model_deinit();

View File

@ -3,7 +3,7 @@
*/
#include <core/settings.h>
#include <gui/window.h>
#include <tests/gui.h>
#include <tests/loop.h>
#include <tests/test.h>
static void test_window()
@ -16,7 +16,7 @@ static void test_window()
g_assert_false(settings_has("gui.window.y"));
g_assert_cmpstr(gtk_window_get_title(window), ==, "Ocarina " CONFIG_VERSION);
gui_test_main_loop();
test_main_loop();
g_assert_true(settings_has("gui.window.width"));
g_assert_true(settings_has("gui.window.height"));
g_assert_true(settings_has("gui.window.x"));
@ -28,7 +28,7 @@ static void test_window()
settings_set("gui.window.y", 42);
gui_window_init("share/ocarina/ocarina.png");
gui_test_main_loop();
test_main_loop();
g_assert_cmpuint(settings_get("gui.window.width"), ==, 800);
g_assert_cmpuint(settings_get("gui.window.height"), ==, 600);
g_assert_cmpuint(settings_get("gui.window.x"), ==, 42);
@ -43,13 +43,13 @@ int main(int argc, char **argv)
gtk_init(&argc, NULL);
gui_builder_init("share/ocarina/ocarina.ui");
gui_window_init("share/ocarina/ocarina.png");
gui_test_init();
test_loop_init();
g_test_init(&argc, &argv, NULL);
g_test_add_func("/Gui/Window", test_window);
ret = g_test_run();
gui_test_deinit();
test_loop_deinit();
gui_window_deinit();
gui_builder_deinit();
settings_deinit();