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 # Configure settings
set(CONFIG_MAJOR 6) set(CONFIG_MAJOR 6)
set(CONFIG_MINOR 5) set(CONFIG_MINOR 5)
set(CONFIG_MICRO 7) set(CONFIG_MICRO 10)
set(CONFIG_RC ON) set(CONFIG_RC ON)
set(CONFIG_VERSION "${CONFIG_MAJOR}.${CONFIG_MINOR}.${CONFIG_MICRO}") set(CONFIG_VERSION "${CONFIG_MAJOR}.${CONFIG_MINOR}.${CONFIG_MICRO}")

View File

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

View File

@ -13,7 +13,7 @@
static const char *SETTINGS_TRACK = "core.audio.cur"; static const char *SETTINGS_TRACK = "core.audio.cur";
static const char *SETTINGS_VOLUME = "core.audio.volume"; static const char *SETTINGS_VOLUME = "core.audio.volume";
static struct file audio_file = FILE_INIT("cur_track", 0); static struct file audio_file = FILE_INIT_DATA("", "cur_track", 0);
static struct track *audio_track = NULL; static struct track *audio_track = NULL;
static int audio_pause_count = -1; static int audio_pause_count = -1;
@ -45,7 +45,8 @@ static struct track *__audio_load(struct track *track, unsigned int flags)
audio_track = track; audio_track = track;
path = track_path(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); g_object_set(G_OBJECT(audio_source), "location", path, NULL);
gst_element_set_state(audio_pipeline, flags & LOAD_PLAYING ? gst_element_set_state(audio_pipeline, flags & LOAD_PLAYING ?
GST_STATE_PLAYING : GST_STATE_PAUSED); GST_STATE_PLAYING : GST_STATE_PAUSED);
@ -131,7 +132,7 @@ static bool __audio_init_idle(void *data)
track = settings_get(SETTINGS_TRACK); track = settings_get(SETTINGS_TRACK);
__audio_load(track_get(track), LOAD_HISTORY); __audio_load(track_get(track), LOAD_HISTORY);
} else if (file_open(&audio_file, OPEN_READ)) { } else if (file_open(&audio_file, OPEN_READ)) {
file_readf(&audio_file, "%u", &track); track = file_readu(&audio_file);
file_close(&audio_file); file_close(&audio_file);
file_remove(&audio_file); file_remove(&audio_file);
__audio_load(track_get(track), LOAD_HISTORY); __audio_load(track_get(track), LOAD_HISTORY);
@ -304,13 +305,20 @@ struct track *audio_prev()
return __audio_load(playlist_prev(), LOAD_PLAYING); 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; audio_pause_count = n;
if (audio_cb) if (audio_cb)
audio_cb->audio_cb_config_pause(audio_pause_count); audio_cb->audio_cb_config_pause(audio_pause_count);
return true;
} }
return false;
}
int audio_get_pause_count(void)
{
return audio_pause_count;
} }
#ifdef CONFIG_TESTING #ifdef CONFIG_TESTING

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) static struct db_entry *__dbe_read(struct database *db, unsigned int index)
{ {
struct db_entry *dbe = NULL; struct db_entry *dbe = NULL;
int valid;
file_readf(&db->db_file, "%d", &valid); if (file_readd(&db->db_file))
if (valid)
dbe = db->db_ops->dbe_read(&db->db_file, index); dbe = db->db_ops->dbe_read(&db->db_file, index);
g_ptr_array_index(db->db_entries, index) = dbe; 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_autosave = autosave;
db->db_entries = g_ptr_array_new(); db->db_entries = g_ptr_array_new();
db->db_keys = g_hash_table_new(g_str_hash, g_str_equal); db->db_keys = g_hash_table_new(g_str_hash, g_str_equal);
file_init(&db->db_file, filepath, fmin); file_init_data(&db->db_file, "", filepath, fmin);
} }
void db_deinit(struct database *db) void db_deinit(struct database *db)
@ -118,7 +116,7 @@ void db_load(struct database *db)
if (file_open(&db->db_file, OPEN_READ) == false) if (file_open(&db->db_file, OPEN_READ) == false)
return; return;
file_readf(&db->db_file, "%u", &size); size = file_readu(&db->db_file);
g_ptr_array_set_size(db->db_entries, size); g_ptr_array_set_size(db->db_entries, size);
for (unsigned int i = 0; i < size; i++) { for (unsigned int i = 0; i < size; i++) {
if (__dbe_read(db, i)) if (__dbe_read(db, i))

View File

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

View File

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

View File

@ -208,6 +208,20 @@ bool playlist_sort(struct playlist *playlist, enum compare_t sort)
return g_slist_length(playlist->pl_sort) > 0; 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) void playlist_set_search(struct playlist *playlist, const gchar *text)
{ {
gchar **tokens = NULL; gchar **tokens = NULL;

View File

@ -5,19 +5,21 @@
#include <core/playlists/artist.h> #include <core/playlists/artist.h>
#include <core/string.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 = { static struct playlist_ops pl_artist_ops = {
.pl_can_select = playlist_generic_can_select, .pl_can_select = playlist_generic_can_select,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
}; };
static struct playlist *__artist_pl_alloc(struct artist *artist) static struct playlist *__artist_pl_alloc(struct artist *artist)
{ {
return playlist_generic_alloc(artist->ar_name, PL_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) 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)) if (!file_open(&artist_file, OPEN_READ))
return true; return true;
file_readf(&artist_file, "%u\n", &n); n = file_readu(&artist_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
name = file_readl(&artist_file); name = file_readl(&artist_file);
playlist = __artist_pl_lookup(name); playlist = __artist_pl_lookup(name);

View File

@ -38,26 +38,32 @@ void playlist_generic_set_callbacks(struct playlist_callbacks *cb)
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) { unsigned int i;
g_queue_init(&playlist->pl_tracks);
playlist->pl_length = 0; if (!playlist)
playlist->pl_random = false; return;
playlist->pl_current = NULL;
playlist->pl_sort = NULL; g_queue_init(&playlist->pl_tracks);
playlist->pl_search = NULL; 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) { va_list argp;
playlist_generic_init(playlist);
playlist_generic_sort(playlist, COMPARE_ARTIST); va_start(argp, nargs);
playlist_generic_sort(playlist, COMPARE_YEAR); __playlist_generic_vinit(playlist, nargs, argp);
playlist_generic_sort(playlist, COMPARE_TRACK); va_end(argp);
}
} }
void playlist_generic_deinit(struct playlist *playlist) 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, 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)); struct playlist *playlist = g_malloc(sizeof(struct playlist));
va_list argp;
playlist->pl_name = name; playlist->pl_name = name;
playlist->pl_type = type; playlist->pl_type = type;
playlist->pl_id = id; playlist->pl_id = id;
playlist->pl_ops = ops; playlist->pl_ops = ops;
playlist_generic_init_sorted(playlist); va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
if (callbacks) if (callbacks)
callbacks->pl_cb_alloc(playlist); callbacks->pl_cb_alloc(playlist);
va_end(argp);
return playlist; return playlist;
} }
@ -134,39 +145,34 @@ void playlist_generic_load(struct playlist *playlist, struct file *file,
{ {
unsigned int f, n, i, t, it = 0; unsigned int f, n, i, t, it = 0;
int field, ascending; int field, ascending;
gchar *line;
if (!playlist) if (!playlist)
return; return;
if (flags & PL_SAVE_ITER) if (flags & PL_SAVE_ITER)
file_readf(file, "%u", &it); it = file_readu(file);
if (flags & PL_SAVE_FLAGS) { if (flags & PL_SAVE_FLAGS) {
file_readf(file, "%u %u", &f, &n); f = file_readu(file);
n = file_readu(file);
playlist_clear_sort(playlist); playlist_clear_sort(playlist);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
file_readf(file, "%u %d", &field, &ascending); field = file_readu(file) + 1;
field += 1; ascending = file_readd(file);
if (!ascending) if (!ascending)
field = -field; field = -field;
playlist->pl_sort = g_slist_append(playlist->pl_sort, playlist->pl_sort = g_slist_append(playlist->pl_sort,
GINT_TO_POINTER(field)); GINT_TO_POINTER(field));
} }
playlist_generic_resort(playlist); playlist_generic_resort(playlist);
if (file_readf(file, "%m\n", &line))
g_free(line);
} }
if (flags & PL_SAVE_TRACKS) { if (flags & PL_SAVE_TRACKS) {
file_readf(file, "%u ", &n); n = file_readu(file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
file_readf(file, "%u", &t); t = file_readu(file);
playlist_generic_add(playlist, track_get(t)); 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); playlist_generic_set_random(playlist, f == PL_RANDOM);
@ -274,6 +280,22 @@ void playlist_generic_resort(struct playlist *playlist)
playlist_generic_update(playlist, NULL); playlist_generic_update(playlist, NULL);
} }
bool playlist_generic_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
GList *nth;
if (old_pos == new_pos || old_pos >= playlist_size(playlist) ||
new_pos > playlist_size(playlist))
return false;
playlist_clear_sort(playlist);
nth = g_queue_pop_nth_link(&playlist->pl_tracks, old_pos);
g_queue_push_nth_link(&playlist->pl_tracks, new_pos, nth);
playlist_generic_update(playlist, NULL);
return true;
}
struct track *playlist_generic_next(struct playlist *playlist) struct track *playlist_generic_next(struct playlist *playlist)
{ {
unsigned int pos, size = playlist_size(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 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; 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) static struct playlist *__lib_pl_alloc(struct library *library)
{ {
return playlist_generic_alloc(library->li_path, PL_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) 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)) if (!file_open(&lib_file, OPEN_READ))
return true; return true;
file_readf(&lib_file, "%u\n", &n); n = file_readu(&lib_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
name = file_readl(&lib_file); name = file_readl(&lib_file);
playlist = __lib_pl_lookup(name); playlist = __lib_pl_lookup(name);
@ -179,6 +181,7 @@ static struct playlist_ops pl_library_ops = {
.pl_delete = pl_library_delete, .pl_delete = pl_library_delete,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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 struct playlist *pl_system_get(unsigned int);
static void pl_system_save(); static void pl_system_save();
static struct file sys_file = FILE_INIT("playlist.db", 0); static struct file sys_file = FILE_INIT_DATA("", "playlist.db", 0);
static struct file sys_deck_f = FILE_INIT("deck", 1); static struct file sys_deck_f = FILE_INIT_DATA("", "deck", 1);
static struct file sys_collection_f = FILE_INIT("library.q", 0); static struct file sys_collection_f = FILE_INIT_DATA("", "library.q", 0);
static struct file sys_pl_file = FILE_INIT("playlist.system", 0); static struct file sys_pl_file = FILE_INIT_DATA("", "playlist.system", 0);
/* /*
@ -82,6 +82,7 @@ static struct playlist_ops favorites_ops = {
.pl_remove = playlist_generic_remove, .pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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_remove = sys_pl_hidden_remove,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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)) if (!file_open(&sys_deck_f, OPEN_READ))
return true; return true;
file_readf(&sys_deck_f, "%u", &num); num = file_readu(&sys_deck_f);
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
file_readf(&sys_deck_f, "%u", &flags); flags = file_readu(&sys_deck_f);
flags &= PL_RANDOM; flags &= PL_RANDOM;
if (i == 0) if (i == 0)
playlist_generic_set_random(playlist, playlist_generic_set_random(playlist,
@ -183,6 +185,7 @@ static struct playlist_ops queued_ops = {
.pl_remove = sys_pl_queued_remove, .pl_remove = sys_pl_queued_remove,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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_remove = sys_pl_hidden_add,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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_can_select = playlist_generic_can_select,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .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)) if (!file_open(&sys_file, OPEN_READ))
return true; return true;
file_readf(&sys_file, "%u\n", &n); n = file_readu(&sys_file);
for (i = 0; i < n; i++) { 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")) { if (string_match(name, "Banned")) {
g_free(name); g_free(name);
name = g_strdup("Hidden"); name = g_strdup("Hidden");
@ -297,7 +303,7 @@ static bool __sys_pl_load_new()
return true; return true;
} }
file_readf(&sys_pl_file, "%u\n", &n); n = file_readu(&sys_pl_file);
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
load = PL_SAVE_METADATA; load = PL_SAVE_METADATA;
name = file_readl(&sys_pl_file); name = file_readl(&sys_pl_file);
@ -413,7 +419,7 @@ void pl_system_init(void)
switch (i) { switch (i) {
case SYS_PL_QUEUED: case SYS_PL_QUEUED:
case SYS_PL_HISTORY: case SYS_PL_HISTORY:
playlist_generic_init(playlist); playlist_generic_init(playlist, 0);
break; break;
case SYS_PL_COLLECTION: case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED: case SYS_PL_UNPLAYED:
@ -422,7 +428,8 @@ void pl_system_init(void)
sys_pl_update(playlist); sys_pl_update(playlist);
case SYS_PL_FAVORITES: case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN: case SYS_PL_HIDDEN:
playlist_generic_init_sorted(playlist); playlist_generic_init(playlist, 4, COMPARE_ARTIST,
COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
break; 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_type = PL_USER;
playlist->pl_playlist.pl_id = index; playlist->pl_playlist.pl_id = index;
playlist->pl_playlist.pl_ops = &user_ops; playlist->pl_playlist.pl_ops = &user_ops;
playlist_generic_init_sorted(&playlist->pl_playlist); playlist_generic_init(&playlist->pl_playlist, 0);
return playlist; return playlist;
} }
@ -82,6 +82,7 @@ static struct playlist_ops user_ops = {
.pl_remove = playlist_generic_remove, .pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
}; };

View File

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

View File

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

View File

@ -35,13 +35,13 @@ static gchar *library_key(struct db_entry *dbe)
static struct db_entry *library_read(struct file *file, unsigned int index) static struct db_entry *library_read(struct file *file, unsigned int index)
{ {
int enabled;
gchar *path; gchar *path;
/* Old "enabled" flag */
if (file_version(file) == 0) if (file_version(file) == 0)
file_readf(file, "%d", &enabled); file_readd(file);
file_readf(file, " %m[^\n]", &path); path = file_readl(file);
return &__library_alloc(path)->li_dbe; return &__library_alloc(path)->li_dbe;
} }

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) static void track_read_v0(struct file *file, struct track *track)
{ {
unsigned int artist_id, album_id, genre_id; struct artist *artist = artist_get(file_readu(file));
struct artist *artist; struct album *album = album_get( file_readu(file));
struct genre *genre; struct genre *genre = genre_get( file_readu(file));
struct album *album;
file_readf(file, "%u %u %u %hu", &artist_id, &album_id, &genre_id, track->tr_track = file_readhu(file);
&track->tr_track);
date_read(file, &track->tr_date); date_read(file, &track->tr_date);
album = album_get(album_id);
artist = artist_get(artist_id);
genre = genre_get(genre_id);
if (album->al_artist != artist || album->al_genre != genre) if (album->al_artist != artist || album->al_genre != genre)
album = album_find(artist, genre, album->al_name, album->al_year); album = album_find(artist, genre, album->al_name, album->al_year);
@ -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) static struct db_entry *track_read(struct file *file, unsigned int index)
{ {
struct track *track = __track_alloc(); struct track *track = __track_alloc();
unsigned int library_id, album_id;
file_readf(file, "%u", &library_id); track->tr_library = library_get(file_readu(file));
track->tr_library = library_get(library_id);
if (file_version(file) == 0) if (file_version(file) == 0)
track_read_v0(file, track); track_read_v0(file, track);
else { else {
file_readf(file, "%u %hu", &album_id, &track->tr_track); track->tr_album = album_get(file_readu(file));
track->tr_album = album_get(album_id); track->tr_track = file_readhu(file);
date_read_stamp(file, &track->tr_date); date_read_stamp(file, &track->tr_date);
} }
file_readf(file, "%hu %hu", &track->tr_count, &track->tr_length); track->tr_count = file_readhu(file);
track->tr_length = file_readhu(file);
play_count += track->tr_count; play_count += track->tr_count;
if (track->tr_count == 0) if (track->tr_count == 0)

View File

@ -9,7 +9,8 @@
#include <gui/treeview.h> #include <gui/treeview.h>
#include <gui/window.h> #include <gui/window.h>
static guint audio_timeout = 0; static guint audio_timeout = 0;
static guint popover_timeout = 0;
static inline void __gui_audio_set_label_markup(GtkLabel *label, static inline void __gui_audio_set_label_markup(GtkLabel *label,
const gchar *size, const gchar *size,
@ -42,16 +43,40 @@ static void __gui_audio_load(struct track *track)
g_free(duration); 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) static void __gui_audio_change_state(GstState state)
{ {
bool playing = (state == GST_STATE_PLAYING); bool playing = (state == GST_STATE_PLAYING);
gtk_widget_set_visible(GTK_WIDGET(gui_play_button()), !playing); gtk_widget_set_visible(GTK_WIDGET(gui_play_button()), !playing);
gtk_widget_set_visible(GTK_WIDGET(gui_pause_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) 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, void __gui_audio_seek(GtkRange *range, GtkScrollType type,
@ -98,6 +182,8 @@ void gui_audio_init()
void gui_audio_deinit() void gui_audio_deinit()
{ {
g_source_remove(audio_timeout); g_source_remove(audio_timeout);
if (popover_timeout > 0)
g_source_remove(popover_timeout);
} }
int gui_audio_timeout(gpointer data) int gui_audio_timeout(gpointer data)
@ -111,3 +197,9 @@ int gui_audio_timeout(gpointer data)
g_free(position); g_free(position);
return G_SOURCE_CONTINUE; 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); gtk_tree_path_free(real);
return path; return path;
} }
void gui_filter_refilter(struct playlist *playlist)
{
if (!playlist || playlist == gui_model_get_playlist())
gtk_tree_model_filter_refilter(gui_filter_get());
}

View File

@ -28,6 +28,12 @@ static GType gui_model_columns[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_FONT] = G_TYPE_STRING, [GUI_MODEL_FONT] = G_TYPE_STRING,
}; };
const GtkTargetEntry gui_model_drag_targets[] = {
{ GUI_DRAG_DATA, GTK_TARGET_SAME_APP, 0 },
};
const unsigned int gui_model_n_targets = G_N_ELEMENTS(gui_model_drag_targets);
static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model) static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model)
{ {
return GTK_TREE_MODEL_LIST_ONLY; return GTK_TREE_MODEL_LIST_ONLY;
@ -169,6 +175,27 @@ static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter,
return FALSE; return FALSE;
} }
static gboolean __gui_model_drag_data_get(GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data)
{
struct gui_model_drag_data *data = g_malloc(sizeof(*data));
data->drag_row = gtk_tree_path_get_indices(path)[0];
data->drag_track = gui_model_path_get_track(path);
gtk_selection_data_set(selection_data, gdk_atom_intern(GUI_DRAG_DATA, false),
8 /* bytes */, (void *)data, sizeof(*data));
g_free(data);
return true;
}
static gboolean __gui_model_drag_data_delete(GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
return true;
}
static void __gui_model_init(GuiModel *model) static void __gui_model_init(GuiModel *model)
{ {
model->gm_stamp = g_random_int(); model->gm_stamp = g_random_int();
@ -203,6 +230,12 @@ static void __gui_tree_model_init(GtkTreeModelIface *iface)
iface->iter_parent = __gui_model_iter_parent; iface->iter_parent = __gui_model_iter_parent;
} }
static void __gui_drag_source_init(GtkTreeDragSourceIface *iface)
{
iface->drag_data_get = __gui_model_drag_data_get;
iface->drag_data_delete = __gui_model_drag_data_delete;
}
static const GTypeInfo gui_model_type_info = { static const GTypeInfo gui_model_type_info = {
.class_size = sizeof(GuiModelClass), .class_size = sizeof(GuiModelClass),
.base_init = NULL, .base_init = NULL,
@ -221,6 +254,12 @@ static const GInterfaceInfo gui_tree_model = {
.interface_data = NULL, .interface_data = NULL,
}; };
static const GInterfaceInfo gui_drag_source = {
.interface_init = (GInterfaceInitFunc)__gui_drag_source_init,
.interface_finalize = NULL,
.interface_data = NULL,
};
void gui_model_init(void) void gui_model_init(void)
{ {
@ -229,6 +268,9 @@ void gui_model_init(void)
(GTypeFlags)0); (GTypeFlags)0);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_MODEL, g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_MODEL,
&gui_tree_model); &gui_tree_model);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_DRAG_SOURCE,
&gui_drag_source);
gui_model = g_object_new(gui_model_type, NULL); gui_model = g_object_new(gui_model_type, NULL);
g_assert(gui_model != NULL); g_assert(gui_model != NULL);

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) static void __gui_playlist_added(struct playlist *playlist, struct track *track)
{ {
gui_model_add(playlist, track); gui_model_add(playlist, track);
gui_filter_refilter(playlist);
__gui_playlist_update_size(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); gui_sidebar_filter_row_expanded(iter, true);
} }
void __gui_playlist_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (gui_sidebar_iter_from_xy(x, y, &iter))
playlist = gui_sidebar_iter_playlist(&iter);
if (!playlist)
playlist = gui_pl_user_add_dialog();
if (!playlist)
goto out;
if (playlist == playlist_lookup(PL_SYSTEM, "Collection") &&
gui_model_get_playlist() == playlist_lookup(PL_SYSTEM, "Hidden"))
__gui_playlist_delete(NULL, NULL);
else if (playlist != playlist_lookup(PL_SYSTEM, "History"))
__gui_playlist_add_selected_to(playlist);
out:
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
}
bool __gui_playlist_init_idle() bool __gui_playlist_init_idle()
{ {
struct playlist *playlist = playlist_current(); 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) void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
{ {
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
bool active = false, sensitive = false; bool active = false, sensitive = false;
struct playlist *playlist = NULL; struct playlist *playlist = NULL;
GtkTreeIter iter, child; GtkTreeIter iter;
if (gtk_tree_selection_get_selected(selection, &model, &iter)) { if (gui_sidebar_iter_current(&iter)) {
__gui_sidebar_filter_iter_convert(&iter, &child); playlist = gui_sidebar_iter_playlist(&iter);
playlist = gui_sidebar_iter_playlist(&child);
active = playlist->pl_random; active = playlist->pl_random;
sensitive = (playlist->pl_ops->pl_set_random != NULL); 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); 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, bool __gui_sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data) gpointer data)
{ {
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); switch (event->keyval) {
struct playlist *playlist = gui_model_get_playlist(); case GDK_KEY_BackSpace:
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter()); return __gui_sidebar_rename(NULL, NULL);
GtkTreeIter iter, child; case GDK_KEY_Return:
return __gui_sidebar_select(NULL, NULL);
if (!playlist || event->keyval != GDK_KEY_Delete) case GDK_KEY_Delete:
return __gui_sidebar_delete(NULL, NULL);
default:
return false; return false;
if (!gtk_tree_selection_get_selected(selection, &model, &iter)) }
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, bool __gui_sidebar_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data) gpointer data)
{ {
GtkTreeModel *model = gtk_tree_view_get_model(treeview); enum playlist_type_t type = PL_MAX_TYPE;
GtkTreeViewColumn *column = gtk_tree_view_get_column(treeview, SB_NAME);
GtkTreeIter iter, child;
GtkTreePath *path; 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, if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL)) &path, NULL, NULL, NULL))
return true; return false;
if (!gtk_tree_model_get_iter(model, &iter, path))
return true; if (event->button == GDK_BUTTON_SECONDARY) {
gtk_tree_view_set_cursor(treeview, path, NULL, false);
if (gui_sidebar_iter_current(&iter))
type = gui_sidebar_iter_type(&iter);
gtk_widget_set_visible(gui_builder_widget("rc_sidebar_rename"),
type == PL_USER);
gtk_menu_popup_at_pointer(gui_sidebar_menu(), (GdkEvent *)event);
} else if (event->type == GDK_2BUTTON_PRESS &&
event->button == GDK_BUTTON_MIDDLE) {
__gui_sidebar_do_rename(path);
} else
ret = false;
__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); gtk_tree_path_free(path);
return true; return ret;
} }
void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data) void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data)
@ -198,6 +256,10 @@ void gui_sidebar_init()
GtkTreeSelection *selection; GtkTreeSelection *selection;
GtkTreeIter iter; GtkTreeIter iter;
gtk_tree_view_enable_model_drag_dest(gui_sidebar_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
if (!gui_sidebar_iter_first(&iter)) { if (!gui_sidebar_iter_first(&iter)) {
__gui_sidebar_add_header(&iter, "Playlists", "emblem-documents"); __gui_sidebar_add_header(&iter, "Playlists", "emblem-documents");
__gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic"); __gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic");
@ -216,6 +278,19 @@ void gui_sidebar_init()
gtk_paned_set_position(gui_sidebar(), pos); gtk_paned_set_position(gui_sidebar(), pos);
} }
gboolean gui_sidebar_iter_current(GtkTreeIter *iter)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter it;
if (!gtk_tree_selection_get_selected(selection, &model, &it))
return false;
__gui_sidebar_filter_iter_convert(&it, iter);
return true;
}
gboolean gui_sidebar_iter_first(GtkTreeIter *iter) gboolean gui_sidebar_iter_first(GtkTreeIter *iter)
{ {
return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter); return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter);
@ -415,3 +490,19 @@ gboolean gui_sidebar_iter_from_string(const gchar *path, GtkTreeIter *child)
__gui_sidebar_filter_iter_convert(&iter, child); __gui_sidebar_filter_iter_convert(&iter, child);
return TRUE; 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; can_scroll = true;
} }
void __gui_treeview_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct gui_model_drag_data *drag_data;
unsigned int to, from;
GtkTreePath *path;
drag_data = (void *)gtk_selection_data_get_data(data);
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
gtk_tree_path_prev(path);
else if (!gtk_tree_view_get_visible_range(gui_treeview(), NULL, &path))
return;
from = drag_data->drag_row;
to = gui_filter_path_get_index(path);
if (playlist_rearrange(gui_model_get_playlist(), from, to)) {
gtk_tree_selection_unselect_all(gui_treeview_selection());
gtk_tree_selection_select_path(gui_treeview_selection(), path);
__gui_treeview_set_sort_indicators();
}
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
gtk_tree_path_free(path);
}
bool __gui_treeview_drag_drop(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, guint time, gpointer user_data)
{
gtk_drag_get_data(GTK_WIDGET(treeview), context,
gdk_atom_intern(GUI_DRAG_DATA, false), time);
return true;
}
void gui_treeview_init() void gui_treeview_init()
{ {
GtkTreeViewColumn *col; GtkTreeViewColumn *col;
@ -167,6 +204,12 @@ void gui_treeview_init()
gtk_tree_view_set_model(gui_treeview(), gtk_tree_view_set_model(gui_treeview(),
GTK_TREE_MODEL(gui_filter_get())); GTK_TREE_MODEL(gui_filter_get()));
gtk_tree_view_enable_model_drag_source(gui_treeview(), GDK_BUTTON1_MASK,
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
gtk_tree_view_enable_model_drag_dest(gui_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) { for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i); col = gtk_tree_view_get_column(gui_treeview(), i);
@ -218,26 +261,22 @@ void gui_treeview_scroll()
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y) 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, if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL)) &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); gtk_tree_path_free(path);
} }
} }
GList *gui_treeview_list_selected_tracks(void) GList *gui_treeview_list_selected_tracks(void)
{ {
GList *rows, *cur, *list = NULL; GtkTreeSelection *selection = gui_treeview_selection();
GtkTreeSelection *selection; GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_first(rows);
selection = gtk_tree_view_get_selection(gui_treeview()); GList *list = NULL;
rows = gtk_tree_selection_get_selected_rows(selection, NULL);
cur = g_list_first(rows);
while (cur) { while (cur) {
list = g_list_append(list, gui_filter_path_get_track(cur->data)); 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. */ /* Called to load the previous track. */
struct track *audio_prev(); 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 #ifdef CONFIG_TESTING
void test_audio_eos(); void test_audio_eos();

View File

@ -78,7 +78,7 @@ struct database {
{ \ { \
.db_size = 0, \ .db_size = 0, \
.db_autosave = autosave, \ .db_autosave = autosave, \
.db_file = FILE_INIT(fname, fmin), \ .db_file = FILE_INIT_DATA("", fname, fmin), \
.db_entries = g_ptr_array_new(), \ .db_entries = g_ptr_array_new(), \
.db_keys = g_hash_table_new(g_str_hash, g_str_equal), \ .db_keys = g_hash_table_new(g_str_hash, g_str_equal), \
.db_ops = ops, \ .db_ops = ops, \

View File

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

View File

@ -71,6 +71,9 @@ void playlist_set_random(struct playlist *, bool);
/* Called to change the sort order of the playlist. */ /* Called to change the sort order of the playlist. */
bool playlist_sort(struct playlist *, enum compare_t); 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 */ /* Called to set the playlist's search text */
void playlist_set_search(struct playlist *, const gchar *); 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 *); void playlist_generic_set_callbacks(struct playlist_callbacks *);
/* Generic playlist init functions. */ /* Generic playlist init functions. */
void playlist_generic_init(struct playlist *); void playlist_generic_init(struct playlist *, unsigned int, ...);
void playlist_generic_init_sorted(struct playlist *);
/* Generic playlist deinit function. */ /* Generic playlist deinit function. */
void playlist_generic_deinit(struct playlist *); void playlist_generic_deinit(struct playlist *);
/* Generic playlist alloc function. */ /* Generic playlist alloc function. */
struct playlist *playlist_generic_alloc(gchar *, enum playlist_type_t, 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. */ /* Generic playlist free function. */
void playlist_generic_free(struct playlist *); 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_sort(struct playlist *, enum compare_t);
void playlist_generic_resort(struct playlist *); 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. */ /* Generic playlist next track operation. */
struct track *playlist_generic_next(struct playlist *); struct track *playlist_generic_next(struct playlist *);

View File

@ -39,6 +39,9 @@ struct playlist_ops {
/* Called to sort the playlist. */ /* Called to sort the playlist. */
void (*pl_sort)(struct playlist *, enum compare_t); 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. */ /* Called to update the current track position. */
int gui_audio_timeout(); int gui_audio_timeout();
int gui_audio_popover_timeout();
/* Called to get the label displaying the album tag. */ /* Called to get the label displaying the album tag. */
static inline GtkLabel *gui_album_tag(void) 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")); return GTK_BUTTON(gui_builder_widget("next_button"));
} }
/* Called to get the pause-fater combobox. */ /* Called to get the pause-after widgets. */
static inline GtkComboBoxText *gui_pause_after(void) 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. */ /* 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. */ /* Called to convert a playlist iterator index into a path. */
GtkTreePath *gui_filter_path_from_index(unsigned int); GtkTreePath *gui_filter_path_from_index(unsigned int);
/* Called to refilter a playlist. Pass NULL to refilter the current playlist */
void gui_filter_refilter(struct playlist *);
/* Called to access the filter search-entry. */ /* Called to access the filter search-entry. */
static inline GtkSearchEntry *gui_filter_search(void) static inline GtkSearchEntry *gui_filter_search(void)
{ {

View File

@ -38,6 +38,15 @@ struct gui_model_class {
}; };
typedef struct gui_model_class GuiModelClass; typedef struct gui_model_class GuiModelClass;
struct gui_model_drag_data {
unsigned int drag_row;
struct track *drag_track;
};
#define GUI_DRAG_DATA "GUI_DRAG_DATA"
extern const GtkTargetEntry gui_model_drag_targets[];
extern const unsigned int gui_model_n_targets;
/* Called to initialize the GuiModel */ /* Called to initialize the GuiModel */
void gui_model_init(void); void gui_model_init(void);

View File

@ -9,6 +9,9 @@
/* Called to initialize the sidebar. */ /* Called to initialize the sidebar. */
void gui_sidebar_init(); void gui_sidebar_init();
/* Called to set an iterator to the currently displayed playlist. */
gboolean gui_sidebar_iter_current(GtkTreeIter *);
/* Called to set an iterator to the first playlist. */ /* Called to set an iterator to the first playlist. */
gboolean gui_sidebar_iter_first(GtkTreeIter *); gboolean gui_sidebar_iter_first(GtkTreeIter *);
@ -73,6 +76,9 @@ gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *,
/* Called to set the a GtkTreeIter to the row at path string */ /* Called to set the a GtkTreeIter to the row at path string */
gboolean gui_sidebar_iter_from_string(const gchar *, GtkTreeIter *); gboolean gui_sidebar_iter_from_string(const gchar *, GtkTreeIter *);
/* Called to set the GtkTreeIter to the row at (x, y) */
gboolean gui_sidebar_iter_from_xy(gint, gint, GtkTreeIter *);
/* Called to get the sidebar widget. */ /* Called to get the sidebar widget. */
static inline GtkPaned *gui_sidebar() static inline GtkPaned *gui_sidebar()
{ {
@ -103,6 +109,12 @@ static inline GtkTreeView *gui_sidebar_treeview()
return GTK_TREE_VIEW(gui_builder_widget("sidebar_treeview")); return GTK_TREE_VIEW(gui_builder_widget("sidebar_treeview"));
} }
/* Called to get the sidebar right-click menu. */
static inline GtkMenu *gui_sidebar_menu()
{
return GTK_MENU(gui_builder_widget("rc_sidebar"));
}
/* Called to get the random button. */ /* Called to get the random button. */
static inline GtkToggleButton *gui_random_button() static inline GtkToggleButton *gui_random_button()
{ {

View File

@ -32,6 +32,12 @@ static inline GtkTreeView *gui_treeview()
return GTK_TREE_VIEW(gui_builder_widget("treeview")); return GTK_TREE_VIEW(gui_builder_widget("treeview"));
} }
/* Called to access the treview selection. */
static inline GtkTreeSelection *gui_treeview_selection()
{
return gtk_tree_view_get_selection(gui_treeview());
}
/* Called to access the sorting display widget. */ /* Called to access the sorting display widget. */
static inline GtkLabel *gui_sorting() static inline GtkLabel *gui_sorting()
{ {

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 --> <!-- Generated with glade 3.20.2 -->
<interface> <interface>
<requires lib="gtk+" version="3.16"/> <requires lib="gtk+" version="3.16"/>
<object class="GtkAdjustment" id="adjustment1"> <object class="GtkAdjustment" id="adjustment1">
@ -88,6 +88,58 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkImage" id="image25">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">dialog-ok</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="image26">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">edit-delete</property>
<property name="icon_size">1</property>
</object>
<object class="GtkImage" id="image27">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">document-edit</property>
<property name="icon_size">1</property>
</object>
<object class="GtkMenu" id="rc_sidebar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Select Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image25</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_select" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rc_sidebar_rename">
<property name="label" translatable="yes">Rename Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image27</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_rename" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem">
<property name="label" translatable="yes">Delete Playlist</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image26</property>
<property name="use_stock">False</property>
<signal name="activate" handler="__gui_sidebar_delete" swapped="no"/>
</object>
</child>
</object>
<object class="GtkImage" id="image3"> <object class="GtkImage" id="image3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
@ -214,7 +266,7 @@
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">center</property> <property name="valign">center</property>
<signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<signal name="clicked" handler="audio_pause" swapped="no"/> <signal name="clicked" handler="__gui_audio_pause" swapped="no"/>
<child> <child>
<object class="GtkImage" id="image2"> <object class="GtkImage" id="image2">
<property name="visible">True</property> <property name="visible">True</property>
@ -480,7 +532,6 @@ audio-volume-medium</property>
<packing> <packing>
<property name="left_attach">3</property> <property name="left_attach">3</property>
<property name="top_attach">0</property> <property name="top_attach">0</property>
<property name="width">2</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -546,132 +597,83 @@ audio-volume-medium</property>
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkLabel"> <object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Pause after</property>
</object>
<packing>
<property name="left_attach">3</property>
<property name="top_attach">1</property>
<property name="height">2</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText" id="pause_after">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="halign">end</property> <property name="halign">end</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="active">0</property> <child>
<items> <object class="GtkEntry" id="pause_entry">
<item translatable="yes">(disabled)</item> <property name="visible">True</property>
<item translatable="yes">this track</item> <property name="can_focus">True</property>
<item translatable="yes">next track</item> <property name="text" translatable="yes">Paused</property>
<item translatable="yes">2 tracks</item> <signal name="activate" handler="__gui_audio_pause_change_text" swapped="no"/>
<item translatable="yes">3 tracks</item> </object>
<item translatable="yes">4 tracks</item> <packing>
<item translatable="yes">5 tracks</item> <property name="expand">False</property>
<item translatable="yes">6 tracks</item> <property name="fill">True</property>
<item translatable="yes">7 tracks</item> <property name="position">0</property>
<item translatable="yes">8 tracks</item> </packing>
<item translatable="yes">9 tracks</item> </child>
<item translatable="yes">10 tracks</item> <child>
<item translatable="yes">11 tracks</item> <object class="GtkButton" id="pause_down">
<item translatable="yes">12 tracks</item> <property name="visible">True</property>
<item translatable="yes">13 tracks</item> <property name="sensitive">False</property>
<item translatable="yes">14 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">15 tracks</item> <property name="receives_default">True</property>
<item translatable="yes">16 tracks</item> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<item translatable="yes">17 tracks</item> <signal name="clicked" handler="__gui_audio_pause_dec" swapped="no"/>
<item translatable="yes">18 tracks</item> <child>
<item translatable="yes">19 tracks</item> <object class="GtkImage">
<item translatable="yes">20 tracks</item> <property name="visible">True</property>
<item translatable="yes">21 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">22 tracks</item> <property name="icon_name">list-remove-symbolic</property>
<item translatable="yes">23 tracks</item> </object>
<item translatable="yes">24 tracks</item> </child>
<item translatable="yes">25 tracks</item> <accelerator key="minus" signal="clicked"/>
<item translatable="yes">26 tracks</item> <accelerator key="KP_Subtract" signal="clicked"/>
<item translatable="yes">27 tracks</item> <style>
<item translatable="yes">28 tracks</item> <class name="down"/>
<item translatable="yes">29 tracks</item> </style>
<item translatable="yes">30 tracks</item> </object>
<item translatable="yes">31 tracks</item> <packing>
<item translatable="yes">32 tracks</item> <property name="expand">False</property>
<item translatable="yes">33 tracks</item> <property name="fill">True</property>
<item translatable="yes">34 tracks</item> <property name="position">1</property>
<item translatable="yes">35 tracks</item> </packing>
<item translatable="yes">36 tracks</item> </child>
<item translatable="yes">37 tracks</item> <child>
<item translatable="yes">38 tracks</item> <object class="GtkButton" id="pause_up">
<item translatable="yes">39 tracks</item> <property name="visible">True</property>
<item translatable="yes">40 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">41 tracks</item> <property name="receives_default">True</property>
<item translatable="yes">42 tracks</item> <signal name="can-activate-accel" handler="__gui_audio_can_accel" swapped="no"/>
<item translatable="yes">43 tracks</item> <signal name="clicked" handler="__gui_audio_pause_inc" swapped="no"/>
<item translatable="yes">44 tracks</item> <child>
<item translatable="yes">45 tracks</item> <object class="GtkImage">
<item translatable="yes">46 tracks</item> <property name="visible">True</property>
<item translatable="yes">47 tracks</item> <property name="can_focus">False</property>
<item translatable="yes">48 tracks</item> <property name="icon_name">list-add-symbolic</property>
<item translatable="yes">49 tracks</item> </object>
<item translatable="yes">50 tracks</item> </child>
<item translatable="yes">51 tracks</item> <accelerator key="plus" signal="clicked"/>
<item translatable="yes">52 tracks</item> <accelerator key="KP_Add" signal="clicked"/>
<item translatable="yes">53 tracks</item> <style>
<item translatable="yes">54 tracks</item> <class name="up"/>
<item translatable="yes">55 tracks</item> </style>
<item translatable="yes">56 tracks</item> </object>
<item translatable="yes">57 tracks</item> <packing>
<item translatable="yes">58 tracks</item> <property name="expand">False</property>
<item translatable="yes">59 tracks</item> <property name="fill">True</property>
<item translatable="yes">60 tracks</item> <property name="position">2</property>
<item translatable="yes">61 tracks</item> </packing>
<item translatable="yes">62 tracks</item> </child>
<item translatable="yes">63 tracks</item> <style>
<item translatable="yes">64 tracks</item> <class name="linked"/>
<item translatable="yes">65 tracks</item> </style>
<item translatable="yes">66 tracks</item>
<item translatable="yes">67 tracks</item>
<item translatable="yes">68 tracks</item>
<item translatable="yes">69 tracks</item>
<item translatable="yes">70 tracks</item>
<item translatable="yes">71 tracks</item>
<item translatable="yes">72 tracks</item>
<item translatable="yes">73 tracks</item>
<item translatable="yes">74 tracks</item>
<item translatable="yes">75 tracks</item>
<item translatable="yes">76 tracks</item>
<item translatable="yes">77 tracks</item>
<item translatable="yes">78 tracks</item>
<item translatable="yes">79 tracks</item>
<item translatable="yes">80 tracks</item>
<item translatable="yes">81 tracks</item>
<item translatable="yes">82 tracks</item>
<item translatable="yes">83 tracks</item>
<item translatable="yes">84 tracks</item>
<item translatable="yes">85 tracks</item>
<item translatable="yes">86 tracks</item>
<item translatable="yes">87 tracks</item>
<item translatable="yes">88 tracks</item>
<item translatable="yes">89 tracks</item>
<item translatable="yes">90 tracks</item>
<item translatable="yes">91 tracks</item>
<item translatable="yes">92 tracks</item>
<item translatable="yes">93 tracks</item>
<item translatable="yes">94 tracks</item>
<item translatable="yes">95 tracks</item>
<item translatable="yes">96 tracks</item>
<item translatable="yes">97 tracks</item>
<item translatable="yes">98 tracks</item>
<item translatable="yes">99 tracks</item>
</items>
<signal name="changed" handler="__gui_audio_pause_changed" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="left_attach">4</property> <property name="left_attach">3</property>
<property name="top_attach">1</property> <property name="top_attach">1</property>
<property name="height">2</property> <property name="height">2</property>
</packing> </packing>
@ -766,6 +768,8 @@ audio-volume-medium</property>
<property name="search_column">1</property> <property name="search_column">1</property>
<property name="enable_tree_lines">True</property> <property name="enable_tree_lines">True</property>
<signal name="button-press-event" handler="__gui_sidebar_button_press" swapped="no"/> <signal name="button-press-event" handler="__gui_sidebar_button_press" swapped="no"/>
<signal name="drag-data-received" handler="__gui_playlist_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="__gui_treeview_drag_drop" swapped="no"/>
<signal name="key-press-event" handler="__gui_sidebar_keypress" swapped="no"/> <signal name="key-press-event" handler="__gui_sidebar_keypress" swapped="no"/>
<signal name="row-activated" handler="__gui_playlist_row_activated" swapped="no"/> <signal name="row-activated" handler="__gui_playlist_row_activated" swapped="no"/>
<signal name="row-collapsed" handler="__gui_playlist_row_collapsed" swapped="no"/> <signal name="row-collapsed" handler="__gui_playlist_row_collapsed" swapped="no"/>
@ -830,7 +834,7 @@ audio-volume-medium</property>
</object> </object>
<packing> <packing>
<property name="resize">False</property> <property name="resize">False</property>
<property name="shrink">False</property> <property name="shrink">True</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -910,6 +914,8 @@ audio-volume-medium</property>
<property name="rubber_banding">True</property> <property name="rubber_banding">True</property>
<property name="tooltip_column">9</property> <property name="tooltip_column">9</property>
<signal name="button-press-event" handler="__gui_playlist_button_press" swapped="no"/> <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="key-press-event" handler="__gui_playlist_keypress" swapped="no"/>
<signal name="row-activated" handler="__gui_treeview_row_activated" swapped="no"/> <signal name="row-activated" handler="__gui_treeview_row_activated" swapped="no"/>
<child internal-child="selection"> <child internal-child="selection">
@ -1115,7 +1121,7 @@ audio-volume-medium</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="top_attach">3</property> <property name="top_attach">3</property>
<property name="width">5</property> <property name="width">4</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -1141,4 +1147,55 @@ audio-volume-medium</property>
<widget name="filter_how"/> <widget name="filter_how"/>
</widgets> </widgets>
</object> </object>
<object class="GtkPopover" id="pause_popover">
<property name="can_focus">False</property>
<property name="relative_to">play_button</property>
<property name="position">bottom</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_homogeneous">True</property>
<property name="column_homogeneous">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Cancel "pause after" configuration?</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_popover_no">
<property name="label" translatable="yes">No</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="__gui_audio_pause_popover_popdown" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="pause_popover_yes">
<property name="label" translatable="yes">Yes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="__gui_audio_pause_popover_clear" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface> </interface>

View File

@ -62,6 +62,7 @@ static void test_init()
g_assert_cmpuint(audio_get_volume(), ==, 100); g_assert_cmpuint(audio_get_volume(), ==, 100);
g_assert_null(audio_cur_track()); g_assert_null(audio_cur_track());
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_NULL);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpuint(load_count, ==, 0); g_assert_cmpuint(load_count, ==, 0);
g_assert_cmpuint(state_count, ==, 0); g_assert_cmpuint(state_count, ==, 0);
@ -201,20 +202,32 @@ void test_autopause()
struct playlist *history = playlist_lookup(PL_SYSTEM, "History"); struct playlist *history = playlist_lookup(PL_SYSTEM, "History");
int i; int i;
audio_pause_after(3); g_assert_true(audio_pause_after(3));
g_assert_cmpuint(pause_count, ==, 3); g_assert_cmpint(pause_count, ==, 3);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
g_assert_false(audio_pause_after(-2));
g_assert_cmpint(pause_count, ==, 3);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
pause_count = 0; pause_count = 0;
audio_pause_after(3); g_assert_false(audio_pause_after(3));
g_assert_cmpuint(pause_count, ==, 0); g_assert_cmpint(pause_count, ==, 0);
g_assert_cmpint(audio_get_pause_count(), ==, 3);
audio_pause_after(5); g_assert_true(audio_pause_after(-1));
g_assert_cmpuint(pause_count, ==, 5); g_assert_cmpint(pause_count, ==, -1);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_true(audio_pause_after(5));
g_assert_cmpint(pause_count, ==, 5);
g_assert_cmpint(audio_get_pause_count(), ==, 5);
state_count = 0; state_count = 0;
for (i = 4; i > -1; i--) { for (i = 4; i > -1; i--) {
test_audio_eos(); test_audio_eos();
g_assert_cmpuint(pause_count, ==, i); g_assert_cmpint(pause_count, ==, i);
g_assert_cmpint(audio_get_pause_count(), ==, i);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert(playlist_at(history, 0) == audio_cur_track()); g_assert(playlist_at(history, 0) == audio_cur_track());
} }
@ -223,6 +236,7 @@ void test_autopause()
test_audio_eos(); test_audio_eos();
while (idle_run_task()) {} while (idle_run_task()) {}
g_assert_cmpint(pause_count, ==, -1); g_assert_cmpint(pause_count, ==, -1);
g_assert_cmpint(audio_get_pause_count(), ==, -1);
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpuint(test_wait_state(), ==, 6); 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) static struct db_entry *int_read(struct file *f, unsigned int index)
{ {
unsigned int val; return &__int_alloc(file_readu(f))->ie_dbe;
file_readf(f, "%u", &val);
return &__int_alloc(val)->ie_dbe;
} }
static void int_write(struct file *file, struct db_entry *dbe) static void int_write(struct file *file, struct db_entry *dbe)
@ -67,8 +65,8 @@ static const struct db_ops int_ops = {
static void test_db_entry() static void test_db_entry()
{ {
struct file f = FILE_INIT_DATA("", "test_db_entry", 0);
struct int_entry *ent; struct int_entry *ent;
struct file f;
ent = INT_ENTRY(int_ops.dbe_alloc("1", 0)); ent = INT_ENTRY(int_ops.dbe_alloc("1", 0));
g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0); g_assert_cmpuint(ent->ie_dbe.dbe_index, ==, 0);
@ -76,7 +74,6 @@ static void test_db_entry()
g_assert_cmpuint(ent->ie_val, ==, 1); g_assert_cmpuint(ent->ie_val, ==, 1);
g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1"); g_assert_cmpstr_free(int_ops.dbe_key(&ent->ie_dbe), ==, "1");
file_init(&f, "test_db_entry", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
int_ops.dbe_write(&f, &ent->ie_dbe); int_ops.dbe_write(&f, &ent->ie_dbe);
file_close(&f); file_close(&f);

View File

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

View File

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

View File

@ -34,6 +34,7 @@ static struct playlist_ops test_ops = {
.pl_remove = playlist_generic_remove, .pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random, .pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort, .pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
}; };
static struct playlist_callbacks test_cb = { static struct playlist_callbacks test_cb = {
.pl_cb_alloc = test_pl_alloc, .pl_cb_alloc = test_pl_alloc,
@ -49,8 +50,7 @@ static void test_null()
g_assert_false(playlist_delete(NULL)); g_assert_false(playlist_delete(NULL));
playlist_generic_free(NULL); playlist_generic_free(NULL);
playlist_generic_init(NULL); playlist_generic_init(NULL, 0);
playlist_generic_init_sorted(NULL);
playlist_generic_deinit(NULL); playlist_generic_deinit(NULL);
g_assert_null(playlist_lookup(PL_MAX_TYPE, "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)); g_assert_false(playlist_sort(NULL, COMPARE_TRACK));
playlist_generic_resort(NULL); playlist_generic_resort(NULL);
playlist_clear_sort(NULL); playlist_clear_sort(NULL);
g_assert_false(playlist_rearrange(NULL, 0, 0));
playlist_set_search(NULL, NULL); playlist_set_search(NULL, NULL);
@ -110,9 +111,10 @@ static void test_playlist()
int i; int i;
g_assert_cmpuint(playlist_size(&p), ==, 0); 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(playlist_size(&p), ==, 0);
g_assert_cmpuint(p.pl_length, ==, 0); g_assert_cmpuint(p.pl_length, ==, 0);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
for (i = 0; i < 13; i++) { for (i = 0; i < 13; i++) {
ex_length += track_get(i)->tr_length; ex_length += track_get(i)->tr_length;
@ -202,8 +204,9 @@ static void test_sorting()
struct track *track; struct track *track;
unsigned int i; unsigned int i;
playlist_generic_init_sorted(&p); playlist_generic_init(&p, 4, COMPARE_ARTIST, COMPARE_YEAR,
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 3); COMPARE_ALBUM, COMPARE_TRACK);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 4);
playlist_clear_sort(&p); playlist_clear_sort(&p);
g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0); g_assert_cmpuint(g_slist_length(p.pl_sort), ==, 0);
@ -279,6 +282,43 @@ static void test_sorting()
g_assert_null(p.pl_sort); 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() static void test_next()
{ {
struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops); struct playlist p = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops);
@ -286,7 +326,7 @@ static void test_next()
unsigned int i; unsigned int i;
g_random_set_seed(0); g_random_set_seed(0);
playlist_generic_init(&p); playlist_generic_init(&p, 0);
for (i = 0; i < 13; i++) for (i = 0; i < 13; i++)
playlist_generic_add(&p, track_get(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 q = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test 2", 0, NULL);
struct playlist r = DEFINE_PLAYLIST(PL_MAX_TYPE, "Test", 0, &test_ops); 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 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; unsigned int i;
for (i = 0; i < 13; 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/Playlist/NULL", test_null);
g_test_add_func("/Core/Playlists/General", test_playlist); g_test_add_func("/Core/Playlists/General", test_playlist);
g_test_add_func("/Core/Playlists/Sorting", test_sorting); 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/Playlists/Next Track", test_next);
g_test_add_func("/Core/Playlist/Save and Load", test_save_load); g_test_add_func("/Core/Playlist/Save and Load", test_save_load);
ret = g_test_run(); ret = g_test_run();

View File

@ -75,8 +75,8 @@ void test_library()
playlist_set_random(playlist, false); playlist_set_random(playlist, false);
g_assert_false(playlist->pl_random); g_assert_false(playlist->pl_random);
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 3); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 4);
playlist_clear_sort(playlist); g_assert_true(playlist_rearrange(playlist, 15, 20));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
g_assert_true(playlist_sort(playlist, COMPARE_ARTIST)); g_assert_true(playlist_sort(playlist, COMPARE_ARTIST));
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 1); 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) { if (i == SYS_PL_QUEUED || i == SYS_PL_HISTORY) {
g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0); g_assert_cmpuint(g_slist_length(playlist->pl_sort), ==, 0);
} else } 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. */ /* Add tracks to the collection. */

View File

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

View File

@ -46,7 +46,7 @@ static void test_album()
struct album *album; struct album *album;
struct artist *koji; struct artist *koji;
struct genre *genre; struct genre *genre;
struct file f; struct file f = FILE_INIT_DATA("", "album_tag", 1);
idle_init(IDLE_SYNC); idle_init(IDLE_SYNC);
@ -58,7 +58,6 @@ static void test_album()
g_assert_true( album_match_token(album, "symphony")); g_assert_true( album_match_token(album, "symphony"));
g_assert_false(album_match_token(album, "skyward")); g_assert_false(album_match_token(album, "skyward"));
file_init(&f, "album_tag", 1);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "0 0 0 \n"); file_writef(&f, "0 0 0 \n");
album_ops->dbe_write(&f, &album->al_dbe); album_ops->dbe_write(&f, &album->al_dbe);

View File

@ -28,10 +28,9 @@ static void test_verify_koji(struct artist *artist)
static void test_artist() static void test_artist()
{ {
struct file f = FILE_INIT_DATA("", "artist_tag", 0);
const struct db_ops *artist_ops = test_artist_ops(); const struct db_ops *artist_ops = test_artist_ops();
struct artist *artist; struct artist *artist;
unsigned int i;
struct file f;
artist = ARTIST(artist_ops->dbe_alloc("Koji Kondo", 0)); 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_true( artist_match_token(artist, "kondo"));
g_assert_false(artist_match_token(artist, "hajime")); g_assert_false(artist_match_token(artist, "hajime"));
file_init(&f, "artist_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n2 "); file_writef(&f, "1 \n2 ");
artist_ops->dbe_write(&f, &artist->ar_dbe); artist_ops->dbe_write(&f, &artist->ar_dbe);
@ -49,13 +47,13 @@ static void test_artist()
artist_ops->dbe_free(&artist->ar_dbe); artist_ops->dbe_free(&artist->ar_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i); file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f, 0)); artist = ARTIST(artist_ops->dbe_read(&f, 0));
test_verify_empty(artist); test_verify_empty(artist);
g_free(artist->ar_name); g_free(artist->ar_name);
artist_ops->dbe_free(&artist->ar_dbe); artist_ops->dbe_free(&artist->ar_dbe);
file_readf(&f, "%u", &i); file_readu(&f);
artist = ARTIST(artist_ops->dbe_read(&f, 0)); artist = ARTIST(artist_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_koji(artist); test_verify_koji(artist);

View File

@ -27,10 +27,9 @@ static void test_verify_vg(struct genre *genre)
static void test_genre() static void test_genre()
{ {
struct file f = FILE_INIT_DATA("", "genre_tag", 0);
const struct db_ops *genre_ops = test_genre_ops(); const struct db_ops *genre_ops = test_genre_ops();
struct genre *genre; struct genre *genre;
unsigned int i;
struct file f;
genre = GENRE(genre_ops->dbe_alloc("Video Game Music", 0)); genre = GENRE(genre_ops->dbe_alloc("Video Game Music", 0));
test_verify_vg(genre); test_verify_vg(genre);
@ -38,7 +37,6 @@ static void test_genre()
g_assert_true( genre_match_token(genre, "music")); g_assert_true( genre_match_token(genre, "music"));
g_assert_false(genre_match_token(genre, "rock")); g_assert_false(genre_match_token(genre, "rock"));
file_init(&f, "genre_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
file_writef(&f, "1 \n1 "); file_writef(&f, "1 \n1 ");
genre_ops->dbe_write(&f, &genre->ge_dbe); genre_ops->dbe_write(&f, &genre->ge_dbe);
@ -47,13 +45,13 @@ static void test_genre()
genre_ops->dbe_free(&genre->ge_dbe); genre_ops->dbe_free(&genre->ge_dbe);
file_open(&f, OPEN_READ); file_open(&f, OPEN_READ);
file_readf(&f, "%u", &i); file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f, 0)); genre = GENRE(genre_ops->dbe_read(&f, 0));
test_verify_empty(genre); test_verify_empty(genre);
g_free(genre->ge_name); g_free(genre->ge_name);
genre_ops->dbe_free(&genre->ge_dbe); genre_ops->dbe_free(&genre->ge_dbe);
file_readf(&f, "%u", &i); file_readu(&f);
genre = GENRE(genre_ops->dbe_read(&f, 0)); genre = GENRE(genre_ops->dbe_read(&f, 0));
file_close(&f); file_close(&f);
test_verify_vg(genre); test_verify_vg(genre);

View File

@ -20,9 +20,9 @@ static void test_verify_link(struct library *library)
static void test_library() static void test_library()
{ {
struct file f = FILE_INIT_DATA("", "library_tag", 0);
const struct db_ops *library_ops = test_library_ops(); const struct db_ops *library_ops = test_library_ops();
struct library *link, *zelda, *library; struct library *link, *zelda, *library;
struct file f;
link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music", 0)); link = LIBRARY(library_ops->dbe_alloc("/home/Link/Music", 0));
zelda = LIBRARY(library_ops->dbe_alloc("/home/Zelda/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_link(link);
test_verify_zelda(zelda); test_verify_zelda(zelda);
file_init(&f, "library_tag", 0);
file_open(&f, OPEN_WRITE); file_open(&f, OPEN_WRITE);
library_ops->dbe_write(&f, &link->li_dbe); library_ops->dbe_write(&f, &link->li_dbe);
file_writef(&f, "\n"); file_writef(&f, "\n");

View File

@ -83,10 +83,9 @@ static void test_track()
time_t rawtime = time(NULL); time_t rawtime = time(NULL);
struct tm *now = localtime(&rawtime); struct tm *now = localtime(&rawtime);
struct track *track; struct track *track;
struct file f; struct file f = FILE_INIT_DATA("", "track_tag", 1);
gchar *date; gchar *date;
file_init(&f, "track_tag", 1);
g_assert_nonnull(library_find("tests/Music")); g_assert_nonnull(library_find("tests/Music"));
date = string_tm2str(now); date = string_tm2str(now);

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_title_tag()), ==, " ");
g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:00"); 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_label_get_text(gui_duration()), ==, "0:00");
g_assert_cmpstr(gtk_combo_box_text_get_active_text(gui_pause_after()), g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Paused");
==, "(disabled)"); g_assert_true(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_up())));
g_assert_cmpuint(gtk_combo_box_get_active( g_assert_false(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
GTK_COMBO_BOX(gui_pause_after())), ==, 0);
g_assert_cmpfloat(gtk_scale_button_get_value(gui_volume_button()), g_assert_cmpfloat(gtk_scale_button_get_value(gui_volume_button()),
==, 100); ==, 100);
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_play_button()))); 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_button())));
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_pause_popover())));
} }
static void test_audio_load() 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_duration()), ==, length);
g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:00"); g_assert_cmpstr(gtk_label_get_text(gui_position()), ==, "0:00");
test_main_loop();
test_main_loop(); test_main_loop();
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_play_button()))); 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_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_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_true( gtk_widget_is_visible(GTK_WIDGET(gui_play_button()))); 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_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()); gtk_button_clicked(gui_play_button());
test_main_loop(); test_main_loop();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_false(gtk_widget_is_visible(GTK_WIDGET(gui_play_button()))); 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_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()); gtk_button_clicked(gui_next_button());
if (track_get(0)->tr_track == 1) if (track_get(0)->tr_track == 1)
@ -79,16 +84,72 @@ static void test_audio_buttons()
gtk_button_clicked(gui_prev_button()); gtk_button_clicked(gui_prev_button());
g_assert(audio_cur_track() == track_get(0)); 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(); test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PLAYING);
g_assert_cmpuint(gtk_combo_box_get_active( g_assert_cmpstr(gtk_entry_get_text(gui_pause_entry()), ==, "Pause after next track");
GTK_COMBO_BOX(gui_pause_after())), ==, 1); 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(); test_audio_eos();
g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED); g_assert_cmpuint(audio_cur_state(), ==, GST_STATE_PAUSED);
g_assert_cmpuint(gtk_combo_box_get_active( g_assert_false(gtk_widget_get_sensitive(GTK_WIDGET(gui_pause_down())));
GTK_COMBO_BOX(gui_pause_after())), ==, 0); 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); gtk_scale_button_set_value(gui_volume_button(), 50);
g_assert_cmpuint(audio_get_volume(), ==, 50); g_assert_cmpuint(audio_get_volume(), ==, 50);

View File

@ -102,9 +102,9 @@ static void test_sidebar_selection()
struct playlist *collection; struct playlist *collection;
GtkTreeSelection *selection; GtkTreeSelection *selection;
GtkToggleButton *random; GtkToggleButton *random;
GtkTreeIter iter, cur;
GtkTreeModel *filter; GtkTreeModel *filter;
GtkTreePath *path; GtkTreePath *path;
GtkTreeIter iter;
unsigned int i, n; unsigned int i, n;
collection = playlist_lookup(PL_SYSTEM, "Collection"); collection = playlist_lookup(PL_SYSTEM, "Collection");
@ -136,6 +136,10 @@ static void test_sidebar_selection()
g_assert_true(gtk_widget_get_sensitive( g_assert_true(gtk_widget_get_sensitive(
GTK_WIDGET(random))); 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); gtk_toggle_button_set_active(random, false);
g_assert_false(collection->pl_random); g_assert_false(collection->pl_random);
gtk_toggle_button_set_active(random, true); gtk_toggle_button_set_active(random, true);
@ -143,6 +147,11 @@ static void test_sidebar_selection()
} else if (i == 1) { } else if (i == 1) {
g_assert(gui_model_get_playlist() == g_assert(gui_model_get_playlist() ==
playlist_lookup(PL_SYSTEM, "History")); playlist_lookup(PL_SYSTEM, "History"));
g_assert_true(gui_sidebar_iter_current(&cur));
g_assert_true(gui_sidebar_iter_playlist(&cur) ==
playlist_lookup(PL_SYSTEM, "History"));
g_assert_false(gtk_toggle_button_get_active(random)); g_assert_false(gtk_toggle_button_get_active(random));
g_assert_false(gtk_widget_get_sensitive( g_assert_false(gtk_widget_get_sensitive(
GTK_WIDGET(random))); GTK_WIDGET(random)));

View File

@ -65,7 +65,7 @@ void test_treeview_select()
GList *list; GList *list;
unsigned int i; unsigned int i;
selection = gtk_tree_view_get_selection(gui_treeview()); selection = gui_treeview_selection();
gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "Collection")); gui_treeview_set_playlist(playlist_lookup(PL_SYSTEM, "Collection"));
g_assert(gui_model_get_playlist() == g_assert(gui_model_get_playlist() ==
@ -112,6 +112,7 @@ void test_treeview_sort()
break; break;
case GUI_MODEL_ARTIST: case GUI_MODEL_ARTIST:
case GUI_MODEL_YEAR: case GUI_MODEL_YEAR:
case GUI_MODEL_ALBUM:
case GUI_MODEL_TRACK_NR: case GUI_MODEL_TRACK_NR:
g_assert_true( g_assert_true(
gtk_tree_view_column_get_sort_indicator(col)); gtk_tree_view_column_get_sort_indicator(col));