Compare commits

...

58 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2017-12-05 14:57:42 -05:00
50 changed files with 1186 additions and 594 deletions

View File

@ -10,7 +10,7 @@ option(CONFIG_TESTING_ARTWORK "Enable album artwork fetching tests" ON)
# Configure settings
set(CONFIG_MAJOR 6)
set(CONFIG_MINOR 5)
set(CONFIG_MICRO 7)
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

@ -13,7 +13,7 @@
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 file audio_file = FILE_INIT_DATA("", "cur_track", 0);
static struct track *audio_track = NULL;
static int audio_pause_count = -1;
@ -45,7 +45,8 @@ static struct track *__audio_load(struct track *track, unsigned int flags)
audio_track = track;
path = track_path(track);
gst_element_set_state(audio_pipeline, GST_STATE_READY);
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);
@ -131,7 +132,7 @@ static bool __audio_init_idle(void *data)
track = settings_get(SETTINGS_TRACK);
__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), LOAD_HISTORY);
@ -304,13 +305,20 @@ struct track *audio_prev()
return __audio_load(playlist_prev(), LOAD_PLAYING);
}
void audio_pause_after(int n)
bool 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

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

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

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

@ -35,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;
}

View File

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

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

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

@ -73,8 +73,12 @@ struct track *audio_next();
/* Called to load the previous track. */
struct track *audio_prev();
/* 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
void test_audio_eos();

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

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

@ -62,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);
@ -201,20 +202,32 @@ 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--) {
test_audio_eos();
g_assert_cmpuint(pause_count, ==, i);
g_assert_cmpint(pause_count, ==, i);
g_assert_cmpint(audio_get_pause_count(), ==, i);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(playlist_at(history, 0) == audio_cur_track());
}
@ -223,6 +236,7 @@ void test_autopause()
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(test_wait_state(), ==, 6);

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

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

View File

@ -83,10 +83,9 @@ 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);

View File

@ -21,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()
@ -46,6 +46,7 @@ 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())));
@ -64,12 +65,16 @@ static void test_audio_buttons()
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)
@ -79,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);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
gtk_button_clicked(gui_pause_up());
g_assert_cmpint(audio_get_pause_count(), ==, 0);
gtk_button_clicked(gui_pause_up());
g_assert_cmpint(audio_get_pause_count(), ==, 1);
gtk_button_clicked(gui_pause_down());
g_assert_cmpint(audio_get_pause_count(), ==, 0);
gtk_button_clicked(gui_pause_down());
g_assert_cmpint(audio_get_pause_count(), ==, -1);
gtk_entry_set_text(gui_pause_entry(), "2 tracks");
gtk_widget_activate(GTK_WIDGET(gui_pause_entry()));
g_assert_cmpint(audio_get_pause_count(), ==, 2);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after 2 tracks");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(gtk_combo_box_get_active(
GTK_COMBO_BOX(gui_pause_after())), ==, 1);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
gtk_button_clicked(gui_pause_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(GTK_BUTTON(gui_builder_widget("pause_popover_yes")));
test_main_loop();
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_play_button());
gtk_button_clicked(gui_pause_up());
gtk_button_clicked(gui_pause_up());
gtk_button_clicked(gui_pause_button());
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(GTK_BUTTON(gui_builder_widget("pause_popover_no")));
g_assert_cmpint(audio_get_pause_count(), ==, 1);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_pause_button());
g_assert_true(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
g_assert_cmpint(gui_audio_popover_timeout(), ==, G_SOURCE_REMOVE);
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
gtk_button_clicked(gui_play_button());
test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after this track");
g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpuint(gtk_combo_box_get_active(
GTK_COMBO_BOX(gui_pause_after())), ==, 0);
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);

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

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