Compare commits

...

75 Commits
v6.5.6 ... next

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
60 changed files with 1667 additions and 838 deletions

View File

@ -10,8 +10,8 @@ option(CONFIG_TESTING_ARTWORK "Enable album artwork fetching tests" ON)
# Configure settings
set(CONFIG_MAJOR 6)
set(CONFIG_MINOR 5)
set(CONFIG_MICRO 6)
set(CONFIG_RC OFF)
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.6
pkgver=6.5.9
pkgrel=1
pkgdesc="A simple GTK+ and GStreamer based music player."
url="http://www.nowheycreamery.com/"

View File

@ -6,84 +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 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 int audio_pause_count = -1;
static guint audio_bus = 0;
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;
if (audio_cb)
audio_cb->audio_cb_state_change(state);
return true;
return gst_element_set_state(audio_pipeline, state) != GST_STATE_CHANGE_FAILURE;
}
static void __audio_gst_load(struct track *track, GstState state)
static struct track *__audio_load(struct track *track, unsigned int flags)
{
gchar *path = track_path(track);
gchar *uri = gst_filename_to_uri(path, NULL);
struct track *prev = audio_track;
gchar *path;
if (!track)
return NULL;
audio_track = track;
gst_element_set_state(audio_player, GST_STATE_NULL);
g_object_set(G_OBJECT(audio_player), "uri", uri, NULL);
__audio_change_state(state);
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);
playlist_played(prev);
if (prev && TRACK_IS_EXTERNAL(prev))
track_free_external(prev);
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;
}
/* Load a track, but don't add it to the history. */
static struct track *__audio_do_load(struct track *track, GstState state)
static void __audio_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
struct track *prev = audio_track;
GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink");
if (track) {
__audio_gst_load(track, state);
playlist_selected(track);
}
playlist_played(prev);
return audio_track;
}
static struct track *__audio_load(struct track *track, GstState state)
{
struct track *ret = __audio_do_load(track, state);
if (ret == track)
playlist_add(playlist_lookup(PL_SYSTEM, "History"), ret);
return ret;
}
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;
}
@ -97,12 +130,12 @@ 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;
@ -115,11 +148,22 @@ void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks)
GstBus *bus;
gst_init(argc, argv);
audio_player = gst_element_factory_make("playbin", "ocarina_player");
audio_cb = callbacks;
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))
@ -131,19 +175,24 @@ void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks)
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));
}
@ -151,7 +200,19 @@ bool audio_load(struct track *track)
{
if (track == audio_track)
return false;
return __audio_load(track, GST_STATE_PLAYING) == track;
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()
@ -162,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;
@ -178,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;
}
@ -206,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);
@ -215,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;
@ -236,62 +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_do_load(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);
/* 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;
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

@ -36,10 +36,8 @@ 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)
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;
@ -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))

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

@ -66,7 +66,7 @@ void playlist_played(struct track *track)
{
unsigned int i;
if (track) {
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_played(track);
}
@ -75,7 +75,7 @@ void playlist_played(struct track *track)
void playlist_selected(struct track *track)
{
unsigned int i;
if (track) {
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_selected(track);
@ -208,6 +208,20 @@ bool playlist_sort(struct playlist *playlist, enum compare_t sort)
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;

View File

@ -5,19 +5,21 @@
#include <core/playlists/artist.h>
#include <core/string.h>
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(struct artist *artist)
{
return playlist_generic_alloc(artist->ar_name, PL_ARTIST,
artist_index(artist), &pl_artist_ops);
artist_index(artist), &pl_artist_ops,
3, COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
}
static bool __artist_pl_add(void *data)
@ -50,7 +52,7 @@ 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);

View File

@ -38,26 +38,32 @@ void playlist_generic_set_callbacks(struct playlist_callbacks *cb)
callbacks = cb;
}
void playlist_generic_init(struct playlist *playlist)
static void __playlist_generic_vinit(struct playlist *playlist,
unsigned int nargs, va_list argp)
{
if (playlist) {
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;
}
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_sorted(struct playlist *playlist)
void playlist_generic_init(struct playlist *playlist, unsigned int nargs, ...)
{
if (playlist) {
playlist_generic_init(playlist);
playlist_generic_sort(playlist, COMPARE_ARTIST);
playlist_generic_sort(playlist, COMPARE_YEAR);
playlist_generic_sort(playlist, COMPARE_TRACK);
}
va_list argp;
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
va_end(argp);
}
void playlist_generic_deinit(struct playlist *playlist)
@ -72,18 +78,23 @@ void playlist_generic_deinit(struct playlist *playlist)
}
struct playlist *playlist_generic_alloc(gchar *name, enum playlist_type_t type,
unsigned int id, struct playlist_ops *ops)
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;
playlist_generic_init_sorted(playlist);
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
if (callbacks)
callbacks->pl_cb_alloc(playlist);
va_end(argp);
return playlist;
}
@ -134,39 +145,34 @@ void playlist_generic_load(struct playlist *playlist, struct file *file,
{
unsigned int f, n, i, t, it = 0;
int field, ascending;
gchar *line;
if (!playlist)
return;
if (flags & PL_SAVE_ITER)
file_readf(file, "%u", &it);
it = file_readu(file);
if (flags & PL_SAVE_FLAGS) {
file_readf(file, "%u %u", &f, &n);
f = file_readu(file);
n = file_readu(file);
playlist_clear_sort(playlist);
for (i = 0; i < n; i++) {
file_readf(file, "%u %d", &field, &ascending);
field += 1;
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 (file_readf(file, "%m\n", &line))
g_free(line);
}
if (flags & PL_SAVE_TRACKS) {
file_readf(file, "%u ", &n);
n = file_readu(file);
for (i = 0; i < n; i++) {
file_readf(file, "%u", &t);
t = file_readu(file);
playlist_generic_add(playlist, track_get(t));
}
if (file_readf(file, "%m\n", &line))
g_free(line);
}
playlist_generic_set_random(playlist, f == PL_RANDOM);
@ -274,6 +280,22 @@ void playlist_generic_resort(struct playlist *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)
{
unsigned int pos, size = playlist_size(playlist);

View File

@ -14,7 +14,7 @@ struct scan_data {
};
static bool __lib_pl_scan_dir(void *);
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;
@ -22,7 +22,9 @@ static struct playlist_ops pl_library_ops;
static struct playlist *__lib_pl_alloc(struct library *library)
{
return playlist_generic_alloc(library->li_path, PL_LIBRARY,
library_index(library), &pl_library_ops);
library_index(library), &pl_library_ops,
4, COMPARE_ARTIST, COMPARE_YEAR,
COMPARE_ALBUM, COMPARE_TRACK);
}
static bool __lib_pl_add(void *data)
@ -55,7 +57,7 @@ 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);
@ -179,6 +181,7 @@ static struct playlist_ops pl_library_ops = {
.pl_delete = pl_library_delete,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};

View File

@ -9,10 +9,10 @@ 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 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);
/*
@ -82,6 +82,7 @@ static struct playlist_ops favorites_ops = {
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -132,6 +133,7 @@ static struct playlist_ops hidden_ops = {
.pl_remove = sys_pl_hidden_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -146,9 +148,9 @@ static bool sys_pl_queued_load()
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 = file_readu(&sys_deck_f);
flags &= PL_RANDOM;
if (i == 0)
playlist_generic_set_random(playlist,
@ -183,6 +185,7 @@ static struct playlist_ops queued_ops = {
.pl_remove = sys_pl_queued_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -207,6 +210,7 @@ static struct playlist_ops collection_ops = {
.pl_remove = sys_pl_hidden_add,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
@ -232,6 +236,7 @@ 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,
};
@ -264,9 +269,10 @@ static bool __sys_pl_load()
if (!file_open(&sys_file, OPEN_READ))
return true;
file_readf(&sys_file, "%u\n", &n);
n = file_readu(&sys_file);
for (i = 0; i < n; i++) {
file_readf(&sys_file, "%*u %m[^\n]\n", &name);
file_readu(&sys_file);
name = file_readl(&sys_file);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
@ -297,7 +303,7 @@ 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++) {
load = PL_SAVE_METADATA;
name = file_readl(&sys_pl_file);
@ -413,7 +419,7 @@ void pl_system_init(void)
switch (i) {
case SYS_PL_QUEUED:
case SYS_PL_HISTORY:
playlist_generic_init(playlist);
playlist_generic_init(playlist, 0);
break;
case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED:
@ -422,7 +428,8 @@ void pl_system_init(void)
sys_pl_update(playlist);
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
playlist_generic_init_sorted(playlist);
playlist_generic_init(playlist, 4, COMPARE_ARTIST,
COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
break;
}
}

View File

@ -15,7 +15,7 @@ static struct user_playlist *__user_db_alloc(gchar *name, unsigned int index)
playlist->pl_playlist.pl_type = PL_USER;
playlist->pl_playlist.pl_id = index;
playlist->pl_playlist.pl_ops = &user_ops;
playlist_generic_init_sorted(&playlist->pl_playlist);
playlist_generic_init(&playlist->pl_playlist, 0);
return playlist;
}
@ -82,6 +82,7 @@ static struct playlist_ops user_ops = {
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};

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);
@ -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

@ -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 */
@ -34,13 +35,13 @@ static gchar *library_key(struct db_entry *dbe)
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, unsigned int index)
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, unsigned int index)
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,19 +128,13 @@ 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);
@ -135,20 +144,19 @@ static void track_read_v0(struct file *file, struct track *track)
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

@ -9,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,
@ -42,16 +43,40 @@ 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());
}
@ -62,9 +87,68 @@ struct audio_callbacks audio_cb = {
};
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,
@ -98,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)
@ -111,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

@ -134,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

@ -28,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;
@ -169,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();
@ -203,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,
@ -221,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)
{
@ -229,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);

View File

@ -70,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();

View File

@ -37,6 +37,7 @@ static void __gui_playlist_alloc(struct playlist *playlist)
static void __gui_playlist_added(struct playlist *playlist, struct track *track)
{
gui_model_add(playlist, track);
gui_filter_refilter(playlist);
__gui_playlist_update_size(playlist);
}
@ -207,6 +208,31 @@ 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_current();

View File

@ -117,15 +117,12 @@ 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;
GtkTreeIter iter, child;
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
__gui_sidebar_filter_iter_convert(&iter, &child);
playlist = gui_sidebar_iter_playlist(&child);
if (gui_sidebar_iter_current(&iter)) {
playlist = gui_sidebar_iter_playlist(&iter);
active = playlist->pl_random;
sensitive = (playlist->pl_ops->pl_set_random != NULL);
}
@ -135,47 +132,108 @@ void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
gtk_widget_set_sensitive(GTK_WIDGET(gui_random_button()), sensitive);
}
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))
return false;
__gui_sidebar_filter_iter_convert(&iter, &child);
if (playlist_delete(playlist))
gtk_tree_store_remove(gui_sidebar_store(), &child);
return true;
}
}
bool __gui_sidebar_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkTreeModel *model = gtk_tree_view_get_model(treeview);
GtkTreeViewColumn *column = gtk_tree_view_get_column(treeview, SB_NAME);
GtkTreeIter iter, child;
enum playlist_type_t type = PL_MAX_TYPE;
GtkTreePath *path;
GtkTreeIter iter;
bool ret = true;
if (event->type != GDK_2BUTTON_PRESS ||
event->button != GDK_BUTTON_MIDDLE)
return false;
if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
return true;
if (!gtk_tree_model_get_iter(model, &iter, path))
return true;
return false;
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;
__gui_sidebar_filter_iter_convert(&iter, &child);
gui_sidebar_iter_set_editable(&child, true);
gtk_tree_view_set_cursor(treeview, path, column, true);
gtk_tree_path_free(path);
return true;
return ret;
}
void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data)
@ -198,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");
@ -216,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);
@ -415,3 +490,19 @@ gboolean gui_sidebar_iter_from_string(const gchar *path, GtkTreeIter *child)
__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

@ -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);
@ -218,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

@ -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

@ -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

@ -71,6 +71,9 @@ 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 *);

View File

@ -40,15 +40,15 @@ struct playlist_callbacks {
void playlist_generic_set_callbacks(struct playlist_callbacks *);
/* Generic playlist init functions. */
void playlist_generic_init(struct playlist *);
void playlist_generic_init_sorted(struct playlist *);
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, struct playlist_ops *,
unsigned int, ...);
/* Generic playlist free function. */
void playlist_generic_free(struct playlist *);
@ -82,6 +82,9 @@ void playlist_generic_set_random(struct playlist *, bool);
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 *);

View File

@ -39,6 +39,9 @@ struct playlist_ops {
/* 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);
};

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

@ -17,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)
@ -72,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

@ -38,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);

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 *);
@ -73,6 +76,9 @@ gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *,
/* 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()
{
@ -103,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>
@ -214,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>
@ -480,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>
@ -546,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>
@ -766,6 +768,8 @@ audio-volume-medium</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"/>
@ -830,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>
@ -910,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">
@ -1115,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>
@ -1141,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

@ -3,35 +3,30 @@
*/
#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++; }
@ -47,12 +42,13 @@ static struct audio_callbacks test_audio_cb = {
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, NULL, &test_audio_cb, IDLE_SYNC);
test_loop_init();
g_assert_false(audio_load(NULL));
g_assert_null(audio_next());
@ -66,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);
@ -73,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()
@ -87,8 +84,8 @@ static void test_playback()
else
g_assert_false(audio_load(tracks[i]));
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "History")), ==, 1);
g_assert_cmpuint(load_count, ==, 1);
g_assert_cmpuint(state_count, ==, 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);
@ -103,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. */
@ -143,7 +142,7 @@ static void test_next()
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(test_wait_state(), ==, 3);
g_assert_cmpuint(playlist_size(playlist_lookup(PL_SYSTEM, "Queued Tracks")), ==, 0);
/* Tracks should now be picked from the collection. */
@ -157,7 +156,7 @@ static void test_next()
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()
@ -170,32 +169,32 @@ static void test_prev()
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(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(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(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(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()
@ -203,40 +202,89 @@ void test_autopause()
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(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());
}
@ -248,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

@ -46,9 +46,7 @@ static gchar *int_key(struct db_entry *dbe)
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,8 +65,8 @@ 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", 0));
g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0);
@ -76,7 +74,6 @@ static void test_db_entry()
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);

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

@ -34,6 +34,7 @@ static struct playlist_ops test_ops = {
.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,
@ -49,8 +50,7 @@ static void test_null()
g_assert_false(playlist_delete(NULL));
playlist_generic_free(NULL);
playlist_generic_init(NULL);
playlist_generic_init_sorted(NULL);
playlist_generic_init(NULL, 0);
playlist_generic_deinit(NULL);
g_assert_null(playlist_lookup(PL_MAX_TYPE, "NULL"));
@ -77,6 +77,7 @@ static void test_null()
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);
@ -110,9 +111,10 @@ static void test_playlist()
int i;
g_assert_cmpuint(playlist_size(&p), ==, 0);
playlist_generic_init(&p);
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;
@ -202,8 +204,9 @@ static void test_sorting()
struct track *track;
unsigned int i;
playlist_generic_init_sorted(&p);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 3);
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);
@ -279,6 +282,43 @@ static void test_sorting()
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);
@ -286,7 +326,7 @@ static void test_next()
unsigned int i;
g_random_set_seed(0);
playlist_generic_init(&p);
playlist_generic_init(&p, 0);
for (i = 0; i < 13; i++)
playlist_generic_add(&p, track_get(i));
@ -329,7 +369,7 @@ static void test_save_load()
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("test.playlist", 0);
struct file f = FILE_INIT_DATA("", "test.playlist", 0);
unsigned int i;
for (i = 0; i < 13; i++) {
@ -403,6 +443,7 @@ int main(int argc, char **argv)
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();

View File

@ -75,8 +75,8 @@ void test_library()
playlist_set_random(playlist, false);
g_assert_false(playlist->pl_random);
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 3);
playlist_clear_sort(playlist);
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);

View File

@ -44,7 +44,7 @@ static void test_init()
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), ==, 3);
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 4);
}
/* Add tracks to the collection. */

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,7 +46,7 @@ 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_SYNC);
@ -58,7 +58,6 @@ static void test_album()
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);

View File

@ -28,10 +28,9 @@ 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", 0));
@ -40,7 +39,6 @@ static void test_artist()
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,13 +47,13 @@ static void test_artist()
artist_ops->dbe_free(&artist->ar_dbe);
file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i);
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);
file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f, 0));
file_close(&f);
test_verify_koji(artist);

View File

@ -27,10 +27,9 @@ 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", 0));
test_verify_vg(genre);
@ -38,7 +37,6 @@ static void test_genre()
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,13 +45,13 @@ static void test_genre()
genre_ops->dbe_free(&genre->ge_dbe);
file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i);
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);
file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f, 0));
file_close(&f);
test_verify_vg(genre);

View File

@ -20,9 +20,9 @@ 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", 0));
zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/Music", 0));
@ -30,7 +30,6 @@ static void test_library()
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");
@ -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

@ -17,27 +17,38 @@ static struct track *test_alloc(const gchar *key)
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"));
@ -107,7 +117,7 @@ static void test_track()
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);
@ -308,6 +345,7 @@ int main(int argc, char **argv)
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

@ -12,6 +12,7 @@
#include <gui/treeview.h>
#include <gui/window.h>
#include <tests/test.h>
#include <tests/loop.h>
static void test_audio_init()
{
@ -20,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()
@ -45,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())));
@ -58,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)
@ -75,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);
@ -124,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()) {};
@ -135,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

@ -4,7 +4,7 @@
#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;
@ -28,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)));
}
@ -46,14 +46,14 @@ int main(int argc, char **argv)
gtk_init(&argc, NULL);
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

@ -102,9 +102,9 @@ 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;
collection = playlist_lookup(PL_SYSTEM, "Collection");
@ -136,6 +136,10 @@ static void test_sidebar_selection()
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(collection->pl_random);
gtk_toggle_button_set_active(random, true);
@ -143,6 +147,11 @@ static void test_sidebar_selection()
} else if (i == 1) {
g_assert(gui_model_get_playlist() ==
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)));

View File

@ -6,7 +6,7 @@
#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",
@ -65,7 +65,7 @@ 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_lookup(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() ==
@ -112,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));
@ -199,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))
@ -221,7 +222,7 @@ int main(int argc, char **argv)
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()) {}
@ -234,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();