Compare commits

..

No commits in common. "next" and "v6.4.14-rc" have entirely different histories.

194 changed files with 9980 additions and 12439 deletions

7
.gitignore vendored
View File

@ -13,10 +13,3 @@ bin/
*.gcno
core.*
.debug
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
Testing/

View File

@ -6,9 +6,9 @@ stages:
build:
stage: build
script:
- cmake -DCONFIG_DEBUG=ON && make
- scons
test:
stage: test
script:
- cmake -DCONFIG_TESTING_VERBOSE=ON -DCONFIG_TESTING_GUI=OFF && make tests
- scons tests/core

View File

@ -1,101 +1,3 @@
6.5.0:
- Add a switch to disable GUI tests
- Don't save and restore the "Last Played" tracks column width
- Save databases if they have been upgraded
- Don't duplicate tracks in the library after a defrag
- Remove "Add New ..." menubutton and replace with Add Library Path button
- Various UI tweaks
- Fix showing the first playlist in the list
- Fix memory leaks in core/
- Fix theme parsing errors
- Fix crash while reading 6.4.x playlists
6.5.0-rc:
- Convert to CMake/CTest
- Save current track in settings database
- Add "Queued Tracks" playlist
- Add support for changing current playlist
- Hide empty playlists
- Save current position in playlists
- Set file version from Ocarina minor version
- Add support for defragmenting tag databases on startup
- Add user created playlists
- Add support for changing volume
- Remove Repeat button
- Various UI improvements
- Add command line options
6.4.20:
- Enable idle processing when tracks are played to update dynamic playlists
6.4.20-rc:
- Use a single GtkTreeModelFilter instance for displaying tracks
- Use a single GuiQueueModel instance for displaying tracks
- Reduce use of database entry index accesses
- Reduce the size of the track tag
6.4.19:
- Fix memory corruption when adding new library playlists
- Use Enter key to accept new library paths
- Remove deleted tracks from artist playlists
- File chooser selects user's music directory
- Remove deleted tracks from artist playlists
6.4.19-rc:
- Allow filtering specific fields
- Replace filtering with a token matching system
- Clean up system playlist code
6.4.18:
- Load each artist playlist in a separate idle task
- UI spacing improvements
6.4.18-rc:
- Remove stack widget from UI
- Add Library Path playlists
- Add Artist playlists
6.4.17:
- Fix memory leak in __playlist_name()
- Remember search text when changing displayed playlist
6.4.17-rc:
- Filter can use GHashTables directly
- Filter can store track pointers instead of indexes
- Remove unused set and index classes
- Convert Collection into a playlist
- Convert History into a playlist
6.4.16:
- Let tests run without fetching album art
- Don't try to read data from nonexistant playlists
- Show hidden folders when selecting new images
- Add preview widget to album art chooser dialog
- Fix blurry album art
- Reduce time spent polling for album art
- Split album art code into a new file
- Fix PKGBUILD dependencies
6.4.16-rc:
- Implement generic playlist layer
- Implement separate queues for each playlist, including dynamic playlists
- Remove core/containers/queue
6.4.15:
- Add idle progress as a tooltip.
- Initialize track labels with the correct initial size
- Initialize album part with the correct size
- Gui spacing updates
6.4.15-rc:
- Write data files to a temporary file first, then rename
- Add support for writing files to the user's XDG_USER_CACHE_DIR
- Add idle tasks that run in a new thread
- Fetch album art
6.4.14:
- GtkPaned spacing improvements
6.4.14-rc:
- Load files and databases using the idle queue
- Switch over to using the glib g_random_int_range() function

View File

@ -1,84 +0,0 @@
cmake_minimum_required(VERSION 3.4)
project(Ocarina)
# Various options for users
option(CONFIG_DEBUG "Compile with debugging symbols" OFF)
option(CONFIG_TESTING_VERBOSE "Enable verbose output when running tests" OFF)
option(CONFIG_TESTING_GUI "Enable running GUI tests (requires an X server)" ON)
option(CONFIG_TESTING_ARTWORK "Enable album artwork fetching tests" ON)
# Configure settings
set(CONFIG_MAJOR 6)
set(CONFIG_MINOR 5)
set(CONFIG_MICRO 10)
set(CONFIG_RC ON)
set(CONFIG_VERSION "${CONFIG_MAJOR}.${CONFIG_MINOR}.${CONFIG_MICRO}")
if (CONFIG_RC)
set(CONFIG_VERSION "${CONFIG_VERSION}-rc")
endif()
set(DEBUG_OPTIONS -Wall -Werror -g -DCONFIG_DEBUG)
if (CONFIG_DEBUG)
add_definitions(${DEBUG_OPTIONS})
set(CONFIG_VERSION "${CONFIG_VERSION}-debug")
endif()
find_package(Git)
if (GIT_FOUND AND IS_DIRECTORY .git/)
configure_file(.git/HEAD .git/HEAD COPYONLY)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --abbrev=0
COMMAND xargs ${GIT_EXECUTABLE} diff --quiet
RESULT_VARIABLE HAS_CHANGED)
if (HAS_CHANGED AND CONFIG_RC)
set(CONFIG_VERSION "${CONFIG_VERSION}+")
endif()
endif()
add_definitions(-DCONFIG_VERSION=\"${CONFIG_VERSION}\")
add_definitions(-DCONFIG_MAJOR=${CONFIG_MAJOR})
add_definitions(-DCONFIG_MINOR=${CONFIG_MINOR})
find_package(PkgConfig REQUIRED)
function(use_module name module)
pkg_check_modules(${name} REQUIRED ${module})
include_directories(${${name}_INCLUDE_DIRS})
add_definitions(${${name}_CFLAGS} ${${name}_CFLAGS_OTHERS})
link_libraries(${${name}_LIBRARIES} ${${name}_LDFLAGS})
endfunction()
use_module(GTK gtk+-3.0)
use_module(GMODULE gmodule-export-2.0)
use_module(GST gstreamer-1.0)
use_module(TAGLIB taglib_c)
use_module(CAA libcoverart)
use_module(MB5 libmusicbrainz5)
# Tell compiler where to find header and source files
include_directories(include)
file(GLOB_RECURSE core "core/*.c")
file(GLOB_RECURSE gui "gui/*.c")
# Configure executable
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin/)
add_executable(ocarina ${core} ${gui})
# Configure install targets
install(TARGETS ocarina DESTINATION /usr/bin/)
install(DIRECTORY share/ DESTINATION /usr/share/)
# Configure release target
set(CONFIG_RELEASE ocarina-${CONFIG_VERSION})
add_custom_target(release COMMAND git archive -v --prefix=${CONFIG_RELEASE}/ -o ${CONFIG_RELEASE}.tar.gz HEAD)
# Set up unit tests
set(CMAKE_TEST_COMMAND ctest --output-on-failure)
if (CONFIG_TESTING_VERBOSE)
set(CMAKE_TEST_COMMAND ctest -V)
endif()
if (IS_DIRECTORY tests/)
enable_testing()
add_custom_target(tests COMMAND ${CMAKE_TEST_COMMAND})
add_subdirectory(tests/)
endif()

View File

@ -1 +0,0 @@
set(CTEST_CUSTOM_PRE_TEST "rm -rf $ENV{HOME}/.local/share/ocarina-test $ENV{HOME}/.cache/ocarina-test")

View File

@ -1,24 +1,23 @@
# Maintainer: Anna Schumaker <anna@nowheycreamery.com>
# Maintainer: Anna Schumaker <anna@ocarinaproject.net>
pkgname=ocarina
pkgver=6.5.9
pkgver=6.4.13
pkgrel=1
pkgdesc="A simple GTK+ and GStreamer based music player."
url="http://www.nowheycreamery.com/"
pkgdesc="A simple GTK+ and gstreamer based music player."
url="http://www.ocarinaproject.net/"
arch=('x86_64' 'i686' 'armv7h')
license=('GPL2')
depends=('gtk3>=3.22' 'gstreamer' 'gst-plugins-base' 'taglib' 'libmusicbrainz5' 'libcoverart')
depends=('gtkmm3>=3.16' 'gstreamer' 'gst-plugins-base' 'taglib')
optdepends=('gst-plugins-good' 'gst-plugins-bad' 'gst-plugins-ugly')
makedepends=('cmake')
makedepends=('scons')
conflicts=()
replaces=()
backup=()
source=("http://nowheycreamery.com/wp-content/ocarina/${pkgname}-${pkgver}.tar.gz")
source=("http://ocarinaproject.net/wp-content/ocarina/${pkgname}-${pkgver}.tar.gz")
sha1sums=('')
build() {
cd "${srcdir}/${pkgname}-${pkgver}"
cmake .
make $MAKEFLAGS
scons $MAKEFLAGS
}
package() {

20
README Normal file
View File

@ -0,0 +1,20 @@
Alternate clone url:
$ git clone git://git.ocarinaproject.net/anna/ocarina.git
Build:
$ scons
Clean:
$ scons -c
Install:
$ sudo scons install
Uninstall:
$ sudo scons -c install
Build tests:
$ scons tests
Clean tests:
$ scons -c tests

View File

@ -1,53 +0,0 @@
# Ocarina 6.5
Ocarina is a simple GTK+ and GStreamer based music player written to let you listen to your music without getting in the way.
### Git
Ocarina is tracked with Git, and can be cloned from the following sources:
* http://git.nowheycreamery.com/anna/ocarina.git
* git://git.nowheycreamery.com/anna/ocarina.git
##### Branches
* [master]
* The most recent release. This branch does not change frequently.
* [next]
* Changes that will be included in the next release. This branch is for testing and bugfixing.
### Building
Ocarina uses `cmake`to control the build process. After cloning the code, run `cmake .` to generate the Makefile. Compile using `make`
##### CMake Options
Ocarina supports the following options, which can be passed to `cmake` through `cmake -D<option>=<ON|OFF>`. For example, `cmake -DCONFIG_DEBUG=ON` would enable debugging.
* CONFIG_DEBUG
* Compile with debugging symbols enabled
* CONFIG_TESTING_VERBOSE
* Enable extra output when running unit tests
* CONFIG_TESTING_GUI
* Enable running GUI unit tests
* CONFIG_TESTING_ARTWORK
* Enable unit tests that fetch album artwork
### Running
Once compiled, Ocarina can be run by invoking `bin/ocarina` on the command line.
##### Runtime options
The following options can be passed to Ocarina during startup
* -h, --help
* Print help message and exit
* -n, --next
* Tell a running Ocarina instance to play the next track
* - P, --pause
* Tell a running Ocarina instance to pause playback
* -p, --play
* Tell a running Ocarina instance to start playback
* -N, --previous
* Tell a running Ocarina instance to play the previous track
* -t, --toggle
* Tell a paused Ocarina instance to begin playback, or tell a playing Ocarina instance to pause.
* -v, --version
* Print Ocarina version and exit
### Installing
Running `make install` will install Ocarina for use by all users. There is currently no "uninstall" option.
### Testing
Running `make tests` will compile and run the Ocarina unit tests. See the CMake Options section above for additional testing options.

59
Sconstruct Normal file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python
import os
CONFIG_VERSION = "6.4.14-rc"
# Set up default environment
CONFIG_DEBUG = os.path.exists(".debug")
CONFIG_CCFLAGS = [ "-O2" ]
if CONFIG_DEBUG == True:
CONFIG_CCFLAGS = [ "-Wall", "-Werror", "-g", "-DCONFIG_DEBUG" ]
class OEnvironment(Environment):
def __init__(self, CCFLAGS = CONFIG_CCFLAGS):
Environment.__init__(self, CCFLAGS = CCFLAGS)
self.Append(CXXFLAGS = "-std=c++11");
self.Append(CPPPATH = os.path.abspath("include"))
self.Append(CXXCOMSTR = "C++ $TARGET")
self.Append(CCCOMSTR = "CC $TARGET")
self.Append(LINKCOMSTR = "Linking $TARGET")
if "DISPLAY" in os.environ.keys():
self.Append(ENV = { "DISPLAY" : os.environ["DISPLAY"] })
self.Debug = CONFIG_DEBUG
self.Version = CONFIG_VERSION
def UsePackage(self, name):
self.ParseConfig("pkg-config --cflags --libs %s" % name)
env = OEnvironment()
Export("env")
include = SConscript("include/Sconscript")
core = SConscript("core/Sconscript")
gui = SConscript("gui/Sconscript")
if os.path.isdir("tests"):
tests = SConscript("tests/Sconscript")
ocarina = env.Program("bin/ocarina", core + gui)
Default(ocarina)
Clean(ocarina, "bin/")
def ocarina_release(target, source, env):
o_vers = "ocarina-%s" % CONFIG_VERSION
os.popen("git archive --prefix=%s/ -o %s.tar.gz HEAD" % (o_vers, o_vers))
print os.popen("sha1sum %s.tar.gz" % o_vers).read()
Command("release", None, ocarina_release)
env.Install("/usr/bin", "bin/ocarina")
env.Install("/usr/share", "share/ocarina/")
env.Install("/usr/share/applications", "share/applications/ocarina.desktop")
install = Alias("install", [ "/usr/bin", "/usr/share", "/usr/share/applications" ])
Depends(install, ocarina)
Clean(install, "/usr/share/ocarina")

23
TODO
View File

@ -4,6 +4,23 @@ Future work:
to the wild. This section will be a list of features that I want, but
should be deferred to a future release so basic support can be coded.
- User created song groups:
Basic add and remove features can be implemented using the
Library and Banned Songs groups. This will give me a chance
to test saving groups on a small scale before letting users
create anything they want.
- Save a user's playlist as a group:
- Library defragment:
Ocarina 6.0 will leave holes in the library when tracks are
deleted, potentially leading to fragmentation and larger-than-
needed file sizes. A "defragment" utility can be created to
clean up unused slots.
To help with fixing groups, a mapping of (old values) ->
(new values) should be kept.
- Fix track durations:
Some tracks in my library are tagged with the wrong duration,
so fix them as they are played.
@ -12,6 +29,10 @@ Future work:
Make a pop-up window for editing the tags of a track. Be sure
to update the library information and the on-disk file.
- Album art:
(easy) Start with album art fetching script
(hard) Build in to Ocarina
- Copy a song group to a different directory:
This can be useful for keeping an external device (like an
Android phone) updated with the music you want to listen to.
@ -34,7 +55,9 @@ Future work:
Store in library :: Track structure
- "About" dialog
- "On demand" databases that load the first time they are accessed?
- Ports:
- OSX
- Windows
- Android
- Command line tools for web interface development?

9
core/Sconscript Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/python
Import("env")
res = Glob("*.c")
res += SConscript("tags/Sconscript")
res += SConscript("containers/Sconscript")
env.UsePackage("gstreamer-1.0")
Return("res")

View File

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

283
core/collection.c Normal file
View File

@ -0,0 +1,283 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/collection.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <glib.h>
#include <stdlib.h>
#include <unistd.h>
static struct file c_file = FILE_INIT("library.q", 0);
static struct queue c_queue;
struct scan_data {
struct library *sd_lib;
gchar *sd_path;
};
static void __scan_dir(void *);
#ifdef CONFIG_TESTING
bool test_collection_error = false;
static inline int __g_access(const gchar *path)
{
if (test_collection_error)
return -1;
return g_access(path, F_OK);
}
#else
#define __g_access(path) g_access(path, F_OK)
#endif /* CONFIG_TESTING */
static void __scan_dir_later(struct library *library, const gchar *dir)
{
struct scan_data *data = g_malloc(sizeof(struct scan_data));
data->sd_lib = library;
data->sd_path = g_strdup(dir);
/* data is freed by __scan_dir() */
idle_schedule(__scan_dir, data);
}
static void __scan_path(struct scan_data *scan, const gchar *name)
{
gchar *path = g_strdup_printf("%s/%s", scan->sd_path, name);
struct track *track;
if (g_file_test(path, G_FILE_TEST_IS_DIR))
__scan_dir_later(scan->sd_lib, path);
else {
track = track_add(scan->sd_lib, path);
if (track)
queue_add(&c_queue, track);
}
g_free(path);
}
static void __scan_dir(void *data)
{
struct scan_data *scan = data;
const char *name;
GDir *dir;
dir = g_dir_open(scan->sd_path, 0, NULL);
if (dir == NULL)
goto out;
name = g_dir_read_name(dir);
while (name != NULL) {
__scan_path(scan, name);
name = g_dir_read_name(dir);
}
g_dir_close(dir);
track_db_commit();
out:
/* Allocated by __scan_dir_later() */
g_free(scan->sd_path);
g_free(scan);
}
static void __validate_library(void *data)
{
struct library *library = data;
struct db_entry *dbe, *next;
struct track *track;
int access;
gchar *path;
db_for_each(dbe, next, track_db_get()) {
track = TRACK(dbe);
if (track->tr_library != library)
continue;
path = track_path(track);
access = __g_access(path);
g_free(path);
if (access < 0) {
if (collection_check_library(library) < 0)
return;
queue_remove_all(&c_queue, track);
track_remove(track);
}
}
}
void __collection_init_idle(void *data)
{
struct db_entry *track, *next;
unsigned int i, n = 0;
int field, ascending;
db_for_each(track, next, track_db_get()) {
if (TRACK(track)->tr_library->li_enabled &&
!playlist_has(PL_HIDDEN, TRACK(track)))
queue_add(&c_queue, TRACK(track));
}
queue_unset_flag(&c_queue, Q_ADD_FRONT);
if (file_open(&c_file, OPEN_READ))
file_readf(&c_file, "%u %u", &c_queue.q_flags, &n);
for (i = 0; i < n; i++) {
file_readf(&c_file, "%u %d", &field, &ascending);
queue_sort(&c_queue, field + 1, (i == 0) ? true : false);
if (ascending == false)
queue_sort(&c_queue, field + 1, false);
}
file_close(&c_file);
if (!c_queue.q_sort) {
queue_sort(&c_queue, COMPARE_ARTIST, true);
queue_sort(&c_queue, COMPARE_YEAR, false);
queue_sort(&c_queue, COMPARE_TRACK, false);
}
queue_set_flag(&c_queue, Q_SAVE_SORT);
queue_set_flag(&c_queue, Q_SAVE_FLAGS);
collection_update_all();
}
/*
* External API begins here
*/
void collection_init(struct queue_ops *ops)
{
queue_init(&c_queue, Q_ENABLED | Q_REPEAT | Q_ADD_FRONT, ops);
idle_schedule(__collection_init_idle, NULL);
}
void collection_deinit()
{
queue_deinit(&c_queue);
}
void collection_save(struct queue *queue, enum queue_flags flag)
{
GSList *cur = c_queue.q_sort;
int field;
if (&c_queue != queue)
return;
if (!file_open(&c_file, OPEN_WRITE))
return;
file_writef(&c_file, "%u %u", c_queue.q_flags, g_slist_length(cur));
while (cur) {
field = GPOINTER_TO_INT(cur->data);
file_writef(&c_file, " %u %d", abs(field) - 1, field > 0);
cur = g_slist_next(cur);
}
file_writef(&c_file, "\n");
file_close(&c_file);
}
struct library *collection_add(const gchar *path)
{
struct library *library = NULL;
if (g_file_test(path, G_FILE_TEST_IS_DIR) == false)
return library;
library = library_find(path);
if (library)
collection_update(library);
return library;
}
void collection_remove(struct library *library)
{
if (library) {
collection_set_enabled(library, false);
track_remove_all(library);
library_remove(library);
}
}
void collection_update(struct library *library)
{
if (!library)
return;
idle_schedule(__validate_library, library);
__scan_dir_later(library, library->li_path);
}
void collection_update_all()
{
struct db_entry *library, *next;
db_for_each(library, next, library_db_get())
collection_update(LIBRARY(library));
}
bool collection_ban(struct track *track)
{
bool ret = playlist_add(PL_HIDDEN, track);
if (ret)
queue_remove_all(&c_queue, track);
return ret;
}
bool collection_unban(struct track *track)
{
bool ret = playlist_remove(PL_HIDDEN, track);
if (ret)
queue_add(&c_queue, track);
return ret;
}
void collection_set_enabled(struct library *library, bool enabled)
{
struct db_entry *dbe, *next;
struct track *track;
if (!library || (library->li_enabled == enabled))
return;
if (enabled && (collection_check_library(library) < 0))
return;
library_set_enabled(library, enabled);
queue_set_flag(&c_queue, Q_ADD_FRONT);
db_for_each(dbe, next, track_db_get()) {
track = TRACK(dbe);
if (track->tr_library == library) {
if (enabled) {
if (!playlist_has(PL_HIDDEN, track))
queue_add(&c_queue, track);
} else
queue_remove_all(&c_queue, track);
}
}
queue_unset_flag(&c_queue, Q_ADD_FRONT);
queue_resort(&c_queue);
}
int collection_check_library(struct library *library)
{
int ret = __g_access(library->li_path);
if (ret < 0) {
g_warning("Can't access library at %s/\n", library->li_path);
collection_set_enabled(library, false);
}
return ret;
}
struct queue *collection_get_queue()
{
return &c_queue;
}

View File

@ -0,0 +1,3 @@
#!/usr/bin/python
res = Glob("*.c")
Return("res")

View File

@ -1,7 +1,7 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/database.h>
#include <core/containers/database.h>
#include <core/idle.h>
#define DB_ENTRY_AT(db, index) \
@ -36,9 +36,11 @@ static struct db_entry *__dbe_next(const struct database *db, unsigned int index
static struct db_entry *__dbe_read(struct database *db, unsigned int index)
{
struct db_entry *dbe = NULL;
int valid;
if (file_readd(&db->db_file))
dbe = db->db_ops->dbe_read(&db->db_file, index);
file_readf(&db->db_file, "%d", &valid);
if (valid)
dbe = db->db_ops->dbe_read(&db->db_file);
g_ptr_array_index(db->db_entries, index) = dbe;
return dbe;
@ -53,6 +55,8 @@ static void __dbe_setup(struct database *db, unsigned int index)
dbe->dbe_key = db->db_ops->dbe_key(dbe);
g_hash_table_insert(db->db_keys, dbe->dbe_key, dbe);
db->db_size++;
if (db->db_ops->dbe_setup)
db->db_ops->dbe_setup(dbe);
}
}
@ -67,14 +71,14 @@ static void __dbe_write(struct database *db, struct db_entry *dbe)
}
void db_init(struct database *db, const char *filepath, bool autosave,
const struct db_ops *ops, unsigned int fmin)
const struct db_ops *ops)
{
db->db_ops = ops;
db->db_size = 0;
db->db_autosave = autosave;
db->db_entries = g_ptr_array_new();
db->db_keys = g_hash_table_new(g_str_hash, g_str_equal);
file_init_data(&db->db_file, "", filepath, fmin);
file_init(&db->db_file, filepath, 0);
}
void db_deinit(struct database *db)
@ -111,23 +115,23 @@ void db_autosave(struct database *db)
void db_load(struct database *db)
{
unsigned int size;
bool save;
if (file_open(&db->db_file, OPEN_READ) == false)
return;
size = file_readu(&db->db_file);
file_readf(&db->db_file, "%u", &size);
g_ptr_array_set_size(db->db_entries, size);
for (unsigned int i = 0; i < size; i++) {
if (__dbe_read(db, i))
__dbe_setup(db, i);
}
save = file_version(&db->db_file) != OCARINA_MINOR_VERSION;
file_close(&db->db_file);
}
if (save)
db_autosave(db);
void db_load_idle(struct database *db)
{
idle_schedule((void (*)(void *))db_load, db);
}
struct db_entry *db_insert(struct database *db, const gchar *key)
@ -135,7 +139,7 @@ struct db_entry *db_insert(struct database *db, const gchar *key)
struct db_entry *item = NULL;
if (key)
item = db->db_ops->dbe_alloc(key, db_actual_size(db));
item = db->db_ops->dbe_alloc(key);
if (item) {
g_ptr_array_add(db->db_entries, item);
@ -155,43 +159,6 @@ void db_remove(struct database *db, struct db_entry *item)
db_autosave(db);
}
bool db_defrag(struct database *db)
{
struct db_entry *dbe;
unsigned int i, cur;
if (db->db_size == db_actual_size(db))
return false;
for (cur = 0, i = 1; cur < db->db_size; cur++) {
if (DB_ENTRY_AT(db, cur))
continue;
while (i <= cur || !DB_ENTRY_AT(db, i))
i++;
dbe = DB_ENTRY_AT(db, i);
g_ptr_array_index(db->db_entries, i) = NULL;
g_ptr_array_index(db->db_entries, cur) = dbe;
dbe->dbe_index = cur;
}
g_ptr_array_set_size(db->db_entries, db->db_size);
db_autosave(db);
return true;
}
void db_rekey(struct database *db, struct db_entry *dbe)
{
if (dbe == NULL)
return;
g_hash_table_remove(db->db_keys, dbe->dbe_key);
g_free(dbe->dbe_key);
dbe->dbe_key = db->db_ops->dbe_key(dbe);
g_hash_table_insert(db->db_keys, dbe->dbe_key, dbe);
}
unsigned int db_actual_size(const struct database *db)
{
if (db->db_entries)
@ -211,7 +178,7 @@ struct db_entry *db_next(const struct database *db, struct db_entry *ent)
return NULL;
}
struct db_entry *db_at(const struct database *db, unsigned int index)
struct db_entry *db_at(struct database *db, unsigned int index)
{
if (index >= db_actual_size(db))
return NULL;

94
core/containers/index.c Normal file
View File

@ -0,0 +1,94 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/containers/index.h>
static struct index_entry *__index_alloc(gchar *key)
{
struct index_entry *ent = g_malloc(sizeof(struct index_entry));
dbe_init(&ent->ie_dbe, ent);
set_init(&ent->ie_set);
ent->ie_key = key;
return ent;
}
static struct db_entry *index_alloc(const gchar *key)
{
return &__index_alloc(g_strdup(key))->ie_dbe;
}
static void index_free(struct db_entry *dbe)
{
struct index_entry *ent = INDEX_ENTRY(dbe);
set_deinit(&ent->ie_set);
g_free(ent);
}
static gchar *index_key(struct db_entry *dbe)
{
return INDEX_ENTRY(dbe)->ie_key;
}
static void index_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%s\n" , INDEX_ENTRY(dbe)->ie_key);
set_write(file, &INDEX_ENTRY(dbe)->ie_set);
}
static struct db_entry *index_read(struct file *file)
{
gchar *key = file_readl(file);
struct index_entry *ent;
ent = __index_alloc(key);
set_read(file, &ent->ie_set);
return &ent->ie_dbe;
}
static const struct db_ops index_ops = {
.dbe_alloc = index_alloc,
.dbe_free = index_free,
.dbe_key = index_key,
.dbe_read = index_read,
.dbe_write = index_write,
};
void index_init(struct database *index, const gchar *filepath, bool autosave)
{
db_init(index, filepath, autosave, &index_ops);
}
struct index_entry *index_insert(struct database *index, const gchar *key,
unsigned int value)
{
struct index_entry *it = INDEX_ENTRY(db_find(index, key));
set_insert(&it->ie_set, value);
db_autosave(index);
return it;
}
void index_remove(struct database *index, const gchar *key, unsigned int value)
{
struct index_entry *it = INDEX_ENTRY(db_get(index, key));
if (it) {
set_remove(&it->ie_set, value);
db_autosave(index);
}
}
bool index_has(struct database *index, const gchar *key, unsigned int value)
{
struct index_entry *it = INDEX_ENTRY(db_get(index, key));
if (!it)
return false;
return set_has(&it->ie_set, value);
}
#ifdef CONFIG_TESTING
const struct db_ops *test_index_ops() { return &index_ops; }
#endif /* CONFIG_TESTING */

29
core/containers/queue.c Normal file
View File

@ -0,0 +1,29 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/containers/queue.h>
guint _q_add_sorted(struct _queue *queue, gpointer data,
GCompareDataFunc func, gpointer user_data)
{
struct _q_iter it;
_q_for_each(queue, &it) {
if (func(_q_iter_val(&it), data, user_data) > 0) {
g_queue_insert_before(&queue->_queue, it.it_iter, data);
return it.it_pos;
}
}
return _q_add_tail(queue, data);
}
gpointer _q_remove_it(struct _queue *queue, struct _q_iter *it)
{
gpointer ret = _q_iter_val(it);
GList *link = it->it_iter;
_q_iter_prev(it);
g_queue_delete_link(&queue->_queue, link);
return ret;
}

43
core/containers/set.c Normal file
View File

@ -0,0 +1,43 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/containers/set.h>
void set_copy(const struct set *lhs, struct set *rhs)
{
struct set_iter it_lhs;
set_for_each(lhs, &it_lhs)
set_insert(rhs, it_lhs.it_val);
}
void set_inline_intersect(const struct set *lhs, struct set *rhs)
{
struct set_iter it_rhs;
set_for_each(rhs, &it_rhs) {
if (!set_has(lhs, it_rhs.it_val))
g_hash_table_iter_remove(&it_rhs.it_iter);
}
}
void set_read(struct file *file, struct set *set)
{
unsigned int num, val, i;
file_readf(file, "%u ", &num);
for (i = 0; i < num; i++) {
file_readf(file, "%u ", &val);
set_insert(set, val);
}
}
void set_write(struct file *file, struct set *set)
{
struct set_iter it;
file_writef(file, "%u ", set_size(set));
set_for_each(set, &it)
file_writef(file, "%u ", it.it_val);
}

View File

@ -1,34 +1,34 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/core.h>
#include <core/collection.h>
#include <core/filter.h>
#include <core/history.h>
#include <core/playlist.h>
#include <core/tags/tags.h>
#include <core/tempq.h>
static bool core_defragment(void *data)
{
if (tags_defragment()) {
playlist_save();
audio_save();
}
return true;
}
void core_init(int *argc, char ***argv, struct playlist_callbacks *playlist_cb,
struct audio_callbacks *audio_cb, enum idle_sync_t idle_sync)
void core_init(int *argc, char ***argv, struct core_init_data *init)
{
idle_init(idle_sync);
settings_init();
filter_init();
tags_init();
playlist_init(playlist_cb);
audio_init(argc, argv, audio_cb);
idle_schedule(IDLE_SYNC, core_defragment, NULL);
playlist_init(init->playlist_ops);
collection_init(init->collection_ops);
history_init(init->history_ops);
tempq_init(init->tempq_ops);
audio_init(argc, argv, init->audio_ops);
}
void core_deinit()
{
audio_deinit();
tempq_deinit();
history_deinit();
collection_deinit();
playlist_deinit();
tags_deinit();
settings_deinit();
idle_deinit();
filter_deinit();
}

View File

@ -3,7 +3,6 @@
*/
#include <core/date.h>
#include <core/string.h>
#include <endian.h>
#include <time.h>
@ -26,14 +25,7 @@ void date_today(struct date *date)
void date_read(struct file *f, struct date *date)
{
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)
{
date->d_stamp = be32toh(file_readu(f));
file_readf(f, "%u %u %u", &date->d_year, &date->d_month, &date->d_day);
}
void date_write(struct file *f, struct date *date)
@ -41,11 +33,6 @@ void date_write(struct file *f, struct date *date)
file_writef(f, "%u %u %u", date->d_year, date->d_month, date->d_day);
}
void date_write_stamp(struct file *f, struct date *date)
{
file_writef(f, "%u", htobe32(date->d_stamp));
}
gchar *date_string(const struct date *date)
{
struct tm tm = {

View File

@ -3,105 +3,51 @@
*/
#include <core/file.h>
#include <core/string.h>
#include <core/version.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#define REPORT_ERROR(fname, error) \
g_printerr("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, error)
#define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno))
#ifdef CONFIG_TESTING
const gchar *OCARINA_DIR = "ocarina-test";
#elif CONFIG_DEBUG
const gchar *OCARINA_DIR = "ocarina-debug";
#else
const gchar *OCARINA_DIR = "ocarina";
#endif
static void __file_init_common(struct file *file, const gchar *subdir,
const gchar *name, unsigned int min,
const gchar *(*user_dir)(void))
#define REPORT_ERROR(fname) \
printf("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, strerror(errno))
static gchar *__file_path(gchar *name)
{
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;
return g_strjoin("/", g_get_user_data_dir(), OCARINA_DIR, name, NULL);
}
static bool __file_open(struct file *file, enum open_mode mode)
static bool __file_mkdir()
{
gchar *cmode, *path;
if (mode == OPEN_READ || mode == OPEN_READ_BINARY) {
cmode = "r";
path = file_path(file);
} else {
cmode = "w";
path = file_write_path(file);
}
file->f_file = g_fopen(path, cmode);
if (!file->f_file)
REPORT_ERRNO(path);
g_free(path);
return file->f_file != NULL;
}
static bool __file_mkdir(struct file *file)
{
gchar *dir = g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, NULL);
gchar *dir = __file_path(NULL);
int ret = g_mkdir_with_parents(dir, 0755);
if (ret != 0)
REPORT_ERRNO(dir);
REPORT_ERROR(dir);
g_free(dir);
return ret == 0;
}
static bool __file_can_write(struct file *file)
void file_init(struct file *file, const gchar *name, unsigned int version)
{
gchar *path = file_path(file);
bool ret = true;
if (g_access(path, F_OK) == 0 && g_access(path, W_OK) < 0)
ret = false;
g_free(path);
return ret;
}
void file_init_data(struct file *file, const gchar *subdir,
const gchar *name, unsigned int min)
{
__file_init_common(file, subdir, name, min, g_get_user_data_dir);
}
void file_init_cache(struct file *file, const gchar *subdir, const gchar *name)
{
__file_init_common(file, subdir, name, 0, g_get_user_cache_dir);
file->f_mode = OPEN_READ;
file->f_version = version;
file->f_prev = 0;
file->f_file = NULL;
g_strlcpy(file->f_name, (name == NULL) ? "" : name, FILE_MAX_LEN);
}
gchar *file_path(struct file *file)
{
if (string_length(file->f_name) == 0)
return g_strdup("");
return g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, file->f_name, NULL);
}
gchar *file_write_path(struct file *file)
{
gchar *tmp, *res;
if (string_length(file->f_name) == 0)
return g_strdup("");
tmp = g_strdup_printf(".%s.tmp", file->f_name);
res = g_build_filename(file->f_user_dir(), OCARINA_NAME,
file->f_subdir, tmp, NULL);
g_free(tmp);
return res;
if (strlen(file->f_name) != 0)
return __file_path(file->f_name);
return g_strdup("");
}
const unsigned int file_version(struct file *file)
@ -114,98 +60,83 @@ const unsigned int file_version(struct file *file)
bool file_exists(struct file *file)
{
gchar *path = file_path(file);
bool ret = g_file_test(path, G_FILE_TEST_EXISTS);
bool ret = g_file_test(path, G_FILE_TEST_EXISTS);
g_free(path);
return ret;
}
static bool __file_open_read(struct file *file, enum open_mode mode)
static bool __file_open_common(struct file *file, enum open_mode mode)
{
if (!file_exists(file))
return false;
if (!__file_open(file, mode))
gchar *path = file_path(file);
file->f_file = g_fopen(path, (mode == OPEN_READ) ? "r" : "w");
g_free(path);
if (!file->f_file) {
REPORT_ERROR(file->f_name);
return false;
}
file->f_mode = mode;
if (mode == OPEN_READ_BINARY)
return true;
file->f_prev = file_readu(file);
if (file->f_prev < file->f_min) {
REPORT_ERROR(file->f_name, "File too old to be upgraded.");
file_close(file);
exit(1);
}
if (file->f_prev > file->f_version) {
REPORT_ERROR(file->f_name, "File too new to be opened.");
file_close(file);
exit(1);
}
return true;
}
static bool __file_open_write(struct file *file, enum open_mode mode)
static bool __file_open_read(struct file *file)
{
if (!__file_mkdir(file))
if (!file_exists(file))
return false;
if (!__file_can_write(file))
return false;
if (!__file_open(file, OPEN_WRITE))
if (!__file_open_common(file, OPEN_READ))
return false;
return file_readf(file, "%u\n", &file->f_prev) == 1;
}
file->f_mode = mode;
if (mode == OPEN_WRITE_BINARY)
return true;
static bool __file_open_write(struct file *file)
{
if (!__file_mkdir())
return false;
if (!__file_open_common(file, OPEN_WRITE))
return false;
return file_writef(file, "%d\n", file->f_version) > 0;
}
bool file_open(struct file *file, enum open_mode mode)
{
if ((string_length(file->f_name) == 0) || (file->f_file != NULL))
return false;
if (mode == CLOSED)
if ((strlen(file->f_name) == 0) || (file->f_file != NULL))
return false;
if (mode == OPEN_READ || mode == OPEN_READ_BINARY)
return __file_open_read(file, mode);
return __file_open_write(file, mode);
if (mode == OPEN_READ)
return __file_open_read(file);
return __file_open_write(file);
}
void file_close(struct file *file)
{
gchar *path = file_path(file);
gchar *tmp = file_write_path(file);
if (file->f_file) {
if (file->f_file)
fclose(file->f_file);
if (file->f_mode == OPEN_WRITE || file->f_mode == OPEN_WRITE_BINARY)
g_rename(tmp, path);
}
file->f_file = NULL;
file->f_mode = CLOSED;
g_free(path);
g_free(tmp);
}
gchar *file_readw(struct file *file)
int file_readf(struct file *file, const char *fmt, ...)
{
gchar *s;
return fscanf(file->f_file, "%ms%*c", &s) ? s : g_strdup("");
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 *s = NULL;
size_t len = 0;
return getline(&s, &len, file->f_file) ? g_strchomp(s) : g_strdup("");
}
gchar *res;
unsigned int file_readu(struct file *file)
{
unsigned int u;
return fscanf(file->f_file, "%u%*c", &u) ? u : 0;
if (file_readf(file, "%m[^\n]\n", &res) == 0)
return g_strdup("");
g_strstrip(res);
return res;
}
int file_writef(struct file *file, const char *fmt, ...)
@ -218,62 +149,6 @@ int file_writef(struct file *file, const char *fmt, ...)
va_end(argp);
if (ret < 0)
REPORT_ERRNO(file->f_name);
REPORT_ERROR(file->f_name);
return ret;
}
gchar *file_read(struct file *file)
{
int fd = fileno(file->f_file);
struct stat st;
gchar *buf;
if (fstat(fd, &st) < 0)
return NULL;
buf = g_malloc0(st.st_size + 1);
if (fread(buf, st.st_size, 1, file->f_file) == 1)
return buf;
g_free(buf);
return NULL;
}
int file_write(struct file *file, const void *data, size_t len)
{
if (fwrite(data, len, 1, file->f_file) == 1)
return len;
return -1;
}
bool file_import(struct file *file, const gchar *srcpath)
{
gchar *contents = NULL;
gsize length = 0;
if (!file->f_file || !srcpath)
return false;
if (!g_file_get_contents(srcpath, &contents, &length, NULL))
return false;
file_write(file, contents, length);
return true;
}
bool file_remove(struct file *file)
{
gchar *path, *dir;
int ret = -1;
if (!file->f_file) {
path = file_path(file);
ret = g_unlink(path);
dir = g_path_get_dirname(path);
if (string_length(file->f_subdir) > 0)
g_rmdir(dir);
g_free(path);
g_free(dir);
}
return ret == 0;
}

69
core/filter.c Normal file
View File

@ -0,0 +1,69 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/containers/index.h>
#include <core/filter.h>
#include <core/string.h>
static struct database filter_index;
void filter_init() { index_init(&filter_index, "", false); }
void filter_deinit() { db_deinit(&filter_index); }
void filter_add(const gchar *text, unsigned int index)
{
const gchar *c = g_utf8_next_char(text);
glong begin, end;
gchar *substr;
for (begin = 0, end = 1; end <= g_utf8_strlen(text, -1); end++) {
substr = g_utf8_substring(text, begin, end);
index_insert(&filter_index, substr, index);
if (g_unichar_isspace(g_utf8_get_char(c))) {
c = g_utf8_next_char(c);
begin = ++end;
}
c = g_utf8_next_char(c);
g_free(substr);
}
}
void filter_search(const gchar *text, struct set *res)
{
gchar *lower = string_lowercase(text);
struct index_entry *found;
glong begin, end;
gchar *c, *substr;
set_clear(res);
c = lower;
for (begin = 0, end = 1; end <= g_utf8_strlen(lower, -1); end++) {
c = g_utf8_next_char(c);
if ((*c != '\0') && !g_unichar_isspace(g_utf8_get_char(c)))
continue;
substr = g_utf8_substring(lower, begin, end);
found = INDEX_ENTRY(db_get(&filter_index, substr));
g_free(substr);
if (!found) {
set_clear(res);
break;
}
if (begin == 0)
set_copy(&found->ie_set, res);
else
set_inline_intersect(&found->ie_set, res);
c = g_utf8_next_char(c);
begin = ++end;
}
g_free(lower);
}

34
core/history.c Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/history.h>
static struct queue history_queue;
#define HISTORY_FLAGS Q_ENABLED | Q_REPEAT | Q_NO_SORT | Q_ADD_FRONT
void history_init(struct queue_ops *history_ops)
{
queue_init(&history_queue, HISTORY_FLAGS, history_ops);
}
void history_deinit()
{
queue_deinit(&history_queue);
}
void history_add(struct track *track)
{
queue_add(&history_queue, track);
_q_iter_set(&history_queue.q_tracks, &history_queue.q_cur, 0);
}
struct track *history_prev()
{
return queue_next(&history_queue);
}
struct queue *history_get_queue()
{
return &history_queue;
}

View File

@ -2,86 +2,28 @@
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <glib.h>
struct idle_task {
bool (*idle_func)(void *);
void (*idle_func)(void *);
void *idle_data;
};
static GThreadPool *idle_pool = NULL;
static GQueue idle_queue = G_QUEUE_INIT;
static enum idle_sync_t idle_mode = IDLE_SYNC;
static unsigned int queued = 0;
static unsigned int serviced = 0;
static GQueue idle_queue = G_QUEUE_INIT;
static float queued = 0.0;
static float serviced = 0.0;
void __idle_free_task(struct idle_task *task)
void idle_schedule(void (*func)(void *), void *data)
{
g_free(task);
g_atomic_int_inc(&serviced);
}
bool __idle_run_task(struct idle_task *task)
{
bool finished = task->idle_func(task->idle_data);
if (finished)
__idle_free_task(task);
return finished;
}
void __idle_thread(gpointer task, gpointer data)
{
if (!__idle_run_task(task))
g_thread_pool_push(idle_pool, task, NULL);
}
void idle_init(enum idle_sync_t sync)
{
idle_mode = sync;
}
void idle_deinit()
{
struct idle_task *task;
while (!g_queue_is_empty(&idle_queue)) {
task = g_queue_pop_head(&idle_queue);
g_free(task);
}
if (idle_pool) {
g_thread_pool_free(idle_pool, true, true);
idle_pool = NULL;
}
queued = 0;
serviced = 0;
}
void idle_schedule(enum idle_sync_t sync, bool (*func)(void *), void *data)
{
struct idle_task *task;
if (sync == IDLE_ASYNC && idle_mode == IDLE_SYNC)
return;
task = g_malloc(sizeof(struct idle_task));
struct idle_task *task = g_malloc(sizeof(struct idle_task));
task->idle_func = func;
task->idle_data = data;
if (sync == IDLE_SYNC)
g_queue_push_tail(&idle_queue, task);
else {
if (!idle_pool)
idle_pool = g_thread_pool_new(__idle_thread, NULL, 1,
false, NULL);
g_thread_pool_push(idle_pool, task, NULL);
}
g_atomic_int_inc(&queued);
g_queue_push_tail(&idle_queue, task);
queued++;
}
bool idle_run_task()
@ -90,22 +32,31 @@ bool idle_run_task()
if (!g_queue_is_empty(&idle_queue)) {
task = g_queue_pop_head(&idle_queue);
if (!__idle_run_task(task))
g_queue_push_tail(&idle_queue, task);
task->idle_func(task->idle_data);
g_free(task);
serviced++;
}
if (g_atomic_int_get(&queued) != g_atomic_int_get(&serviced))
return true;
queued = 0;
serviced = 0;
return false;
if (g_queue_is_empty(&idle_queue)) {
queued = 0.0;
serviced = 0.0;
}
return !g_queue_is_empty(&idle_queue);
}
float idle_progress()
{
if (g_atomic_int_get(&serviced) == 0 &&
g_atomic_int_get(&queued) == 0)
if (g_queue_is_empty(&idle_queue))
return 1.0;
return (float)g_atomic_int_get(&serviced) / (float)queued;
return serviced / queued;
}
void idle_cancel()
{
struct idle_task *task;
while (!g_queue_is_empty(&idle_queue)) {
task = g_queue_pop_head(&idle_queue);
g_free(task);
}
}

View File

@ -1,236 +1,126 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/playlist.h>
#include <core/settings.h>
#include <core/collection.h>
#include <core/string.h>
#include <core/playlist.h>
static const gchar *SETTINGS_CUR_TYPE = "core.playlist.cur.type";
static const gchar *SETTINGS_CUR_ID = "core.playlist.cur.id";
static const gchar *SETTINGS_PREV_TYPE = "core.playlist.prev.type";
static const gchar *SETTINGS_PREV_ID = "core.playlist.prev.id";
static struct playlist *current = NULL;
static struct playlist *previous = NULL;
struct playlist_type *playlist_types[] = {
[PL_SYSTEM] = &pl_system,
[PL_ARTIST] = &pl_artist,
[PL_LIBRARY] = &pl_library,
[PL_USER] = &pl_user,
};
static struct database playlist_db;
static struct queue playlist_q;
static enum playlist_t playlist_cur;
static const gchar *playlist_names[2] = { "Favorites", "Banned" };
static struct playlist *__playlist_saved(const gchar *s_type, const gchar *s_id)
static inline struct index_entry *__playlist_lookup(enum playlist_t plist)
{
unsigned int type, id;
if (!settings_has(s_type) || !settings_has(s_id))
return NULL;
type = settings_get(s_type);
id = settings_get(s_id);
return playlist_types[type]->pl_get(id);
return INDEX_ENTRY(db_get(&playlist_db, playlist_names[plist]));
}
void playlist_init(struct playlist_callbacks *cb)
static inline bool __playlist_is_static(enum playlist_t plist)
{
playlist_generic_set_callbacks(cb);
pl_system_init();
pl_artist_init();
pl_user_init();
pl_library_init();
return (plist == PL_FAVORITED) || (plist == PL_HIDDEN);
}
current = __playlist_saved(SETTINGS_CUR_TYPE, SETTINGS_CUR_ID);
previous = __playlist_saved(SETTINGS_PREV_TYPE, SETTINGS_PREV_ID);
if (!current)
current = playlist_lookup(PL_SYSTEM, "Collection");
static inline bool __playlist_is_dynamic(enum playlist_t plist)
{
return !__playlist_is_static(plist);
}
static void __playlist_fill_static(enum playlist_t plist)
{
struct index_entry *ent = __playlist_lookup(plist);
struct set_iter it;
if (ent) {
set_for_each(&ent->ie_set, &it)
queue_add(&playlist_q, track_get(it.it_val));
}
}
static void __playlist_fill_dynamic(enum playlist_t plist)
{
unsigned int count = 0, average = 0;
struct db_entry *track, *next;
if (plist == PL_MOST_PLAYED || plist == PL_LEAST_PLAYED)
average = track_db_average_plays();
db_for_each(track, next, track_db_get()) {
count = TRACK(track)->tr_count;
if (plist == PL_UNPLAYED && count == 0)
queue_add(&playlist_q, TRACK(track));
else if (plist == PL_MOST_PLAYED && count > average)
queue_add(&playlist_q, TRACK(track));
else if (plist == PL_LEAST_PLAYED && count <= average && count > 0)
queue_add(&playlist_q, TRACK(track));
}
}
void playlist_init(struct queue_ops *ops)
{
queue_init(&playlist_q, Q_ENABLED | Q_REPEAT, ops);
queue_sort(&playlist_q, COMPARE_ARTIST, true);
queue_sort(&playlist_q, COMPARE_YEAR, false);
queue_sort(&playlist_q, COMPARE_TRACK, false);
queue_set_flag(&playlist_q, Q_NO_SORT);
index_init(&playlist_db, "playlist.db", true);
db_load_idle(&playlist_db);
}
void playlist_deinit()
{
pl_system_deinit();
pl_artist_deinit();
pl_user_deinit();
pl_library_deinit();
queue_deinit(&playlist_q);
db_deinit(&playlist_db);
}
void playlist_save()
bool playlist_add(enum playlist_t plist, struct track *track)
{
unsigned int i;
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_save();
}
void playlist_played(struct track *track)
{
unsigned int i;
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_played(track);
}
}
void playlist_selected(struct track *track)
{
unsigned int i;
if (track && !TRACK_IS_EXTERNAL(track)) {
for (i = 0; i < PL_MAX_TYPE; i++)
playlist_types[i]->pl_selected(track);
if (playlist_size(current) == 0)
playlist_select(previous);
}
}
struct playlist *playlist_new(enum playlist_type_t type, const gchar *name)
{
if (type < PL_MAX_TYPE && playlist_types[type]->pl_new)
return playlist_types[type]->pl_new(name);
return NULL;
}
bool playlist_delete(struct playlist *playlist)
{
enum playlist_type_t type;
bool ret;
if (!playlist || !playlist->pl_ops->pl_delete)
if (!track || !__playlist_is_static(plist) || playlist_has(plist, track))
return false;
type = playlist->pl_type;
ret = playlist->pl_ops->pl_delete(playlist);
if (ret)
playlist_types[type]->pl_save();
return ret;
}
struct playlist *playlist_lookup(enum playlist_type_t type, const gchar *name)
{
if (type >= PL_MAX_TYPE)
return NULL;
return playlist_types[type]->pl_lookup(name);
}
struct playlist *playlist_get(enum playlist_type_t type, unsigned int id)
{
if (type >= PL_MAX_TYPE)
return NULL;
return playlist_types[type]->pl_get(id);
}
struct playlist *playlist_current(void)
{
return current;
}
bool playlist_select(struct playlist *playlist)
{
if (!playlist || (playlist == current))
return false;
if (!playlist->pl_ops->pl_can_select)
return false;
if (!playlist->pl_ops->pl_can_select(playlist))
return false;
previous = current;
current = playlist;
settings_set(SETTINGS_CUR_TYPE, current->pl_type);
settings_set(SETTINGS_CUR_ID, current->pl_id);
if (previous) {
settings_set(SETTINGS_PREV_TYPE, previous->pl_type);
settings_set(SETTINGS_PREV_ID, previous->pl_id);
}
index_insert(&playlist_db, playlist_names[plist],
track->tr_dbe.dbe_index);
if (playlist_cur == plist)
queue_add(&playlist_q, track);
return true;
}
struct track *playlist_next(void)
bool playlist_remove(enum playlist_t plist, struct track *track)
{
struct track *track = playlist_generic_next(current);
if (track && current->pl_type < PL_MAX_TYPE)
playlist_types[current->pl_type]->pl_save();
return track;
}
struct track *playlist_prev(void)
{
return playlist_generic_next(playlist_lookup(PL_SYSTEM, "History"));
}
bool playlist_add(struct playlist *playlist, struct track *track)
{
bool ret;
if (!track || !playlist || !playlist->pl_ops->pl_add)
if (!track || !playlist_has(plist, track))
return false;
ret = playlist->pl_ops->pl_add(playlist, track);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
if (playlist == playlist_lookup(PL_SYSTEM, "Queued Tracks"))
playlist_select(playlist);
return ret;
index_remove(&playlist_db, playlist_names[plist],
track->tr_dbe.dbe_index);
if (playlist_cur == plist)
queue_remove_all(&playlist_q, track);
return true;
}
bool playlist_remove(struct playlist *playlist, struct track *track)
bool playlist_has(enum playlist_t plist, struct track *track)
{
bool ret;
if (!track || !playlist || !playlist->pl_ops->pl_remove)
if (!track || __playlist_is_dynamic(plist))
return false;
ret = playlist->pl_ops->pl_remove(playlist, track);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
return index_has(&playlist_db, playlist_names[plist], track->tr_dbe.dbe_index);
}
void playlist_set_random(struct playlist *playlist, bool enabled)
void playlist_select(enum playlist_t plist)
{
if (playlist && playlist->pl_ops->pl_set_random) {
playlist->pl_ops->pl_set_random(playlist, enabled);
if (playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
}
queue_clear(&playlist_q);
queue_set_flag(&playlist_q, Q_ADD_FRONT);
if (__playlist_is_static(plist))
__playlist_fill_static(plist);
else
__playlist_fill_dynamic(plist);
queue_unset_flag(&playlist_q, Q_ADD_FRONT);
playlist_cur = plist;
queue_resort(&playlist_q);
}
bool playlist_sort(struct playlist *playlist, enum compare_t sort)
struct queue *playlist_get_queue()
{
if (!playlist || !playlist->pl_ops->pl_sort)
return false;
playlist->pl_ops->pl_sort(playlist, sort);
if (playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return g_slist_length(playlist->pl_sort) > 0;
}
bool playlist_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
bool ret;
if (!playlist || !playlist->pl_ops->pl_rearrange)
return false;
ret = playlist->pl_ops->pl_rearrange(playlist, old_pos, new_pos);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
}
void playlist_set_search(struct playlist *playlist, const gchar *text)
{
gchar **tokens = NULL;
if (!playlist)
return;
if (playlist->pl_search)
g_strfreev(playlist->pl_search);
if (strlen(text) > 0)
tokens = g_str_tokenize_and_fold(text, NULL, NULL);
playlist->pl_search = tokens;
return &playlist_q;
}

View File

@ -1,157 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/artist.h>
#include <core/string.h>
static struct file artist_file = FILE_INIT_DATA("", "playlist.artist", 0);
static struct playlist_ops pl_artist_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static struct playlist *__artist_pl_alloc(struct artist *artist)
{
return playlist_generic_alloc(artist->ar_name, PL_ARTIST,
artist_index(artist), &pl_artist_ops,
3, COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
}
static bool __artist_pl_add(void *data)
{
struct playlist *playlist = (struct playlist *)data;
struct artist *artist = artist_lookup(playlist->pl_name);
struct db_entry *dbe, *next;
db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_album->al_artist == artist)
playlist_generic_add_front(playlist, TRACK(dbe));
}
playlist_generic_resort(playlist);
return true;
}
static struct playlist *__artist_pl_lookup(const gchar *name)
{
struct artist *artist = artist_lookup(name);
return artist ? artist->ar_playlist : NULL;
}
static bool __artist_pl_load(void *data)
{
struct playlist *playlist;
unsigned int i, n;
gchar *name;
if (!file_open(&artist_file, OPEN_READ))
return true;
n = file_readu(&artist_file);
for (i = 0; i < n; i++) {
name = file_readl(&artist_file);
playlist = __artist_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &artist_file,
PL_SAVE_METADATA);
g_free(name);
}
file_close(&artist_file);
return true;
}
static void pl_artist_save(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
if (!file_open(&artist_file, OPEN_WRITE))
return;
file_writef(&artist_file, "%u\n", artist_db_get()->db_size);
db_for_each(dbe, next, artist_db_get()) {
playlist = ARTIST(dbe)->ar_playlist;
file_writef(&artist_file, "%s\n", playlist->pl_name);
playlist_generic_save(playlist, &artist_file, PL_SAVE_METADATA);
}
file_close(&artist_file);
}
static struct playlist *pl_artist_lookup(const gchar *name)
{
return __artist_pl_lookup(name);
}
static struct playlist *pl_artist_get(unsigned int id)
{
struct artist *artist = artist_get(id);
return artist ? artist->ar_playlist : NULL;
}
static void pl_artist_played(struct track *track)
{
struct artist *artist = track->tr_album->al_artist;
playlist_generic_update(artist->ar_playlist, track);
}
struct playlist_type pl_artist = {
.pl_save = pl_artist_save,
.pl_lookup = pl_artist_lookup,
.pl_get = pl_artist_get,
.pl_played = pl_artist_played,
.pl_selected = pl_artist_played,
};
void pl_artist_init(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, artist_db_get()) {
playlist = __artist_pl_alloc(ARTIST(dbe));
ARTIST(dbe)->ar_playlist = playlist;
idle_schedule(IDLE_SYNC, __artist_pl_add, playlist);
}
idle_schedule(IDLE_SYNC, __artist_pl_load, NULL);
}
void pl_artist_deinit()
{
struct db_entry *dbe, *next;
db_for_each(dbe, next, artist_db_get()) {
playlist_generic_free(ARTIST(dbe)->ar_playlist);
ARTIST(dbe)->ar_playlist = NULL;
}
}
void pl_artist_new_track(struct track *track)
{
struct artist *artist = track->tr_album->al_artist;
struct playlist *playlist = (struct playlist *)artist->ar_playlist;
if (!playlist) {
playlist = __artist_pl_alloc(artist);
artist->ar_playlist = playlist;
}
playlist_generic_add(playlist, track);
}
void pl_artist_delete_track(struct track *track)
{
struct artist *artist = track->tr_album->al_artist;
playlist_generic_remove(artist->ar_playlist, track);
}

View File

@ -1,313 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/generic.h>
#include <stdlib.h>
static struct playlist_callbacks *callbacks = NULL;
static int __playlist_generic_find_sort(gconstpointer a, gconstpointer b)
{
return abs(GPOINTER_TO_INT(a)) - abs(GPOINTER_TO_INT(b));
}
static int __playlist_generic_less_than(gconstpointer a, gconstpointer b,
gpointer data)
{
struct track *lhs = (struct track *)a;
struct track *rhs = (struct track *)b;
GSList *cur = ((struct playlist *)data)->pl_sort;
int res, field;
while (cur) {
field = GPOINTER_TO_INT(cur->data);
res = track_compare(lhs, rhs, abs(field));
if (res != 0)
break;
cur = g_slist_next(cur);
};
return (field > 0) ? res : -res;
}
void playlist_generic_set_callbacks(struct playlist_callbacks *cb)
{
callbacks = cb;
}
static void __playlist_generic_vinit(struct playlist *playlist,
unsigned int nargs, va_list argp)
{
unsigned int i;
if (!playlist)
return;
g_queue_init(&playlist->pl_tracks);
playlist->pl_length = 0;
playlist->pl_random = false;
playlist->pl_current = NULL;
playlist->pl_sort = NULL;
playlist->pl_search = NULL;
for (i = 0; i < nargs; i++)
playlist_generic_sort(playlist, va_arg(argp, unsigned int));
}
void playlist_generic_init(struct playlist *playlist, unsigned int nargs, ...)
{
va_list argp;
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
va_end(argp);
}
void playlist_generic_deinit(struct playlist *playlist)
{
if (playlist) {
playlist_generic_clear(playlist);
playlist_clear_sort(playlist);
g_strfreev(playlist->pl_search);
playlist->pl_search = NULL;
playlist->pl_length = 0;
}
}
struct playlist *playlist_generic_alloc(gchar *name, enum playlist_type_t type,
unsigned int id, struct playlist_ops *ops,
unsigned int nargs, ...)
{
struct playlist *playlist = g_malloc(sizeof(struct playlist));
va_list argp;
playlist->pl_name = name;
playlist->pl_type = type;
playlist->pl_id = id;
playlist->pl_ops = ops;
va_start(argp, nargs);
__playlist_generic_vinit(playlist, nargs, argp);
if (callbacks)
callbacks->pl_cb_alloc(playlist);
va_end(argp);
return playlist;
}
void playlist_generic_free(struct playlist *playlist)
{
if (playlist) {
playlist_generic_deinit(playlist);
g_free(playlist);
}
}
void playlist_generic_save(struct playlist *playlist, struct file *file,
unsigned int flags)
{
playlist_iter it;
GSList *sort;
int field;
if (!playlist)
return;
if (flags & PL_SAVE_ITER)
file_writef(file, "%u ", playlist_current_index(playlist));
if (flags & PL_SAVE_FLAGS) {
sort = playlist->pl_sort;
file_writef(file, "%u ", playlist->pl_random ? PL_RANDOM : 0);
file_writef(file, "%u", g_slist_length(sort));
while (sort) {
field = GPOINTER_TO_INT(sort->data);
file_writef(file, " %u %d", abs(field) - 1, field > 0);
sort = g_slist_next(sort);
}
file_writef(file, "\n");
}
if (flags & PL_SAVE_TRACKS) {
file_writef(file, "%u", playlist_size(playlist));
playlist_for_each(playlist, it)
file_writef(file, " %u",
track_index(playlist_iter_track(it)));
file_writef(file, "\n");
}
}
void playlist_generic_load(struct playlist *playlist, struct file *file,
unsigned int flags)
{
unsigned int f, n, i, t, it = 0;
int field, ascending;
if (!playlist)
return;
if (flags & PL_SAVE_ITER)
it = file_readu(file);
if (flags & PL_SAVE_FLAGS) {
f = file_readu(file);
n = file_readu(file);
playlist_clear_sort(playlist);
for (i = 0; i < n; i++) {
field = file_readu(file) + 1;
ascending = file_readd(file);
if (!ascending)
field = -field;
playlist->pl_sort = g_slist_append(playlist->pl_sort,
GINT_TO_POINTER(field));
}
playlist_generic_resort(playlist);
}
if (flags & PL_SAVE_TRACKS) {
n = file_readu(file);
for (i = 0; i < n; i++) {
t = file_readu(file);
playlist_generic_add(playlist, track_get(t));
}
}
playlist_generic_set_random(playlist, f == PL_RANDOM);
playlist_current_set(playlist, it);
}
bool playlist_generic_can_select(struct playlist *playlist)
{
return playlist_size(playlist) > 0;
}
void playlist_generic_clear(struct playlist *playlist)
{
unsigned int n;
if (!playlist)
return;
n = playlist_size(playlist);
g_queue_clear(&playlist->pl_tracks);
playlist->pl_length = 0;
playlist->pl_current = NULL;
if (callbacks)
callbacks->pl_cb_removed(playlist, NULL, n);
}
bool playlist_generic_add(struct playlist *playlist, struct track *track)
{
if (!playlist || !track || playlist_has(playlist, track))
return false;
playlist->pl_length += track->tr_length;
if (playlist->pl_sort) {
g_queue_insert_sorted(&playlist->pl_tracks, track,
__playlist_generic_less_than, playlist);
} else
g_queue_push_tail(&playlist->pl_tracks, track);
if (callbacks)
callbacks->pl_cb_added(playlist, track);
return true;
}
bool playlist_generic_add_front(struct playlist *playlist, struct track *track)
{
if (!playlist || !track)
return false;
playlist->pl_length += track->tr_length;
g_queue_push_head(&playlist->pl_tracks, track);
if (callbacks)
callbacks->pl_cb_added(playlist, track);
return true;
}
bool playlist_generic_remove(struct playlist *playlist, struct track *track)
{
unsigned int count;
if (!playlist || !track)
return false;
while (playlist_current_track(playlist) == track)
playlist_current_previous(playlist);
count = g_queue_remove_all(&playlist->pl_tracks, track);
playlist->pl_length -= (count * track->tr_length);
if (callbacks)
callbacks->pl_cb_removed(playlist, track, count);
return count > 0;
}
void playlist_generic_update(struct playlist *playlist, struct track *track)
{
if (playlist && callbacks)
callbacks->pl_cb_updated(playlist, track);
}
void playlist_generic_set_random(struct playlist *playlist, bool enabled)
{
playlist->pl_random = enabled;
}
void playlist_generic_sort(struct playlist *playlist, enum compare_t field)
{
gpointer sort = GINT_TO_POINTER(field);
GSList *found = g_slist_find_custom(playlist->pl_sort, sort,
__playlist_generic_find_sort);
if (found)
found->data = GINT_TO_POINTER(-field);
else
playlist->pl_sort = g_slist_append(playlist->pl_sort, sort);
playlist_generic_resort(playlist);
}
void playlist_generic_resort(struct playlist *playlist)
{
if (!playlist || !playlist->pl_sort)
return;
g_queue_sort(&playlist->pl_tracks, __playlist_generic_less_than, playlist);
playlist_generic_update(playlist, NULL);
}
bool playlist_generic_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
GList *nth;
if (old_pos == new_pos || old_pos >= playlist_size(playlist) ||
new_pos > playlist_size(playlist))
return false;
playlist_clear_sort(playlist);
nth = g_queue_pop_nth_link(&playlist->pl_tracks, old_pos);
g_queue_push_nth_link(&playlist->pl_tracks, new_pos, nth);
playlist_generic_update(playlist, NULL);
return true;
}
struct track *playlist_generic_next(struct playlist *playlist)
{
unsigned int pos, size = playlist_size(playlist);
if (size == 0)
return NULL;
else if (playlist->pl_random) {
pos = playlist_current_index(playlist);
pos += g_random_int_range(1, size);
playlist_current_set(playlist, pos % size);
} else if (!playlist_current_next(playlist))
playlist_current_set(playlist, 0);
return playlist_current_track(playlist);
}

View File

@ -1,277 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/artist.h>
#include <core/playlists/library.h>
#include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <unistd.h>
struct scan_data {
struct library *sd_library;
gchar *sd_path;
};
static bool __lib_pl_scan_dir(void *);
static struct file lib_file = FILE_INIT_DATA("", "playlist.library", 0);
static struct playlist_ops pl_library_ops;
static struct playlist *__lib_pl_alloc(struct library *library)
{
return playlist_generic_alloc(library->li_path, PL_LIBRARY,
library_index(library), &pl_library_ops,
4, COMPARE_ARTIST, COMPARE_YEAR,
COMPARE_ALBUM, COMPARE_TRACK);
}
static bool __lib_pl_add(void *data)
{
struct playlist *playlist = (struct playlist *)data;
struct library *library = library_lookup(playlist->pl_name);
struct db_entry *dbe, *next;
db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_library == library)
playlist_generic_add_front(playlist, TRACK(dbe));
}
playlist_generic_resort(playlist);
return true;
}
static struct playlist *__lib_pl_lookup(const gchar *name)
{
struct library *library = library_lookup(name);
return library ? library->li_playlist : NULL;
}
static bool __lib_pl_load(void *data)
{
struct playlist *playlist;
unsigned int i, n;
gchar *name;
if (!file_open(&lib_file, OPEN_READ))
return true;
n = file_readu(&lib_file);
for (i = 0; i < n; i++) {
name = file_readl(&lib_file);
playlist = __lib_pl_lookup(name);
if (playlist)
playlist_generic_load(playlist, &lib_file,
PL_SAVE_METADATA);
g_free(name);
}
file_close(&lib_file);
return true;
}
static void __lib_pl_scan_dir_idle(struct library *library, const gchar *path)
{
struct scan_data *scan = g_malloc(sizeof(struct scan_data));
scan->sd_library = library;
scan->sd_path = g_strdup(path);
/* scan data is freed by __lib_pl_scan_dir() */
idle_schedule(IDLE_SYNC, __lib_pl_scan_dir, scan);
}
static void __lib_pl_read_path(struct scan_data *scan, const gchar *name)
{
gchar *path = g_build_filename(scan->sd_path, name, NULL);
struct playlist *playlist = scan->sd_library->li_playlist;
struct track *track;
if (g_file_test(path, G_FILE_TEST_IS_DIR))
__lib_pl_scan_dir_idle(scan->sd_library, path);
else {
track = track_add(scan->sd_library, path);
if (track) {
playlist_generic_add(playlist, track);
pl_system_new_track(track);
pl_artist_new_track(track);
}
}
g_free(path);
}
static bool __lib_pl_scan_dir(void *data)
{
struct scan_data *scan = (struct scan_data *)data;
const gchar *name;
GDir *dir;
dir = g_dir_open(scan->sd_path, 0, NULL);
if (!dir)
goto out;
name = g_dir_read_name(dir);
while (name != NULL) {
__lib_pl_read_path(scan, name);
name = g_dir_read_name(dir);
}
g_dir_close(dir);
track_db_commit();
out:
/* Allocated by __lib_pl_scan_dir_idle() */
g_free(scan->sd_path);
g_free(scan);
return true;
}
static bool __lib_pl_update(void *data)
{
struct playlist *playlist = (struct playlist *)data;
struct library *library = library_lookup(playlist->pl_name);
struct db_entry *dbe, *next;
gchar *path;
db_for_each(dbe, next, track_db_get()) {
if (TRACK(dbe)->tr_library != library)
continue;
path = track_path(TRACK(dbe));
if (g_access(path, F_OK) < 0) {
pl_system_delete_track(TRACK(dbe));
pl_artist_delete_track(TRACK(dbe));
playlist_generic_remove(playlist, TRACK(dbe));
track_remove(TRACK(dbe));
}
g_free(path);
}
track_db_commit();
__lib_pl_scan_dir_idle(library, library->li_path);
return true;
}
static bool pl_library_delete(struct playlist *playlist)
{
struct library *library = library_lookup(playlist->pl_name);
playlist_iter it;
if (!library)
return false;
playlist_for_each(playlist, it) {
pl_system_delete_track(playlist_iter_track(it));
pl_artist_delete_track(playlist_iter_track(it));
pl_user_delete_track(playlist_iter_track(it));
}
playlist_generic_free(playlist);
track_remove_all(library);
library_remove(library);
return true;
}
static struct playlist_ops pl_library_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_delete = pl_library_delete,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static void pl_library_save(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
if (!file_open(&lib_file, OPEN_WRITE))
return;
file_writef(&lib_file, "%u\n", library_db_get()->db_size);
db_for_each(dbe, next, library_db_get()) {
playlist = LIBRARY(dbe)->li_playlist;
file_writef(&lib_file, "%s\n", playlist->pl_name);
playlist_generic_save(playlist, &lib_file, PL_SAVE_METADATA);
}
file_close(&lib_file);
}
static struct playlist *pl_library_lookup(const gchar *name)
{
return __lib_pl_lookup(name);
}
static struct playlist *pl_library_get(unsigned int id)
{
struct library *library = LIBRARY(db_at(library_db_get(), id));
return library ? library->li_playlist : NULL;
}
static struct playlist *pl_library_new(const gchar *name)
{
struct library *library;
if (__lib_pl_lookup(name) || !g_file_test(name, G_FILE_TEST_IS_DIR))
return NULL;
library = library_find(name);
library->li_playlist = __lib_pl_alloc(library);
__lib_pl_scan_dir_idle(library, name);
return library->li_playlist;
}
static void pl_library_played(struct track *track)
{
struct library *library = track->tr_library;
playlist_generic_update(library->li_playlist, track);
}
struct playlist_type pl_library = {
.pl_save = pl_library_save,
.pl_lookup = pl_library_lookup,
.pl_get = pl_library_get,
.pl_new = pl_library_new,
.pl_played = pl_library_played,
.pl_selected = pl_library_played,
};
void pl_library_init(void)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, library_db_get()) {
playlist = __lib_pl_alloc(LIBRARY(dbe));
LIBRARY(dbe)->li_playlist = playlist;
idle_schedule(IDLE_SYNC, __lib_pl_add, playlist);
idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
}
idle_schedule(IDLE_SYNC, __lib_pl_load, NULL);
}
void pl_library_deinit()
{
struct db_entry *dbe, *next;
db_for_each(dbe, next, library_db_get()) {
playlist_generic_free(LIBRARY(dbe)->li_playlist);
LIBRARY(dbe)->li_playlist = NULL;
}
}
void pl_library_update(struct playlist *playlist)
{
idle_schedule(IDLE_SYNC, __lib_pl_update, playlist);
}

View File

@ -1,454 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlists/system.h>
#include <core/string.h>
static struct playlist *pl_system_lookup(const gchar *);
static struct playlist *pl_system_get(unsigned int);
static void pl_system_save();
static struct file sys_file = FILE_INIT_DATA("", "playlist.db", 0);
static struct file sys_deck_f = FILE_INIT_DATA("", "deck", 1);
static struct file sys_collection_f = FILE_INIT_DATA("", "library.q", 0);
static struct file sys_pl_file = FILE_INIT_DATA("", "playlist.system", 0);
/*
* Generic system playlist operations.
*/
static bool sys_pl_delete_clear(struct playlist *playlist)
{
playlist_generic_clear(playlist);
pl_system_save();
return false;
}
static bool sys_pl_update_check(struct playlist *playlist, struct track *track)
{
switch (playlist->pl_id) {
case SYS_PL_UNPLAYED:
return track->tr_count == 0;
case SYS_PL_LEAST_PLAYED:
return track->tr_count <= track_db_average_plays() &&
track->tr_count > 0;
case SYS_PL_MOST_PLAYED:
return track->tr_count > track_db_average_plays();
}
return true;
}
static bool sys_pl_update_func(void *data)
{
struct playlist *playlist = (struct playlist *)data;
struct db_entry *dbe, *next;
db_for_each(dbe, next, track_db_get()) {
struct track *track = TRACK(dbe);
if (!sys_pl_update_check(playlist, track))
playlist_generic_remove(playlist, track);
else if (!playlist_has(pl_system_get(SYS_PL_HIDDEN), track) &&
!playlist_has(playlist, track))
playlist_generic_add_front(playlist, track);
}
playlist_generic_resort(playlist);
return true;
}
static void sys_pl_update(struct playlist *playlist)
{
switch (playlist->pl_id) {
case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED:
case SYS_PL_LEAST_PLAYED:
case SYS_PL_MOST_PLAYED:
idle_schedule(IDLE_SYNC, sys_pl_update_func, playlist);
default:
break;
}
}
/*
* Favorite tracks playlist operations.
*/
static struct playlist_ops favorites_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_delete_clear,
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
/*
* Hidden tracks playlist operations.
*/
static bool sys_pl_hidden_add(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_add(pl_system_get(SYS_PL_HIDDEN), track);
playlist_generic_remove(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_remove(pl_system_get(SYS_PL_UNPLAYED), track);
playlist_generic_remove(pl_system_get(SYS_PL_MOST_PLAYED), track);
playlist_generic_remove(pl_system_get(SYS_PL_LEAST_PLAYED), track);
return ret;
}
static bool sys_pl_hidden_remove(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_remove(playlist, track);
unsigned int average = track_db_average_plays();
unsigned int add_id = SYS_PL_LEAST_PLAYED;
add_id = (track->tr_count == 0) ? SYS_PL_UNPLAYED : add_id;
add_id = (track->tr_count > average) ? SYS_PL_MOST_PLAYED : add_id;
playlist_generic_add(pl_system_get(SYS_PL_COLLECTION), track);
playlist_generic_add(pl_system_get(add_id), track);
return ret;
}
static bool sys_pl_hidden_clear(struct playlist *playlist)
{
struct track *track;
while (playlist_size(playlist) > 0) {
track = playlist_at(playlist, 0);
sys_pl_hidden_remove(playlist, track);
}
pl_system_save();
return false;
}
static struct playlist_ops hidden_ops = {
.pl_add = sys_pl_hidden_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_hidden_clear,
.pl_remove = sys_pl_hidden_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
/*
* Queued tracks playlist operations.
*/
static bool sys_pl_queued_load()
{
struct playlist *playlist = pl_system_get(SYS_PL_QUEUED);
unsigned int num, i, flags = 0;
if (!file_open(&sys_deck_f, OPEN_READ))
return true;
num = file_readu(&sys_deck_f);
for (i = 0; i < num; i++) {
flags = file_readu(&sys_deck_f);
flags &= PL_RANDOM;
if (i == 0)
playlist_generic_set_random(playlist,
flags == PL_RANDOM);
playlist_generic_load(playlist, &sys_deck_f, PL_SAVE_TRACKS);
}
file_close(&sys_deck_f);
file_remove(&sys_deck_f);
return true;
}
static bool sys_pl_queued_delete(struct playlist *playlist)
{
playlist_generic_set_random(playlist, false);
playlist_clear_sort(playlist);
return sys_pl_delete_clear(playlist);
}
static bool sys_pl_queued_remove(struct playlist *playlist, struct track *track)
{
bool ret = playlist_generic_remove(playlist, track);
if (playlist_size(playlist) == 0)
sys_pl_queued_delete(playlist);
return ret;
}
static struct playlist_ops queued_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = sys_pl_queued_delete,
.pl_remove = sys_pl_queued_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
/*
* Collection playlist operations.
*/
static bool sys_pl_collection_load()
{
struct playlist *playlist = pl_system_get(SYS_PL_COLLECTION);
if (file_open(&sys_collection_f, OPEN_READ)) {
playlist_generic_load(playlist, &sys_collection_f, PL_SAVE_FLAGS);
file_close(&sys_collection_f);
file_remove(&sys_collection_f);
}
return true;
}
static struct playlist_ops collection_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_remove = sys_pl_hidden_add,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
/*
* History playlist operations.
*/
static bool sys_pl_history_add(struct playlist *playlist, struct track *track)
{
playlist_generic_add_front(playlist, track);
playlist_current_set(playlist, 0);
return true;
}
static struct playlist_ops history_ops = {
.pl_add = sys_pl_history_add,
};
/*
* Unplayed, Most Played, and Least Played tracks playlist operations.
*/
static struct playlist_ops dynamic_ops = {
.pl_can_select = playlist_generic_can_select,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
#define SYS_PLAYLIST(id, name, ops) \
[id] = DEFINE_PLAYLIST(PL_SYSTEM, name, id, ops)
static struct playlist sys_playlists[SYS_PL_NUM_PLAYLISTS] = {
SYS_PLAYLIST(SYS_PL_FAVORITES, "Favorites", &favorites_ops),
SYS_PLAYLIST(SYS_PL_HIDDEN, "Hidden", &hidden_ops),
SYS_PLAYLIST(SYS_PL_QUEUED, "Queued Tracks", &queued_ops),
SYS_PLAYLIST(SYS_PL_COLLECTION, "Collection", &collection_ops),
SYS_PLAYLIST(SYS_PL_HISTORY, "History", &history_ops),
SYS_PLAYLIST(SYS_PL_UNPLAYED, "Unplayed", &dynamic_ops),
SYS_PLAYLIST(SYS_PL_MOST_PLAYED, "Most Played", &dynamic_ops),
SYS_PLAYLIST(SYS_PL_LEAST_PLAYED, "Least Played", &dynamic_ops),
};
static bool __sys_pl_update_save()
{
pl_system_save();
return true;
}
static bool __sys_pl_load()
{
struct playlist *playlist;
unsigned int i, n;
gchar *name;
if (!file_open(&sys_file, OPEN_READ))
return true;
n = file_readu(&sys_file);
for (i = 0; i < n; i++) {
file_readu(&sys_file);
name = file_readl(&sys_file);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
playlist = pl_system_lookup(name);
g_free(name);
if (playlist)
playlist_generic_load(playlist, &sys_file, PL_SAVE_TRACKS);
}
file_close(&sys_file);
file_remove(&sys_file);
return true;
}
static bool __sys_pl_load_new()
{
struct playlist *playlist;
unsigned int i, n, load;
gchar *name;
if (!file_open(&sys_pl_file, OPEN_READ)) {
__sys_pl_load();
sys_pl_collection_load();
sys_pl_queued_load();
__sys_pl_update_save();
return true;
}
n = file_readu(&sys_pl_file);
for (i = 0; i < n; i++) {
load = PL_SAVE_METADATA;
name = file_readl(&sys_pl_file);
if (string_match(name, "Banned")) {
g_free(name);
name = g_strdup("Hidden");
}
playlist = pl_system_lookup(name);
g_free(name);
switch (i) {
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_QUEUED:
load = PL_SAVE_ALL;
}
playlist_generic_load(playlist, &sys_pl_file, load);
}
file_close(&sys_pl_file);
return true;
}
static void pl_system_save(void)
{
struct playlist *playlist;
unsigned int i, save;
if (!file_open(&sys_pl_file, OPEN_WRITE))
return;
file_writef(&sys_pl_file, "%u\n", SYS_PL_NUM_PLAYLISTS);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
save = PL_SAVE_METADATA;
playlist = pl_system_get(i);
switch (i) {
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
case SYS_PL_QUEUED:
save = PL_SAVE_ALL;
}
file_writef(&sys_pl_file, "%s\n", playlist->pl_name);
playlist_generic_save(playlist, &sys_pl_file, save);
}
file_close(&sys_pl_file);
}
static struct playlist *pl_system_lookup(const gchar *name)
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
if (string_match(name, pl_system_get(i)->pl_name))
return pl_system_get(i);
}
return NULL;
}
static struct playlist *pl_system_get(unsigned int id)
{
return (id < SYS_PL_NUM_PLAYLISTS) ? &sys_playlists[id] : NULL;
}
static void pl_system_played(struct track *track)
{
unsigned int i;
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_update(pl_system_get(i), track);
sys_pl_update(pl_system_lookup("Unplayed"));
sys_pl_update(pl_system_lookup("Most Played"));
sys_pl_update(pl_system_lookup("Least Played"));
}
static void pl_system_selected(struct track *track)
{
unsigned int i;
sys_pl_queued_remove(pl_system_get(SYS_PL_QUEUED), track);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_update(pl_system_get(i), track);
}
struct playlist_type pl_system = {
.pl_save = pl_system_save,
.pl_lookup = pl_system_lookup,
.pl_get = pl_system_get,
.pl_played = pl_system_played,
.pl_selected = pl_system_selected,
};
void pl_system_init(void)
{
struct playlist *playlist;
unsigned int i;
idle_schedule(IDLE_SYNC, __sys_pl_load_new, NULL);
for (i = 0; i < SYS_PL_NUM_PLAYLISTS; i++) {
playlist = pl_system_get(i);
switch (i) {
case SYS_PL_QUEUED:
case SYS_PL_HISTORY:
playlist_generic_init(playlist, 0);
break;
case SYS_PL_COLLECTION:
case SYS_PL_UNPLAYED:
case SYS_PL_MOST_PLAYED:
case SYS_PL_LEAST_PLAYED:
sys_pl_update(playlist);
case SYS_PL_FAVORITES:
case SYS_PL_HIDDEN:
playlist_generic_init(playlist, 4, COMPARE_ARTIST,
COMPARE_YEAR, COMPARE_ALBUM, COMPARE_TRACK);
break;
}
}
}
void pl_system_deinit()
{
for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_deinit(pl_system_get(i));
}
void pl_system_new_track(struct track *track)
{
playlist_generic_add(pl_system_lookup("Collection"), track);
playlist_generic_add(pl_system_lookup("Unplayed"), track);
}
void pl_system_delete_track(struct track *track)
{
for (unsigned int i = 0; i < SYS_PL_NUM_PLAYLISTS; i++)
playlist_generic_remove(pl_system_get(i), track);
}

View File

@ -1,178 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/playlists/user.h>
static struct database user_db;
static struct playlist_ops user_ops;
static struct user_playlist *__user_db_alloc(gchar *name, unsigned int index)
{
struct user_playlist *playlist = g_malloc(sizeof(struct user_playlist));
dbe_init(&playlist->pl_dbe, playlist);
playlist->pl_playlist.pl_name = name;
playlist->pl_playlist.pl_type = PL_USER;
playlist->pl_playlist.pl_id = index;
playlist->pl_playlist.pl_ops = &user_ops;
playlist_generic_init(&playlist->pl_playlist, 0);
return playlist;
}
static struct db_entry *user_db_alloc(const gchar *name, unsigned int index)
{
return &__user_db_alloc(g_strdup(name), index)->pl_dbe;
}
static void user_db_free(struct db_entry *dbe)
{
playlist_generic_deinit(&USER_PLAYLIST(dbe)->pl_playlist);
g_free(USER_PLAYLIST(dbe)->pl_playlist.pl_name);
g_free(USER_PLAYLIST(dbe));
}
static gchar *user_db_key(struct db_entry *dbe)
{
return g_strdup(USER_PLAYLIST(dbe)->pl_playlist.pl_name);
}
static struct db_entry *user_db_read(struct file *file, unsigned int index)
{
gchar *name = file_readl(file);
struct user_playlist *playlist = __user_db_alloc(name, index);
playlist_generic_load(&playlist->pl_playlist, file, PL_SAVE_ALL);
return &playlist->pl_dbe;
}
static void user_db_write(struct file *file, struct db_entry *dbe)
{
struct playlist *playlist = &USER_PLAYLIST(dbe)->pl_playlist;
file_writef(file, "%s\n", playlist->pl_name);
playlist_generic_save(playlist, file, PL_SAVE_ALL);
}
static const struct db_ops user_db_ops = {
.dbe_alloc = user_db_alloc,
.dbe_free = user_db_free,
.dbe_key = user_db_key,
.dbe_read = user_db_read,
.dbe_write = user_db_write,
};
static bool pl_user_delete(struct playlist *playlist)
{
struct db_entry *dbe = db_get(&user_db, playlist->pl_name);
if (dbe) {
db_remove(&user_db, dbe);
db_defrag(&user_db);
}
return dbe != NULL;
}
static struct playlist_ops user_ops = {
.pl_add = playlist_generic_add,
.pl_can_select = playlist_generic_can_select,
.pl_delete = pl_user_delete,
.pl_remove = playlist_generic_remove,
.pl_set_random = playlist_generic_set_random,
.pl_sort = playlist_generic_sort,
.pl_rearrange = playlist_generic_rearrange,
};
static void pl_user_save(void)
{
db_save(&user_db);
}
static struct playlist *pl_user_lookup(const gchar *name)
{
struct db_entry *dbe = db_get(&user_db, name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
static struct playlist *pl_user_get(unsigned int id)
{
struct db_entry *dbe = db_at(&user_db, id);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
static struct playlist *pl_user_new(const gchar *name)
{
struct db_entry *dbe;
if (db_get(&user_db, name))
return NULL;
dbe = db_insert(&user_db, name);
return dbe ? &USER_PLAYLIST(dbe)->pl_playlist : NULL;
}
static void pl_user_played(struct track *track)
{
struct db_entry *dbe, *next;
db_for_each(dbe, next, &user_db)
playlist_generic_update(&USER_PLAYLIST(dbe)->pl_playlist, track);
}
struct playlist_type pl_user = {
.pl_save = pl_user_save,
.pl_lookup = pl_user_lookup,
.pl_get = pl_user_get,
.pl_new = pl_user_new,
.pl_played = pl_user_played,
.pl_selected = pl_user_played,
};
void pl_user_init(void)
{
db_init(&user_db, "playlist.user", true, &user_db_ops, 0);
db_load(&user_db);
}
void pl_user_deinit()
{
db_deinit(&user_db);
}
struct database *pl_user_db_get()
{
return &user_db;
}
void pl_user_delete_track(struct track *track)
{
struct db_entry *dbe, *next;
struct playlist *playlist;
db_for_each(dbe, next, &user_db) {
playlist = &USER_PLAYLIST(dbe)->pl_playlist;
playlist_generic_remove(playlist, track);
}
}
bool pl_user_rename(struct playlist *playlist, const gchar *name)
{
struct db_entry *dbe;
if (!playlist || db_get(&user_db, name))
return false;
dbe = db_get(&user_db, playlist->pl_name);
if (!dbe)
return false;
g_free(playlist->pl_name);
playlist->pl_name = g_strdup(name);
db_rekey(&user_db, dbe);
pl_user_save();
return true;
}

271
core/queue.c Normal file
View File

@ -0,0 +1,271 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/queue.h>
#include <core/string.h>
#include <stdlib.h>
static int track_less_than(const void *a, const void *b, void *data)
{
struct track *lhs = (struct track *)a;
struct track *rhs = (struct track *)b;
GSList *cur = (GSList *)data;
int res, field;
while (cur) {
field = GPOINTER_TO_INT(cur->data);
if (field > 0)
res = track_compare(lhs, rhs, field);
else
res = track_compare(rhs, lhs, abs(field));
if (res == 0) {
cur = g_slist_next(cur);
continue;
}
break;
};
return res;
}
static inline void *__queue_init(struct queue *queue)
{
if (queue->q_ops)
return queue->q_ops->qop_init(queue);
return NULL;
}
static inline void __queue_deinit(struct queue *queue)
{
if (queue->q_ops)
queue->q_ops->qop_deinit(queue);
}
static inline unsigned int __queue_added(struct queue *queue,
struct track *track,
unsigned int pos)
{
queue->q_length += track->tr_length;
if (queue->q_ops)
queue->q_ops->qop_added(queue, pos);
return pos;
}
static inline bool __queue_erase(struct queue *queue, struct _q_iter *it)
{
struct track *track = (struct track *)_q_iter_val(it);
if (queue->q_ops)
return queue->q_ops->qop_erase(queue, track);
return true;
}
static inline void __queue_remove(struct queue *queue, struct _q_iter *it)
{
unsigned int pos = it->it_pos;
struct track *track = (struct track *)_q_remove_it(&queue->q_tracks, it);
queue->q_length -= track->tr_length;
if (queue->q_cur.it_pos == pos)
_q_iter_prev(&queue->q_cur);
if (queue->q_ops)
queue->q_ops->qop_removed(queue, pos);
}
static inline void __queue_clear(struct queue *queue, unsigned int n)
{
queue->q_length = 0;
if (queue->q_ops)
queue->q_ops->qop_cleared(queue, n);
}
static inline void __queue_updated(struct queue *queue, unsigned int pos)
{
if (queue->q_ops)
queue->q_ops->qop_updated(queue, pos);
}
static inline struct track *__queue_selected(struct queue *queue, unsigned int pos)
{
struct track *track = (struct track *)_q_iter_val(&queue->q_cur);
if (queue_has_flag(queue, Q_REPEAT) == false)
__queue_remove(queue, &queue->q_cur);
else
__queue_updated(queue, pos);
return track;
}
static inline void __queue_save(struct queue *queue, enum queue_flags flag)
{
if (queue->q_ops && queue_has_flag(queue, flag))
queue->q_ops->qop_save(queue, flag);
}
void queue_init(struct queue *queue, unsigned int flags,
const struct queue_ops *ops)
{
queue->q_flags = flags;
queue->q_length = 0;
queue->q_sort = NULL;
queue->q_ops = ops;
queue->q_cur.it_pos = -1;
queue->q_cur.it_iter = NULL;
_q_init(&queue->q_tracks);
queue->q_private = __queue_init(queue);
}
void queue_deinit(struct queue *queue)
{
queue_clear(queue);
__queue_deinit(queue);
g_slist_free(queue->q_sort);
queue->q_sort = NULL;
}
void queue_set_flag(struct queue *queue, enum queue_flags flag)
{
queue->q_flags |= flag;
__queue_save(queue, Q_SAVE_FLAGS);
}
void queue_unset_flag(struct queue *queue, enum queue_flags flag)
{
queue->q_flags &= ~flag;
__queue_save(queue, Q_SAVE_FLAGS);
}
unsigned int queue_add(struct queue *queue, struct track *track)
{
unsigned int pos;
if (queue_has_flag(queue, Q_ADD_FRONT))
pos = _q_add_head(&queue->q_tracks, track);
else if (queue->q_sort)
pos = _q_add_sorted(&queue->q_tracks, track,
track_less_than, queue->q_sort);
else
pos = _q_add_tail(&queue->q_tracks, track);
return __queue_added(queue, track, pos);
}
void queue_erase(struct queue *queue, unsigned int index)
{
struct _q_iter it;
_q_iter_set(&queue->q_tracks, &it, index);
if (__queue_erase(queue, &it))
__queue_remove(queue, &it);
}
void queue_remove(struct queue *queue, unsigned int index)
{
struct _q_iter it;
_q_iter_set(&queue->q_tracks, &it, index);
__queue_remove(queue, &it);
}
void queue_remove_all(struct queue *queue, struct track *track)
{
struct _q_iter it;
while (queue_at(queue, 0) == track)
queue_remove(queue, 0);
_q_for_each(&queue->q_tracks, &it) {
if (_q_iter_val(&it) == track)
__queue_remove(queue, &it);
}
}
void queue_clear(struct queue *queue)
{
unsigned int n = queue_size(queue);
_q_clear(&queue->q_tracks);
__queue_clear(queue, n);
}
void queue_updated(struct queue *queue, struct track *track)
{
struct _q_iter it;
_q_for_each(&queue->q_tracks, &it) {
if (_q_iter_val(&it) == track)
__queue_updated(queue, it.it_pos);
}
}
struct track *queue_selected(struct queue *queue, unsigned int index)
{
if (index > queue_size(queue))
return NULL;
if (queue->q_cur.it_pos != index)
_q_iter_set(&queue->q_tracks, &queue->q_cur, index);
return __queue_selected(queue, index);
}
struct track *queue_next(struct queue *queue)
{
unsigned int pos, size = queue_size(queue);
if (!queue_has_flag(queue, Q_ENABLED))
return NULL;
else if (size == 0)
return NULL;
if (size == 1)
_q_iter_set(&queue->q_tracks, &queue->q_cur, 0);
else if (queue_has_flag(queue, Q_RANDOM)) {
pos = g_random_int_range(1, (size < 15) ? size : size / 3);
pos += queue->q_cur.it_pos;
_q_iter_set(&queue->q_tracks, &queue->q_cur, pos % size);
} else {
_q_iter_next(&queue->q_cur);
if (!queue->q_cur.it_iter)
_q_iter_set(&queue->q_tracks, &queue->q_cur, 0);
}
return __queue_selected(queue, queue->q_cur.it_pos);
}
void queue_resort(struct queue *queue)
{
_q_sort(&queue->q_tracks, track_less_than, queue->q_sort);
for (unsigned int i = 0; i < queue_size(queue); i++)
__queue_updated(queue, i);
}
void queue_sort(struct queue *queue, enum compare_t sort, bool reset)
{
GSList *cur = NULL;
int field;
if (queue_has_flag(queue, Q_NO_SORT))
return;
if (reset) {
g_slist_free(queue->q_sort);
queue->q_sort = NULL;
}
cur = queue->q_sort;
while (cur) {
field = GPOINTER_TO_INT(cur->data);
if (abs(field) == sort) {
cur->data = GINT_TO_POINTER(-field);
goto out_sort;
}
cur = g_slist_next(cur);
}
queue->q_sort = g_slist_append(queue->q_sort, GINT_TO_POINTER(sort));
out_sort:
queue_resort(queue);
__queue_save(queue, Q_SAVE_SORT);
}

View File

@ -46,60 +46,51 @@ gchar *string_tm2str(struct tm *tm)
return buf;
}
int string_compare_tokens(gchar **lhs, gchar **rhs)
static gunichar __string_get_char(const gchar *str, const gchar *cur,
const gchar *res)
{
unsigned int i, cmp;
gunichar c = g_utf8_get_char(cur);
gchar *prev = g_utf8_find_prev_char(str, res);
if (!lhs[0] && rhs[0])
return 1;
if (lhs[0] && !rhs[0])
return -1;
for (i = 0; lhs[i]; i++) {
if (!rhs[i])
break;
cmp = g_utf8_collate(lhs[i], rhs[i]);
if (cmp != 0)
return cmp;
if (g_unichar_ismark(c))
return '\0';
if (g_unichar_ispunct(c))
return '\0';
if (g_unichar_isspace(c)) {
if (!prev || (*prev == ' '))
return '\0';
return ' ';
}
if (lhs[i])
return 1;
if (rhs[i])
return -1;
return 0;
return g_unichar_tolower(c);
}
bool string_match_token(const gchar *prefix, gchar **tokens)
gchar *string_lowercase(const gchar *str)
{
unsigned int i;
gchar *res = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT);
gchar *i, *j = res;
gunichar c;
for (i = 0; tokens[i]; i++) {
if (g_str_has_prefix(tokens[i], prefix))
return true;
}
return false;
}
bool string_is_subdir(const gchar *a, const gchar *b)
{
gchar **parent = b ? g_strsplit(b, "/", -1) : NULL;
gchar **child = a ? g_strsplit(a, "/", -1) : NULL;
bool subdir = true;
int i;
if (!parent || !child)
return false;
for (i = 0; parent[i]; i++) {
if (!child[i] || g_utf8_collate(parent[i], child[i]) != 0) {
subdir = false;
break;
for (i = res; *i != '\0'; i = g_utf8_next_char(i)) {
c = __string_get_char(res, i, j);
if (c) {
*j = c;
j = g_utf8_next_char(j);
}
}
*j = '\0';
g_strfreev(parent);
g_strfreev(child);
return subdir;
g_strchomp(res);
return g_realloc(res, strlen(res) + 1);
}
int string_compare(const gchar *lhs, const gchar *rhs)
{
if (strlen(lhs) == 0) {
if (strlen(rhs) == 0)
return 0;
return 1;
}
if (strlen(rhs) == 0)
return -1;
return g_utf8_collate(lhs, rhs);
}

6
core/tags/Sconscript Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/python
Import("env")
env.UsePackage("taglib_c")
res = Glob("*.c")
Return("res")

View File

@ -1,307 +1,85 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/string.h>
#include <core/version.h>
#include <core/tags/album.h>
#include <core/tags/genre.h>
#include <coverart/caa_c.h>
#include <musicbrainz5/mb5_c.h>
#ifdef CONFIG_TESTING
#define OCARINA_AGENT "ocarina-test"
#else
#define OCARINA_AGENT OCARINA_NAME
#endif
#define ALBUM_DB_MIN 0 /* Ocarina 6.0 */
static struct database album_db;
static bool album_db_upgraded = false;
struct album_cache_file {
struct file ac_file;
gchar *ac_subdir;
gchar *ac_name;
};
struct album_cache_file *__album_alloc_file(struct album *al)
static gchar *__album_key(const gchar *name, unsigned int year)
{
struct album_cache_file *ret = g_malloc(sizeof(struct album_cache_file));
gchar *name = g_uri_escape_string(al->al_name, " ", true);
ret->ac_subdir = g_strdup_printf("%d", al->al_year);
ret->ac_name = g_strdup_printf("%s.jpg", name);
file_init_cache(&ret->ac_file, ret->ac_subdir, ret->ac_name);
g_free(name);
return ret;
return g_strdup_printf("%u/%s", year, name);
}
static inline void __album_free_file(struct album_cache_file *acf)
{
g_free(acf->ac_subdir);
g_free(acf->ac_name);
g_free(acf);
}
static bool __album_fetch_cover(struct album *album, gchar *releaseid)
{
struct album_cache_file *file;
CaaImageData image;
CaaCoverArt *caa;
gchar error[256];
caa = caa_coverart_new(OCARINA_AGENT);
if (!caa)
return false;
image = caa_coverart_fetch_front(caa, releaseid);
if (!image) {
caa_coverart_get_lasterrormessage(caa, error, sizeof(error));
g_printf("Cover Art Archive: %s\n", error);
goto out;
}
file = __album_alloc_file(album);
if (file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
file_write(&file->ac_file, caa_imagedata_data(image),
caa_imagedata_size(image));
file_close(&file->ac_file);
}
__album_free_file(file);
caa_imagedata_delete(image);
out:
caa_coverart_delete(caa);
return album_artwork_exists(album);
}
static bool __album_foreach_fetch(struct album *album, Mb5Metadata metadata)
{
Mb5ReleaseList list = mb5_metadata_get_releaselist(metadata);
gchar releaseid[40];
Mb5Release release;
unsigned int i;
for (i = 0; i < mb5_release_list_size(list); i++) {
release = mb5_release_list_item(list, i);
if (!release)
break;
mb5_release_get_id(release, releaseid, sizeof(releaseid));
if (__album_fetch_cover(album, releaseid))
return true;
}
return false;
}
static bool __album_run_query(struct album *album, gchar *term1,
gchar *term2, gchar *term3)
{
gchar *param, *query = "query";
Mb5Metadata data = NULL;
unsigned int code;
gchar error[256];
bool ret = false;
Mb5Query *mb5;
param = g_strjoin(" AND ", term1, term2, term3, NULL);
do {
mb5 = mb5_query_new(OCARINA_AGENT, NULL, 0);
if (!mb5)
break;
data = mb5_query_query(mb5, "release", "", "", 1, &query, &param);
code = mb5_query_get_lasthttpcode(mb5);
if (mb5_query_get_lastresult(mb5) != 0) {
mb5_query_get_lasterrormessage(mb5, error, sizeof(error));
g_printf("MusicBrainz: %s\n", error);
}
mb5_query_delete(mb5);
} while (code == 503);
if (data) {
ret = __album_foreach_fetch(album, data);
mb5_metadata_delete(data);
}
g_free(param);
return ret;
}
static bool __album_query_artist(struct album *album, struct artist *al_artist,
gchar *lower)
{
gchar *release, *artist, *year;
bool found = false;
if (!al_artist || !string_length(al_artist->ar_name) ||
strcmp(al_artist->ar_tokens[0], "various") == 0)
return false;
release = g_strdup_printf("release:\"%s\"~", lower);
artist = g_strdup_printf("artist:\"%s\"~", al_artist->ar_name);
year = g_strdup_printf("date:%d*", album->al_year);
if (album->al_year > 0)
found = __album_run_query(album, release, artist, year);
if (!found)
found = __album_run_query(album, release, artist, NULL);
if (!found && album->al_year > 0)
found = __album_run_query(album, lower, artist, year);
if (!found)
found = __album_run_query(album, lower, artist, NULL);
g_free(release);
g_free(artist);
g_free(year);
return found;
}
static bool __album_fetch_artwork(struct album *album)
{
gchar *lower;
if (album_artwork_exists(album))
return true;
if (string_length(album->al_name) == 0)
return true;
lower = g_strjoinv(" ", album->al_tokens);
if (!__album_query_artist(album, album->al_artist, lower))
__album_run_query(album, lower, NULL, NULL);
g_free(lower);
return true;
}
static gchar *__album_key(struct artist *artist, struct genre *genre,
const gchar *name, unsigned int year)
{
if (!artist || !genre)
return g_strdup_printf("%u/%s", year, name);
return g_strdup_printf("%u/%u/%u/%s", artist_index(artist),
genre_index(genre), year, name);
}
static struct album *__album_alloc(struct artist *artist, struct genre *genre,
gchar *name, unsigned int year)
static struct album *__album_alloc(gchar *name, unsigned int year)
{
struct album *album = g_malloc(sizeof(struct album));
dbe_init(&album->al_dbe, album);
album->al_year = year;
album->al_name = name;
album->al_tokens = g_str_tokenize_and_fold(name, NULL, &album->al_alts);
album->al_artist = artist;
album->al_genre = genre;
album->al_year = year;
album->al_name = name;
album->al_lower = string_lowercase(album->al_name);
if (!album_artwork_exists(album) && artist && genre)
idle_schedule(IDLE_ASYNC, IDLE_FUNC(__album_fetch_artwork), album);
return album;
}
static struct db_entry *__album_alloc_v0(const gchar *key)
static struct db_entry *album_alloc(const gchar *key)
{
unsigned int year;
gchar *name;
if (sscanf(key, "%u/%m[^\n]", &year, &name) == 1)
name = g_strdup("");
return &__album_alloc(NULL, NULL, name, year)->al_dbe;
}
static struct db_entry *album_alloc(const gchar *key, unsigned int index)
{
unsigned int artist_id, genre_id, year, n;
gchar *name;
n = sscanf(key, "%u/%u/%u/%m[^\n]", &artist_id, &genre_id, &year, &name);
if (n == 1)
return __album_alloc_v0(key);
else if (n == 3)
name = g_strdup("");
return &__album_alloc(artist_get(artist_id), genre_get(genre_id),
name, year)->al_dbe;
return &__album_alloc(name, year)->al_dbe;
}
static void album_free(struct db_entry *dbe)
{
g_free(ALBUM(dbe)->al_name);
g_strfreev(ALBUM(dbe)->al_tokens);
g_strfreev(ALBUM(dbe)->al_alts);
g_free(ALBUM(dbe)->al_lower);
g_free(ALBUM(dbe));
}
static gchar *album_key(struct db_entry *dbe)
{
return __album_key(ALBUM(dbe)->al_artist, ALBUM(dbe)->al_genre,
ALBUM(dbe)->al_name, ALBUM(dbe)->al_year);
return __album_key(ALBUM(dbe)->al_name, ALBUM(dbe)->al_year);
}
static struct album *__album_parse_v0(gchar *line)
static struct db_entry *album_read(struct file *file)
{
unsigned int year;
gchar *name;
if (sscanf(line, "%u %m[^\n]", &year, &name) == 1)
name = g_strdup("");
return __album_alloc(NULL, NULL, name, year);
}
static struct db_entry *album_read(struct file *file, unsigned int index)
{
unsigned int year, artist_id, genre_id, n;
struct album *album;
gchar *line, *name;
line = file_readl(file);
if (file_version(file) == 0) {
album = __album_parse_v0(line);
album_db_upgraded = true;
goto out;
}
n = sscanf(line, "%u %u %u %m[^\n]", &artist_id, &genre_id, &year, &name);
if (n == 3)
if (sscanf(line, "%u %m[^\n]", &year, &name) == 1)
name = g_strdup("");
album = __album_alloc(artist_get(artist_id),
genre_get(genre_id), name, year);
out:
g_free(line);
return &album->al_dbe;
return &__album_alloc(name, year)->al_dbe;
}
static void album_write(struct file *file, struct db_entry *dbe)
{
struct album *album = ALBUM(dbe);
struct artist *artist = album->al_artist;
struct genre *genre = album->al_genre;
file_writef(file, "%u %u %u %s", artist ? artist_index(artist) : 0,
genre ? genre_index(genre) : 0,
album->al_year, album->al_name);
file_writef(file, "%u %s", ALBUM(dbe)->al_year, ALBUM(dbe)->al_name);
}
static const struct db_ops album_ops = {
.dbe_alloc = album_alloc,
.dbe_free = album_free,
.dbe_key = album_key,
.dbe_read = album_read,
.dbe_write = album_write,
album_alloc,
album_free,
album_key,
album_read,
NULL,
album_write,
};
void album_db_init()
{
db_init(&album_db, "album.db", true, &album_ops, ALBUM_DB_MIN);
db_load(&album_db);
db_init(&album_db, "album.db", true, &album_ops);
db_load_idle(&album_db);
}
void album_db_deinit()
@ -309,30 +87,9 @@ void album_db_deinit()
db_deinit(&album_db);
}
bool album_db_defrag()
struct album *album_find(const gchar *name, unsigned int year)
{
return db_defrag(&album_db);
}
bool album_db_upgrade_done()
{
struct db_entry *dbe, *next;
if (album_db_upgraded == false)
return false;
db_for_each(dbe, next, &album_db) {
if (!ALBUM(dbe)->al_artist && !ALBUM(dbe)->al_genre)
db_remove(&album_db, dbe);
}
return true;
}
struct album *album_find(struct artist *artist, struct genre *genre,
const gchar *name, unsigned int year)
{
gchar *key = __album_key(artist, genre, name, year);
gchar *key = __album_key(name, year);
struct album *album = ALBUM(db_find(&album_db, key));
g_free(key);
return album;
@ -345,7 +102,7 @@ struct album *album_get(const unsigned int index)
int album_compare(struct album *lhs, struct album *rhs)
{
return string_compare_tokens(lhs->al_tokens, rhs->al_tokens);
return string_compare(lhs->al_lower, rhs->al_lower);
}
int album_compare_year(struct album *lhs, struct album *rhs)
@ -355,52 +112,6 @@ int album_compare_year(struct album *lhs, struct album *rhs)
return lhs->al_year - rhs->al_year;
}
bool album_match_token(struct album *album, const gchar *string)
{
return string_match_token(string, album->al_tokens) ||
string_match_token(string, album->al_alts);
}
bool album_artwork_exists(struct album *album)
{
struct album_cache_file *file;
bool ret;
file = __album_alloc_file(album);
ret = file_exists(&file->ac_file);
__album_free_file(file);
return ret;
}
gchar *album_artwork_path(struct album *album)
{
struct album_cache_file *file;
gchar *ret = NULL;
file = __album_alloc_file(album);
if (file_exists(&file->ac_file))
ret = file_path(&file->ac_file);
__album_free_file(file);
return ret;
}
bool album_artwork_import(struct album *album, gchar *path)
{
struct album_cache_file *file;
bool ret = false;
file = __album_alloc_file(album);
if (path && file_open(&file->ac_file, OPEN_WRITE_BINARY)) {
ret = file_import(&file->ac_file, path);
file_close(&file->ac_file);
}
__album_free_file(file);
return ret;
}
#ifdef CONFIG_TESTING
const struct db_ops *test_album_ops() { return &album_ops; }
#endif /* CONFIG_TESTING */

View File

@ -12,23 +12,21 @@ static struct artist *__artist_alloc(gchar *name)
struct artist *artist = g_malloc(sizeof(struct artist));
dbe_init(&artist->ar_dbe, artist);
artist->ar_name = name;
artist->ar_tokens = g_str_tokenize_and_fold(name, NULL, &artist->ar_alts);
artist->ar_playlist = NULL;
artist->ar_name = name;
artist->ar_lower = string_lowercase(name);
return artist;
}
static struct db_entry *artist_alloc(const gchar *name, unsigned int index)
static struct db_entry *artist_alloc(const gchar *name)
{
return &__artist_alloc(g_strdup(name))->ar_dbe;
}
static void artist_free(struct db_entry *dbe)
{
g_strfreev(ARTIST(dbe)->ar_tokens);
g_strfreev(ARTIST(dbe)->ar_alts);
g_free(ARTIST(dbe)->ar_lower);
g_free(ARTIST(dbe));
}
@ -37,7 +35,7 @@ static gchar *artist_key(struct db_entry *dbe)
return ARTIST(dbe)->ar_name;
}
struct db_entry *artist_read(struct file *file, unsigned int index)
struct db_entry *artist_read(struct file *file)
{
return &__artist_alloc(file_readl(file))->ar_dbe;
}
@ -49,18 +47,19 @@ static void artist_write(struct file *file, struct db_entry *dbe)
static const struct db_ops artist_ops = {
.dbe_alloc = artist_alloc,
.dbe_free = artist_free,
.dbe_key = artist_key,
.dbe_read = artist_read,
.dbe_write = artist_write,
artist_alloc,
artist_free,
artist_key,
artist_read,
NULL,
artist_write,
};
void artist_db_init()
{
db_init(&artist_db, "artist.db", true, &artist_ops, 0);
db_load(&artist_db);
db_init(&artist_db, "artist.db", true, &artist_ops);
db_load_idle(&artist_db);
}
void artist_db_deinit()
@ -68,21 +67,11 @@ void artist_db_deinit()
db_deinit(&artist_db);
}
const struct database *artist_db_get()
{
return &artist_db;
}
struct artist *artist_find(const gchar *name)
{
return ARTIST(db_find(&artist_db, name));
}
struct artist *artist_lookup(const gchar *name)
{
return ARTIST(db_get(&artist_db, name));
}
struct artist *artist_get(const unsigned int index)
{
return ARTIST(db_at(&artist_db, index));
@ -90,14 +79,9 @@ struct artist *artist_get(const unsigned int index)
int artist_compare(struct artist *lhs, struct artist *rhs)
{
return string_compare_tokens(lhs->ar_tokens, rhs->ar_tokens);
return string_compare(lhs->ar_lower, rhs->ar_lower);
}
bool artist_match_token(struct artist *artist, const gchar *string)
{
return string_match_token(string, artist->ar_tokens) ||
string_match_token(string, artist->ar_alts);
}
#ifdef CONFIG_TESTING
const struct db_ops *test_artist_ops() { return &artist_ops; }
#endif /* CONFIG_TESTING */

View File

@ -12,21 +12,20 @@ static struct genre *__genre_alloc(gchar *name)
struct genre *genre = g_malloc(sizeof(struct genre));
dbe_init(&genre->ge_dbe, genre);
genre->ge_name = name;
genre->ge_tokens = g_str_tokenize_and_fold(name, NULL, &genre->ge_alts);
genre->ge_name = name;
genre->ge_lower = string_lowercase(name);
return genre;
}
static struct db_entry *genre_alloc(const gchar *name, unsigned int index)
static struct db_entry *genre_alloc(const gchar *name)
{
return &__genre_alloc(g_strdup(name))->ge_dbe;
}
static void genre_free(struct db_entry *dbe)
{
g_strfreev(GENRE(dbe)->ge_tokens);
g_strfreev(GENRE(dbe)->ge_alts);
g_free(GENRE(dbe)->ge_lower);
g_free(GENRE(dbe));
}
@ -35,7 +34,7 @@ static gchar *genre_key(struct db_entry *dbe)
return GENRE(dbe)->ge_name;
}
static struct db_entry *genre_read(struct file *file, unsigned int index)
static struct db_entry *genre_read(struct file *file)
{
return &__genre_alloc(file_readl(file))->ge_dbe;
}
@ -47,18 +46,19 @@ static void genre_write(struct file *file, struct db_entry *dbe)
static const struct db_ops genre_ops = {
.dbe_alloc = genre_alloc,
.dbe_free = genre_free,
.dbe_key = genre_key,
.dbe_read = genre_read,
.dbe_write = genre_write,
genre_alloc,
genre_free,
genre_key,
genre_read,
NULL,
genre_write,
};
void genre_db_init()
{
db_init(&genre_db, "genre.db", true, &genre_ops, 0);
db_load(&genre_db);
db_init(&genre_db, "genre.db", true, &genre_ops);
db_load_idle(&genre_db);
}
void genre_db_deinit()
@ -78,13 +78,7 @@ struct genre *genre_get(const unsigned int index)
int genre_compare(struct genre *lhs, struct genre *rhs)
{
return string_compare_tokens(lhs->ge_tokens, rhs->ge_tokens);
}
bool genre_match_token(struct genre *genre, const gchar *string)
{
return string_match_token(string, genre->ge_tokens) ||
string_match_token(string, genre->ge_alts);
return string_compare(lhs->ge_lower, rhs->ge_lower);
}
#ifdef CONFIG_TESTING

View File

@ -1,26 +1,26 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/string.h>
#include <core/tags/library.h>
#define LIBRARY_DB_MIN 0 /* Ocarina 6.0 */
static struct database library_db;
static struct library *__library_alloc(gchar *path)
static struct library *__library_alloc(gchar *path, bool enabled)
{
struct library *library = g_malloc(sizeof(struct library));
dbe_init(&library->li_dbe, library);
library->li_path = path;
library->li_playlist = NULL;
library->li_size = 0;
library->li_enabled = enabled;
library->li_path = path;
return library;
}
static struct db_entry *library_alloc(const gchar *path, unsigned int index)
static struct db_entry *library_alloc(const gchar *path)
{
return &__library_alloc(g_strdup(path))->li_dbe;
return &__library_alloc(g_strdup(path), true)->li_dbe;
}
static void library_free(struct db_entry *dbe)
@ -33,37 +33,36 @@ static gchar *library_key(struct db_entry *dbe)
return LIBRARY(dbe)->li_path;
}
static struct db_entry *library_read(struct file *file, unsigned int index)
static struct db_entry *library_read(struct file *file)
{
int enabled;
gchar *path;
/* Old "enabled" flag */
if (file_version(file) == 0)
file_readd(file);
path = file_readl(file);
return &__library_alloc(path)->li_dbe;
file_readf(file, "%d %m[^\n]", &enabled, &path);
return &__library_alloc(path, enabled)->li_dbe;
}
static void library_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%s", LIBRARY(dbe)->li_path);
file_writef(file, "%d %s", LIBRARY(dbe)->li_enabled,
LIBRARY(dbe)->li_path);
}
static const struct db_ops library_ops = {
.dbe_alloc = library_alloc,
.dbe_free = library_free,
.dbe_key = library_key,
.dbe_read = library_read,
.dbe_write = library_write,
library_alloc,
library_free,
library_key,
library_read,
NULL,
library_write,
};
void library_db_init()
{
db_init(&library_db, "library.db", true, &library_ops, LIBRARY_DB_MIN);
db_load(&library_db);
db_init(&library_db, "library.db", true, &library_ops);
db_load_idle(&library_db);
}
void library_db_deinit()
@ -71,11 +70,6 @@ void library_db_deinit()
db_deinit(&library_db);
}
bool library_db_defrag()
{
return db_defrag(&library_db);
}
const struct database *library_db_get()
{
return &library_db;
@ -83,22 +77,7 @@ const struct database *library_db_get()
struct library *library_find(const gchar *path)
{
struct library *library = library_lookup(path);
if (library)
return library;
return LIBRARY(db_insert(&library_db, path));
}
struct library *library_lookup(const gchar *path)
{
struct db_entry *dbe, *next;
db_for_each(dbe, next, &library_db) {
if (string_is_subdir(path, LIBRARY(dbe)->li_path))
return LIBRARY(dbe);
}
return NULL;
return LIBRARY(db_find(&library_db, path));
}
struct library *library_get(const unsigned int index)
@ -112,6 +91,12 @@ void library_remove(struct library *library)
db_remove(&library_db, &library->li_dbe);
}
void library_set_enabled(struct library *library, bool enabled)
{
library->li_enabled = enabled;
db_save(&library_db);
}
gchar *library_file(struct library *library, const gchar *path)
{
return g_strdup_printf("%s/%s", library->li_path, path);

View File

@ -1,7 +1,6 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/tags/album.h>
#include <core/tags/artist.h>
#include <core/tags/genre.h>
@ -9,22 +8,13 @@
#include <core/tags/tags.h>
#include <core/tags/track.h>
static bool tags_upgrade_done(void *data)
{
if (album_db_upgrade_done())
track_db_commit();
return true;
}
void tags_init()
{
album_db_init();
artist_db_init();
genre_db_init();
album_db_init();
library_db_init();
track_db_init();
idle_schedule(IDLE_SYNC, tags_upgrade_done, NULL);
}
void tags_deinit()
@ -32,19 +22,6 @@ void tags_deinit()
track_db_deinit();
library_db_deinit();
genre_db_deinit();
album_db_deinit();
artist_db_deinit();
}
bool tags_defragment(void *data)
{
bool album = album_db_defrag();
bool library = library_db_defrag();
bool track = track_db_defrag();
if (library)
track_db_rekey();
if (album || library || track)
track_db_commit();
return track;
album_db_deinit();
}

View File

@ -1,13 +1,14 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/filter.h>
#include <core/string.h>
#include <core/tags/track.h>
#include <glib.h>
#include <taglib/tag_c.h>
#define TRACK_DB_MIN 0 /* Ocarina 6.0 */
static struct database track_db;
static unsigned int unplayed_count = 0;
static unsigned int play_count = 0;
@ -17,7 +18,7 @@ static gchar *__track_key(struct library *library, gchar *path)
gchar *res;
if (library)
res = g_strdup_printf("%u/%s", library_index(library), path);
res = g_strdup_printf("%u/%s", library->li_dbe.dbe_index, path);
else
res = g_strdup("");
@ -41,70 +42,48 @@ static struct track *__track_alloc()
return track;
}
static struct track *__track_alloc_filepath(const gchar *filepath)
{
TagLib_File *file = taglib_file_new(filepath);
const TagLib_AudioProperties *audio;
struct track *track = NULL;
struct artist *artist;
struct genre *genre;
TagLib_Tag *tag;
struct db_entry *track_alloc(const gchar *key)
{
const TagLib_AudioProperties *audio;
struct library *library;
struct track *track = NULL;
unsigned int lib_id;
TagLib_File *file;
TagLib_Tag *tag;
char *fullpath, *path;
sscanf(key, "%u/%m[^\n]", &lib_id, &path);
library = library_get(lib_id);
fullpath = library_file(library, path);
file = taglib_file_new(fullpath);
if (!file || !taglib_file_is_valid(file)) {
g_printerr("WARNING: Could not read tags for: %s\n", filepath);
return NULL;
printf("WARNING: Could not read tags for: %s\n", fullpath);
goto out;
}
track = __track_alloc();
tag = taglib_file_tag(file);
audio = taglib_file_audioproperties(file);
artist = artist_find(taglib_tag_artist(tag));
genre = genre_find(taglib_tag_genre(tag));
track = __track_alloc();
tag = taglib_file_tag(file);
audio = taglib_file_audioproperties(file);
track->tr_album = album_find(artist, genre, taglib_tag_album(tag),
taglib_tag_year(tag));
track->tr_album = album_find(taglib_tag_album(tag), taglib_tag_year(tag));
track->tr_artist = artist_find(taglib_tag_artist(tag));
track->tr_genre = genre_find(taglib_tag_genre(tag));
track->tr_library = library;
unplayed_count++;
track->tr_count = 0;
track->tr_length = taglib_audioproperties_length(audio);
track->tr_track = taglib_tag_track(tag);
date_set(&track->tr_date, 0, 0, 0);
track->tr_title = g_strdup(taglib_tag_title(tag));
track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
&track->tr_alts);
track->tr_path = g_strdup(key);
track->tr_title = g_strdup(taglib_tag_title(tag));
track->tr_lower = string_lowercase(track->tr_title);
taglib_tag_free_strings();
taglib_file_free(file);
return track;
}
static void __track_free(struct track *track)
{
g_strfreev(track->tr_tokens);
g_strfreev(track->tr_alts);
g_free(track->tr_title);
g_free(track);
}
struct db_entry *track_alloc(const gchar *key, unsigned int index)
{
struct library *library;
char *fullpath, *path;
struct track *track;
unsigned int lib_id;
sscanf(key, "%u/%m[^\n]", &lib_id, &path);
library = library_get(lib_id);
fullpath = library_file(library, path);
track = __track_alloc_filepath(fullpath);
if (track) {
track->tr_library = library;
track->tr_path = g_strdup(key);
unplayed_count++;
}
out:
g_free(path);
g_free(fullpath);
return track ? &track->tr_dbe : NULL;
@ -117,8 +96,22 @@ static void track_free(struct db_entry *dbe)
play_count -= track->tr_count;
if (track->tr_count == 0)
unplayed_count--;
if (track->tr_library)
track->tr_library->li_size--;
__track_free(track);
g_free(track->tr_title);
g_free(track->tr_lower);
g_free(track);
}
static void track_setup(struct db_entry *dbe)
{
struct track *track = TRACK(dbe);
filter_add(track->tr_lower, dbe->dbe_index);
filter_add(track->tr_artist->ar_lower, dbe->dbe_index);
filter_add(track->tr_album->al_lower, dbe->dbe_index);
track->tr_library->li_size++;
}
static gchar *track_key(struct db_entry *dbe)
@ -126,48 +119,29 @@ static gchar *track_key(struct db_entry *dbe)
return TRACK(dbe)->tr_path;
}
static void track_read_v0(struct file *file, struct track *track)
{
struct artist *artist = artist_get(file_readu(file));
struct album *album = album_get( file_readu(file));
struct genre *genre = genre_get( file_readu(file));
track->tr_track = file_readhu(file);
date_read(file, &track->tr_date);
if (album->al_artist != artist || album->al_genre != genre)
album = album_find(artist, genre, album->al_name, album->al_year);
track->tr_album = album;
}
static struct db_entry *track_read(struct file *file, unsigned int index)
static struct db_entry *track_read(struct file *file)
{
unsigned int library_id, artist_id, album_id, genre_id;
struct track *track = __track_alloc();
track->tr_library = library_get(file_readu(file));
if (file_version(file) == 0)
track_read_v0(file, track);
else {
track->tr_album = album_get(file_readu(file));
track->tr_track = file_readhu(file);
date_read_stamp(file, &track->tr_date);
}
track->tr_count = file_readhu(file);
track->tr_length = file_readhu(file);
file_readf(file, "%u %u %u %u %u", &library_id, &artist_id, &album_id,
&genre_id, &track->tr_track);
date_read(file, &track->tr_date);
file_readf(file, "%u %u", &track->tr_count, &track->tr_length);
play_count += track->tr_count;
if (track->tr_count == 0)
unplayed_count++;
track->tr_title = file_readl(file);
track->tr_tokens = g_str_tokenize_and_fold(track->tr_title, NULL,
&track->tr_alts);
track->tr_album = album_get(album_id);
track->tr_artist = artist_get(artist_id);
track->tr_genre = genre_get(genre_id);
track->tr_library = library_get(library_id);
track->tr_title = file_readl(file);
track->tr_lower = string_lowercase(track->tr_title);
track->tr_path = __track_key(track->tr_library, file_readl(file));
return &track->tr_dbe;
}
static void track_write(struct file *file, struct db_entry *dbe)
@ -175,11 +149,13 @@ static void track_write(struct file *file, struct db_entry *dbe)
struct track *track = TRACK(dbe);
gchar *path = __track_path(track);
file_writef(file, "%u %u %hu ", library_index(track->tr_library),
album_index(track->tr_album),
track->tr_track);
date_write_stamp(file, &track->tr_date);
file_writef(file, " %hu %hu %s\n%s", track->tr_count,
file_writef(file, "%u %u %u %u %u ", track->tr_library->li_dbe.dbe_index,
track->tr_artist->ar_dbe.dbe_index,
track->tr_album->al_dbe.dbe_index,
track->tr_genre->ge_dbe.dbe_index,
track->tr_track);
date_write(file, &track->tr_date);
file_writef(file, " %u %u %s\n%s\n", track->tr_count,
track->tr_length,
track->tr_title,
path);
@ -189,18 +165,19 @@ static void track_write(struct file *file, struct db_entry *dbe)
static const struct db_ops track_ops = {
.dbe_alloc = track_alloc,
.dbe_free = track_free,
.dbe_key = track_key,
.dbe_read = track_read,
.dbe_write = track_write,
track_alloc,
track_free,
track_key,
track_read,
track_setup,
track_write,
};
void track_db_init()
{
db_init(&track_db, "track.db", false, &track_ops, TRACK_DB_MIN);
db_load(&track_db);
db_init(&track_db, "track.db", false, &track_ops);
db_load_idle(&track_db);
}
void track_db_deinit()
@ -213,30 +190,6 @@ void track_db_commit()
db_save(&track_db);
}
bool track_db_defrag()
{
return db_defrag(&track_db);
}
void track_db_rekey()
{
struct db_entry *dbe, *next;
struct track *track;
unsigned int lib;
gchar *path;
db_for_each(dbe, next, &track_db) {
track = TRACK(dbe);
sscanf(track->tr_path, "%u/%m[^\n]", &lib, &path);
if (lib != library_index(track->tr_library)) {
track->tr_path = __track_key(track->tr_library, path);
db_rekey(&track_db, dbe);
} else
g_free(path);
}
}
const struct database *track_db_get()
{
return &track_db;
@ -259,24 +212,6 @@ unsigned int track_db_average_plays()
return play_count / (track_db.db_size - unplayed_count);
}
struct track *track_alloc_external(const gchar *filepath)
{
struct track *track = __track_alloc_filepath(filepath);
if (track) {
track->tr_library = NULL;
track->tr_path = g_strdup(filepath);
}
return track;
}
void track_free_external(struct track *track)
{
if (TRACK_IS_EXTERNAL(track)) {
g_free(track->tr_path);
__track_free(track);
}
}
struct track *track_add(struct library *library, const gchar *filepath)
{
unsigned int offset = strlen(library->li_path) + 1;
@ -311,42 +246,23 @@ struct track *track_get(const unsigned int index)
return TRACK(db_at(&track_db, index));
}
struct track *track_lookup(const gchar *filepath)
{
struct library *library = library_lookup(filepath);
unsigned int offset;
struct track *track;
gchar *key;
if (!library)
return NULL;
offset = strlen(library->li_path) + 1;
key = __track_key(library, g_strdup(filepath + offset));
track = TRACK(db_get(&track_db, key));
g_free(key);
return track;
}
int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare)
{
switch (compare) {
case COMPARE_ARTIST:
return artist_compare(lhs->tr_album->al_artist,
rhs->tr_album->al_artist);
return artist_compare(lhs->tr_artist, rhs->tr_artist);
case COMPARE_ALBUM:
return album_compare(lhs->tr_album, rhs->tr_album);
case COMPARE_COUNT:
return lhs->tr_count - rhs->tr_count;
case COMPARE_GENRE:
return genre_compare(lhs->tr_album->al_genre,
rhs->tr_album->al_genre);
return genre_compare(lhs->tr_genre, rhs->tr_genre);
case COMPARE_LENGTH:
return lhs->tr_length - rhs->tr_length;
case COMPARE_PLAYED:
return date_compare(&lhs->tr_date, &rhs->tr_date);
case COMPARE_TITLE:
return string_compare_tokens(lhs->tr_tokens, rhs->tr_tokens);
return string_compare(lhs->tr_lower, rhs->tr_lower);
case COMPARE_TRACK:
return lhs->tr_track - rhs->tr_track;
case COMPARE_YEAR:
@ -356,12 +272,6 @@ int track_compare(struct track *lhs, struct track *rhs, enum compare_t compare)
return 0; /* We should never get here. */
}
bool track_match_token(struct track *track, const gchar *token)
{
return string_match_token(token, track->tr_tokens) ||
string_match_token(token, track->tr_alts);
}
gchar *track_path(struct track *track)
{
gchar *path, *res;
@ -372,13 +282,11 @@ gchar *track_path(struct track *track)
g_free(path);
return res;
}
return g_strdup(track->tr_path);
return g_strdup("");
}
void track_played(struct track *track)
{
if (TRACK_IS_EXTERNAL(track))
return;
if (track->tr_count == 0)
unplayed_count--;
track->tr_count++;

167
core/tempq.c Normal file
View File

@ -0,0 +1,167 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/collection.h>
#include <core/file.h>
#include <core/history.h>
#include <core/idle.h>
#include <core/tempq.h>
#include <glib.h>
static struct file tempq_file = FILE_INIT("deck", 1);
static struct queue_ops *tempq_ops = NULL;
static GSList *tempq_list;
static struct file tempq_file;
static struct queue *__tempq_alloc(unsigned int flags)
{
struct queue *queue = g_malloc(sizeof(struct queue));
flags = flags | Q_SAVE_FLAGS | Q_SAVE_SORT;
tempq_list = g_slist_append(tempq_list, queue);
queue_init(queue, flags, tempq_ops);
return queue;
}
static void __tempq_free(struct queue *queue)
{
tempq_list = g_slist_remove(tempq_list, queue);
queue_deinit(queue);
g_free(queue);
}
static void __tempq_read_queue()
{
unsigned int flags, count, i, track;
struct queue *queue;
file_readf(&tempq_file, "%u %u", &flags, &count);
queue = __tempq_alloc(flags);
for (i = 0; i < count; i++) {
file_readf(&tempq_file, "%u", &track);
queue_add(queue, track_get(track));
}
}
static void __tempq_write_queue(struct queue *queue)
{
struct _q_iter it;
struct track *track;
file_writef(&tempq_file, "%u %u", queue->q_flags, queue_size(queue));
_q_for_each(&queue->q_tracks, &it) {
track = (struct track *)_q_iter_val(&it);
file_writef(&tempq_file, " %u", track->tr_dbe.dbe_index);
}
file_writef(&tempq_file, "\n");
}
static void __tempq_init_idle(void *data)
{
unsigned int num, i;
if (!file_open(&tempq_file, OPEN_READ))
return;
if (file_version(&tempq_file) < 1)
goto out;
file_readf(&tempq_file, "%u", &num);
for (i = 0; i < num; i++)
__tempq_read_queue();
out:
file_close(&tempq_file);
}
void tempq_init(struct queue_ops *ops)
{
tempq_ops = ops;
idle_schedule(__tempq_init_idle, NULL);
}
void tempq_deinit()
{
while (tempq_list)
__tempq_free((struct queue *)tempq_list->data);
}
void tempq_save(struct queue *queue, enum queue_flags flag)
{
GSList *cur;
if (!file_open(&tempq_file, OPEN_WRITE))
return;
file_writef(&tempq_file, "%zu\n", g_slist_length(tempq_list));
for (cur = tempq_list; cur; cur = g_slist_next(cur))
__tempq_write_queue((struct queue *)cur->data);
file_close(&tempq_file);
}
struct queue *tempq_alloc(unsigned int flags)
{
struct queue *queue = __tempq_alloc(Q_ENABLED | flags);
tempq_save(queue, Q_ENABLED);
return queue;
}
void tempq_free(struct queue *queue)
{
__tempq_free(queue);
tempq_save(NULL, Q_ENABLED);
}
struct queue *tempq_get(unsigned int index)
{
return (struct queue *)g_slist_nth_data(tempq_list, index);
}
unsigned int tempq_index(struct queue *queue)
{
return g_slist_index(tempq_list, queue);
}
void tempq_move(struct queue *queue, unsigned int index)
{
GSList *cur = g_slist_find(tempq_list, queue);
GSList *nth = g_slist_nth(tempq_list, index);
if (cur && nth && (cur != nth)) {
tempq_list = g_slist_remove(tempq_list, queue);
tempq_list = g_slist_insert_before(tempq_list, nth, queue);
tempq_save(queue, Q_ENABLED);
}
}
struct track *tempq_next()
{
struct track *track = NULL;
struct queue *queue = NULL;
GSList *cur;
for (cur = tempq_list; cur; cur = g_slist_next(cur)) {
queue = (struct queue *)cur->data;
if (queue_has_flag(queue, Q_ENABLED)) {
track = queue_next(queue);
break;
}
}
if (queue && queue_size(queue) == 0)
tempq_free(queue);
return track;
}
unsigned int tempq_count()
{
return g_slist_length(tempq_list);
}
void tempq_updated(struct track *track)
{
GSList *cur;
for (cur = tempq_list; cur; cur = g_slist_next(cur))
queue_updated((struct queue *)cur->data, track);
}

8
gui/Sconscript Normal file
View File

@ -0,0 +1,8 @@
#!/usr/bin/python
Import("env")
env.UsePackage("gtk+-3.0")
env.UsePackage("gmodule-export-2.0");
res = Glob("*.c")
Return("res")

View File

@ -1,147 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <gui/artwork.h>
#include <gui/window.h>
#include <glib/gi18n.h>
#define ARTWORK_PREVIEW_SIZE 150
static struct album *artwork_current = NULL;
static unsigned int artwork_timeout = 0;
static cairo_surface_t *__gui_artwork_scale(cairo_surface_t *orig, int new_h)
{
int old_h = cairo_image_surface_get_height(orig);
int old_w = cairo_image_surface_get_width(orig);
int new_w = (old_w * new_h) / old_h;
int scale = gtk_widget_get_scale_factor(GTK_WIDGET(gui_artwork()));
cairo_content_t content = cairo_surface_get_content(orig);
cairo_surface_t *scaled = cairo_surface_create_similar(orig, content,
new_w, new_h);
cairo_t *cairo = cairo_create(scaled);
cairo_scale(cairo, (double)(scale * new_w) / old_w,
(double)(scale * new_h) / old_h);
cairo_set_source_surface(cairo, orig, 0, 0);
cairo_paint(cairo);
cairo_destroy(cairo);
return scaled;
}
static cairo_surface_t *__gui_artwork_get(gchar *path, int new_h)
{
GdkPixbuf *pixbuf = path ? gdk_pixbuf_new_from_file(path, NULL) : NULL;
cairo_surface_t *surface = NULL;
cairo_surface_t *scaled = NULL;
if (!pixbuf)
return NULL;
surface = gdk_cairo_surface_create_from_pixbuf(pixbuf, 0, NULL);
if (surface) {
scaled = __gui_artwork_scale(surface, new_h);
cairo_surface_destroy(surface);
}
g_object_unref(G_OBJECT(pixbuf));
return scaled;
}
static bool __gui_artwork_set_path(GtkImage *image, gchar *path, int new_h)
{
cairo_surface_t *surface = __gui_artwork_get(path, new_h);
bool status = surface != NULL;
if (surface) {
gtk_image_set_from_surface(image, surface);
cairo_surface_destroy(surface);
} else
gtk_image_set_from_icon_name(image, "image-missing",
GTK_ICON_SIZE_DIALOG);
return status;
}
static gboolean __gui_artwork_timeout(gpointer data)
{
struct track *track = audio_cur_track();
int height = gui_builder_widget_height("artwork");
gchar *path;
bool status;
if (!track || track->tr_album == artwork_current)
return G_SOURCE_REMOVE;
path = album_artwork_path(track->tr_album);
status = __gui_artwork_set_path(gui_artwork(), path, height);
gtk_widget_set_sensitive(GTK_WIDGET(gui_artwork()), status);
artwork_current = status ? track->tr_album : NULL;
artwork_timeout = status ? artwork_timeout : 0;
g_free(path);
return status ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
}
void __gui_artwork_update_preview(GtkFileChooser *chooser, gpointer data)
{
GtkWidget *preview = gtk_file_chooser_get_preview_widget(chooser);
gchar *path = gtk_file_chooser_get_preview_filename(chooser);
__gui_artwork_set_path(GTK_IMAGE(preview), path, ARTWORK_PREVIEW_SIZE);
g_free(path);
}
void __gui_artwork_select_cover(GtkButton *button)
{
struct track *track = audio_cur_track();
GtkWidget *preview, *dialog;
GtkFileFilter *filter;
gchar *path;
if (!track)
return;
filter = gtk_file_filter_new();
preview = gtk_image_new_from_icon_name("", GTK_ICON_SIZE_DIALOG);
dialog = gtk_file_chooser_dialog_new("Choose an image", gui_window(),
GTK_FILE_CHOOSER_ACTION_OPEN,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Open"), GTK_RESPONSE_ACCEPT,
NULL);
gtk_file_filter_add_mime_type(filter, "image/*");
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), true);
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog), preview);
gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog), false);
g_signal_connect(dialog, "update-preview",
(GCallback)__gui_artwork_update_preview, NULL);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
gui_artwork_import(track, path);
g_free(path);
}
gtk_widget_destroy(dialog);
}
void gui_artwork_set_cover(void)
{
if (__gui_artwork_timeout(NULL) != G_SOURCE_CONTINUE)
return;
artwork_timeout = g_timeout_add(2000, __gui_artwork_timeout, NULL);
}
void gui_artwork_import(struct track *track, gchar *path)
{
album_artwork_import(track->tr_album, path);
if (track == audio_cur_track()) {
artwork_current = NULL;
gui_artwork_set_cover();
}
}

View File

@ -1,205 +1,126 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/collection.h>
#include <core/playlist.h>
#include <core/string.h>
#include <gui/artwork.h>
#include <gui/audio.h>
#include <gui/idle.h>
#include <gui/playlist.h>
#include <gui/treeview.h>
#include <gui/window.h>
#include <gui/builder.h>
#include <gui/view.h>
static guint audio_timeout = 0;
static guint popover_timeout = 0;
static inline void __gui_audio_set_label_markup(GtkLabel *label,
const gchar *size,
const gchar *text)
static inline void __audio_set_label(const gchar *label, const gchar *size,
const gchar *text)
{
const gchar *fmt = "<span size='%s'>%s</span>";
gchar *markup = g_markup_printf_escaped(fmt, size, text);
gtk_label_set_markup(label, markup);
gchar *markup = g_markup_printf_escaped("<span size='%s'>%s</span>",
size, text);
gtk_label_set_markup(GTK_LABEL(gui_builder_widget(label)), markup);
g_free(markup);
}
static void __gui_audio_load(struct track *track)
static inline void __audio_set_time_label(const gchar *label, unsigned int time)
{
gchar *duration = string_sec2str(track->tr_length);
__gui_audio_set_label_markup(gui_title_tag(), "xx-large",
track->tr_title);
__gui_audio_set_label_markup(gui_album_tag(), "x-large",
track->tr_album->al_name);
__gui_audio_set_label_markup(gui_artist_tag(), "x-large",
track->tr_album->al_artist->ar_name);
__gui_audio_set_label_markup(gui_duration(), "large", duration);
__gui_audio_set_label_markup(gui_position(), "large", "0:00");
gtk_adjustment_set_upper(gui_seek(), track->tr_length);
gui_pl_system_track_loaded(track);
gui_treeview_scroll();
gui_artwork_set_cover();
gui_idle_enable();
g_free(duration);
gchar *str = string_sec2str(time);
__audio_set_label(label, "large", str);
g_free(str);
}
static void __gui_audio_set_pause_text(int n, GstState state)
static void __audio_load(struct track *track)
{
bool sensitive = true;
gchar *text;
__audio_set_label("o_title", "xx-large", track->tr_title);
__audio_set_label("o_artist", "x-large", track->tr_artist->ar_name);
__audio_set_label("o_album", "x-large", track->tr_album->al_name);
__audio_set_time_label("o_duration", track->tr_length);
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);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui_builder_widget("o_hide")),
playlist_has(PL_HIDDEN, track));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gui_builder_widget("o_favorite")),
playlist_has(PL_FAVORITED, track));
gui_view_scroll();
}
static void __gui_audio_change_state(GstState state)
static void __audio_change_state(GstState state)
{
bool playing = (state == GST_STATE_PLAYING);
gtk_widget_set_visible(GTK_WIDGET(gui_play_button()), !playing);
gtk_widget_set_visible(GTK_WIDGET(gui_pause_button()), playing);
__gui_audio_set_pause_text(audio_get_pause_count(), state);
}
static void __gui_audio_config_pause(int n)
{
__gui_audio_set_pause_text(n, audio_cur_state());
}
struct audio_callbacks audio_cb = {
.audio_cb_load = __gui_audio_load,
.audio_cb_state_change = __gui_audio_change_state,
.audio_cb_config_pause = __gui_audio_config_pause,
};
void __gui_audio_pause(GtkButton *button, gpointer data)
{
audio_pause();
if (audio_get_pause_count() > -1) {
gtk_popover_popup(gui_pause_popover());
popover_timeout = g_timeout_add_seconds(10,
gui_audio_popover_timeout, NULL);
if (state == GST_STATE_PLAYING) {
gtk_widget_hide(gui_builder_widget("o_play"));
gtk_widget_show(gui_builder_widget("o_pause"));
} else {
gtk_widget_show(gui_builder_widget("o_play"));
gtk_widget_hide(gui_builder_widget("o_pause"));
}
}
void __gui_audio_pause_change_text(GtkEntry *entry, gpointer data)
static void __audio_config_pause(int n)
{
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());
GtkComboBox *combo = GTK_COMBO_BOX(gui_builder_widget("o_pause_after"));
gtk_combo_box_set_active(combo, n + 1);
}
void __gui_audio_pause_inc(GtkButton *button, gpointer data)
void __audio_pause_changed(GtkComboBox *combo, gpointer data)
{
audio_pause_after(audio_get_pause_count() + 1);
int val = gtk_combo_box_get_active(combo) - 1;
audio_pause_after(val);
}
void __gui_audio_pause_dec(GtkButton *button, gpointer data)
{
audio_pause_after(audio_get_pause_count() - 1);
}
void __gui_audio_pause_popover_popdown(GtkButton *button, gpointer data)
{
gtk_popover_popdown(gui_pause_popover());
#ifdef CONFIG_TESTING
gtk_widget_hide(GTK_WIDGET(gui_pause_popover()));
#endif /* CONFIG_TESTING */
g_source_remove(popover_timeout);
popover_timeout = 0;
}
void __gui_audio_pause_popover_clear(GtkButton *button, gpointer data)
{
audio_pause_after(-1);
__gui_audio_pause_popover_popdown(button, data);
}
void __gui_audio_seek(GtkRange *range, GtkScrollType type,
double value, gpointer data)
void __audio_seek(GtkRange *range, GtkScrollType type, double value, gpointer data)
{
audio_seek(value * GST_SECOND);
}
void __gui_audio_volume_changed(GtkScaleButton *button, gdouble value,
gpointer data)
void __audio_favorite(GtkToggleButton *toggle, gpointer data)
{
audio_set_volume((unsigned int)value);
if (gtk_toggle_button_get_active(toggle))
playlist_add(PL_FAVORITED, audio_cur_track());
else
playlist_remove(PL_FAVORITED, audio_cur_track());
}
gboolean __gui_audio_can_accel(GtkWidget *widget, guint signal_id)
void __audio_hide(GtkToggleButton *toggle, gpointer data)
{
if (gtk_toggle_button_get_active(toggle)) {
if (collection_ban(audio_cur_track()))
audio_next();
} else
collection_unban(audio_cur_track());
}
static int __audio_timeout(gpointer data)
{
GtkAdjustment *progress = data;
gtk_adjustment_set_upper(progress, audio_duration() / GST_SECOND);
gtk_adjustment_set_value(progress, audio_position() / GST_SECOND);
__audio_set_time_label("o_position", audio_position() / GST_SECOND);
return G_SOURCE_CONTINUE;
}
gboolean __audio_can_accel(GtkWidget *widget, guint signal_id)
{
GtkWindow *window = GTK_WINDOW(gui_builder_widget("o_window"));
g_signal_stop_emission_by_name(widget, "can-activate-accel");
return !GTK_IS_ENTRY(gtk_window_get_focus(gui_window())) &&
return !GTK_IS_ENTRY(gtk_window_get_focus(window)) &&
gtk_widget_is_visible(widget) &&
gtk_widget_is_sensitive(widget);
}
struct audio_ops audio_ops = {
__audio_load,
__audio_change_state,
__audio_config_pause,
};
void gui_audio_init()
{
gtk_scale_button_set_value(gui_volume_button(), audio_get_volume());
gtk_button_set_relief(GTK_BUTTON(gui_volume_button()), GTK_RELIEF_NORMAL);
audio_timeout = g_timeout_add(500, gui_audio_timeout, NULL);
g_timeout_add(500, __audio_timeout, gui_builder_object("o_progress"));
}
void gui_audio_deinit()
#ifdef CONFIG_TESTING
void test_gui_audio_timeout()
{
g_source_remove(audio_timeout);
if (popover_timeout > 0)
g_source_remove(popover_timeout);
}
int gui_audio_timeout(gpointer data)
{
gchar *position = string_sec2str(audio_position() / GST_SECOND);
gtk_adjustment_set_upper(gui_seek(), audio_duration() / GST_SECOND);
gtk_adjustment_set_value(gui_seek(), audio_position() / GST_SECOND);
__gui_audio_set_label_markup(gui_position(), "large", position);
g_free(position);
return G_SOURCE_CONTINUE;
}
int gui_audio_popover_timeout(gpointer data)
{
__gui_audio_pause_popover_popdown(NULL, data);
return G_SOURCE_REMOVE;
__audio_timeout(gui_builder_object("o_progress"));
}
#endif /* CONFIG_TESTING */

View File

@ -29,11 +29,6 @@ GtkWidget *gui_builder_widget(const char *name)
return GTK_WIDGET(gui_builder_object(name));
}
int gui_builder_widget_height(const char *name)
{
return gtk_widget_get_allocated_height(gui_builder_widget(name));
}
#ifdef CONFIG_TESTING
GtkBuilder *test_get_gui_builder()
{

248
gui/collection.c Normal file
View File

@ -0,0 +1,248 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/collection.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/tempq.h>
#include <gui/builder.h>
#include <gui/collection.h>
#include <gui/idle.h>
#include <gui/sidebar.h>
enum collection_sidebar_columns {
C_SB_IMAGE,
C_SB_PATH,
C_SB_LIBRARY,
};
static GtkTreeModel *c_model;
static void __collection_set_header(GtkTreeIter *iter, const gchar *image,
const gchar *text)
{
gtk_tree_store_set(GTK_TREE_STORE(c_model), iter, C_SB_IMAGE, image,
C_SB_PATH, text, -1);
}
static void __collection_set_library(GtkTreeIter *iter, struct library *library)
{
gtk_tree_store_set(GTK_TREE_STORE(c_model), iter,
C_SB_IMAGE, "folder",
C_SB_PATH, library->li_path,
C_SB_LIBRARY, library, -1);
}
static struct library *__collection_get_library(GtkTreeIter *iter)
{
struct library *library;
gtk_tree_model_get(c_model, iter, C_SB_LIBRARY, &library, -1);
return library;
}
void __collection_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *column, gpointer data)
{
struct library *library = NULL;
GtkTreeIter iter;
if (gtk_tree_model_get_iter(c_model, &iter, path))
library = __collection_get_library(&iter);
if (!library)
return;
collection_update(library);
gui_idle_enable();
}
void __collection_toggled(GtkCheckMenuItem *check, gpointer data)
{
GtkTreeView *treeview = GTK_TREE_VIEW(gui_builder_widget("o_collection_view"));
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct library *library = NULL;
GtkTreePath *path;
GtkTreeIter iter;
GList *rows;
rows = gtk_tree_selection_get_selected_rows(selection, &c_model);
if (!rows)
return;
path = rows->data;
if (gtk_tree_model_get_iter(c_model, &iter, path))
library = __collection_get_library(&iter);
if (!library)
return;
collection_set_enabled(library, gtk_check_menu_item_get_active(check));
__collection_set_library(&iter, library);
}
bool __collection_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct library *library = NULL;
GtkTreePath *path;
GtkTreeIter iter;
GList *rows;
if (event->keyval != GDK_KEY_Delete)
return false;
rows = gtk_tree_selection_get_selected_rows(selection, &c_model);
path = rows->data;
if (gtk_tree_model_get_iter(c_model, &iter, path))
library = __collection_get_library(&iter);
if (!library)
goto out;
collection_remove(library);
gtk_tree_store_remove(GTK_TREE_STORE(c_model), &iter);
out:
g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
return true;
}
bool __collection_buttonpress(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
GtkCheckMenuItem *check = GTK_CHECK_MENU_ITEM(gui_builder_widget("o_collection_enabled"));
GtkTreeView *treeview = GTK_TREE_VIEW(gui_builder_widget("o_collection_view"));
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkMenu *menu = GTK_MENU(gui_builder_widget("o_collection_rc"));
struct library *library = NULL;
GtkTreePath *path;
GtkTreeIter iter;
if (event->button != 3)
return false;
if (gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
gtk_tree_selection_select_path(selection, path);
if (gtk_tree_model_get_iter(c_model, &iter, path))
library = __collection_get_library(&iter);
if (library) {
gtk_check_menu_item_set_active(check, library->li_enabled);
gtk_menu_popup(menu, NULL, NULL, NULL, NULL,
event->button, event->time);
}
gtk_tree_path_free(path);
return true;
}
void __collection_add(GtkButton *button, GtkFileChooser *chooser)
{
gchar *filename = gtk_file_chooser_get_filename(chooser);
GtkTreeIter iter, last;
gtk_tree_model_get_iter_first(c_model, &iter);
gtk_tree_model_iter_nth_child(c_model, &last, &iter, 0);
while (__collection_get_library(&last) != NULL)
gtk_tree_model_iter_next(c_model, &last);
gtk_tree_store_insert_before(GTK_TREE_STORE(c_model), &iter, NULL, &last);
__collection_set_library(&iter, collection_add(filename));
gui_idle_enable();
g_free(filename);
}
void __collection_selection_changed(GtkTreeSelection *selection,
GtkFileChooser *chooser)
{
GtkStack *stack = GTK_STACK(gui_builder_widget("o_stack"));
struct library *library;
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(selection, &c_model, &iter)) {
gtk_stack_set_visible_child_name(stack, "chooser");
library = __collection_get_library(&iter);
gui_sidebar_selected(SB_COLLECTION, NULL);
if (library)
gtk_file_chooser_set_current_folder(chooser,
library->li_path);
}
}
static void *__collection_init(struct queue *queue)
{
return gui_queue_alloc(queue, "Collection", GQ_CAN_RANDOM);
}
static void __collection_added(struct queue *queue, unsigned int pos)
{
gui_queue_added(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
}
static bool __collection_erase(struct queue *queue, struct track *track)
{
return playlist_add(PL_HIDDEN, track);
}
static void __collection_removed(struct queue *queue, unsigned int pos)
{
gui_queue_removed(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
}
void __gui_collection_init_idle()
{
struct db_entry *library, *next;
GtkTreeIter parent, iter, last;
gtk_tree_model_get_iter_first(c_model, &parent);
while (!gtk_tree_model_iter_has_child(c_model, &parent))
gtk_tree_model_iter_next(c_model, &parent);
gtk_tree_model_iter_nth_child(c_model, &last, &parent, 0);
/* Add library paths. */
db_for_each(library, next, library_db_get()) {
gtk_tree_store_insert_before(GTK_TREE_STORE(c_model), &iter, &parent, &last);
__collection_set_library(&iter, LIBRARY(library));
}
}
void gui_collection_init()
{
GtkTreeIter parent, iter;
GtkTreeView *treeview;
c_model = GTK_TREE_MODEL(gui_builder_object("o_collection_store"));
/* Add "Collection" header. */
gtk_tree_store_insert(GTK_TREE_STORE(c_model), &parent, NULL, -1);
__collection_set_header(&parent, "system-file-manager", "<big>Collection</big>");
/* Add "Add new Library" entry. */
gtk_tree_store_insert(GTK_TREE_STORE(c_model), &iter, &parent, -1);
__collection_set_header(&iter, "folder-new",
"<span style='italic'>&lt;Add new path&gt;</span>");
treeview = GTK_TREE_VIEW(gui_builder_widget("o_collection_view"));
gtk_tree_view_expand_all(treeview);
gtk_tree_selection_set_select_function(
gtk_tree_view_get_selection(treeview),
gui_sidebar_on_select, NULL, NULL);
idle_schedule(__gui_collection_init_idle, NULL);
gui_sidebar_set_size(gui_queue(collection_get_queue()));
gui_idle_enable();
}
struct queue_ops collection_ops = {
.qop_init = __collection_init,
.qop_deinit = gui_queue_free,
.qop_added = __collection_added,
.qop_erase = __collection_erase,
.qop_removed = __collection_removed,
.qop_cleared = gui_queue_cleared,
.qop_save = collection_save,
.qop_updated = gui_queue_updated,
};

View File

@ -1,142 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/string.h>
#include <gui/filter.h>
#include <gui/model.h>
static GtkTreeModelFilter *filter_model = NULL;
static inline GtkTreePath *__gui_filter_convert_path(GtkTreePath *path)
{
return gtk_tree_model_filter_convert_path_to_child_path(filter_model,
path);
}
static inline gboolean __gui_filter_match_token(struct track *track,
const gchar *token,
unsigned int how)
{
switch (how) {
case GUI_FILTER_ALBUM:
return album_match_token(track->tr_album, token);
case GUI_FILTER_ARTIST:
return artist_match_token(track->tr_album->al_artist, token);
case GUI_FILTER_GENRE:
return genre_match_token(track->tr_album->al_genre, token);
case GUI_FILTER_TITLE:
return track_match_token(track, token);
case GUI_FILTER_DEFAULT:
return track_match_token(track, token) ||
album_match_token(track->tr_album, token) ||
artist_match_token(track->tr_album->al_artist, token) ||
genre_match_token(track->tr_album->al_genre, token);
default:
return false;
}
}
static gboolean __gui_filter_visible_func(GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
{
unsigned int i, how = gtk_combo_box_get_active(gui_filter_how());
gchar **search = gui_model_get_playlist()->pl_search;
struct track *track;
if (!search)
return TRUE;
track = gui_model_iter_get_track(iter);
for (i = 0; search[i]; i++) {
if (!__gui_filter_match_token(track, search[i], how))
return FALSE;
}
return TRUE;
}
void __gui_filter_search_changed(GtkSearchEntry *search, gpointer data)
{
playlist_set_search(gui_model_get_playlist(),
gtk_entry_get_text(GTK_ENTRY(search)));
gtk_tree_model_filter_refilter(gui_filter_get());
}
void __gui_filter_how_changed(int n)
{
__gui_filter_search_changed(gui_filter_search(), NULL);
}
void gui_filter_init()
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_model_get());
GtkTreeModel *filter = gtk_tree_model_filter_new(model, NULL);
filter_model = GTK_TREE_MODEL_FILTER(filter);
gtk_tree_model_filter_set_visible_func(filter_model,
__gui_filter_visible_func,
NULL, NULL);
}
void gui_filter_deinit()
{
g_object_unref(G_OBJECT(filter_model));
}
void gui_filter_set_playlist(struct playlist *playlist)
{
gchar **search = playlist ? (gchar **)playlist->pl_search : NULL;
gchar *text = search ? g_strjoinv(" ", search) : g_strdup("");
gui_model_set_playlist(playlist);
gtk_entry_set_text(GTK_ENTRY(gui_filter_search()), text);
g_free(text);
}
GtkTreeModelFilter *gui_filter_get()
{
return filter_model;
}
struct track *gui_filter_path_get_track(GtkTreePath *path)
{
GtkTreePath *real = __gui_filter_convert_path(path);
struct track *track = real ? gui_model_path_get_track(real) : NULL;
gtk_tree_path_free(real);
return track;
}
void gui_filter_path_load_track(GtkTreePath *path)
{
struct track *track = gui_filter_path_get_track(path);
unsigned int index = gtk_tree_path_get_indices(path)[0];
audio_load(track);
playlist_current_set(gui_model_get_playlist(), index);
}
unsigned int gui_filter_path_get_index(GtkTreePath *path)
{
GtkTreePath *real = __gui_filter_convert_path(path);
unsigned int ret = gtk_tree_path_get_indices(real)[0];
gtk_tree_path_free(real);
return ret;
}
GtkTreePath *gui_filter_path_from_index(unsigned int index)
{
GtkTreePath *real, *path;
real = gtk_tree_path_new_from_indices(index, -1);
path = gtk_tree_model_filter_convert_child_path_to_path(filter_model,
real);
gtk_tree_path_free(real);
return path;
}
void gui_filter_refilter(struct playlist *playlist)
{
if (!playlist || playlist == gui_model_get_playlist())
gtk_tree_model_filter_refilter(gui_filter_get());
}

38
gui/history.c Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <gui/queue.h>
#include <gui/sidebar.h>
static void *__history_init(struct queue *queue)
{
return gui_queue_alloc(queue, "History", 0);
}
static void __history_added(struct queue *queue, unsigned int pos)
{
gui_queue_added(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
}
static bool __history_erase(struct queue *queue, struct track *track)
{
return false;
}
static void __history_removed(struct queue *queue, unsigned int pos)
{
gui_queue_removed(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
}
struct queue_ops history_ops = {
.qop_init = __history_init,
.qop_deinit = gui_queue_free,
.qop_added = __history_added,
.qop_erase = __history_erase,
.qop_removed = __history_removed,
.qop_cleared = gui_queue_cleared,
.qop_updated = gui_queue_updated,
};

View File

@ -1,31 +1,37 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <gui/idle.h>
#include <core/idle.h>
#include <gui/builder.h>
#include <gtk/gtk.h>
static guint idle_id = 0;
static gboolean __on_idle(gpointer data)
{
bool more = idle_run_task();
gchar *text = g_strdup_printf("%f%%", idle_progress() * 100);
GtkProgressBar *progress = GTK_PROGRESS_BAR(data);
gtk_widget_set_visible(GTK_WIDGET(gui_progress_bar()), more);
gtk_progress_bar_set_fraction(gui_progress_bar(), idle_progress());
gtk_widget_set_tooltip_text(GTK_WIDGET(gui_progress_bar()), text);
g_free(text);
idle_id = more ? idle_id : 0;
return more ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE;
if (idle_run_task()) {
gtk_progress_bar_set_fraction(progress, idle_progress());
return gtk_widget_is_visible(gui_builder_widget("o_window"));
} else {
gtk_widget_hide(GTK_WIDGET(progress));
idle_id = 0;
return G_SOURCE_REMOVE;
}
}
void gui_idle_enable()
{
idle_id = g_idle_add(__on_idle, NULL);
GtkWidget *progress = gui_builder_widget("o_idle_progress");
gtk_widget_show(progress);
idle_id = g_idle_add(__on_idle, progress);
}
void gui_idle_disable()
{
if (idle_id)
g_source_remove(idle_id);
idle_cancel();
}

View File

@ -2,57 +2,64 @@
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/queue.h>
#include <core/string.h>
#include <gui/builder.h>
#include <gui/model.h>
static gboolean __gui_model_iter_nth_child(GtkTreeModel *, GtkTreeIter *,
GtkTreeIter *, gint);
static const GTypeInfo queue_type_info;
static const GInterfaceInfo queue_tree_model;
static GObjectClass *parent_class = NULL;
static gchar *font_current = "bold";
static gchar *font_regular = "";
static struct playlist *cur_playlist = NULL;
static GObjectClass *parent_class = NULL;
static GuiModel *gui_model = NULL;
static GType gui_model_type = 0;
static GType gui_model_columns[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_TRACK_NR] = G_TYPE_UINT,
[GUI_MODEL_TITLE] = G_TYPE_STRING,
[GUI_MODEL_LENGTH] = G_TYPE_STRING,
[GUI_MODEL_ARTIST] = G_TYPE_STRING,
[GUI_MODEL_ALBUM] = G_TYPE_STRING,
[GUI_MODEL_YEAR] = G_TYPE_UINT,
[GUI_MODEL_GENRE] = G_TYPE_STRING,
[GUI_MODEL_COUNT] = G_TYPE_UINT,
[GUI_MODEL_LAST_PLAY] = G_TYPE_STRING,
[GUI_MODEL_FILE_PATH] = G_TYPE_STRING,
[GUI_MODEL_FONT] = G_TYPE_STRING,
static GType queue_columns[Q_MODEL_N_COLUMNS] = {
[Q_MODEL_TRACK_NR] = G_TYPE_UINT,
[Q_MODEL_TITLE] = G_TYPE_STRING,
[Q_MODEL_LENGTH] = G_TYPE_STRING,
[Q_MODEL_ARTIST] = G_TYPE_STRING,
[Q_MODEL_ALBUM] = G_TYPE_STRING,
[Q_MODEL_YEAR] = G_TYPE_UINT,
[Q_MODEL_GENRE] = G_TYPE_STRING,
[Q_MODEL_COUNT] = G_TYPE_UINT,
[Q_MODEL_LAST_PLAY] = G_TYPE_STRING,
[Q_MODEL_FILE_PATH] = G_TYPE_STRING,
[Q_MODEL_FONT] = G_TYPE_STRING,
};
const GtkTargetEntry gui_model_drag_targets[] = {
{ GUI_DRAG_DATA, GTK_TARGET_SAME_APP, 0 },
};
static gboolean __queue_model_iter_nth(GuiQueueModel *model,
GtkTreeIter *iter,
gint n)
{
if (n >= queue_size(model->gqm_queue))
return FALSE;
const unsigned int gui_model_n_targets = G_N_ELEMENTS(gui_model_drag_targets);
_q_iter_set(&model->gqm_queue->q_tracks, &model->gqm_iter, n);
iter->stamp = model->gqm_stamp;
iter->user_data = &model->gqm_iter;
iter->user_data2 = _q_iter_val(&model->gqm_iter);
return TRUE;
}
static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model)
static GtkTreeModelFlags _queue_model_get_flags(GtkTreeModel *model)
{
return GTK_TREE_MODEL_LIST_ONLY;
}
static gint __gui_model_get_n_columns(GtkTreeModel *model)
static gint _queue_model_get_n_columns(GtkTreeModel *model)
{
return GUI_MODEL_N_COLUMNS;
return Q_MODEL_N_COLUMNS;
}
static GType __gui_model_get_column_type(GtkTreeModel *model, gint index)
static GType _queue_model_get_column_type(GtkTreeModel *model, gint index)
{
g_return_val_if_fail(index >= 0, G_TYPE_INVALID);
g_return_val_if_fail(index < GUI_MODEL_N_COLUMNS, G_TYPE_INVALID);
return gui_model_columns[index];
g_return_val_if_fail(index >= 0 && index < Q_MODEL_N_COLUMNS,
G_TYPE_INVALID);
return queue_columns[index];
}
static gboolean __gui_model_get_iter(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreePath *path)
static gboolean _queue_model_get_iter(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreePath *path)
{
gint *indices, depth;
@ -60,324 +67,257 @@ static gboolean __gui_model_get_iter(GtkTreeModel *model, GtkTreeIter *iter,
indices = gtk_tree_path_get_indices_with_depth(path, &depth);
g_assert(depth == 1);
return __gui_model_iter_nth_child(model, iter, NULL, indices[0]);
return __queue_model_iter_nth(GUI_QUEUE_MODEL(model), iter, indices[0]);
}
static GtkTreePath *__gui_model_get_path(GtkTreeModel *model, GtkTreeIter *iter)
static GtkTreePath *_queue_model_get_path(GtkTreeModel *model, GtkTreeIter *iter)
{
struct _q_iter *q_it;
GtkTreePath *path;
unsigned int pos;
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
g_return_val_if_fail(iter->user_data2, FALSE);
q_it = iter->user_data;
path = gtk_tree_path_new();
pos = playlist_iter_index(cur_playlist, iter->user_data);
gtk_tree_path_append_index(path, pos);
gtk_tree_path_append_index(path, q_it->it_pos);
return path;
}
static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
gint column, GValue *value)
static void _queue_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
gint column, GValue *value)
{
struct track *track = playlist_iter_track(iter->user_data);
struct track *track = iter->user_data2;
gchar *str;
g_return_if_fail(iter != NULL);
g_return_if_fail(iter->user_data != NULL);
g_return_if_fail(column < GUI_MODEL_N_COLUMNS);
g_return_if_fail(iter->user_data2 != NULL);
g_return_if_fail(column < Q_MODEL_N_COLUMNS);
g_value_init(value, gui_model_columns[column]);
g_value_init(value, queue_columns[column]);
switch (column) {
case GUI_MODEL_TRACK_NR:
case Q_MODEL_TRACK_NR:
g_value_set_uint(value, track->tr_track);
break;
case GUI_MODEL_TITLE:
case Q_MODEL_TITLE:
g_value_set_static_string(value, track->tr_title);
break;
case GUI_MODEL_LENGTH:
case Q_MODEL_LENGTH:
g_value_take_string(value, string_sec2str(track->tr_length));
break;
case GUI_MODEL_ARTIST:
g_value_set_static_string(value, track->tr_album->al_artist->ar_name);
case Q_MODEL_ARTIST:
g_value_set_static_string(value, track->tr_artist->ar_name);
break;
case GUI_MODEL_ALBUM:
case Q_MODEL_ALBUM:
g_value_set_static_string(value, track->tr_album->al_name);
break;
case GUI_MODEL_YEAR:
case Q_MODEL_YEAR:
g_value_set_uint(value, track->tr_album->al_year);
break;
case GUI_MODEL_GENRE:
g_value_set_static_string(value, track->tr_album->al_genre->ge_name);
case Q_MODEL_GENRE:
g_value_set_static_string(value, track->tr_genre->ge_name);
break;
case GUI_MODEL_COUNT:
case Q_MODEL_COUNT:
g_value_set_uint(value, track->tr_count);
break;
case GUI_MODEL_LAST_PLAY:
case Q_MODEL_LAST_PLAY:
g_value_take_string(value, track_last_play(track));
break;
case GUI_MODEL_FILE_PATH:
case Q_MODEL_FILE_PATH:
str = track_path(track);
g_value_take_string(value, g_markup_escape_text(str, -1));
g_free(str);
break;
case GUI_MODEL_FONT:
str = (track == audio_cur_track()) ? "bold" : "";
g_value_take_string(value, g_strdup(str));
case Q_MODEL_FONT:
str = (track == audio_cur_track()) ? font_current : font_regular;
g_value_set_static_string(value, str);
break;
}
}
static gboolean __gui_model_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
static gboolean _queue_model_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
{
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
GuiQueueModel *gqm = GUI_QUEUE_MODEL(model);
iter->user_data = playlist_iter_next(iter->user_data);
return iter->user_data != NULL;
}
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
g_return_val_if_fail(iter->user_data2, FALSE);
static gboolean __gui_model_iter_children(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreeIter *parent)
{
return __gui_model_iter_nth_child(model, iter, parent, 0);
}
static gboolean __gui_model_iter_has_child(GtkTreeModel *model, GtkTreeIter *iter)
{
return FALSE;
}
static gint __gui_model_iter_n_children(GtkTreeModel *model, GtkTreeIter *iter)
{
if (iter != NULL || !cur_playlist)
return 0;
return playlist_size(cur_playlist);
}
static gboolean __gui_model_iter_nth_child(GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n)
{
if (parent || !cur_playlist || n >= playlist_size(cur_playlist))
_q_iter_next(&gqm->gqm_iter);
if (gqm->gqm_iter.it_iter == NULL)
return FALSE;
iter->stamp = gui_model->gm_stamp;
iter->user_data = playlist_iter_get(cur_playlist, n);
return iter->user_data != NULL;
iter->stamp = gqm->gqm_stamp;
iter->user_data = &gqm->gqm_iter;
iter->user_data2 = _q_iter_val(&gqm->gqm_iter);
return TRUE;
}
static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreeIter *child)
static gboolean _queue_model_iter_children(GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *parent)
{
if (parent)
return FALSE;
return __queue_model_iter_nth(GUI_QUEUE_MODEL(model), iter, 0);
}
static gboolean _queue_model_iter_has_child(GtkTreeModel *model,
GtkTreeIter *iter)
{
return FALSE;
}
static gboolean __gui_model_drag_data_get(GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data)
static gint _queue_model_iter_n_children(GtkTreeModel *model,
GtkTreeIter *iter)
{
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;
if (iter != NULL)
return 0;
return queue_size(GUI_QUEUE_MODEL(model)->gqm_queue);
}
static gboolean __gui_model_drag_data_delete(GtkTreeDragSource *drag_source,
GtkTreePath *path)
static gboolean _queue_model_iter_nth_child(GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *parent,
gint n)
{
return true;
if (parent)
return FALSE;
return __queue_model_iter_nth(GUI_QUEUE_MODEL(model), iter, n);
}
static void __gui_model_init(GuiModel *model)
static gboolean _queue_model_iter_parent(GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *child)
{
model->gm_stamp = g_random_int();
return FALSE;
}
static void __gui_model_finalize(GObject *object)
static void _queue_model_init(GuiQueueModel *model)
{
model->gqm_stamp = g_random_int();
}
static void _queue_model_finalize(GObject *object)
{
parent_class->finalize(object);
}
static void __gui_model_class_init(GuiModelClass *class)
static void _queue_model_class_init(GuiQueueModelClass *class)
{
GObjectClass *object_class = (GObjectClass *)class;
GObjectClass *object_class;
object_class->finalize = __gui_model_finalize;
parent_class = g_type_class_peek_parent(class);
object_class = (GObjectClass *)class;
object_class->finalize = _queue_model_finalize;
}
static void __gui_tree_model_init(GtkTreeModelIface *iface)
static void _queue_tree_model_init(GtkTreeModelIface *iface)
{
iface->get_flags = __gui_model_get_flags;
iface->get_n_columns = __gui_model_get_n_columns;
iface->get_column_type = __gui_model_get_column_type;
iface->get_iter = __gui_model_get_iter;
iface->get_path = __gui_model_get_path;
iface->get_value = __gui_model_get_value;
iface->iter_next = __gui_model_iter_next;
iface->iter_children = __gui_model_iter_children;
iface->iter_has_child = __gui_model_iter_has_child;
iface->iter_n_children = __gui_model_iter_n_children;
iface->iter_nth_child = __gui_model_iter_nth_child;
iface->iter_parent = __gui_model_iter_parent;
iface->get_flags = _queue_model_get_flags;
iface->get_n_columns = _queue_model_get_n_columns;
iface->get_column_type = _queue_model_get_column_type;
iface->get_iter = _queue_model_get_iter;
iface->get_path = _queue_model_get_path;
iface->get_value = _queue_model_get_value;
iface->iter_next = _queue_model_iter_next;
iface->iter_children = _queue_model_iter_children;
iface->iter_has_child = _queue_model_iter_has_child;
iface->iter_n_children = _queue_model_iter_n_children;
iface->iter_nth_child = _queue_model_iter_nth_child;
iface->iter_parent = _queue_model_iter_parent;
}
static void __gui_drag_source_init(GtkTreeDragSourceIface *iface)
GuiQueueModel *gui_queue_model_new(struct queue *queue)
{
iface->drag_data_get = __gui_model_drag_data_get;
iface->drag_data_delete = __gui_model_drag_data_delete;
GuiQueueModel *model = g_object_new(GUI_QUEUE_MODEL_TYPE, NULL);
g_assert(model != NULL);
model->gqm_queue = queue;
return model;
}
static const GTypeInfo gui_model_type_info = {
.class_size = sizeof(GuiModelClass),
.base_init = NULL,
.base_finalize = NULL,
.class_init = (GClassInitFunc)__gui_model_class_init,
.class_finalize = NULL,
.class_data = NULL,
.instance_size = sizeof(GuiModel),
.n_preallocs = 0,
.instance_init = (GInstanceInitFunc)__gui_model_init,
};
static const GInterfaceInfo gui_tree_model = {
.interface_init = (GInterfaceInitFunc)__gui_tree_model_init,
.interface_finalize = 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)
GType gui_queue_model_get_type()
{
gui_model_type = g_type_register_static(G_TYPE_OBJECT, "GuiModel",
&gui_model_type_info,
(GTypeFlags)0);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_MODEL,
&gui_tree_model);
g_type_add_interface_static(gui_model_type, GTK_TYPE_TREE_DRAG_SOURCE,
&gui_drag_source);
static GType queue_type = 0;
if (queue_type == 0) {
queue_type = g_type_register_static(G_TYPE_OBJECT,
"GuiQueueModel",
&queue_type_info,
(GTypeFlags)0);
g_type_add_interface_static(queue_type,
GTK_TYPE_TREE_MODEL,
&queue_tree_model);
}
gui_model = g_object_new(gui_model_type, NULL);
g_assert(gui_model != NULL);
return queue_type;
}
void gui_model_deinit(void)
void gui_queue_model_add(GuiQueueModel *model, unsigned int row)
{
g_object_unref(gui_model);
gui_model_type = 0;
gui_model = NULL;
cur_playlist = NULL;
}
GtkTreePath *path = gtk_tree_path_new_from_indices(row, -1);
GtkTreeIter iter;
static void __gui_model_set_runtime(void)
{
gchar *len = NULL;
if (cur_playlist)
len = string_sec2str_long(cur_playlist->pl_length);
gtk_label_set_text(gui_model_runtime(), len);
g_free(len);
}
static gboolean __gui_model_foreach_changed(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
if (!data || data == gui_model_iter_get_track(iter))
gtk_tree_model_row_changed(model, path, iter);
return FALSE;
}
GuiModel *gui_model_get(void)
{
return gui_model;
}
GType gui_model_get_type()
{
return gui_model_type;
}
void gui_model_add(struct playlist *playlist, struct track *track)
{
GtkTreePath *path;
GtkTreeIter iter;
if (cur_playlist != playlist)
return;
path = gtk_tree_path_new_from_indices(0, -1);
__gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(gui_model), path, &iter);
_queue_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
gtk_tree_model_row_inserted(GTK_TREE_MODEL(model), path, &iter);
gtk_tree_path_free(path);
__gui_model_set_runtime();
}
void gui_model_remove(struct playlist *playlist, struct track *track,
unsigned int n)
void gui_queue_model_remove(GuiQueueModel *model, unsigned int row)
{
GtkTreePath *path = gtk_tree_path_new_from_indices(row, -1);
gtk_tree_model_row_deleted(GTK_TREE_MODEL(model), path);
gtk_tree_path_free(path);
}
void gui_queue_model_clear(GuiQueueModel *model, unsigned int n)
{
GtkTreePath *path;
unsigned int i;
if (cur_playlist != playlist)
return;
for (i = 1; i <= n; i++)
gui_queue_model_remove(model, n - i);
}
path = gtk_tree_path_new_from_indices(n - 1, -1);
for (i = 0; i < n; i++) {
gtk_tree_model_row_deleted(GTK_TREE_MODEL(gui_model), path);
gtk_tree_path_prev(path);
}
void gui_queue_model_update(GuiQueueModel *model, unsigned int row)
{
GtkTreePath *path = gtk_tree_path_new_from_indices(row, -1);
GtkTreeIter iter;
_queue_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
gtk_tree_model_row_changed(GTK_TREE_MODEL(model), path, &iter);
gtk_tree_path_free(path);
__gui_model_set_runtime();
}
void gui_model_update(struct playlist *playlist, struct track *track)
{
if (cur_playlist == playlist)
gtk_tree_model_foreach(GTK_TREE_MODEL(gui_model),
__gui_model_foreach_changed, track);
__gui_model_set_runtime();
}
void gui_model_set_playlist(struct playlist *playlist)
{
if (cur_playlist)
gui_model_remove(cur_playlist, NULL, playlist_size(cur_playlist));
cur_playlist = playlist;
__gui_model_set_runtime();
if (playlist && playlist_size(playlist) > 0)
gui_model_add(playlist, 0);
}
struct playlist *gui_model_get_playlist(void)
{
return cur_playlist;
}
struct track * gui_model_path_get_track(GtkTreePath *path)
struct track * gui_queue_model_path_get_track(GuiQueueModel *model,
GtkTreePath *path)
{
GtkTreeIter iter;
__gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path);
return gui_model_iter_get_track(&iter);
_queue_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
return gui_queue_model_iter_get_track(model, &iter);
}
static const GTypeInfo queue_type_info = {
.class_size = sizeof(GuiQueueModelClass),
.base_init = NULL,
.base_finalize = NULL,
.class_init = (GClassInitFunc)_queue_model_class_init,
.class_finalize = NULL,
.class_data = NULL,
.instance_size = sizeof(GuiQueueModel),
.n_preallocs = 0,
.instance_init = (GInstanceInitFunc)_queue_model_init,
};
static const GInterfaceInfo queue_tree_model = {
.interface_init = (GInterfaceInitFunc)_queue_tree_model_init,
.interface_finalize = NULL,
.interface_data = NULL,
};

View File

@ -4,34 +4,32 @@
#include <core/core.h>
#include <gui/audio.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/collection.h>
#include <gui/history.h>
#include <gui/idle.h>
#include <gui/model.h>
#include <gui/playlist.h>
#include <gui/settings.h>
#include <gui/sidebar.h>
#include <gui/treeview.h>
#include <gui/tempq.h>
#include <gui/view.h>
#include <gui/window.h>
#define OCARINA_FLAGS (G_APPLICATION_HANDLES_COMMAND_LINE)
static const GOptionEntry ocarina_options[] = {
{ "next", 'n', 0, G_OPTION_ARG_NONE, NULL, "Play next track", NULL },
{ "pause", 'P', 0, G_OPTION_ARG_NONE, NULL, "Pause playback", NULL },
{ "play", 'p', 0, G_OPTION_ARG_NONE, NULL, "Start playback", NULL },
{ "previous", 'N', 0, G_OPTION_ARG_NONE, NULL, "Play previous track", NULL },
{ "sync", 's', 0, G_OPTION_ARG_NONE, NULL, "Don't run background tasks", NULL },
{ "toggle", 't', 0, G_OPTION_ARG_NONE, NULL, "Toggle playback state", NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, NULL, "Print version and exit", NULL },
{ NULL },
};
#define OCARINA_FLAGS (G_APPLICATION_FLAGS_NONE)
#ifndef CONFIG_DEBUG
const static gchar *OCARINA_APP = "org.gtk.ocarina";
const static gchar *OCARINA_NAME = "org.gtk.ocarina";
#else
const static gchar *OCARINA_APP = "org.gtk.ocarina-debug";
const static gchar *OCARINA_NAME = "org.gtk.ocarina-debug";
#endif
static enum idle_sync_t idle_sync = IDLE_ASYNC;
struct core_init_data init_data = {
&collection_ops,
&history_ops,
&playlist_ops,
&tempq_ops,
&audio_ops,
};
static int startup_argc;
static char **startup_argv;
@ -48,72 +46,27 @@ static gchar *find_file_path(const gchar *file)
static void __ocarina_activate(GApplication *application, gpointer data)
{
gtk_application_add_window(GTK_APPLICATION(application), gui_window());
}
static int __ocarina_local_options(GApplication *application,
GVariantDict *options, gpointer data)
{
if (g_variant_dict_contains(options, "sync"))
idle_sync = IDLE_SYNC;
if (!g_variant_dict_contains(options, "version"))
return -1;
g_printf("Ocarina %s\n", get_version());
g_printf("GTK+ %u.%u.%u\n", GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION);
g_printf("%s\n", gst_version_string());
g_printf("%s, %s\n", __DATE__, __TIME__);
return 0;
}
static int __ocarina_command_line(GApplication *application,
GApplicationCommandLine *command,
gpointer data)
{
GVariantDict *options;
gchar **args;
g_application_activate(application);
args = g_application_command_line_get_arguments(command, NULL);
if (args && args[1]) {
audio_load_filepath(args[1]);
g_strfreev(args);
}
options = g_application_command_line_get_options_dict(command);
if (g_variant_dict_contains(options, "next"))
audio_next();
else if (g_variant_dict_contains(options, "pause"))
audio_pause();
else if (g_variant_dict_contains(options, "play"))
audio_play();
else if (g_variant_dict_contains(options, "previous"))
audio_prev();
else if (g_variant_dict_contains(options, "toggle")) {
if (audio_cur_state() == GST_STATE_PLAYING)
audio_pause();
else
audio_play();
}
return 0;
gtk_application_add_window(GTK_APPLICATION(application),
GTK_WINDOW(gui_builder_widget("o_window")));
}
static void __ocarina_startup(GApplication *application, gpointer data)
{
gchar *ui = find_file_path("ocarina.ui");
gchar *ui = find_file_path("ocarina6.glade");
gchar *icon = find_file_path("ocarina.png");
gui_builder_init(ui);
core_init(&startup_argc, &startup_argv, &playlist_cb, &audio_cb, idle_sync);
core_init(&startup_argc, &startup_argv, &init_data);
gui_settings_init();
gui_view_init();
gui_window_init(icon);
gui_model_init();
gui_filter_init();
gui_treeview_init();
gui_sidebar_init();
gui_collection_init();
gui_playlist_init();
gui_audio_init();
gui_idle_enable();
gui_sidebar_select_first();
g_free(ui);
g_free(icon);
@ -122,30 +75,22 @@ static void __ocarina_startup(GApplication *application, gpointer data)
static void __ocarina_shutdown(GApplication *application, gpointer data)
{
gui_idle_disable();
gui_audio_deinit();
core_deinit();
gui_treeview_deinit();
gui_filter_deinit();
gui_model_deinit();
gui_window_deinit();
gui_settings_deinit();
gui_builder_deinit();
}
int main(int argc, char **argv)
{
GtkApplication *ocarina = gtk_application_new(OCARINA_APP, OCARINA_FLAGS);
GtkApplication *ocarina = gtk_application_new(OCARINA_NAME, OCARINA_FLAGS);
startup_argc = argc;
startup_argv = argv;
g_application_add_main_option_entries(G_APPLICATION(ocarina), ocarina_options);
g_application_add_option_group(G_APPLICATION(ocarina), gst_init_get_option_group());
g_signal_connect(G_APPLICATION(ocarina), "activate", (GCallback)__ocarina_activate, NULL);
g_signal_connect(G_APPLICATION(ocarina), "handle-local-options",
(GCallback)__ocarina_local_options, NULL);
g_signal_connect(G_APPLICATION(ocarina), "command-line", (GCallback)__ocarina_command_line, NULL);
g_signal_connect(G_APPLICATION(ocarina), "startup", (GCallback)__ocarina_startup, NULL);
g_signal_connect(G_APPLICATION(ocarina), "shutdown", (GCallback)__ocarina_shutdown, NULL);
g_signal_connect(G_APPLICATION(ocarina), "activate", (GCallback)__ocarina_activate, NULL);
g_signal_connect(G_APPLICATION(ocarina), "startup", (GCallback)__ocarina_startup, NULL);
g_signal_connect(G_APPLICATION(ocarina), "shutdown", (GCallback)__ocarina_shutdown, NULL);
return g_application_run(G_APPLICATION(ocarina), argc, argv);
}

View File

@ -1,259 +1,107 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/string.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <core/collection.h>
#include <gui/builder.h>
#include <gui/playlist.h>
#include <gui/treeview.h>
#include <gui/sidebar.h>
static void (*update_size[PL_MAX_TYPE])(struct playlist *) = {
[PL_SYSTEM] = gui_pl_system_update,
[PL_ARTIST] = gui_pl_artist_update,
[PL_LIBRARY] = gui_pl_library_update,
[PL_USER] = gui_pl_user_update,
enum playlist_sidebar_columns {
P_SB_IMAGE,
P_SB_NAME,
P_SB_PLAYLIST,
};
static void (*select_playlist[PL_MAX_TYPE])(struct playlist *) = {
[PL_SYSTEM] = gui_pl_system_select,
[PL_ARTIST] = gui_pl_artist_select,
[PL_LIBRARY] = gui_pl_library_select,
[PL_USER] = gui_pl_user_select,
};
static GtkTreeStore *p_store;
static enum playlist_t p_cur;
static inline void __gui_playlist_update_size(struct playlist *playlist)
static void __playlist_set(GtkTreeIter *iter, const gchar *name,
const gchar *image, enum playlist_t plist)
{
update_size[playlist->pl_type](playlist);
gtk_tree_store_set(p_store, iter, P_SB_NAME, name,
P_SB_IMAGE, image,
P_SB_PLAYLIST, plist, -1);
}
static void __gui_playlist_alloc(struct playlist *playlist)
static void __playlist_add(GtkTreeIter *parent, const gchar *name,
const gchar *image, enum playlist_t plist)
{
if (playlist->pl_type == PL_ARTIST)
gui_pl_artist_add(playlist);
GtkTreeIter iter;
gtk_tree_store_insert(p_store, &iter, parent, -1);
__playlist_set(&iter, name, image, plist);
}
static void __gui_playlist_added(struct playlist *playlist, struct track *track)
void __playlist_selection_changed(GtkTreeSelection *selection, gpointer data)
{
gui_model_add(playlist, track);
gui_filter_refilter(playlist);
__gui_playlist_update_size(playlist);
}
static void __gui_playlist_removed(struct playlist *playlist, struct track *track,
unsigned int n)
{
gui_model_remove(playlist, track, n);
__gui_playlist_update_size(playlist);
}
struct playlist_callbacks playlist_cb = {
.pl_cb_alloc = __gui_playlist_alloc,
.pl_cb_added = __gui_playlist_added,
.pl_cb_removed = __gui_playlist_removed,
.pl_cb_updated = gui_model_update,
};
static void __gui_playlist_add_selected_to(struct playlist *playlist)
{
GList *cur, *list = NULL;
struct track *track;
if (!playlist)
return;
list = gui_treeview_list_selected_tracks();
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
playlist_add(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
}
void __gui_playlist_add_favorites(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Favorites"));
}
void __gui_playlist_add_hidden(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Hidden"));
}
void __gui_playlist_add_user(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(gui_pl_user_add_dialog());
}
void __gui_playlist_add_other(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(data);
}
void __gui_playlist_add_queued(GtkMenuItem *item, gpointer data)
{
__gui_playlist_add_selected_to(playlist_lookup(PL_SYSTEM, "Queued Tracks"));
}
void __gui_playlist_delete(GtkMenuItem *item, gpointer data)
{
struct playlist *playlist = gui_model_get_playlist();
GList *cur, *list = NULL;
struct track *track;
if (!playlist)
return;
list = gui_treeview_list_selected_tracks();
cur = g_list_first(list);
while (cur) {
track = (struct track *)cur->data;
playlist_remove(playlist, track);
cur = g_list_next(cur);
}
g_list_free(list);
}
void __gui_playlist_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data)
{
switch (event->keyval) {
case GDK_KEY_f:
__gui_playlist_add_favorites(NULL, NULL);
break;
case GDK_KEY_h:
__gui_playlist_add_hidden(NULL, NULL);
break;
case GDK_KEY_p:
__gui_playlist_add_user(NULL, NULL);
break;
case GDK_KEY_q:
__gui_playlist_add_queued(NULL, NULL);
break;
case GDK_KEY_Delete:
__gui_playlist_delete(NULL, NULL);
default:
break;
}
}
static GtkWidget *__gui_playlist_build_submenu(void)
{
struct playlist *playlist;
GList *list = gui_pl_user_list();
GList *cur = g_list_first(list);
GtkWidget *submenu, *item;
if (!list)
return NULL;
submenu = gtk_menu_new();
while (cur) {
playlist = (struct playlist *)cur->data;
item = gtk_menu_item_new_with_label(playlist->pl_name);
gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
g_signal_connect(item, "activate",
G_CALLBACK(__gui_playlist_add_other),
playlist);
cur = g_list_next(cur);
}
gtk_widget_show_all(submenu);
g_list_free(list);
return submenu;
}
bool __gui_playlist_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkWidget *submenu;
if (event->button != GDK_BUTTON_SECONDARY)
return false;
submenu = __gui_playlist_build_submenu();
gtk_menu_item_set_submenu(gui_rc_add_to_other(), submenu);
gtk_widget_set_visible(GTK_WIDGET(gui_rc_add_to_other()),
submenu != NULL);
gui_treeview_select_path_at_pos(event->x, event->y);
gtk_menu_popup_at_pointer(gui_rc_menu(), (GdkEvent *)event);
return true;
}
void __gui_playlist_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
struct playlist *prev = playlist_current();
gui_sidebar_filter_path_select(path);
__gui_playlist_update_size(prev);
__gui_playlist_update_size(playlist_current());
}
void __gui_playlist_row_collapsed(GtkTreeView *treeview, GtkTreeIter *iter,
GtkTreePath *path, gpointer data)
{
gui_sidebar_filter_row_expanded(iter, false);
}
void __gui_playlist_row_expanded(GtkTreeView *treeview, GtkTreeIter *iter,
GtkTreePath *path, gpointer data)
{
gui_sidebar_filter_row_expanded(iter, true);
}
void __gui_playlist_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct playlist *playlist;
GtkStack *stack = GTK_STACK(gui_builder_widget("o_stack"));
GtkTreeModel *model = GTK_TREE_MODEL(p_store);
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 (gtk_tree_selection_get_selected(selection, &model, &iter)) {
gtk_tree_model_get(model, &iter, P_SB_PLAYLIST, &p_cur, -1);
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);
gtk_stack_set_visible_child_name(stack, "queues");
gui_sidebar_selected(SB_PLAYLIST,
gui_queue(playlist_get_queue()));
playlist_select(p_cur);
}
}
bool __gui_playlist_init_idle()
static void *__playlist_init(struct queue *queue)
{
struct playlist *playlist = playlist_current();
GtkTreeModel *filter = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter;
return gui_queue_alloc(queue, "Playlist", 0);
}
gtk_tree_model_get_iter_first(filter, &iter);
do {
gui_sidebar_filter_set_expand(&iter);
} while (gtk_tree_model_iter_next(filter, &iter));
select_playlist[playlist->pl_type](playlist);
return true;
static bool __playlist_erase(struct queue *queue, struct track *track)
{
/* collection_unban() and playlist_remove() handle queue changes */
if (gui_playlist_cur() == PL_HIDDEN)
collection_unban(track);
else
playlist_remove(gui_playlist_cur(), track);
return false;
}
void gui_playlist_init()
{
gui_pl_system_init();
gui_pl_artist_init();
gui_pl_user_init();
gui_pl_library_init();
GtkTreeView *treeview;
GtkTreeIter parent;
idle_schedule(IDLE_SYNC, __gui_playlist_init_idle, NULL);
p_store = GTK_TREE_STORE(gui_builder_object("o_playlist_store"));
/* Add "Playlist" header. */
gtk_tree_store_insert(p_store, &parent, NULL, -1);
gtk_tree_store_insert(p_store, &parent, NULL, -1);
__playlist_set(&parent, "<span size='large'>Playlists</span>",
"emblem-documents", 0);
/* Add playlists. */
__playlist_add(&parent, "Favorites", "emblem-favorite", PL_FAVORITED);
__playlist_add(&parent, "Hidden", "window-close",PL_HIDDEN);
__playlist_add(&parent, "Most Played", "go-up", PL_MOST_PLAYED);
__playlist_add(&parent, "Least Played", "go-down", PL_LEAST_PLAYED);
__playlist_add(&parent, "Unplayed", "audio-x-generic", PL_UNPLAYED);
treeview = GTK_TREE_VIEW(gui_builder_widget("o_playlist_view"));
gtk_tree_view_expand_all(treeview);
gtk_tree_selection_set_select_function(
gtk_tree_view_get_selection(treeview),
gui_sidebar_on_select, NULL, NULL);
gtk_tree_store_insert(p_store, &parent, NULL, -1);
}
enum playlist_t gui_playlist_cur()
{
return p_cur;
}
struct queue_ops playlist_ops = {
.qop_init = __playlist_init,
.qop_deinit = gui_queue_free,
.qop_added = gui_queue_added,
.qop_erase = __playlist_erase,
.qop_removed = gui_queue_removed,
.qop_cleared = gui_queue_cleared,
.qop_updated = gui_queue_updated,
};

View File

@ -1,70 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlist.h>
#include <gui/sidebar.h>
static bool __gui_pl_artist_header(GtkTreeIter *iter)
{
if (gui_sidebar_iter_first(iter))
return gui_sidebar_iter_find(iter, "Collection", PL_SYSTEM);
return false;
}
static bool __gui_pl_artist_init_idle()
{
struct db_entry *artist, *next;
struct playlist *playlist;
GtkTreeIter iter;
if (!__gui_pl_artist_header(&iter))
return false;
db_for_each(artist, next, artist_db_get()) {
playlist = ARTIST(artist)->ar_playlist;
gui_sidebar_iter_sort_child(&iter, playlist, "system-users");
}
return true;
}
void gui_pl_artist_init()
{
idle_schedule(IDLE_SYNC, __gui_pl_artist_init_idle, NULL);
}
bool gui_pl_artist_add(struct playlist *playlist)
{
GtkTreeIter iter;
if (!__gui_pl_artist_header(&iter))
return false;
gui_sidebar_iter_sort_child(&iter, playlist, "system-users");
return true;
}
void gui_pl_artist_update(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_artist_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&child);
}
void gui_pl_artist_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_artist_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}

View File

@ -1,117 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlist.h>
#include <gui/idle.h>
#include <gui/playlists/library.h>
#include <gui/sidebar.h>
#include <gui/window.h>
#include <glib/gi18n.h>
void __gui_pl_library_choose(GtkButton *button, gpointer data)
{
GtkFileFilter *filter;
const gchar *music;
GtkWidget *dialog;
gchar *path;
gint res;
filter = gtk_file_filter_new();
gtk_file_filter_add_mime_type(filter, "inode/directory");
dialog = gtk_file_chooser_dialog_new("Add Music", gui_window(),
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_Open"), GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
music = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
if (music)
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
music);
res = gtk_dialog_run(GTK_DIALOG(dialog));
if (res == GTK_RESPONSE_ACCEPT) {
path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
gui_pl_library_add(path);
g_free(path);
}
gtk_widget_destroy(dialog);
}
static bool __gui_pl_library_header(GtkTreeIter *iter)
{
if (gui_sidebar_iter_first(iter))
return gui_sidebar_iter_find(iter, "Library", PL_MAX_TYPE);
return false;
}
static bool __gui_pl_library_init_idle()
{
struct db_entry *library, *next;
struct playlist *playlist;
GtkTreeIter iter;
if (!__gui_pl_library_header(&iter))
return false;
db_for_each(library, next, library_db_get()) {
playlist = LIBRARY(library)->li_playlist;
gui_sidebar_iter_sort_child(&iter, playlist, "folder");
}
#ifndef CONFIG_TESTING
if (library_db_get()->db_size == 0)
__gui_pl_library_choose(NULL, NULL);
#endif /* CONFIG_TESTING */
return true;
}
void gui_pl_library_init()
{
idle_schedule(IDLE_SYNC, __gui_pl_library_init_idle, NULL);
}
struct playlist *gui_pl_library_add(const gchar *filename)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!__gui_pl_library_header(&iter))
return false;
playlist = playlist_new(PL_LIBRARY, filename);
if (playlist) {
gui_sidebar_iter_sort_child(&iter, playlist, "folder");
gui_idle_enable();
}
return playlist;
}
void gui_pl_library_update(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_library_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&child);
}
void gui_pl_library_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_library_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}

View File

@ -1,139 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/string.h>
#include <gui/playlists/system.h>
#include <gui/sidebar.h>
static struct playlist *favorites;
static struct playlist *hidden;
static bool __gui_pl_system_is_playlist(struct playlist *playlist)
{
return string_match(playlist->pl_name, "Favorites") ||
string_match(playlist->pl_name, "Hidden");
}
static bool __gui_pl_system_is_dynamic(struct playlist *playlist)
{
return string_match(playlist->pl_name, "Most Played") ||
string_match(playlist->pl_name, "Least Played") ||
string_match(playlist->pl_name, "Unplayed");
}
static bool __gui_pl_system_find_descend_header(GtkTreeIter *iter,
const gchar *name)
{
GtkTreeIter header;
if (!gui_sidebar_iter_first(&header))
return false;
if (!gui_sidebar_iter_find(&header, name, PL_MAX_TYPE))
return false;
return gui_sidebar_iter_down(&header, iter);
}
void __gui_pl_system_favorite_toggled(GtkToggleButton *toggle, gpointer data)
{
if (gtk_toggle_button_get_active(toggle))
playlist_add(favorites, audio_cur_track());
else
playlist_remove(favorites, audio_cur_track());
}
void __gui_pl_system_hide_toggled(GtkToggleButton *toggle, gpointer data)
{
if (gtk_toggle_button_get_active(toggle)) {
if (playlist_add(hidden, audio_cur_track()))
audio_next();
} else
playlist_remove(hidden, audio_cur_track());
}
static bool __gui_pl_system_init_idle()
{
GtkTreeIter iter;
/* Add toplevel playlists. */
gui_sidebar_iter_first(&iter);
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Queued Tracks"),
"audio-x-generic");
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "Collection"),
"media-optical");
gui_sidebar_iter_add(&iter, playlist_lookup(PL_SYSTEM, "History"),
"document-open-recent");
/* Add user-modifiable playlists. */
gui_sidebar_iter_find(&iter, "Playlists", PL_MAX_TYPE);
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Favorites"),
"emblem-favorite");
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Hidden"),
"window-close");
/* Add dynamic playlists. */
gui_sidebar_iter_find(&iter, "Dynamic", PL_MAX_TYPE);
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Most Played"),
"go-up");
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Least Played"),
"go-down");
gui_sidebar_iter_append_child(&iter, playlist_lookup(PL_SYSTEM, "Unplayed"),
"audio-x-generic");
return true;
}
void gui_pl_system_init()
{
favorites = playlist_lookup(PL_SYSTEM, "Favorites");
hidden = playlist_lookup(PL_SYSTEM, "Hidden");
idle_schedule(IDLE_SYNC, __gui_pl_system_init_idle, NULL);
}
void gui_pl_system_update(struct playlist *playlist)
{
GtkTreeIter iter;
if (__gui_pl_system_is_playlist(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Playlists"))
return;
} else if (__gui_pl_system_is_dynamic(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Dynamic"))
return;
} else {
if (!gui_sidebar_iter_first(&iter))
return;
}
if (gui_sidebar_iter_find(&iter, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&iter);
}
void gui_pl_system_select(struct playlist *playlist)
{
GtkTreeIter iter;
if (__gui_pl_system_is_playlist(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Playlists"))
return;
} else if (__gui_pl_system_is_dynamic(playlist)) {
if (!__gui_pl_system_find_descend_header(&iter, "Dynamic"))
return;
} else {
if (!gui_sidebar_iter_first(&iter))
return;
}
if (gui_sidebar_iter_find(&iter, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&iter);
}
void gui_pl_system_track_loaded(struct track *track)
{
gtk_toggle_button_set_active(gui_favorite_button(),
playlist_has(favorites, track));
gtk_toggle_button_set_active(gui_hide_button(),
playlist_has(hidden, track));
}

View File

@ -1,153 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/playlist.h>
#include <gui/sidebar.h>
#include <gui/window.h>
#include <glib/gi18n.h>
static bool __gui_pl_user_header(GtkTreeIter *iter)
{
if (gui_sidebar_iter_first(iter))
return gui_sidebar_iter_find(iter, "Playlists", PL_MAX_TYPE);
return false;
}
static gint __gui_pl_user_compare(gconstpointer a, gconstpointer b)
{
struct playlist *pl_a = (struct playlist *)a;
struct playlist *pl_b = (struct playlist *)b;
return g_utf8_collate(pl_a->pl_name, pl_b->pl_name);
}
static bool __gui_pl_user_init_idle()
{
struct db_entry *user, *next;
struct playlist *playlist;
GtkTreeIter iter;
if (!__gui_pl_user_header(&iter))
return false;
db_for_each(user, next, pl_user_db_get()) {
playlist = &USER_PLAYLIST(user)->pl_playlist;
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic");
}
return true;
}
void __gui_pl_user_editing_started(GtkCellRenderer *renderer,
GtkCellEditable *editable,
gchar *path, gpointer data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!gui_sidebar_iter_from_string(path, &iter))
return;
playlist = gui_sidebar_iter_playlist(&iter);
if (GTK_IS_ENTRY(editable))
gtk_entry_set_text(GTK_ENTRY(editable), playlist->pl_name);
}
void __gui_pl_user_edited(GtkCellRendererText *renderer, gchar *path,
gchar *new_name, gpointer data)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!gui_sidebar_iter_from_string(path, &iter))
return;
playlist = gui_sidebar_iter_playlist(&iter);
pl_user_rename(playlist, new_name);
gui_sidebar_iter_update_playlist(&iter, playlist);
gui_sidebar_iter_set_editable(&iter, false);
}
void gui_pl_user_init()
{
idle_schedule(IDLE_SYNC, __gui_pl_user_init_idle, NULL);
}
struct playlist *gui_pl_user_add(const gchar *name)
{
struct playlist *playlist;
GtkTreeIter iter;
if (!__gui_pl_user_header(&iter))
return NULL;
playlist = playlist_new(PL_USER, name);
if (playlist)
gui_sidebar_iter_sort_child(&iter, playlist, "text-x-generic");
return playlist;
}
struct playlist *gui_pl_user_add_dialog(void)
{
unsigned int flags = GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL;
GtkWidget *entry, *dialog, *content;
struct playlist *playlist = NULL;
entry = gtk_entry_new();
dialog = gtk_dialog_new_with_buttons("New Playlist Name?",
gui_window(), flags,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_OK"), GTK_RESPONSE_ACCEPT,
NULL);
content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
gtk_entry_set_activates_default(GTK_ENTRY(entry), true);
gtk_container_add(GTK_CONTAINER(content), entry);
gtk_widget_show_all(dialog);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
playlist = gui_pl_user_add(gtk_entry_get_text(GTK_ENTRY(entry)));
gtk_widget_destroy(dialog);
return playlist;
}
void gui_pl_user_update(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_user_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_update(&child);
}
void gui_pl_user_select(struct playlist *playlist)
{
GtkTreeIter iter, child;
if (!__gui_pl_user_header(&iter))
return;
if (!gui_sidebar_iter_down(&iter, &child))
return;
if (gui_sidebar_iter_find(&child, playlist->pl_name, playlist->pl_type))
gui_sidebar_iter_select(&child);
}
GList *gui_pl_user_list(void)
{
struct db_entry *user, *next;
struct playlist *playlist;
GList *list = NULL;
db_for_each(user, next, pl_user_db_get()) {
playlist = &USER_PLAYLIST(user)->pl_playlist;
list = g_list_insert_sorted(list, playlist,
__gui_pl_user_compare);
}
return list;
}

186
gui/queue.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/filter.h>
#include <core/string.h>
#include <gui/builder.h>
#include <gui/model.h>
#include <gui/queue.h>
#include <gui/view.h>
static struct gui_queue *gq_queue = NULL;
static void __queue_set_runtime(struct gui_queue *queue)
{
gchar *len;
if (queue != gq_queue)
return;
len = string_sec2str_long(queue->gq_queue->q_length);
gtk_label_set_text(GTK_LABEL(gui_builder_widget("o_runtime")), len);
g_free(len);
}
static void __queue_toggle_flag(bool active, GtkWidget *widget,
enum queue_flags flag)
{
if (gq_queue == NULL)
return;
/*
* Some GTK themes have trouble with toggle buttons,
* so let's help users by changing image sensitivity.
*/
if (active)
queue_set_flag(gq_queue->gq_queue, flag);
else
queue_unset_flag(gq_queue->gq_queue, flag);
gtk_widget_set_sensitive(widget, active);
}
void __queue_random(GtkToggleButton *button, gpointer data)
{
__queue_toggle_flag(gtk_toggle_button_get_active(button),
gtk_button_get_image(GTK_BUTTON(button)),
Q_RANDOM);
}
void __queue_repeat(GtkToggleButton *button, gpointer data)
{
__queue_toggle_flag(gtk_toggle_button_get_active(button),
gtk_button_get_image(GTK_BUTTON(button)),
Q_REPEAT);
}
void __queue_disabled(GtkSwitch *enabled, GParamSpec *pspec, gpointer data)
{
__queue_toggle_flag(gtk_switch_get_active(enabled),
gui_builder_widget("o_treeview"),
Q_ENABLED);
}
static gboolean __queue_visible_func(GtkTreeModel *model, GtkTreeIter *iter,
gpointer data)
{
struct track *track;
if (!gq_queue || (strlen(gtk_entry_get_text(GTK_ENTRY(data))) == 0))
return TRUE;
track = gui_queue_model_iter_get_track(gq_queue->gq_model, iter);
return set_has(&gq_queue->gq_visible, track->tr_dbe.dbe_index);
}
void __queue_filter(GtkSearchEntry *entry, gpointer data)
{
const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
if (!gq_queue)
return;
if (strlen(text) > 0)
filter_search(text, &gq_queue->gq_visible);
gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(gq_queue->gq_filter));
gui_view_scroll();
}
struct gui_queue *gui_queue_alloc(struct queue *queue, const gchar *text,
unsigned int flags)
{
struct gui_queue *gq = g_malloc(sizeof(struct gui_queue));
gq->gq_flags = flags;
gq->gq_text = g_strdup(text);
gq->gq_model = gui_queue_model_new(queue);
gq->gq_filter = gtk_tree_model_filter_new(
GTK_TREE_MODEL(gq->gq_model), NULL);
gq->gq_queue = queue;
set_init(&gq->gq_visible);
gtk_tree_model_filter_set_visible_func(
GTK_TREE_MODEL_FILTER(gq->gq_filter),
__queue_visible_func,
gui_builder_object("o_search"), NULL);
return gq;
}
void gui_queue_free(struct queue *queue)
{
struct gui_queue *gq = gui_queue(queue);
set_deinit(&gq->gq_visible);
queue->q_private = NULL;
if (gq_queue == gq)
gui_view_set_model(NULL);
g_object_unref(gq->gq_model);
g_free(gq->gq_text);
g_free(gq);
}
void gui_queue_show(struct gui_queue *queue)
{
GtkTreeView *view = GTK_TREE_VIEW(gui_builder_widget("o_treeview"));
GtkButton *random = GTK_BUTTON(gui_builder_widget("o_random"));
GtkButton *repeat = GTK_BUTTON(gui_builder_widget("o_repeat"));
GtkSwitch *enabled = GTK_SWITCH(gui_builder_widget("o_enable"));
GtkEntry *search = GTK_ENTRY(gui_builder_widget("o_search"));
GtkLabel *runtime = GTK_LABEL(gui_builder_widget("o_runtime"));
bool has_random = false, has_repeat = false, is_enabled = false;;
gq_queue = queue;
gtk_widget_set_sensitive(GTK_WIDGET(random), gui_queue_can_random(queue));
gtk_widget_set_sensitive(GTK_WIDGET(repeat), gui_queue_can_repeat(queue));
gtk_widget_set_sensitive(GTK_WIDGET(enabled), gui_queue_can_disable(queue));
if (queue) {
has_random = queue_has_flag(queue->gq_queue, Q_RANDOM);
has_repeat = queue_has_flag(queue->gq_queue, Q_REPEAT);
is_enabled = queue_has_flag(queue->gq_queue, Q_ENABLED);
gui_view_set_model(GTK_TREE_MODEL_FILTER(queue->gq_filter));
__queue_set_runtime(queue);
} else
gtk_label_set_text(runtime, "");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(random), has_random);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(repeat), has_repeat);
gtk_switch_set_active(GTK_SWITCH(enabled), is_enabled);
/*
* Some GTK themes have trouble with toggle buttons,
* so let's help users know what the current state is.
*/
gtk_widget_set_sensitive(gtk_button_get_image(random), has_random);
gtk_widget_set_sensitive(gtk_button_get_image(repeat), has_repeat);
gtk_widget_set_sensitive(GTK_WIDGET(view), is_enabled);
gtk_widget_set_sensitive(GTK_WIDGET(search), queue != NULL);
gtk_entry_set_text(search, "");
}
void gui_queue_added(struct queue *queue, unsigned int row)
{
gui_queue_model_add(gui_queue(queue)->gq_model, row);
__queue_set_runtime(gui_queue(queue));
}
void gui_queue_removed(struct queue *queue, unsigned int row)
{
gui_queue_model_remove(gui_queue(queue)->gq_model, row);
__queue_set_runtime(gui_queue(queue));
}
void gui_queue_cleared(struct queue *queue, unsigned int n)
{
gui_queue_model_clear(gui_queue(queue)->gq_model, n);
__queue_set_runtime(gui_queue(queue));
}
void gui_queue_updated(struct queue *queue, unsigned int row)
{
gui_queue_model_update(gui_queue(queue)->gq_model, row);
__queue_set_runtime(gui_queue(queue));
}

View File

@ -2,10 +2,10 @@
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/file.h>
#include <core/settings.h>
#include <gui/settings.h>
static GHashTable *gui_settings = NULL;
static struct file gui_settings_file = FILE_INIT_DATA("", "settings", 0);
static struct file gui_settings_file = FILE_INIT("settings", 0);
static void __settings_save_item(gpointer key, gpointer value, gpointer data)
@ -29,16 +29,14 @@ static void __settings_read()
unsigned int num, i, value;
gchar *key;
num = file_readu(&gui_settings_file);
file_readf(&gui_settings_file, "%u\n", &num);
for (i = 0; i < num; i++) {
key = file_readw(&gui_settings_file);
value = file_readu(&gui_settings_file);
file_readf(&gui_settings_file, "%m[^ ] %u\n", &key, &value);
g_hash_table_insert(gui_settings, key, GUINT_TO_POINTER(value));
}
}
void settings_init()
void gui_settings_init()
{
gui_settings = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
@ -48,38 +46,38 @@ void settings_init()
file_close(&gui_settings_file);
}
void settings_deinit()
void gui_settings_deinit()
{
g_hash_table_destroy(gui_settings);
gui_settings = NULL;
}
void settings_set(const gchar *key, unsigned int value)
void gui_settings_set(const gchar *key, unsigned int value)
{
if (gui_settings && key) {
if (gui_settings) {
g_hash_table_replace(gui_settings, g_strdup(key),
GUINT_TO_POINTER(value));
__settings_save();
}
}
unsigned int settings_get(const gchar *key)
unsigned int gui_settings_get(const gchar *key)
{
if (gui_settings && key)
if (gui_settings)
return GPOINTER_TO_UINT(g_hash_table_lookup(gui_settings, key));
return 0;
}
bool settings_has(const gchar *key)
bool gui_settings_has(const gchar *key)
{
if (gui_settings && key)
if (gui_settings)
return g_hash_table_contains(gui_settings, key);
return false;
}
#ifdef CONFIG_TESTING
GHashTable *test_get_settings()
GHashTable *test_get_gui_settings()
{
return gui_settings;
}

View File

@ -1,508 +1,216 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/settings.h>
#include <core/string.h>
#include <gui/model.h>
#include <core/collection.h>
#include <core/history.h>
#include <core/queue.h>
#include <core/tempq.h>
#include <gui/builder.h>
#include <gui/queue.h>
#include <gui/settings.h>
#include <gui/sidebar.h>
#include <gui/treeview.h>
const gchar *SIDEBAR_SETTING = "gui.sidebar.pos";
const gchar *COLLECTION_FMT = "<big>Collection</big>\n%d track%s";
const gchar *HISTORY_FMT = "<big>History</big>\n%d track%s";
static GtkListStore *sb_store;
enum sidebar_columns {
SB_IMAGE,
SB_NAME,
SB_TYPE,
SB_EDITABLE,
SB_IMAGE_SZ,
SB_TEXT,
SB_QUEUE,
};
const gchar *SIDEBAR_SETTING = "gui.sidebar.pos";
static gchar *__gui_sidebar_size_str(struct playlist *playlist)
static void __sidebar_set_size(GtkTreeIter *iter, const gchar *name,
unsigned int size)
{
const gchar *fmt = "%s\n%d track%s";
unsigned int size;
const gchar *fmt = "<big>%s</big>\n%d track%s";
gchar *text;
if (!playlist)
return NULL;
size = playlist_size(playlist);
if (playlist_current() == playlist)
fmt = "<b>%s\n%d track%s</b>";
return g_markup_printf_escaped(fmt, playlist->pl_name, size,
(size != 1) ? "s" : "");
}
static void __gui_sidebar_set(GtkTreeIter *iter, const gchar *name,
const gchar *image, enum playlist_type_t type)
{
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, name,
SB_IMAGE, image,
SB_TYPE, type,
SB_EDITABLE, false, -1);
}
static void __gui_sidebar_set_playlist(GtkTreeIter *iter,
struct playlist *playlist,
const gchar *image)
{
gchar *text = __gui_sidebar_size_str(playlist);
__gui_sidebar_set(iter, text, image, playlist->pl_type);
text = g_strdup_printf(fmt, name, size, (size == 1) ? "" : "s");
gtk_list_store_set(sb_store, iter, SB_TEXT, text, -1);
g_free(text);
}
static void __gui_sidebar_add_header(GtkTreeIter *iter, const gchar *name,
const gchar *image)
static void __sidebar_set_queue(GtkTreeIter *iter, struct gui_queue *queue)
{
gchar *formatted = g_strdup_printf("<big>%s</big>", name);
gtk_tree_store_insert(gui_sidebar_store(), iter, NULL, -1);
__gui_sidebar_set(iter, NULL, NULL, PL_MAX_TYPE);
gtk_tree_store_insert(gui_sidebar_store(), iter, NULL, -1);
__gui_sidebar_set(iter, formatted, image, PL_MAX_TYPE);
g_free(formatted);
gtk_list_store_set(sb_store, iter, SB_QUEUE, queue, -1);
}
static int __gui_sidebar_compare(GtkTreeIter *iter, const gchar *name,
enum playlist_type_t type)
static struct gui_queue *__sidebar_get_queue(GtkTreeIter *iter)
{
gchar *cur;
int ret;
struct gui_queue *queue;
if (gui_sidebar_iter_type(iter) != type)
return gui_sidebar_iter_type(iter) - type;
cur = gui_sidebar_iter_name(iter);
ret = g_utf8_collate(cur, name);
g_free(cur);
return ret;
gtk_tree_model_get(GTK_TREE_MODEL(sb_store), iter, SB_QUEUE, &queue, -1);
return queue;
}
static inline void __gui_sidebar_filter_iter_convert(GtkTreeIter *iter,
GtkTreeIter *child)
static bool __sidebar_find_queue(struct gui_queue *queue, GtkTreeIter *iter)
{
gtk_tree_model_filter_convert_iter_to_child_iter(gui_sidebar_filter(),
child, iter);
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sb_store), iter);
do {
if (__sidebar_get_queue(iter) == queue)
return true;
} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(sb_store), iter));
return false;
}
static gchar *__gui_sidebar_filter_iter_name(GtkTreeIter *iter)
void __sidebar_resize(GtkPaned *pane, GParamSpec *pspec, gpointer data)
{
GtkTreeIter child;
__gui_sidebar_filter_iter_convert(iter, &child);
return gui_sidebar_iter_name(&child);
gui_settings_set(SIDEBAR_SETTING, gtk_paned_get_position(pane));
}
static gboolean __gui_sidebar_visible_func(GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data)
bool __sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event, gpointer data)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
if (type == PL_SYSTEM || type == PL_ARTIST)
return playlist_size(gui_sidebar_iter_playlist(iter)) > 0;
return TRUE;
}
static gboolean __gui_sidebar_can_select(GtkTreeSelection *selection,
GtkTreeModel *model, GtkTreePath *path,
gboolean selected, gpointer data)
{
GtkTreeIter iter, child;
gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, &child);
return gui_sidebar_iter_type(&child) != PL_MAX_TYPE;
}
void __gui_sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
{
bool active = false, sensitive = false;
struct playlist *playlist = NULL;
GtkTreeIter iter;
if (gui_sidebar_iter_current(&iter)) {
playlist = gui_sidebar_iter_playlist(&iter);
active = playlist->pl_random;
sensitive = (playlist->pl_ops->pl_set_random != NULL);
}
gui_treeview_set_playlist(playlist);
gtk_toggle_button_set_active(gui_random_button(), active);
gtk_widget_set_sensitive(GTK_WIDGET(gui_random_button()), sensitive);
}
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)
{
GtkTreeModel *model = GTK_TREE_MODEL(sb_store);
struct gui_queue *queue;
GtkTreePath *path;
GtkTreeIter iter;
GList *rows;
if (!gui_sidebar_iter_current(&iter))
if (event->keyval != GDK_KEY_Delete)
return false;
if (playlist_delete(gui_model_get_playlist()))
gtk_tree_store_remove(gui_sidebar_store(), &iter);
rows = gtk_tree_selection_get_selected_rows(selection, &model);
path = rows->data;
if (gtk_tree_model_get_iter(model, &iter, path))
queue = __sidebar_get_queue(&iter);
if (tempq_index(queue->gq_queue) == -1)
goto out;
tempq_free(queue->gq_queue);
out:
g_list_free_full(rows, (GDestroyNotify)gtk_tree_path_free);
return true;
}
bool __gui_sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data)
void __sidebar_selection_changed(GtkTreeSelection *selection, gpointer data)
{
switch (event->keyval) {
case GDK_KEY_BackSpace:
return __gui_sidebar_rename(NULL, NULL);
case GDK_KEY_Return:
return __gui_sidebar_select(NULL, NULL);
case GDK_KEY_Delete:
return __gui_sidebar_delete(NULL, NULL);
default:
return false;
GtkStack *stack = GTK_STACK(gui_builder_widget("o_stack"));
GtkTreeModel *model = GTK_TREE_MODEL(sb_store);
GtkTreeIter iter;
if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
gtk_stack_set_visible_child_name(stack, "queues");
gui_sidebar_selected(SB_SIDEBAR, __sidebar_get_queue(&iter));
gtk_tree_path_free(path);
}
}
bool __gui_sidebar_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
void __sidebar_deselect(const gchar *widget)
{
enum playlist_type_t type = PL_MAX_TYPE;
GtkTreePath *path;
GtkTreeIter iter;
bool ret = true;
GtkTreeView *treeview = GTK_TREE_VIEW(gui_builder_widget(widget));
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
return false;
if (event->button == GDK_BUTTON_SECONDARY) {
gtk_tree_view_set_cursor(treeview, path, NULL, false);
if (gui_sidebar_iter_current(&iter))
type = gui_sidebar_iter_type(&iter);
gtk_widget_set_visible(gui_builder_widget("rc_sidebar_rename"),
type == PL_USER);
gtk_menu_popup_at_pointer(gui_sidebar_menu(), (GdkEvent *)event);
} else if (event->type == GDK_2BUTTON_PRESS &&
event->button == GDK_BUTTON_MIDDLE) {
__gui_sidebar_do_rename(path);
} else
ret = false;
gtk_tree_path_free(path);
return ret;
}
void __gui_sidebar_resized(GtkPaned *pane, GParamSpec *pspec, gpointer data)
{
settings_set(SIDEBAR_SETTING, gtk_paned_get_position(pane));
}
void __gui_sidebar_random_toggled(GtkToggleButton *button, gpointer data)
{
struct playlist *playlist = gui_model_get_playlist();
bool active = gtk_toggle_button_get_active(button);
if (playlist)
playlist_set_random(playlist, active);
gtk_tree_selection_unselect_all(selection);
}
void gui_sidebar_init()
{
int pos = settings_get(SIDEBAR_SETTING);
GtkTreeSelection *selection;
GtkPaned *pane = GTK_PANED(gui_builder_widget("o_sidebar"));
GtkTreeIter iter;
int pos;
/* Set up entries in the liststore. */
sb_store = GTK_LIST_STORE(gui_builder_object("o_sidebar_store"));
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sb_store), &iter);
__sidebar_set_queue(&iter, gui_queue(collection_get_queue()));
gtk_tree_model_iter_next(GTK_TREE_MODEL(sb_store), &iter);
__sidebar_set_queue(&iter, gui_queue(history_get_queue()));
/* Set sidebar width. */
pos = gui_settings_get(SIDEBAR_SETTING);
if (pos > 0)
gtk_paned_set_position(pane, pos);
}
void gui_sidebar_select_first()
{
GtkTreeView *treeview = GTK_TREE_VIEW(gui_builder_widget("o_sidebar_view"));
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
struct gui_queue *gq;
GtkTreePath *path;
GtkTreeIter iter;
gtk_tree_view_enable_model_drag_dest(gui_sidebar_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
if (!gui_sidebar_iter_first(&iter)) {
__gui_sidebar_add_header(&iter, "Playlists", "emblem-documents");
__gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic");
__gui_sidebar_add_header(&iter, "Library", "emblem-system");
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
gtk_tree_selection_set_select_function(selection,
__gui_sidebar_can_select,
NULL, NULL);
gtk_tree_model_filter_set_visible_func(gui_sidebar_filter(),
__gui_sidebar_visible_func,
NULL, NULL);
}
if (pos > 0)
gtk_paned_set_position(gui_sidebar(), pos);
}
gboolean gui_sidebar_iter_current(GtkTreeIter *iter)
{
GtkTreeView *treeview = gui_sidebar_treeview();
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter it;
if (!gtk_tree_selection_get_selected(selection, &model, &it))
return false;
__gui_sidebar_filter_iter_convert(&it, iter);
return true;
}
gboolean gui_sidebar_iter_first(GtkTreeIter *iter)
{
return gtk_tree_model_get_iter_first(gui_sidebar_model(), iter);
}
gboolean gui_sidebar_iter_next(GtkTreeIter *iter)
{
return gtk_tree_model_iter_next(gui_sidebar_model(), iter);
}
gboolean gui_sidebar_iter_down(GtkTreeIter *iter, GtkTreeIter *child)
{
return gtk_tree_model_iter_children(gui_sidebar_model(), child, iter);
}
gchar *gui_sidebar_iter_name(GtkTreeIter *iter)
{
gchar *text = NULL, *parsed = NULL, *name, **split;
gtk_tree_model_get(gui_sidebar_model(), iter, SB_NAME, &text, -1);
if (!text)
return g_strdup("");
pango_parse_markup(text, -1, 0, NULL, &parsed, NULL, NULL);
if (!parsed)
return g_strdup("");
split = g_strsplit(parsed, "\n", 2);
name = g_strdup(split[0]);
g_strfreev(split);
g_free(parsed);
g_free(text);
return name;
}
enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *iter)
{
enum playlist_type_t type;
gtk_tree_model_get(gui_sidebar_model(), iter, SB_TYPE, &type, -1);
return type;
}
bool gui_sidebar_iter_editable(GtkTreeIter *iter)
{
gboolean editable;
gtk_tree_model_get(gui_sidebar_model(), iter, SB_EDITABLE, &editable, -1);
return editable == TRUE;
}
struct playlist *gui_sidebar_iter_playlist(GtkTreeIter *iter)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
gchar *name = gui_sidebar_iter_name(iter);
struct playlist *playlist = playlist_lookup(type, name);
g_free(name);
return playlist;
}
void gui_sidebar_iter_add(GtkTreeIter *iter, struct playlist *playlist,
const gchar *image)
{
GtkTreeIter new;
gtk_tree_store_insert_before(gui_sidebar_store(), &new, NULL, iter);
__gui_sidebar_set_playlist(&new, playlist, image);
}
void gui_sidebar_iter_sort_child(GtkTreeIter *iter, struct playlist *playlist,
const gchar *image)
{
GtkTreeIter child, new;
if (!gui_sidebar_iter_down(iter, &child))
goto out_append;
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(sb_store), &iter);
do {
if (__gui_sidebar_compare(&child, playlist->pl_name,
playlist->pl_type) >= 0) {
gtk_tree_store_insert_before(gui_sidebar_store(),
&new, iter, &child);
__gui_sidebar_set_playlist(&new, playlist, image);
return;
}
} while (gui_sidebar_iter_next(&child));
gq = __sidebar_get_queue(&iter);
} while (!queue_has_flag(gq->gq_queue, Q_ENABLED) &&
gtk_tree_model_iter_next(GTK_TREE_MODEL(sb_store), &iter));
out_append:
gui_sidebar_iter_append_child(iter, playlist, image);
path = gtk_tree_model_get_path(GTK_TREE_MODEL(sb_store), &iter);
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
void gui_sidebar_iter_append_child(GtkTreeIter *iter, struct playlist *playlist,
const gchar *image)
gboolean gui_sidebar_on_select(GtkTreeSelection *selection,
GtkTreeModel *model, GtkTreePath *path,
gboolean selected, gpointer data)
{
GtkTreeIter new;
gtk_tree_store_insert_before(gui_sidebar_store(), &new, iter, NULL);
__gui_sidebar_set_playlist(&new, playlist, image);
return gtk_tree_path_get_depth(path) != 1;
}
void gui_sidebar_iter_update_playlist(GtkTreeIter *iter,
struct playlist *playlist)
void gui_sidebar_selected(enum sidebar_selection_t selected,
struct gui_queue *queue)
{
gchar *text;
if (selected != SB_COLLECTION)
__sidebar_deselect("o_collection_view");
if (selected != SB_PLAYLIST)
__sidebar_deselect("o_playlist_view");
if (selected != SB_SIDEBAR)
__sidebar_deselect("o_sidebar_view");
if (!playlist)
gui_queue_show(queue);
}
void gui_sidebar_add(struct gui_queue *queue)
{
const gchar *text = "<big>Queued Tracks</big>\n0 tracks";
GtkTreeIter iter, sibling;
if (!GTK_IS_TREE_MODEL(sb_store))
return;
text = __gui_sidebar_size_str(playlist);
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, text, -1);
g_free(text);
gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(sb_store),
&sibling, NULL,
tempq_index(queue->gq_queue));
gtk_list_store_insert_before(sb_store, &iter, &sibling);
gtk_list_store_set(sb_store, &iter, SB_IMAGE, "audio-x-generic",
SB_IMAGE_SZ, GTK_ICON_SIZE_BUTTON,
SB_TEXT, text,
SB_QUEUE, queue, -1);
__sidebar_set_size(&iter, queue->gq_text, queue_size(queue->gq_queue));
}
void gui_sidebar_iter_update(GtkTreeIter *iter)
void gui_sidebar_remove(struct gui_queue *queue)
{
gui_sidebar_iter_update_playlist(iter, gui_sidebar_iter_playlist(iter));
GtkTreeIter iter;
if (__sidebar_find_queue(queue, &iter))
gtk_list_store_remove(sb_store, &iter);
}
void gui_sidebar_iter_select(GtkTreeIter *iter)
void gui_sidebar_set_size(struct gui_queue *queue)
{
GtkTreeSelection *selection;
GtkTreeIter filter;
GtkTreeIter iter;
gtk_tree_model_filter_convert_child_iter_to_iter(gui_sidebar_filter(),
&filter, iter);
if (!GTK_IS_TREE_MODEL(sb_store))
return;
selection = gtk_tree_view_get_selection(gui_sidebar_treeview());
gtk_tree_selection_select_iter(selection, &filter);
}
bool gui_sidebar_iter_set_editable(GtkTreeIter *iter, bool editable)
{
enum playlist_type_t type = gui_sidebar_iter_type(iter);
if (type != PL_USER)
return false;
gtk_tree_store_set(gui_sidebar_store(), iter, SB_EDITABLE, editable, -1);
return true;
}
void gui_sidebar_filter_path_select(GtkTreePath *path)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter, child;
gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, &child);
if (playlist_select(gui_sidebar_iter_playlist(&child)))
gui_sidebar_iter_update(&child);
}
void gui_sidebar_filter_set_expand(GtkTreeIter *iter)
{
gchar *name = __gui_sidebar_filter_iter_name(iter);
gchar *setting = g_strdup_printf("gui.sidebar.expand.%s", name);
GtkTreePath *path;
if (settings_get(setting) == true) {
path = gtk_tree_model_get_path(
GTK_TREE_MODEL(gui_sidebar_filter()), iter);
gtk_tree_view_expand_row(gui_sidebar_treeview(), path, false);
gtk_tree_path_free(path);
}
g_free(setting);
g_free(name);
}
void gui_sidebar_filter_row_expanded(GtkTreeIter *iter, bool expanded)
{
gchar *name = __gui_sidebar_filter_iter_name(iter);
gchar *setting = g_strdup_printf("gui.sidebar.expand.%s", name);
settings_set(setting, expanded);
g_free(setting);
g_free(name);
}
gboolean gui_sidebar_iter_find(GtkTreeIter *iter, const gchar *name,
enum playlist_type_t type)
{
do {
if (__gui_sidebar_compare(iter, name, type) == 0)
return TRUE;
} while (gui_sidebar_iter_next(iter));
return FALSE;
}
gboolean gui_sidebar_iter_from_string(const gchar *path, GtkTreeIter *child)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_from_string(model, &iter, path))
return FALSE;
__gui_sidebar_filter_iter_convert(&iter, child);
return TRUE;
}
gboolean gui_sidebar_iter_from_xy(gint x, gint y, GtkTreeIter *child)
{
GtkTreeModel *model = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreePath *path;
GtkTreeIter iter;
if (!gtk_tree_view_get_path_at_pos(gui_sidebar_treeview(), x, y,
&path, NULL, NULL, NULL))
return false;
gtk_tree_model_get_iter(model, &iter, path);
__gui_sidebar_filter_iter_convert(&iter, child);
gtk_tree_path_free(path);
return true;
if (__sidebar_find_queue(queue, &iter))
__sidebar_set_size(&iter, queue->gq_text,
queue_size(queue->gq_queue));
}

59
gui/tempq.c Normal file
View File

@ -0,0 +1,59 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/tempq.h>
#include <gui/queue.h>
#include <gui/sidebar.h>
#define TEMPQ_FLAGS (GQ_CAN_RANDOM | GQ_CAN_REPEAT | GQ_CAN_DISABLE)
static void *__tempq_init(struct queue *queue)
{
struct gui_queue *gq;
gq = gui_queue_alloc(queue, "Queued Tracks", TEMPQ_FLAGS);
gui_sidebar_add(gq);
return gq;
}
static void __tempq_deinit(struct queue *queue)
{
gui_sidebar_remove(gui_queue(queue));
gui_queue_free(queue);
}
static void __tempq_added(struct queue *queue, unsigned int pos)
{
gui_queue_added(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
tempq_save(queue, Q_ENABLED);
}
static bool __tempq_erase(struct queue *queue, struct track *track)
{
return true;
}
static void __tempq_removed(struct queue *queue, unsigned int pos)
{
gui_queue_removed(queue, pos);
gui_sidebar_set_size(gui_queue(queue));
tempq_save(queue, Q_ENABLED);
}
static void __tempq_cleared(struct queue *queue, unsigned int n)
{
gui_queue_cleared(queue, n);
gui_sidebar_set_size(gui_queue(queue));
}
struct queue_ops tempq_ops = {
.qop_init = __tempq_init,
.qop_deinit = __tempq_deinit,
.qop_added = __tempq_added,
.qop_erase = __tempq_erase,
.qop_removed = __tempq_removed,
.qop_cleared = __tempq_cleared,
.qop_save = tempq_save,
.qop_updated = gui_queue_updated,
};

View File

@ -1,289 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/settings.h>
#include <gui/filter.h>
#include <gui/model.h>
#include <gui/treeview.h>
#include <stdlib.h>
struct col_map_entry {
enum compare_t compare;
const gchar *setting;
};
static const struct col_map_entry GUI_COL_MAP[GUI_MODEL_N_COLUMNS] = {
[GUI_MODEL_TRACK_NR] = { COMPARE_TRACK, "gui.queue.track" },
[GUI_MODEL_TITLE] = { COMPARE_TITLE, "gui.queue.title" },
[GUI_MODEL_LENGTH] = { COMPARE_LENGTH, "gui.queue.length" },
[GUI_MODEL_ARTIST] = { COMPARE_ARTIST, "gui.queue.artist" },
[GUI_MODEL_ALBUM] = { COMPARE_ALBUM, "gui.queue.album" },
[GUI_MODEL_YEAR] = { COMPARE_YEAR, "gui.queue.year" },
[GUI_MODEL_GENRE] = { COMPARE_GENRE, "gui.queue.genre" },
[GUI_MODEL_COUNT] = { COMPARE_COUNT, "gui.queue.count" },
[GUI_MODEL_LAST_PLAY] = { COMPARE_PLAYED, NULL },
};
static unsigned int sort_count = 0;
static gchar *sort_text = NULL;
static bool can_scroll = true;
static int __gui_treeview_colum_match_sort(enum compare_t compare)
{
struct playlist *playlist = gui_model_get_playlist();
GSList *cur = playlist ? playlist->pl_sort : NULL;
while (cur) {
int field = GPOINTER_TO_INT(cur->data);
if (abs(field) == compare)
return field;
cur = g_slist_next(cur);
}
return 0;
}
static void __gui_treeview_set_sort_indicators()
{
GtkTreeViewColumn *col;
unsigned int i, order;
int field;
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i);
if (!col)
continue;
field = __gui_treeview_colum_match_sort(GUI_COL_MAP[i].compare);
order = (field > 0) ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
gtk_tree_view_column_set_sort_indicator(col, field != 0);
gtk_tree_view_column_set_sort_order(col, order);
}
}
static void __gui_treeview_clear_sorting()
{
sort_count = 0;
if (sort_text) {
g_free(sort_text);
sort_text = NULL;
gtk_label_set_text(gui_sorting(), "");
}
}
static void __gui_treeview_set_sorting(gchar *text)
{
gchar *formatted;
__gui_treeview_clear_sorting();
if (!text)
return;
sort_text = g_strdup(text);
formatted = g_strdup_printf("Sorting: {%s}", text);
gtk_label_set_text(gui_sorting(), formatted);
g_free(formatted);
}
static int __gui_treeview_dec_sort(gpointer data)
{
if (sort_count > 0)
sort_count--;
if (sort_count == 0)
__gui_treeview_clear_sorting();
return FALSE;
}
static gchar *__gui_treeview_sort_text_append(GtkTreeViewColumn *col)
{
const gchar *title = gtk_tree_view_column_get_title(col);
gchar *text, **split;
unsigned int i;
if (!sort_text)
return g_strdup(title);
if (gtk_tree_view_column_get_sort_order(col) == GTK_SORT_ASCENDING)
return g_strdup_printf("%s, %s", sort_text, title);
/* Find the column and prefix it with a minus sign */
split = g_strsplit(sort_text, ", ", 0);
for (i = 0; split[i] != NULL; i++) {
if (g_strcmp0(split[i], title) == 0)
break;
}
g_free(split[i]);
split[i] = g_strdup_printf("-%s", title);
text = g_strjoinv(", ", split);
g_strfreev(split);
return text;
}
static void __gui_treeview_column_clicked(GtkTreeViewColumn *col,
gpointer data)
{
struct playlist *playlist = gui_model_get_playlist();
enum compare_t compare = GPOINTER_TO_UINT(data);
gchar *text;
if (!playlist)
return;
if (sort_count == 0)
playlist_clear_sort(playlist);
if (!playlist_sort(playlist, compare))
return;
__gui_treeview_set_sort_indicators();
text = __gui_treeview_sort_text_append(col);
__gui_treeview_set_sorting(text);
g_free(text);
sort_count++;
g_timeout_add_seconds(3, __gui_treeview_dec_sort, NULL);
}
void __gui_treeview_column_resized(GtkTreeViewColumn *col, GParamSpec *pspec,
gpointer data)
{
settings_set(GUI_COL_MAP[GPOINTER_TO_UINT(data)].setting,
gtk_tree_view_column_get_width(col));
}
void __gui_treeview_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
can_scroll = false;
gui_filter_path_load_track(path);
can_scroll = true;
}
void __gui_treeview_drag_data_received(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
struct gui_model_drag_data *drag_data;
unsigned int to, from;
GtkTreePath *path;
drag_data = (void *)gtk_selection_data_get_data(data);
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
gtk_tree_path_prev(path);
else if (!gtk_tree_view_get_visible_range(gui_treeview(), NULL, &path))
return;
from = drag_data->drag_row;
to = gui_filter_path_get_index(path);
if (playlist_rearrange(gui_model_get_playlist(), from, to)) {
gtk_tree_selection_unselect_all(gui_treeview_selection());
gtk_tree_selection_select_path(gui_treeview_selection(), path);
__gui_treeview_set_sort_indicators();
}
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
gtk_tree_path_free(path);
}
bool __gui_treeview_drag_drop(GtkTreeView *treeview, GdkDragContext *context,
gint x, gint y, guint time, gpointer user_data)
{
gtk_drag_get_data(GTK_WIDGET(treeview), context,
gdk_atom_intern(GUI_DRAG_DATA, false), time);
return true;
}
void gui_treeview_init()
{
GtkTreeViewColumn *col;
int i, pos;
gtk_tree_view_set_model(gui_treeview(),
GTK_TREE_MODEL(gui_filter_get()));
gtk_tree_view_enable_model_drag_source(gui_treeview(), GDK_BUTTON1_MASK,
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
gtk_tree_view_enable_model_drag_dest(gui_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
for (i = 0; i < GUI_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(gui_treeview(), i);
if (col) {
g_signal_connect(col, "clicked",
G_CALLBACK(__gui_treeview_column_clicked),
GUINT_TO_POINTER(GUI_COL_MAP[i].compare));
g_signal_connect(col, "notify::width",
G_CALLBACK(__gui_treeview_column_resized),
GUINT_TO_POINTER(i));
pos = settings_get(GUI_COL_MAP[i].setting);
if (pos > 0)
gtk_tree_view_column_set_fixed_width(col, pos);
}
}
}
void gui_treeview_deinit()
{
__gui_treeview_clear_sorting();
}
void gui_treeview_set_playlist(struct playlist *playlist)
{
gui_filter_set_playlist(playlist);
__gui_treeview_clear_sorting();
__gui_treeview_set_sort_indicators();
gui_treeview_scroll();
}
void gui_treeview_scroll()
{
int pos = playlist_current_index(gui_model_get_playlist());
GtkTreePath *path;
if (!can_scroll || pos < 0)
return;
path = gui_filter_path_from_index(pos);
if (!path)
return;
gtk_tree_view_set_cursor(gui_treeview(), path, NULL, false);
gtk_tree_view_scroll_to_cell(gui_treeview(), path, NULL, true, 0.5, 0.5);
gtk_tree_path_free(path);
}
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y)
{
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos(gui_treeview(), x, y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(gui_treeview_selection(), path);
gtk_tree_path_free(path);
}
}
GList *gui_treeview_list_selected_tracks(void)
{
GtkTreeSelection *selection = gui_treeview_selection();
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_first(rows);
GList *list = NULL;
while (cur) {
list = g_list_append(list, gui_filter_path_get_track(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
gtk_tree_selection_unselect_all(selection);
return list;
}

370
gui/view.c Normal file
View File

@ -0,0 +1,370 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/collection.h>
#include <core/playlist.h>
#include <core/tempq.h>
#include <gui/builder.h>
#include <gui/model.h>
#include <gui/queue.h>
#include <gui/settings.h>
#include <gui/view.h>
#include <stdlib.h>
static const gchar *QUEUE_SETTINGS[Q_MODEL_N_COLUMNS] = {
[Q_MODEL_TRACK_NR] = "gui.queue.track",
[Q_MODEL_TITLE] = "gui.queue.title",
[Q_MODEL_LENGTH] = "gui.queue.length",
[Q_MODEL_ARTIST] = "gui.queue.artist",
[Q_MODEL_ALBUM] = "gui.queue.album",
[Q_MODEL_YEAR] = "gui.queue.year",
[Q_MODEL_GENRE] = "gui.queue.genre",
[Q_MODEL_COUNT] = "gui.queue.count",
[Q_MODEL_LAST_PLAY] = "gui.queue.played",
[Q_MODEL_FILE_PATH] = "gui.queue.filepath",
[Q_MODEL_FONT] = "gui.queue.font",
};
static const enum compare_t QUEUE_SORT[Q_MODEL_N_COLUMNS] = {
[Q_MODEL_TRACK_NR] = COMPARE_TRACK,
[Q_MODEL_TITLE] = COMPARE_TITLE,
[Q_MODEL_LENGTH] = COMPARE_LENGTH,
[Q_MODEL_ARTIST] = COMPARE_ARTIST,
[Q_MODEL_ALBUM] = COMPARE_ALBUM,
[Q_MODEL_YEAR] = COMPARE_YEAR,
[Q_MODEL_GENRE] = COMPARE_GENRE,
[Q_MODEL_COUNT] = COMPARE_COUNT,
[Q_MODEL_LAST_PLAY] = COMPARE_PLAYED,
};
static GtkTreeView *view_treeview = NULL;
static GtkTreeModelFilter *view_filter = NULL;
static unsigned int view_sort_count = 0;
static bool view_no_scroll = false;
static inline GuiQueueModel *__view_filter_get_model()
{
if (view_filter == NULL)
return NULL;
return GUI_QUEUE_MODEL(gtk_tree_model_filter_get_model(view_filter));
}
static inline struct queue *__view_filter_get_queue()
{
if (view_filter == NULL)
return NULL;
return __view_filter_get_model()->gqm_queue;
}
static inline GtkTreePath *__view_filter_convert_path(GtkTreePath *orig)
{
return gtk_tree_model_filter_convert_path_to_child_path(view_filter, orig);
}
static struct track *__view_filter_get_track(GtkTreePath *orig)
{
GuiQueueModel *model = __view_filter_get_model();
GtkTreePath *real = __view_filter_convert_path(orig);
struct track *track = gui_queue_model_path_get_track(model, real);
gtk_tree_path_free(real);
return track;
}
static unsigned int __view_filter_get_index(GtkTreePath *orig)
{
GtkTreePath *real = __view_filter_convert_path(orig);
unsigned int ret = gtk_tree_path_get_indices(real)[0];
gtk_tree_path_free(real);
return ret;
}
static unsigned int __view_get_column_index(GtkTreeViewColumn *col)
{
unsigned int i;
for (i = 0; i < Q_MODEL_N_COLUMNS; i++) {
if (col == gtk_tree_view_get_column(view_treeview, i))
return i;
}
return Q_MODEL_N_COLUMNS;
}
static inline void __view_display_sorting(gchar *text)
{
gtk_label_set_text(GTK_LABEL(gui_builder_widget("o_sorting")), text);
}
static int __view_dec_sort(gpointer data)
{
if (view_sort_count > 0)
view_sort_count--;
if (view_sort_count == 0)
__view_display_sorting("");
return FALSE;
}
static void __view_set_column_sort_indicator(GtkTreeViewColumn *col,
unsigned int index)
{
struct queue *queue = __view_filter_get_queue();
GSList *cur = queue ? queue->q_sort : NULL;
unsigned int order = GTK_SORT_ASCENDING;
bool show = false;
int field;
while (cur) {
order = GTK_SORT_ASCENDING;
field = GPOINTER_TO_INT(cur->data);
if (abs(field) == QUEUE_SORT[index]) {
show = true;
if (field < 0)
order = GTK_SORT_DESCENDING;
break;
}
cur = g_slist_next(cur);
}
gtk_tree_view_column_set_sort_indicator(col, show);
gtk_tree_view_column_set_sort_order(col, order);
}
static void __view_set_sort_indicators()
{
GtkTreeViewColumn *col;
for (unsigned int i = 0; i < Q_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(view_treeview, i);
if (col)
__view_set_column_sort_indicator(col, i);
}
}
void __view_row_activated(GtkTreeView *treeview, GtkTreePath *path,
GtkTreeViewColumn *col, gpointer data)
{
view_no_scroll = true;
audio_load(__view_filter_get_track(path));
queue_selected(__view_filter_get_model()->gqm_queue,
gtk_tree_path_get_indices(path)[0]);
view_no_scroll = false;
}
void __view_column_resized(GtkTreeViewColumn *col, GParamSpec *pspec,
gpointer data)
{
unsigned int index = __view_get_column_index(col);
gui_settings_set(QUEUE_SETTINGS[index],
gtk_tree_view_column_get_width(col));
}
void __view_column_clicked(GtkTreeViewColumn *col, gpointer data)
{
struct queue *queue = __view_filter_get_queue();
unsigned int index = __view_get_column_index(col);
gchar *text;
if (!queue || queue_has_flag(queue, Q_NO_SORT))
return;
queue_sort(queue, QUEUE_SORT[index], view_sort_count == 0);
if (view_sort_count == 0) {
text = g_strdup_printf("Sorting within %s",
gtk_tree_view_column_get_title(col));
__view_display_sorting(text);
g_free(text);
}
__view_set_sort_indicators();
view_sort_count++;
g_timeout_add_seconds(3, __view_dec_sort, NULL);
}
static void __view_add_to_queue(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
queue_add(data, __view_filter_get_track(path));
}
static void __view_add_to_playlist(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
{
playlist_add(GPOINTER_TO_UINT(data), __view_filter_get_track(path));
}
static void __view_delete_selection(GtkTreeSelection *selection)
{
struct queue *queue = __view_filter_get_queue();
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
queue_erase(queue, __view_filter_get_index(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
static void __view_process_selection(GtkTreeView *treeview, unsigned int keyval)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
unsigned int flags = 0;
struct queue *queue;
switch (keyval) {
case GDK_KEY_KP_0 ... GDK_KEY_KP_9:
keyval = (keyval - GDK_KEY_KP_0) + GDK_KEY_0;
case GDK_KEY_0 ... GDK_KEY_9:
queue = tempq_get(keyval - GDK_KEY_0);
if (queue)
gtk_tree_selection_selected_foreach(selection,
__view_add_to_queue, queue);
break;
case GDK_KEY_f:
gtk_tree_selection_selected_foreach(selection,
__view_add_to_playlist,
GUINT_TO_POINTER(PL_FAVORITED));
break;
case GDK_KEY_r:
flags = Q_RANDOM;
case GDK_KEY_q:
queue = tempq_alloc(flags);
gtk_tree_selection_selected_foreach(selection,
__view_add_to_queue, queue);
break;
case GDK_KEY_Delete:
__view_delete_selection(selection);
break;
}
}
void __view_keypress(GtkTreeView *treeview, GdkEventKey *event, gpointer data)
{
__view_process_selection(treeview, event->keyval);
}
void __view_rc_new_queue(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_q);
}
void __view_rc_add_to_queue(GtkMenuItem *item, gpointer data)
{
unsigned int i;
gchar *name;
for (i = 0; i < 10; i++) {
name = g_strdup_printf("o_queue_%d", i);
if (GTK_WIDGET(item) == gui_builder_widget(name))
__view_process_selection(view_treeview, GDK_KEY_0 + i);
g_free(name);
}
}
void __view_rc_add_favorites(GtkMenuItem *item, gpointer data)
{
__view_process_selection(view_treeview, GDK_KEY_f);
}
void __view_rc_add_hidden(GtkMenuItem *item, gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(view_treeview);
GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
GList *cur = g_list_reverse(rows);
while (cur) {
collection_ban(__view_filter_get_track(cur->data));
cur = g_list_next(cur);
}
g_list_free_full(rows, (GDestroyNotify) gtk_tree_path_free);
}
bool __view_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
GtkMenu *menu = GTK_MENU(gui_builder_widget("o_menu"));
unsigned int i, size = tempq_count();
GtkTreePath *path;
gchar *name;
if (event->button != 3)
return false;
/* Select path if it isn't already selected */
if (gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
{
gtk_tree_selection_select_path(selection, path);
gtk_tree_path_free(path);
}
/* Determine which menu items can be shown */
gtk_widget_set_visible(gui_builder_widget("o_new_queue"), size < 10);
gtk_widget_set_visible(gui_builder_widget("o_add_to_queue"), size > 0);
for (i = 0; i < 10; i++) {
name = g_strdup_printf("o_queue_%d", i);
gtk_widget_set_visible(gui_builder_widget(name), i < size);
g_free(name);
}
gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
return true;
}
void gui_view_init()
{
GtkTreeViewColumn *col;
int i, pos;
view_treeview = GTK_TREE_VIEW(gui_builder_widget("o_treeview"));
for (i = 0; i < Q_MODEL_N_COLUMNS; i++) {
col = gtk_tree_view_get_column(view_treeview, i);
pos = gui_settings_get(QUEUE_SETTINGS[i]);
if (col && pos > 0)
gtk_tree_view_column_set_fixed_width(col, pos);
}
}
void gui_view_set_model(GtkTreeModelFilter *filter)
{
view_filter = filter;
gtk_tree_view_set_model(view_treeview, GTK_TREE_MODEL(filter));
view_sort_count = 0;
__view_display_sorting("");
__view_set_sort_indicators();
gui_view_scroll();
}
void gui_view_scroll()
{
struct queue *queue = __view_filter_get_queue();
GtkTreePath *real, *path;
if (!queue || (int)queue->q_cur.it_pos < 0 || view_no_scroll)
return;
real = gtk_tree_path_new_from_indices(queue->q_cur.it_pos, -1);
path = gtk_tree_model_filter_convert_child_path_to_path(view_filter, real);
if (!path)
goto out;
gtk_tree_view_set_cursor(view_treeview, path, NULL, false);
gtk_tree_view_scroll_to_cell(view_treeview, path, NULL, TRUE, 0.5, 0.5);
gtk_tree_path_free(path);
out:
gtk_tree_path_free(real);
}

View File

@ -1,47 +1,31 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/settings.h>
#include <core/version.h>
#include <gui/window.h>
#include <gui/builder.h>
#include <gui/settings.h>
static const gchar *SETTINGS_WIDTH = "gui.window.width";
static const gchar *SETTINGS_HEIGHT = "gui.window.height";
static const gchar *SETTINGS_X = "gui.window.x";
static const gchar *SETTINGS_Y = "gui.window.y";
static int saved_width = 0;
static int saved_height = 0;
static int saved_x = 0;
static int saved_y = 0;
gboolean __window_configure(GtkWindow *window, GdkEventConfigure *event,
gpointer data)
{
int width = settings_get(SETTINGS_WIDTH);
int height = settings_get(SETTINGS_HEIGHT);
int x = settings_get(SETTINGS_X);
int y = settings_get(SETTINGS_Y);
int width = gui_settings_get(SETTINGS_WIDTH);
int height = gui_settings_get(SETTINGS_HEIGHT);
if (event->width != width) {
saved_width = width;
settings_set(SETTINGS_WIDTH, event->width);
gui_settings_set(SETTINGS_WIDTH, event->width);
}
if (event->height != height) {
saved_height = height;
settings_set(SETTINGS_HEIGHT, event->height);
}
if (event->x != x) {
saved_x = x;
settings_set(SETTINGS_X, event->x);
}
if (event->y != y) {
saved_y = y;
settings_set(SETTINGS_Y, event->y);
gui_settings_set(SETTINGS_HEIGHT, event->height);
}
return FALSE;
@ -52,10 +36,8 @@ gboolean __window_state(GtkWindow *window, GdkEventWindowState *event,
{
if (event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED &&
event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
settings_set(SETTINGS_WIDTH, saved_width);
settings_set(SETTINGS_HEIGHT, saved_height);
settings_set(SETTINGS_X, saved_x);
settings_set(SETTINGS_Y, saved_y);
gui_settings_set(SETTINGS_WIDTH, saved_width);
gui_settings_set(SETTINGS_HEIGHT, saved_height);
}
return FALSE;
@ -64,25 +46,21 @@ gboolean __window_state(GtkWindow *window, GdkEventWindowState *event,
void gui_window_init(const gchar *icon)
{
GtkWindow *window = GTK_WINDOW(gui_builder_widget("o_window"));
gchar *title = g_strdup_printf("Ocarina %s", get_version());
gtk_window_set_title(gui_window(), title);
gtk_window_set_icon_from_file(gui_window(), icon, NULL);
gtk_window_set_title(window, title);
gtk_window_set_icon_from_file(window, icon, NULL);
saved_width = settings_get(SETTINGS_WIDTH);
saved_height = settings_get(SETTINGS_HEIGHT);
saved_x = settings_get(SETTINGS_X);
saved_y = settings_get(SETTINGS_Y);
if (saved_width > 0 || saved_height > 0)
gtk_window_resize(gui_window(), saved_width, saved_height);
if (saved_x > 0 || saved_y > 0)
gtk_window_move(gui_window(), saved_x, saved_y);
saved_width = gui_settings_get(SETTINGS_WIDTH);
saved_height = gui_settings_get(SETTINGS_HEIGHT);
if (saved_width > 0 && saved_height > 0)
gtk_window_resize(window, saved_width, saved_height);
g_free(title);
}
void gui_window_deinit()
{
gtk_widget_destroy(GTK_WIDGET(gui_window()));
gtk_widget_destroy(gui_builder_widget("o_window"));
}

18
include/Sconscript Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/python
import subprocess
Import("env")
version = str(env.Version)
if env.Debug == True:
version += "-debug"
try:
devnull = open("/dev/null", "w")
diff = subprocess.check_output("git diff $(git describe --abbrev=0)",
stderr=devnull, shell=True)
if diff != "":
version += "+"
except:
pass
env.Append( CCFLAGS = [ "-DCONFIG_VERSION='\"%s\"'" % version ] )

View File

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

62
include/core/collection.h Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The collection manager is in charge of creating, updating, and removing
* Library and Track tags. This code also manages a special queue used by
* the GUI to display all tracks in the collection.
*
* The entire collection queue does not need to be saved, so instead the
* collection manager only stores flags and the current sort order:
*
* flags sort.length ... sort[N].field sort[N].ascending ...
*/
#ifndef OCARINA_CORE_COLLECTION_H
#define OCARINA_CORE_COLLECTION_H
#include <core/queue.h>
/* Called to initialize the collection manager. */
void collection_init(struct queue_ops *);
/* Called to deinitialize the collection manager. */
void collection_deinit();
/* Called to save the collection queue. */
void collection_save(struct queue *, enum queue_flags);
/* Called to add a new library directory to the collection manager. */
struct library *collection_add(const gchar *);
/* Called to remove a library directory from the collection manager. */
void collection_remove(struct library *);
/* Called to update a library directory. */
void collection_update(struct library *);
/* Called to update all library paths. */
void collection_update_all();
/* Called to ban a track from the collection. */
bool collection_ban(struct track *);
/* Called to unban a track from the collection. */
bool collection_unban(struct track *);
/* Called to enable or disable a library directory. */
void collection_set_enabled(struct library *, bool);
/* Called to check if the library path is good. */
int collection_check_library(struct library *);
/* Called to access the collection queue. */
struct queue *collection_get_queue();
#ifdef CONFIG_TESTING
extern bool test_collection_error;
#endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_COLLECTION_H */

View File

@ -48,7 +48,7 @@ static inline void *DBE_DATA(struct db_entry *dbe)
struct db_ops {
/* Allocate a new struct db_entry from a given key. */
struct db_entry *(*dbe_alloc)(const gchar *, unsigned int);
struct db_entry *(*dbe_alloc)(const gchar *);
/* Free a struct db_entry. */
void (*dbe_free)(struct db_entry *);
@ -57,7 +57,10 @@ struct db_ops {
gchar *(*dbe_key)(struct db_entry *);
/* Read a single struct db_entry from disk. */
struct db_entry *(*dbe_read)(struct file *, unsigned int);
struct db_entry *(*dbe_read)(struct file *);
/* Set up a struct db_entry after adding to the database. */
void (*dbe_setup)(struct db_entry *);
/* Write a single struct db_entry to disk. */
void (*dbe_write)(struct file *, struct db_entry *);
@ -74,11 +77,11 @@ struct database {
const struct db_ops *db_ops; /* The database's operations vector. */
};
#define DB_INIT(fname, autosave, ops, fmin) \
#define DB_INIT(fname, autosave, ops) \
{ \
.db_size = 0, \
.db_autosave = autosave, \
.db_file = FILE_INIT_DATA("", fname, fmin), \
.db_file = FILE_INIT(fname, 0), \
.db_entries = g_ptr_array_new(), \
.db_keys = g_hash_table_new(g_str_hash, g_str_equal), \
.db_ops = ops, \
@ -89,8 +92,7 @@ struct database {
* Initialize a database using filepath as a location on disk to store data
* and autosave as a hint for if this database should be automatically saved.
*/
void db_init(struct database *, const char *, bool, const struct db_ops *,
unsigned int);
void db_init(struct database *, const char *, bool, const struct db_ops *);
/* Called to prevent memory leaks by freeing all remaining database entries. */
void db_deinit(struct database *);
@ -104,6 +106,9 @@ void db_autosave(struct database *);
/* Called to read the database from disk. */
void db_load(struct database *);
/* Called to read the database from disk, but only when idle. */
void db_load_idle(struct database *);
/* Returns the size of the backing std::vector. */
unsigned int db_actual_size(const struct database *);
@ -117,18 +122,9 @@ struct db_entry *db_insert(struct database *, const gchar *);
/* Called to remove an item from the database. */
void db_remove(struct database *, struct db_entry *);
/*
* Called to shrink the database by removing any NULL pointers without
* changing the order of items in the database.
* Returns true if the database has been modified.
*/
bool db_defrag(struct database *);
/* Called to change the key of a database entry. */
void db_rekey(struct database *, struct db_entry *);
/* Returns the database item at the requested index. */
struct db_entry *db_at(const struct database *, unsigned int);
struct db_entry *db_at(struct database *, unsigned int);
/* Returns the database item with the specified key. */
struct db_entry *db_get(struct database *, const gchar *);

View File

@ -0,0 +1,38 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* The struct index_entry is used to associate a database key
* with a set of integers, creating an inverted index.
*/
#ifndef OCARINA_CORE_CONTAINERS_INDEX_H
#define OCARINA_CORE_CONTAINERS_INDEX_H
#include <core/containers/database.h>
#include <core/containers/set.h>
struct index_entry {
gchar *ie_key;
struct set ie_set;
struct db_entry ie_dbe;
};
#define INDEX_ENTRY(dbe) ((struct index_entry *)DBE_DATA(dbe))
/* Initialize a database for use as an index. */
void index_init(struct database *, const gchar *, bool);
/* Add a value to an index item with the specified key. */
struct index_entry *index_insert(struct database *, const gchar *, unsigned int);
/* Remove a value from an index item with the specified key. */
void index_remove(struct database *, const gchar *, unsigned int);
/* Called to check if the index has the specified (key, value) pair. */
bool index_has(struct database *, const gchar *, unsigned int);
#ifdef CONFIG_TESTING
const struct db_ops *test_index_ops();
#endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_CONTAINERS_INDEX_H */

View File

@ -0,0 +1,108 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_CONTAINERS_QUEUE_H
#define OCARINA_CORE_CONTAINERS_QUEUE_H
#include <glib.h>
struct _queue {
GQueue _queue;
};
struct _q_iter {
guint it_pos;
GList *it_iter;
};
/* Called to initialize a queue iterator. */
static inline void _q_iter_init(const struct _queue *queue, struct _q_iter *it)
{
it->it_iter = g_list_first(queue->_queue.head);
it->it_pos = 0;
}
/* Called to advance a queue iterator by one step. */
static inline void _q_iter_next(struct _q_iter *it)
{
it->it_iter = g_list_next(it->it_iter);
it->it_pos++;
}
/* Called to rewind a queue iterator by one step. */
static inline void _q_iter_prev(struct _q_iter *it)
{
it->it_iter = g_list_previous(it->it_iter);
it->it_pos--;
}
/* Called to set a queue iterator to a specific position. */
static inline void _q_iter_set(struct _queue *queue, struct _q_iter *it,
unsigned int pos)
{
it->it_iter = g_queue_peek_nth_link(&queue->_queue, pos);
it->it_pos = pos;
}
/* Called to access the value of a queue iterator. */
static inline gpointer _q_iter_val(struct _q_iter *it)
{
return (it->it_iter) ? it->it_iter->data : NULL;
}
#define _q_for_each(queue, it) \
for (_q_iter_init(queue, it); (it)->it_iter; _q_iter_next(it))
#define _Q_INIT() \
{ \
._queue = G_QUEUE_INIT, \
}
/* Called to initialize a queue. */
static inline void _q_init(struct _queue *queue)
{
g_queue_init(&queue->_queue);
}
/* Called to find the size of a queue. */
static inline guint _q_size(struct _queue *queue)
{
return g_queue_get_length(&queue->_queue);
}
/* Called to add an item to the head of a queue. */
static inline guint _q_add_head(struct _queue *queue, gpointer data)
{
g_queue_push_head(&queue->_queue, data);
return 0;
}
/* Called to add an item to the tail of a queue. */
static inline guint _q_add_tail(struct _queue *queue, gpointer data)
{
g_queue_push_tail(&queue->_queue, data);
return _q_size(queue) - 1;
}
/* Called to add an item to a sorted queue. */
guint _q_add_sorted(struct _queue *, gpointer, GCompareDataFunc, gpointer);
/* Called to remove all items from a queue */
static inline void _q_clear(struct _queue *queue)
{
g_queue_clear(&queue->_queue);
}
/* Called to remove an item by iterator. */
gpointer _q_remove_it(struct _queue *, struct _q_iter *);
/* Called to sort the queue. */
static inline void _q_sort(struct _queue *queue, GCompareDataFunc func,
gpointer user_data)
{
g_queue_sort(&queue->_queue, func, user_data);
}
#endif /* OCARINA_CORE_CONTAINERS_QUEUE_H */

View File

@ -0,0 +1,91 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_CONTAINERS_SET_H
#define OCARINA_CORE_CONTAINERS_SET_H
#include <core/file.h>
#include <glib.h>
#include <stdbool.h>
struct set {
GHashTable *s_set;
};
struct set_iter {
unsigned int it_val;
GHashTableIter it_iter;
};
#define SET_INIT() \
{ \
.s_set = g_hash_table_new(g_direct_hash, g_direct_equal), \
}
static inline void set_init(struct set *set)
{
set->s_set = g_hash_table_new(g_direct_hash, g_direct_equal);
}
static inline void set_deinit(struct set *set)
{
g_hash_table_destroy(set->s_set);
}
static inline void set_insert(struct set *set, unsigned int value)
{
g_hash_table_add(set->s_set, GUINT_TO_POINTER(value));
}
static inline void set_remove(struct set *set, unsigned int value)
{
g_hash_table_remove(set->s_set, GUINT_TO_POINTER(value));
}
static inline void set_clear(struct set *set)
{
g_hash_table_remove_all(set->s_set);
}
static inline bool set_has(const struct set *set, unsigned int value)
{
return g_hash_table_contains(set->s_set, GUINT_TO_POINTER(value));
}
static inline unsigned int set_size(struct set *set)
{
return g_hash_table_size(set->s_set);
}
/* Copy values from set1 into set2. */
void set_copy(const struct set *, struct set *);
/* Remove values from set2 that are not also in set1. */
void set_inline_intersect(const struct set *, struct set *);
/* Read values from file. */
void set_read(struct file *, struct set *);
/* Write values to file. */
void set_write(struct file *, struct set *);
static inline bool set_iter_next(struct set_iter *it)
{
gpointer key;
bool ret = g_hash_table_iter_next(&it->it_iter, &key, NULL);
it->it_val = GPOINTER_TO_INT(key);
return ret;
}
static inline void set_iter_init(const struct set *set, struct set_iter *it)
{
g_hash_table_iter_init(&it->it_iter, set->s_set);
}
#define set_for_each(set, it) \
for (set_iter_init(set, it); set_iter_next(it); )
#endif /* OCARINA_CORE_CONTAINERS_SET_H */

View File

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

View File

@ -5,18 +5,12 @@
#define OCARINA_CORE_DATE_H
#include <core/file.h>
#include <stdint.h>
struct date {
union {
struct {
uint16_t d_year;
uint8_t d_month;
uint8_t d_day;
};
uint32_t d_stamp;
};
unsigned int d_year;
unsigned int d_month;
unsigned int d_day;
};
@ -28,11 +22,9 @@ void date_today(struct date *);
/* Read the date from file. */
void date_read(struct file *, struct date *);
void date_read_stamp(struct file *, struct date *);
/* Write the date to file. */
void date_write(struct file *, struct date *);
void date_write_stamp(struct file *, struct date *);
/*
* Convert the date into a string.

View File

@ -23,61 +23,44 @@
#ifndef OCARINA_CORE_FILE_H
#define OCARINA_CORE_FILE_H
#include <core/version.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <stdbool.h>
#define FILE_MAX_LEN 16
enum open_mode {
CLOSED, /* File is not open. */
OPEN_READ, /* File is open for reading text. */
OPEN_READ_BINARY, /* File is open for reading binary data. */
OPEN_WRITE, /* File is open for writing text. */
OPEN_WRITE_BINARY, /* File is open for writing binary data. */
OPEN_READ,
OPEN_WRITE,
};
struct file {
FILE *f_file; /* The file's IO stream. */
const gchar *f_name; /* The file's basename. */
const gchar *f_subdir; /* The file's subdirectory. */
enum open_mode f_mode; /* The file's current open mode. */
unsigned int f_version; /* The file's current data version. */
unsigned int f_prev; /* The file's on-disk data version. */
unsigned int f_min; /* The file's minimum data version. */
const gchar *(*f_user_dir)(void); /* The file's user directory. */
enum open_mode f_mode; /* The file's current open mode. */
unsigned int f_version; /* The file's current data version. */
unsigned int f_prev; /* The file's on-disk data version. */
FILE *f_file; /* The file's IO stream. */
gchar f_name[FILE_MAX_LEN]; /* The file's basename. */
};
#define FILE_INIT_DATA(fdir, fname, min) \
{ \
.f_file = NULL, \
.f_name = fname, \
.f_subdir = fdir, \
.f_mode = CLOSED, \
.f_version = OCARINA_MINOR_VERSION, \
.f_prev = 0, \
.f_min = min, \
.f_user_dir = g_get_user_data_dir, \
#define FILE_INIT(fname, version) \
{ \
.f_mode = OPEN_READ, \
.f_version = version, \
.f_prev = 0, .f_file = NULL, \
.f_name = fname, \
}
/* Initialize a file object. */
void file_init_data(struct file *, const gchar *, const gchar *, unsigned int);
void file_init_cache(struct file *, const gchar *, const gchar *);
/* Initialize a new file object. */
void file_init(struct file *, const gchar *, unsigned int);
/*
* Returns the full path of the file or an empty string if filename is not set.
* NOTE: This function allocates a new string that MUST be freed with g_free().
* This function allocates a new string that MUST be freed with g_free().
*/
gchar *file_path(struct file *);
/*
* Returns the path to the temporary file used for writes.
* NOTE: This function allocates a new string that MUST be freed with g_free().
*/
gchar *file_write_path(struct file *);
/* Returns the version number of the file. */
const unsigned int file_version(struct file *);
@ -88,42 +71,32 @@ bool file_exists(struct file *);
* Call to open a file for either reading or writing. Callers
* are expected to call file_close() when IO is completed.
*
* When opening a file for reading (OPEN_READ / OPEN_READ_BINARY):
* - Check if the file exists
* - If open for reading text (OPEN_READ):
* - Read in file->_prev_version from the start of the file.
* When opening a file for reading (OPEN_READ):
* - Check if the file exists.
* - Read in file->_prev_version from the start of the file.
*
* When opening a file for writing (OPEN_WRITE / OPEN_WRITE_BINARY):
* When opening a file for writing (OPEN_WRITE):
* - Create missing directories as needed.
* - Open a temporary file to protect data if Ocarina crashes.
* - If open for writing text (OPEN_WRITE):
* - Write file->_version to the start of the file (data files only).
* - Write file->_version to the start of the file.
*
* Returns true if the open was successful and false otherwise.
*/
bool file_open(struct file *, enum open_mode);
/*
* Closes an open file, setting file->f_file to NULL and file->f_mode
* to CLOSED. If the file was opened for writing, then rename the
* temporary file to file_path().
*/
/* Close an open file, setting file->f_file to NULL. */
void file_close(struct file *);
/*
* Called to read an unsigned int, signed int, single word, or entire
* line from the file.
* NOTE: file_readw() and file_readl() both return a new string that
* MUST be freed with g_free()
* Read an entire line from the file and return it to the caller.
* This function allocates a new string that MUST be freed with g_free().
*/
gchar *file_readw(struct file *);
gchar *file_readl(struct file *);
unsigned int file_readu(struct file *);
static inline int file_readd(struct file *file)
{ return (int)file_readu(file); }
static inline unsigned short int file_readhu(struct file *file)
{ return (unsigned short int)file_readu(file); }
/*
* Read from a file with an fscanf(3) style format string.
* Returns the number of items matched.
*/
int file_readf(struct file *, const char *, ...);
/*
* Write to a file with an fprintf(3) style format string.
@ -131,22 +104,4 @@ static inline unsigned short int file_readhu(struct file *file)
*/
int file_writef(struct file *, const char *, ...);
/*
* Reads the contents of a file as binary data.
* NOTE: This function returns a new string which MUST be freed with g_free().
*/
gchar *file_read(struct file *);
/*
* Write binary data a cache file, similar to fwrite(3).
* Returns the number of bytes successfully written.
*/
int file_write(struct file *, const void *, size_t);
/* Import a file into the cache. */
bool file_import(struct file *, const gchar *);
/* Removes a closed file from disk. */
bool file_remove(struct file *);
#endif /* OCARINA_CORE_FILE_H */

24
include/core/filter.h Normal file
View File

@ -0,0 +1,24 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The filter layer is used to search for a subset of
* songs based on an input string.
*/
#ifndef OCARINA_CORE_FILTER_H
#define OCARINA_CORE_FILTER_H
#include <core/containers/set.h>
/* Called to initialize the filter index. */
void filter_init();
/* Called to clean up the filter index. */
void filter_deinit();
/* Add the input string to the index. */
void filter_add(const gchar *, unsigned int);
/* Search for the input string in the index. */
void filter_search(const gchar *, struct set *);
#endif /* OCARINA_CORE_FILTER_H */

26
include/core/history.h Normal file
View File

@ -0,0 +1,26 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_HISTORY_H
#define OCARINA_CORE_HISTORY_H
#include <core/queue.h>
/* Called to initialize the history queue. */
void history_init(struct queue_ops *);
/* Called to deinitialize the history queue. */
void history_deinit();
/* Called to add a track to the history queue. */
void history_add(struct track *);
/* Called to pick a track from the history. */
struct track *history_prev();
/* Called to access the queue of recent tracks. */
struct queue *history_get_queue();
#endif /* OCARINA_CORE_HISTORY_H */

View File

@ -15,22 +15,8 @@
#define OCARINA_CORE_IDLE_H
#include <stdbool.h>
enum idle_sync_t {
IDLE_SYNC, /* Run task in the main thread. */
IDLE_ASYNC, /* Run task in a separate thread. */
};
#define IDLE_FUNC(x) ((bool (*)(void *))x)
/* Called to initialize the idle queue. */
void idle_init(enum idle_sync_t);
/* Called to deinitialize the idle queue. */
void idle_deinit();
/* Called to schedule a function to run later. */
void idle_schedule(enum idle_sync_t, bool (*)(void *), void *);
void idle_schedule(void (*)(void *), void *);
/*
* Called to run the next task on the idle queue.
@ -41,4 +27,7 @@ bool idle_run_task();
/* Called to find the percentage of idle tasks that have been run. */
float idle_progress();
/* Called to cancel all idle tasks on the queue. */
void idle_cancel();
#endif /* OCARINA_CORE_IDLE_H */

View File

@ -7,75 +7,40 @@
*/
#ifndef OCARINA_CORE_PLAYLIST_H
#define OCARINA_CORE_PLAYLIST_H
#include <core/playlists/artist.h>
#include <core/playlists/generic.h>
#include <core/playlists/library.h>
#include <core/playlists/system.h>
#include <core/playlists/user.h>
#include <core/containers/index.h>
#include <core/queue.h>
enum playlist_t {
PL_FAVORITED, /* Songs that the user likes. */
PL_HIDDEN, /* Songs that the user has hidden. */
PL_UNPLAYED, /* Songs that have not been played yet. */
PL_MOST_PLAYED, /* Songs with an above average play count. */
PL_LEAST_PLAYED, /* Songs with a below average play count. */
};
/* Called to initialize the playlist manager. */
void playlist_init(struct playlist_callbacks *);
void playlist_init(struct queue_ops *);
/* Called to deinitialize the playlist manager. */
void playlist_deinit();
/* Called to force-save all playlists. */
void playlist_save();
/* Called to notify all playlists that a track has been played. */
void playlist_played(struct track *);
/* Called to notify all playlists that a track has been selected. */
void playlist_selected(struct track *);
/* Called to create a new playlist. */
struct playlist *playlist_new(enum playlist_type_t, const gchar *);
/* Called to delete a playlist. */
bool playlist_delete(struct playlist *);
/* Called to look up playlists either by name or id. */
struct playlist *playlist_lookup(enum playlist_type_t, const gchar *);
struct playlist *playlist_get(enum playlist_type_t, unsigned int);
/* Called to access the current playlist. */
struct playlist *playlist_current(void);
/* Called to select the current playlist. */
bool playlist_select(struct playlist *);
/* Called to get the next track from the default playlist. */
struct track *playlist_next(void);
/* Called to get a previously played track. */
struct track *playlist_prev(void);
/* Called to add a track to a playlist. */
bool playlist_add(struct playlist *, struct track *);
bool playlist_add(enum playlist_t, struct track *);
/* Called to remove a track from a playlist. */
bool playlist_remove(struct playlist *, struct track *);
bool playlist_remove(enum playlist_t, struct track *);
/* Called to check if a specific track is in the playlist. */
bool playlist_has(struct playlist *, struct track *);
bool playlist_has(enum playlist_t, struct track *);
/* Called to fill the queue with a specific playlist. */
void playlist_select(enum playlist_t);
/* Called to set the playlist's random flag. */
void playlist_set_random(struct playlist *, bool);
/* Called to change the sort order of the playlist. */
bool playlist_sort(struct playlist *, enum compare_t);
/* Called to manually rearrange the order of the playlist. */
bool playlist_rearrange(struct playlist *, unsigned int, unsigned int);
/* Called to set the playlist's search text */
void playlist_set_search(struct playlist *, const gchar *);
/* Called to access the playlist queue. */
struct queue *playlist_get_queue();
#endif /* OCARINA_CORE_PLAYLIST_H */

View File

@ -1,24 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_ARTIST_H
#define OCARINA_CORE_PLAYLISTS_ARTIST_H
#include <core/playlists/generic.h>
/* Artist playlist type. */
extern struct playlist_type pl_artist;
/* Called to initialize artist playlists. */
void pl_artist_init(void);
/* Called to deinitialize library playlists. */
void pl_artist_deinit();
/* Called to tell system playlists about a new track. */
void pl_artist_new_track(struct track *);
/* Called to tell artist playlists that a track is getting deleted. */
void pl_artist_delete_track(struct track *);
#endif /* OCARINA_CORE_PLAYLISTS_ARTIST_H */

View File

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

View File

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

View File

@ -1,21 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_LIBRARY_H
#define OCARINA_CORE_PLAYLISTS_LIBRARY_H
#include <core/playlists/generic.h>
/* Library playlist type. */
extern struct playlist_type pl_library;
/* Called to initialize library playlists. */
void pl_library_init(void);
/* Called to deinitialize system playlists. */
void pl_library_deinit();
/* Called to update a library path. */
void pl_library_update(struct playlist *);
#endif /* OCARINA_CORE_PLAYLISTS_LIBRARY_H */

View File

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

View File

@ -1,38 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_SYSTEM_H
#define OCARINA_CORE_PLAYLISTS_SYSTEM_H
#include <core/playlists/generic.h>
enum sys_playlist_t {
SYS_PL_FAVORITES, /* Songs that the user likes. */
SYS_PL_HIDDEN, /* Songs that the user has hidden. */
SYS_PL_QUEUED, /* Songs that the user has queued up. */
SYS_PL_COLLECTION, /* Songs that have not been hidden. */
SYS_PL_HISTORY, /* Songs that have just been played. */
SYS_PL_UNPLAYED, /* Songs that have not been played yet. */
SYS_PL_MOST_PLAYED, /* Songs with an above average play count. */
SYS_PL_LEAST_PLAYED, /* Songs with a below average play count. */
SYS_PL_NUM_PLAYLISTS, /* Number of system playlists. */
};
/* System playlist type. */
extern struct playlist_type pl_system;
/* Called to initialize system playlists. */
void pl_system_init(void);
/* Called to deinitialize system playlists. */
void pl_system_deinit();
/* Called to tell system playlists about a new track. */
void pl_system_new_track(struct track *);
/* Called to tell system playlists that a track is getting deleted. */
void pl_system_delete_track(struct track *);
#endif /* OCARINA_CORE_PLAYLISTS_SYSTEM_H */

View File

@ -1,33 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_PLAYLISTS_USER_H
#define OCARINA_CORE_PLAYLISTS_USER_H
#include <core/playlists/generic.h>
struct user_playlist {
struct playlist pl_playlist;
struct db_entry pl_dbe;
};
#define USER_PLAYLIST(dbe) ((struct user_playlist *)DBE_DATA(dbe))
/* User playlist type. */
extern struct playlist_type pl_user;
/* Called to initialize user playlists. */
void pl_user_init(void);
/* Called to deinitialize user playlists. */
void pl_user_deinit();
/* Called to tell user playlists that a track is getting deleted. */
void pl_user_delete_track(struct track *);
/* Called to rename a user playlist. */
bool pl_user_rename(struct playlist *, const gchar *);
struct database *pl_user_db_get();
#endif /* OCARINA_CORE_PLAYLISTS_USER_H */

134
include/core/queue.h Normal file
View File

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

View File

@ -9,7 +9,6 @@
#include <glib.h>
#include <string.h>
#include <stdbool.h>
/* Convert number of seconds into a string with format mm:ss. */
@ -21,6 +20,9 @@ gchar *string_sec2str_long(unsigned int);
/* Convert a struct tm to a locale-dependant date string. */
gchar *string_tm2str(struct tm *);
/* Convert the input string to lowercase, dropping special characters. */
gchar *string_lowercase(const gchar *);
/*
* Compare two strings.
*
@ -28,24 +30,6 @@ gchar *string_tm2str(struct tm *);
* if ret = 0: lhs == rhs.
* if ret > 0: lhs > rhs, or lhs is empty.
*/
int string_compare_tokens(gchar **, gchar **);
/* Returns True if the two strings match. */
static inline bool string_match(const gchar *a, const gchar *b)
{
return (a && b) ? g_utf8_collate(a, b) == 0 : false;
}
/* Returns True if one of the tokens begins with the specified prefix. */
bool string_match_token(const gchar *, gchar **);
/* Returns True if string a is a subdirectory of string b. */
bool string_is_subdir(const gchar *, const gchar *);
/* Return the length of the string, with NULL checks */
static inline int string_length(const gchar *str)
{
return str ? strlen(str) : 0;
}
int string_compare(const gchar *, const gchar *);
#endif /* OCARINA_CORE_STRING_H */

View File

@ -4,26 +4,22 @@
* The album tag is used to store the name and year of albums
* added to the tag database.
*
* When writing an album tag to disk, write out the album_artist, album_genre,
* and album_year fields followed by the album_name on the same line:
* When writing an album tag to disk, write out the album_year field
* followed by the album_name on the same line:
*
* ...0 0 1998 Hyrule Symphony
* ...0 0 2006 Twilight Princess
* ...0 0 2011 Skyward Sword
* ... 1998 Hyrule Symphony
* ... 2006 Twilight Princess
* ... 2011 Skyward Sword
*/
#ifndef OCARINA_CORE_TAGS_ALBUM_H
#define OCARINA_CORE_TAGS_ALBUM_H
#include <core/database.h>
#include <core/tags/artist.h>
#include <core/containers/database.h>
struct album {
unsigned int al_year; /* This album's year. */
gchar *al_name; /* This album's name. */
gchar **al_tokens; /* This album's tokenized strings. */
gchar **al_alts; /* This album's alternate ascii tokens. */
struct artist *al_artist;
struct genre *al_genre;
unsigned int al_year; /* This album's year. */
gchar *al_name; /* This album's name. */
gchar *al_lower; /* This album's name (lowercased). */
struct db_entry al_dbe;
};
@ -36,15 +32,8 @@ void album_db_init();
/* Called to clean up the album database. */
void album_db_deinit();
/* Called to defragment the album database. */
bool album_db_defrag();
/* Called to clean up the album database after an upgrade. */
bool album_db_upgrade_done();
/* Called to find an album tag by artist, name and year. */
struct album *album_find(struct artist *, struct genre *,
const gchar *, unsigned int);
/* Called to find an album tag by name and year. */
struct album *album_find(const gchar *, unsigned int);
/* Called to get an album tag with a specific index. */
struct album *album_get(const unsigned int);
@ -55,27 +44,6 @@ int album_compare(struct album *, struct album *);
/* Called to compare two album tags by year. */
int album_compare_year(struct album *, struct album *);
/* Called to check if an artist has a token that matches the given string. */
bool album_match_token(struct album *album, const gchar *);
/* Called to find the database index of the album tag. */
static inline unsigned int album_index(struct album *album)
{
return album->al_dbe.dbe_index;
}
/* Called to check if album artwork has been downloaded. */
bool album_artwork_exists(struct album *);
/*
* Called to find the path to an album's artwork.
* This function returns a new string that MUST be freed with g_free().
*/
gchar *album_artwork_path(struct album *);
/* Called to manually set artwork for a given album. */
bool album_artwork_import(struct album *, gchar *);
#ifdef CONFIG_TESTING
const struct db_ops *test_album_ops();
#endif /* CONFIG_TESTING */

View File

@ -13,13 +13,11 @@
#ifndef OCARINA_CORE_TAGS_ARTIST_H
#define OCARINA_CORE_TAGS_ARTIST_H
#include <core/database.h>
#include <core/containers/database.h>
struct artist {
gchar *ar_name; /* This artist's name. */
gchar **ar_tokens; /* This artist's tokenized strings. */
gchar **ar_alts; /* This artist's alternate ascii tokens. */
void *ar_playlist; /* This artist's associated playlist. */
gchar *ar_name; /* This artist's name. */
gchar *ar_lower; /* This artist's name (lowercased). */
struct db_entry ar_dbe;
};
@ -32,16 +30,8 @@ void artist_db_init();
/* Called to clean up the artist database. */
void artist_db_deinit();
/* Called to access the artist database. */
const struct database *artist_db_get();
/*
* Called to find an artist tag by name. The difference is that artist_find()
* will allocate a new artiststruct if the requested one doesn't exist yet,
* but library_lookup() will return NULL in this situation.
*/
/* Called to find an artist tag by name. */
struct artist *artist_find(const gchar *);
struct artist *artist_lookup(const gchar *);
/* Called to get an artist tag with a specific index. */
struct artist *artist_get(const unsigned int);
@ -49,15 +39,6 @@ struct artist *artist_get(const unsigned int);
/* Called to compare two artist tags. */
int artist_compare(struct artist *, struct artist *);
/* Called to check if an artist has a token that matches the given string. */
bool artist_match_token(struct artist *artist, const gchar *);
/* Called to find the database index of the artist tag. */
static inline unsigned int artist_index(struct artist *artist)
{
return artist->ar_dbe.dbe_index;
}
#ifdef CONFIG_TESTING
const struct db_ops *test_artist_ops();
#endif /* CONFIG_TESTING */

View File

@ -13,12 +13,11 @@
#ifndef OCARINA_CORE_TAGS_GENRE_H
#define OCARINA_CORE_TAGS_GENRE_H
#include <core/database.h>
#include <core/containers/database.h>
struct genre {
gchar *ge_name; /* This genre's name. */
gchar **ge_tokens; /* This genre's tokenized strings. */
gchar **ge_alts; /* This genre's alternate ascii tokens. */
gchar *ge_name; /* This genre's name. */
gchar *ge_lower; /* This genre's name (lowercased). */
struct db_entry ge_dbe;
};
@ -40,15 +39,6 @@ struct genre *genre_get(const unsigned int);
/* Called to compare two genre tags. */
int genre_compare(struct genre *, struct genre *);
/* Called to check if a genre has a token that matches the given string. */
bool genre_match_token(struct genre *, const gchar *);
/* Called to find the database index of the genre tag. */
static inline unsigned int genre_index(struct genre *genre)
{
return genre->ge_dbe.dbe_index;
}
#ifdef CONFIG_TESTING
const struct db_ops *test_genre_ops();
#endif /* CONFIG_TESTING */

View File

@ -13,11 +13,12 @@
#ifndef OCARINA_CORE_TAGS_LIBRARY_H
#define OCARINA_CORE_TAGS_LIBRARY_H
#include <core/database.h>
#include <core/containers/database.h>
struct library {
gchar *li_path; /* This library's root path. */
void *li_playlist; /* This library's associated playlist. */
unsigned int li_size; /* This library's track count. */
bool li_enabled;/* True if this library is enabled. */
gchar *li_path; /* This library's root path. */
struct db_entry li_dbe;
};
@ -30,21 +31,11 @@ void library_db_init();
/* Called to clean up the library database. */
void library_db_deinit();
/* Called to defragment the library database. */
bool library_db_defrag();
/* Called to access the library database. */
const struct database *library_db_get();
/*
* Called to find a library tag by path. The difference is that
* library_find() will allocate a new library struct if the requested one
* doesn't exist yet, but library_lookup() will return NULL in this situation.
*
* Note that path may be a subdirectory of the returned library.
*/
/* Called to find a library tag by library path. */
struct library *library_find(const gchar *);
struct library *library_lookup(const gchar *);
/* Called to get a library tag with a specific index. */
struct library *library_get(const unsigned int);
@ -52,11 +43,8 @@ struct library *library_get(const unsigned int);
/* Called to remove a specific library tag. */
void library_remove(struct library *);
/* Called to find the database index of the library tag. */
static inline unsigned int library_index(struct library *library)
{
return library->li_dbe.dbe_index;
}
/* Called to configure if the library tag is enabled. */
void library_set_enabled(struct library *, bool);
/*
* Called to find the full path of files under the library directory.

View File

@ -11,7 +11,4 @@ void tags_init();
/* Called to clean up all databases. */
void tags_deinit();
/* Called to defragment all databases. */
bool tags_defragment();
#endif /* OCARINA_CORE_TAGS_TAGS_H */

View File

@ -7,19 +7,20 @@
* When writing a Track tag to disk, write as many fields as
* possible on one line before spilling over to a second:
*
* library datestamp
* | album | count title
* | | number | | length | path
* | | | | | | | |
* ... 0 2 12 3741780495 13 232 Ocarina Medley |
* Hyrule Symphony/12 - Ocarina Medley.mp3 <---
* ... 0 2 13 3741780495 10 288 Legend of Zelda Medley
* library number count
* | artist | year | length
* | | album| | month | | title
* | | | genre | | day| | | path
* | | | | | | | | | | | |
* ... 0 1 2 1 12 2015 10 15 13 232 Ocarina Medley |
* Hyrule Symphony/12 - Ocarina Medley.mp3 <---
* ... 0 1 2 1 13 2015 10 15 10 288 Legend of Zelda Medley
* Hyrule Symphony/13 - Legend of Zelda Medly.mp3
*/
#ifndef OCARINA_CORE_TAGS_TRACK_H
#define OCARINA_CORE_TAGS_TRACK_H
#include <core/database.h>
#include <core/containers/database.h>
#include <core/date.h>
#include <core/tags/album.h>
#include <core/tags/artist.h>
@ -42,23 +43,23 @@ enum compare_t {
struct track {
struct album *tr_album; /* This track's associated album. */
struct artist *tr_artist; /* This track's associated artist. */
struct genre *tr_genre; /* This track's associated genre. */
struct library *tr_library; /* This track's associated library. */
unsigned short int tr_count; /* This track's play count. */
unsigned short int tr_length; /* This track's length, in seconds. */
unsigned short int tr_track; /* This track's track number. */
unsigned int tr_count; /* This track's play count. */
unsigned int tr_length; /* This track's length, in seconds. */
unsigned int tr_track; /* This track's track number. */
struct date tr_date; /* This track's last-played date. */
gchar *tr_path; /* This track's path, relative to the library. */
gchar *tr_title; /* This track's title. */
gchar **tr_tokens; /* This track's tokenized strings. */
gchar **tr_alts; /* This track's alternate ascii tokens. */
struct date tr_date; /* This track's last-played date. */
gchar *tr_path; /* This track's path, relative to the library. */
gchar *tr_title; /* This track's title. */
gchar *tr_lower; /* This track's title (lowercased). */
struct db_entry tr_dbe;
};
#define TRACK(dbe) ((struct track *)DBE_DATA(dbe))
#define TRACK_IS_EXTERNAL(track) (track->tr_library == NULL)
/* Called to initialize the track database. */
@ -70,12 +71,6 @@ void track_db_deinit();
/* Called to commit the track database. */
void track_db_commit();
/* Called to defragment the track database. */
bool track_db_defrag();
/* Called to update track database keys. */
void track_db_rekey();
/* Called to access the track database. */
const struct database *track_db_get();
@ -88,12 +83,6 @@ unsigned int track_db_count_plays();
/* Called to find the average play count of tracks in the database. */
unsigned int track_db_average_plays();
/* Called to allocate a track without adding it to the database. */
struct track *track_alloc_external(const gchar *);
/* Called to free an external track. */
void track_free_external(struct track *);
/* Called to add a track tag to the database. */
struct track *track_add(struct library *, const gchar *);
@ -106,21 +95,9 @@ void track_remove_all(struct library *);
/* Called to get a track tag with a specific index. */
struct track *track_get(const unsigned int);
/* Called to look up a track tag using only a filepath. */
struct track *track_lookup(const gchar *);
/* Called to compare two track tags */
int track_compare(struct track *, struct track *, enum compare_t);
/* Called to check if a track has a token that matches the given string */
bool track_match_token(struct track *, const gchar *);
/* Called to find the database index of the track tag. */
static inline unsigned int track_index(struct track *track)
{
return track->tr_dbe.dbe_index;
}
/*
* Called to find the full path of the track tag.
* This function returns a new string that MUST be freed with g_free().

51
include/core/tempq.h Normal file
View File

@ -0,0 +1,51 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The temporary queue manager controls access to any temporary
* queues created by the user. When saving to disk:
*
* length(tempq_list)
* flags num_tracks track_0 track_1 ... track_N
* flags num_tracks track_0 track_1 ... track_N
*/
#ifndef OCARINA_CORE_DECK_H
#define OCARINA_CORE_DECK_H
#include <core/queue.h>
/* Called to initialize the temporary queue manager. */
void tempq_init(struct queue_ops *);
/* Called to deinitialize the temporary queue manager. */
void tempq_deinit();
/* Called to save the temporary queue list. */
void tempq_save(struct queue *, enum queue_flags);
/* Called to allocate a new temporary queue. */
struct queue *tempq_alloc(unsigned int);
/* Called to free a temporary queue. */
void tempq_free(struct queue *);
/* Called to find a temporary queue by index. */
struct queue *tempq_get(unsigned int);
/* Called to find the index of a temporary queue. */
unsigned int tempq_index(struct queue *);
/* Called to move a temporary queue to a new index in the list. */
void tempq_move(struct queue *, unsigned int);
/* Called to find the next track to play. */
struct track *tempq_next();
/* Called to find the number of temporary queues currently allocated. */
unsigned int tempq_count();
/* Called to tell temporary queues that a track has been updated. */
void tempq_updated(struct track *);
#endif /* OCARINA_CORE_DECK_H */

View File

@ -4,17 +4,6 @@
#ifndef OCARINA_CORE_VERSION_H
#define OCARINA_CORE_VERSION_H
#ifdef CONFIG_TESTING
#define OCARINA_NAME "ocarina-test/"CONFIG_TESTING_DIR
#elif CONFIG_DEBUG
#define OCARINA_NAME "ocarina-debug"
#else
#define OCARINA_NAME "ocarina"
#endif
#define OCARINA_MAJOR_VERSION CONFIG_MAJOR
#define OCARINA_MINOR_VERSION CONFIG_MINOR
/*
* Scons sets the current version by passing the
* -DCONFIG_VERSION macro when Ocarina is compiled.

View File

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

View File

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

View File

@ -17,9 +17,6 @@ GObject *gui_builder_object(const char *);
/* Called to get a widget from the GTK builder. */
GtkWidget *gui_builder_widget(const char *);
/* Called to find the height of a GTK builder widget. */
int gui_builder_widget_height(const char *);
#ifdef CONFIG_TESTING
GtkBuilder *test_get_gui_builder();
#endif /* CONFIG_TESTING */

13
include/gui/collection.h Normal file
View File

@ -0,0 +1,13 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_COLLECTION_H
#define OCARINA_GUI_COLLECTION_H
/* Called to initialize the GUI collection code. */
void gui_collection_init();
/* Collection operations passed to core_init() */
extern struct queue_ops collection_ops;
#endif /* OCARINA_GUI_COLLECTION_H */

View File

@ -1,56 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_FILTER_H
#define OCARINA_GUI_FILTER_H
#include <core/playlist.h>
#include <gui/builder.h>
enum gui_filter_how {
GUI_FILTER_DEFAULT,
GUI_FILTER_ALBUM,
GUI_FILTER_ARTIST,
GUI_FILTER_GENRE,
GUI_FILTER_TITLE,
};
/* Called to initialize the filter model. */
void gui_filter_init();
/* Called to deinitialize the filter model. */
void gui_filter_deinit();
/* Called to set the current playlist. */
void gui_filter_set_playlist(struct playlist *);
/* Called to get the filter model. */
GtkTreeModelFilter *gui_filter_get();
/* Called to convert a filter model path into a track. */
struct track *gui_filter_path_get_track(GtkTreePath *);
/* Called to load the track at path. */
void gui_filter_path_load_track(GtkTreePath *);
/* Called to convert a filter model path into a queue index. */
unsigned int gui_filter_path_get_index(GtkTreePath *);
/* Called to convert a playlist iterator index into a path. */
GtkTreePath *gui_filter_path_from_index(unsigned int);
/* Called to refilter a playlist. Pass NULL to refilter the current playlist */
void gui_filter_refilter(struct playlist *);
/* Called to access the filter search-entry. */
static inline GtkSearchEntry *gui_filter_search(void)
{
return GTK_SEARCH_ENTRY(gui_builder_widget("filter_search"));
}
/* Called to access the filter-how chooser. */
static inline GtkComboBox *gui_filter_how(void)
{
return GTK_COMBO_BOX(gui_builder_widget("filter_how"));
}
#endif /* OCARINA_GUI_FILTER_H */

10
include/gui/history.h Normal file
View File

@ -0,0 +1,10 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_HISTORY_H
#define OCARINA_GUI_HISTORY_H
/* History operations passed to core_init() */
extern struct queue_ops history_ops;
#endif /* OCARINA_GUI_HISTORY_H */

View File

@ -3,8 +3,6 @@
*/
#ifndef OCARINA_GUI_IDLE_H
#define OCARINA_GUI_IDLE_H
#include <core/idle.h>
#include <gui/builder.h>
/* Called to enable processing idle queue tasks. */
void gui_idle_enable();
@ -12,10 +10,4 @@ void gui_idle_enable();
/* Called to disable processing idle queue tasks. */
void gui_idle_disable();
/* Called to get a pointer to the idle progress bar. */
static inline GtkProgressBar *gui_progress_bar()
{
return GTK_PROGRESS_BAR(gui_builder_widget("progress_bar"));
}
#endif /* OCARINA_GUI_IDLE_H */

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