Compare commits

...

No commits in common. "next" and "Scion-1.x" have entirely different histories.

210 changed files with 2698 additions and 16543 deletions

10
.gitattributes vendored
View File

@ -1,10 +0,0 @@
tests/ export-ignore
Doxyfile export-ignore
.gitattributes export-ignore
.gitignore export-ignore
DESIGN export-ignore
GOALS export-ignore
PKGBUILD export-ignore
TODO export-ignore

22
.gitignore vendored
View File

@ -1,22 +0,0 @@
*.o
*.sw*
*.out
*.glade~
*.ui~
share/ocarina/#*
bin/
.sconsign.dblite
*.patch
*.tar.gz
*.gcov
*.gcda
*.gcno
core.*
.debug
CMakeCache.txt
CMakeFiles
Makefile
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
Testing/

View File

@ -1,14 +0,0 @@
stages:
- build
- test
build:
stage: build
script:
- cmake -DCONFIG_DEBUG=ON && make
test:
stage: test
script:
- cmake -DCONFIG_TESTING_VERBOSE=ON -DCONFIG_TESTING_GUI=OFF && make tests

148
CHANGELOG
View File

@ -1,148 +0,0 @@
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
- Improve track average play count calculation
6.4.13:
- Enable bolding the current track in the GtkTreeView
- Enable GtkTreeView fixed-height mode
- Fix bug where temporary queues don't save
- Fix bug where a queue is not selected on startup
- Fix bugs related to scrolling on track change
6.4.13-rc:
- Rewrite GtkTreeView code
- Save and Restore treeview column widths
- Only have a single GtkTreeView instance
- Finish conversion of Ocarina to C
6.4.12:
- Don't reenable tempqueues when restarting
- Control automatic pausing with a GtkComboBox
- Move current position slider into the top section
- Update tooltips on various buttons
- Various UI tweaks
- Swap position of random/repeat and favorite/hide buttons
6.4.12-rc:
- Rewrite our custom GtkTreeModel and add tests
- Move shuffle and repeat buttons into the sidebar
6.4.11:
- Remove stop button
- Move collection enabled checkboxes into a right click menu
- Various UI tweaks (Thanks to Colin Fulton for helping here)
6.4.11-rc:
- Convert window code to C
- Convert collection gui code to C
- Convert playlist gui code to C
- Add sidebar and hide notebook tabs
6.4.10:
- Block audio accelerator keys if a GtkEntry widget has focus
- Various UI tweaks
- Add changelog
6.4.10-rc:
- Add generic settings functions
- Convert code using GTK+ builder to C
- Convert audio code to C

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

339
LICENSE
View File

@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

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

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.

40
TODO
View File

@ -1,40 +0,0 @@
Future work:
I want to set reasonable expectations for Ocarina 6 so that I don't
have to spend a large amount of time coding before releasing something
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.
- Fix track durations:
Some tracks in my library are tagged with the wrong duration,
so fix them as they are played.
- Track tag editor:
Make a pop-up window for editing the tags of a track. Be sure
to update the library information and the on-disk file.
- 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.
Complications: I have an mp3 mirror of all my music, and I
want the mp3s to be synced. Perhaps track mirrors in Ocarina?
- Mirror directory:
I rip music to .flac, but keep an mp3 mirror directory to sync
to other computers and phones. An Ocarina tool to manage a
COMPLETE library mirror might be a good idea so I no longer
need to manage it externally. This can still be done with a
script, a cron job, and maybe a "mirror this track" option in
the library? Perhaps create a mirror group?
- AirPlay / remote audio support
- Replaygain support
External script to calculate values?
Calculate value after first playback?
Store in library :: Track structure
- "About" dialog
- Ports:
- OSX
- Windows
- Android

View File

@ -1,344 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/audio.h>
#include <core/idle.h>
#include <core/playlist.h>
#include <core/settings.h>
#define LOAD_PLAYING (1 << 0) /* Begin playback after loading */
#define LOAD_HISTORY (1 << 1) /* Add the track to the history */
#define LOAD_DEFAULT (LOAD_PLAYING | LOAD_HISTORY)
static const char *SETTINGS_TRACK = "core.audio.cur";
static const char *SETTINGS_VOLUME = "core.audio.volume";
static struct file audio_file = FILE_INIT_DATA("", "cur_track", 0);
static struct track *audio_track = NULL;
static int audio_pause_count = -1;
static GstElement *audio_pipeline = NULL;
static GstElement *audio_source = NULL;
static GstElement *audio_decoder = NULL;
static GstElement *audio_converter = NULL;
static GstElement *audio_volume = NULL;
static GstElement *audio_sink = NULL;
static guint audio_bus_id = 0;
static struct audio_callbacks *audio_cb = NULL;
static bool __audio_change_state(GstState state)
{
if (audio_cur_state() == state)
return false;
return gst_element_set_state(audio_pipeline, state) != GST_STATE_CHANGE_FAILURE;
}
static struct track *__audio_load(struct track *track, unsigned int flags)
{
struct track *prev = audio_track;
gchar *path;
if (!track)
return 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);
playlist_selected(track);
if (flags & LOAD_HISTORY && !TRACK_IS_EXTERNAL(track))
playlist_add(playlist_lookup(PL_SYSTEM, "History"), track);
if (audio_cb)
audio_cb->audio_cb_load(track);
audio_save();
g_free(path);
return track;
}
static void __audio_pad_added(GstElement *element, GstPad *pad, gpointer data)
{
GstPad *sink = gst_element_get_static_pad(audio_decoder, "sink");
gst_element_link(element, audio_converter);
gst_pad_link(pad, sink);
gst_object_unref(sink);
}
static gboolean __audio_message(GstBus *bus, GstMessage *message, gpointer data)
{
GstObject *source = GST_OBJECT(GST_MESSAGE_SRC(message));
gchar *debug = NULL;
GError *error = NULL;
GstState old, state, next;
unsigned int load_flags = LOAD_DEFAULT;
switch (GST_MESSAGE_TYPE(message)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(message, &error, &debug);
g_printerr("ERROR from element %s: %s\n",
GST_OBJECT_NAME(source), error->message);
g_printerr("DEBUG details: %s\n", debug ? debug : "none");
g_error_free(error);
g_free(debug);
if (audio_cur_state() != GST_STATE_PLAYING)
load_flags = LOAD_HISTORY;
__audio_load(playlist_next(), load_flags);
break;
case GST_MESSAGE_EOS:
track_played(audio_track);
if (audio_pause_count >= 0) {
audio_pause_after(audio_pause_count - 1);
if (audio_pause_count == -1)
load_flags = LOAD_HISTORY;
}
__audio_load(playlist_next(), load_flags);
break;
case GST_MESSAGE_STATE_CHANGED:
if (!audio_cb || source != GST_OBJECT(audio_pipeline))
break;
gst_message_parse_state_changed(message, &old, &state, &next);
if (state == GST_STATE_PLAYING || state == GST_STATE_PAUSED) {
if (next == GST_STATE_VOID_PENDING)
audio_cb->audio_cb_state_change(state);
}
default:
break;
}
return true;
}
static bool __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);
file_close(&audio_file);
file_remove(&audio_file);
__audio_load(track_get(track), LOAD_HISTORY);
}
return true;
}
void audio_init(int *argc, char ***argv, struct audio_callbacks *callbacks)
{
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);
gst_bin_add_many(GST_BIN(audio_pipeline), audio_source, audio_decoder,
audio_converter, audio_volume,
audio_sink, NULL);
gst_element_link(audio_source, audio_decoder);
gst_element_link_many(audio_converter, audio_volume, audio_sink, NULL);
g_signal_connect(audio_decoder, "pad-added", G_CALLBACK(__audio_pad_added), NULL);
gst_object_unref(bus);
if (settings_has(SETTINGS_VOLUME))
volume = settings_get(SETTINGS_VOLUME);
audio_set_volume(volume);
idle_schedule(IDLE_SYNC, __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);
audio_pipeline = NULL;
audio_source = NULL;
audio_decoder = NULL;
audio_converter = NULL;
audio_volume = NULL;
audio_sink = NULL;
audio_track = NULL;
gst_deinit();
}
void audio_save()
{
if (audio_track && !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);
}
struct track *audio_cur_track()
{
return audio_track;
}
GstState audio_cur_state()
{
GstState cur = GST_STATE_NULL;
if (audio_pipeline)
gst_element_get_state(audio_pipeline,
&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)
return false;
return __audio_change_state(GST_STATE_PLAYING);
}
bool audio_pause()
{
if (!audio_track)
return false;
return __audio_change_state(GST_STATE_PAUSED);
}
bool audio_seek(gint64 offset)
{
if (!audio_track)
return false;
return gst_element_seek_simple(audio_pipeline,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH,
offset);
}
gint64 audio_position()
{
gint64 position;
if (gst_element_query_position(audio_pipeline,
GST_FORMAT_TIME,
&position))
return position;
return 0;
}
gint64 audio_duration()
{
gint64 duration;
if (gst_element_query_duration(audio_pipeline,
GST_FORMAT_TIME,
&duration))
return duration;
if (audio_track)
return audio_track->tr_length * GST_SECOND;
return 0;
}
struct track *audio_next()
{
return __audio_load(playlist_next(), LOAD_DEFAULT);
}
struct track *audio_prev()
{
return __audio_load(playlist_prev(), LOAD_PLAYING);
}
bool audio_pause_after(int n)
{
if (n >= -1 && n != audio_pause_count) {
audio_pause_count = n;
if (audio_cb)
audio_cb->audio_cb_config_pause(audio_pause_count);
return true;
}
return false;
}
int audio_get_pause_count(void)
{
return audio_pause_count;
}
#ifdef CONFIG_TESTING
void test_audio_eos()
{
GstMessage *message = gst_message_new_eos(GST_OBJECT(audio_pipeline));
__audio_message(NULL, message, NULL);
gst_message_unref(message);
}
void test_audio_error(GError *error, gchar *debug)
{
GstMessage *message = gst_message_new_error(
GST_OBJECT(audio_pipeline), error, debug);
__audio_message(NULL, message, NULL);
gst_message_unref(message);
}
GstElement *test_audio_pipeline()
{
return audio_pipeline;
}
#endif /* CONFIG_TESTING */

View File

@ -1,34 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/core.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)
{
idle_init(idle_sync);
settings_init();
tags_init();
playlist_init(playlist_cb);
audio_init(argc, argv, audio_cb);
idle_schedule(IDLE_SYNC, core_defragment, NULL);
}
void core_deinit()
{
audio_deinit();
playlist_deinit();
tags_deinit();
settings_deinit();
idle_deinit();
}

View File

@ -1,232 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/database.h>
#include <core/idle.h>
#define DB_ENTRY_AT(db, index) \
(struct db_entry *)g_ptr_array_index(db->db_entries, index)
static void __dbe_free(struct database *db, struct db_entry *dbe)
{
if (dbe) {
g_hash_table_remove(db->db_keys, dbe->dbe_key);
g_free(dbe->dbe_key);
g_ptr_array_index(db->db_entries, dbe->dbe_index) = NULL;
db->db_ops->dbe_free(dbe);
db->db_size--;
}
}
static struct db_entry *__dbe_next(const struct database *db, unsigned int index)
{
if (!db->db_entries)
return NULL;
for (; index < db->db_entries->len; index++) {
if (DB_ENTRY_AT(db, index))
return DB_ENTRY_AT(db, index);
}
return NULL;
}
static struct db_entry *__dbe_read(struct database *db, unsigned int index)
{
struct db_entry *dbe = NULL;
if (file_readd(&db->db_file))
dbe = db->db_ops->dbe_read(&db->db_file, index);
g_ptr_array_index(db->db_entries, index) = dbe;
return dbe;
}
static void __dbe_setup(struct database *db, unsigned int index)
{
struct db_entry *dbe = DB_ENTRY_AT(db, index);
if (dbe) {
dbe->dbe_index = index;
dbe->dbe_key = db->db_ops->dbe_key(dbe);
g_hash_table_insert(db->db_keys, dbe->dbe_key, dbe);
db->db_size++;
}
}
static void __dbe_write(struct database *db, struct db_entry *dbe)
{
if (dbe) {
file_writef(&db->db_file, "%u ", true);
db->db_ops->dbe_write(&db->db_file, dbe);
} else
file_writef(&db->db_file, "%u", false);
file_writef(&db->db_file, "\n");
}
void db_init(struct database *db, const char *filepath, bool autosave,
const struct db_ops *ops, unsigned int fmin)
{
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);
}
void db_deinit(struct database *db)
{
struct db_entry *dbe, *next;
db_for_each(dbe, next, db)
__dbe_free(db, dbe);
g_ptr_array_free(db->db_entries, true);
g_hash_table_destroy(db->db_keys);
db->db_entries = NULL;
db->db_keys = NULL;
}
void db_save(struct database *db)
{
if (file_open(&db->db_file, OPEN_WRITE) == false)
return;
file_writef(&db->db_file, "%u\n", db_actual_size(db));
for (unsigned int i = 0; i < db_actual_size(db); i++)
__dbe_write(db, DB_ENTRY_AT(db, i));
file_close(&db->db_file);
}
void db_autosave(struct database *db)
{
if (db->db_autosave == true)
db_save(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);
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);
}
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));
if (item) {
g_ptr_array_add(db->db_entries, item);
__dbe_setup(db, db_actual_size(db) - 1);
db_autosave(db);
}
return item;
}
void db_remove(struct database *db, struct db_entry *item)
{
if (item == NULL)
return;
if (db_at(db, item->dbe_index) != item)
return;
__dbe_free(db, 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)
return db->db_entries->len;
return 0;
}
struct db_entry *db_first(const struct database *db)
{
return __dbe_next(db, 0);
}
struct db_entry *db_next(const struct database *db, struct db_entry *ent)
{
if (ent)
return __dbe_next(db, ent->dbe_index + 1);
return NULL;
}
struct db_entry *db_at(const struct database *db, unsigned int index)
{
if (index >= db_actual_size(db))
return NULL;
return DB_ENTRY_AT(db, index);
}
struct db_entry *db_get(struct database *db, const gchar *key)
{
return (struct db_entry *)g_hash_table_lookup(db->db_keys, key);
}
struct db_entry *db_find(struct database *db, const gchar *key)
{
struct db_entry *dbe = db_get(db, key);
if (dbe)
return dbe;
return db_insert(db, key);
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/date.h>
#include <core/string.h>
#include <endian.h>
#include <time.h>
void date_set(struct date *date, unsigned int year,
unsigned int month, unsigned int day)
{
if (date) {
date->d_year = year;
date->d_month = month;
date->d_day = day;
}
}
void date_today(struct date *date)
{
time_t rawtime = time(NULL);
struct tm *now = localtime(&rawtime);
date_set(date, now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
}
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));
}
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 = {
.tm_year = date->d_year - 1900,
.tm_mon = date->d_month - 1,
.tm_mday = date->d_day,
};
return string_tm2str(&tm);
}
int date_compare(const struct date *lhs, const struct date *rhs)
{
int ret = lhs->d_year - rhs->d_year;
if (ret != 0)
return ret;
ret = lhs->d_month - rhs->d_month;
if (ret != 0)
return ret;
return lhs->d_day - rhs->d_day;
}

View File

@ -1,279 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#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))
static void __file_init_common(struct file *file, const gchar *subdir,
const gchar *name, unsigned int min,
const gchar *(*user_dir)(void))
{
file->f_file = NULL;
file->f_name = name;
file->f_subdir = subdir;
file->f_mode = CLOSED;
file->f_version = OCARINA_MINOR_VERSION;
file->f_prev = 0;
file->f_min = min;
file->f_user_dir = user_dir;
}
static bool __file_open(struct file *file, enum open_mode mode)
{
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);
int ret = g_mkdir_with_parents(dir, 0755);
if (ret != 0)
REPORT_ERRNO(dir);
g_free(dir);
return ret == 0;
}
static bool __file_can_write(struct file *file)
{
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);
}
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;
}
const unsigned int file_version(struct file *file)
{
if (file->f_file && (file->f_mode == OPEN_READ))
return file->f_prev;
return file->f_version;
}
bool file_exists(struct file *file)
{
gchar *path = file_path(file);
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)
{
if (!file_exists(file))
return false;
if (!__file_open(file, mode))
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)
{
if (!__file_mkdir(file))
return false;
if (!__file_can_write(file))
return false;
if (!__file_open(file, OPEN_WRITE))
return false;
file->f_mode = mode;
if (mode == OPEN_WRITE_BINARY)
return true;
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)
return false;
if (mode == OPEN_READ || mode == OPEN_READ_BINARY)
return __file_open_read(file, mode);
return __file_open_write(file, mode);
}
void file_close(struct file *file)
{
gchar *path = file_path(file);
gchar *tmp = file_write_path(file);
if (file->f_file) {
fclose(file->f_file);
if (file->f_mode == OPEN_WRITE || file->f_mode == OPEN_WRITE_BINARY)
g_rename(tmp, path);
}
file->f_file = NULL;
file->f_mode = CLOSED;
g_free(path);
g_free(tmp);
}
gchar *file_readw(struct file *file)
{
gchar *s;
return fscanf(file->f_file, "%ms%*c", &s) ? s : g_strdup("");
}
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("");
}
unsigned int file_readu(struct file *file)
{
unsigned int u;
return fscanf(file->f_file, "%u%*c", &u) ? u : 0;
}
int file_writef(struct file *file, const char *fmt, ...)
{
va_list argp;
int ret;
va_start(argp, fmt);
ret = g_vfprintf(file->f_file, fmt, argp);
va_end(argp);
if (ret < 0)
REPORT_ERRNO(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;
}

View File

@ -1,111 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <glib.h>
struct idle_task {
bool (*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;
void __idle_free_task(struct idle_task *task)
{
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));
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);
}
bool idle_run_task()
{
struct idle_task *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);
}
if (g_atomic_int_get(&queued) != g_atomic_int_get(&serviced))
return true;
queued = 0;
serviced = 0;
return false;
}
float idle_progress()
{
if (g_atomic_int_get(&serviced) == 0 &&
g_atomic_int_get(&queued) == 0)
return 1.0;
return (float)g_atomic_int_get(&serviced) / (float)queued;
}

View File

@ -1,236 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/playlist.h>
#include <core/settings.h>
#include <core/string.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 playlist *__playlist_saved(const gchar *s_type, const gchar *s_id)
{
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);
}
void playlist_init(struct playlist_callbacks *cb)
{
playlist_generic_set_callbacks(cb);
pl_system_init();
pl_artist_init();
pl_user_init();
pl_library_init();
current = __playlist_saved(SETTINGS_CUR_TYPE, SETTINGS_CUR_ID);
previous = __playlist_saved(SETTINGS_PREV_TYPE, SETTINGS_PREV_ID);
if (!current)
current = playlist_lookup(PL_SYSTEM, "Collection");
}
void playlist_deinit()
{
pl_system_deinit();
pl_artist_deinit();
pl_user_deinit();
pl_library_deinit();
}
void playlist_save()
{
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)
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);
}
return true;
}
struct track *playlist_next(void)
{
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)
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;
}
bool playlist_remove(struct playlist *playlist, struct track *track)
{
bool ret;
if (!track || !playlist || !playlist->pl_ops->pl_remove)
return false;
ret = playlist->pl_ops->pl_remove(playlist, track);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
}
void playlist_set_random(struct playlist *playlist, bool enabled)
{
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();
}
}
bool playlist_sort(struct playlist *playlist, enum compare_t sort)
{
if (!playlist || !playlist->pl_ops->pl_sort)
return false;
playlist->pl_ops->pl_sort(playlist, sort);
if (playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return g_slist_length(playlist->pl_sort) > 0;
}
bool playlist_rearrange(struct playlist *playlist, unsigned int old_pos,
unsigned int new_pos)
{
bool ret;
if (!playlist || !playlist->pl_ops->pl_rearrange)
return false;
ret = playlist->pl_ops->pl_rearrange(playlist, old_pos, new_pos);
if (ret && playlist->pl_type < PL_MAX_TYPE)
playlist_types[playlist->pl_type]->pl_save();
return ret;
}
void playlist_set_search(struct playlist *playlist, const gchar *text)
{
gchar **tokens = NULL;
if (!playlist)
return;
if (playlist->pl_search)
g_strfreev(playlist->pl_search);
if (strlen(text) > 0)
tokens = g_str_tokenize_and_fold(text, NULL, NULL);
playlist->pl_search = tokens;
}

View File

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

View File

@ -1,86 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/file.h>
#include <core/settings.h>
static GHashTable *gui_settings = NULL;
static struct file gui_settings_file = FILE_INIT_DATA("", "settings", 0);
static void __settings_save_item(gpointer key, gpointer value, gpointer data)
{
file_writef(&gui_settings_file, "%s %u\n", (const gchar *)key,
GPOINTER_TO_UINT(value));
}
static void __settings_save()
{
file_open(&gui_settings_file, OPEN_WRITE);
file_writef(&gui_settings_file, "%u\n", g_hash_table_size(gui_settings));
g_hash_table_foreach(gui_settings, __settings_save_item, NULL);
file_close(&gui_settings_file);
}
static void __settings_read()
{
unsigned int num, i, value;
gchar *key;
num = file_readu(&gui_settings_file);
for (i = 0; i < num; i++) {
key = file_readw(&gui_settings_file);
value = file_readu(&gui_settings_file);
g_hash_table_insert(gui_settings, key, GUINT_TO_POINTER(value));
}
}
void settings_init()
{
gui_settings = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, NULL);
if (file_open(&gui_settings_file, OPEN_READ))
__settings_read();
file_close(&gui_settings_file);
}
void settings_deinit()
{
g_hash_table_destroy(gui_settings);
gui_settings = NULL;
}
void settings_set(const gchar *key, unsigned int value)
{
if (gui_settings && key) {
g_hash_table_replace(gui_settings, g_strdup(key),
GUINT_TO_POINTER(value));
__settings_save();
}
}
unsigned int settings_get(const gchar *key)
{
if (gui_settings && key)
return GPOINTER_TO_UINT(g_hash_table_lookup(gui_settings, key));
return 0;
}
bool settings_has(const gchar *key)
{
if (gui_settings && key)
return g_hash_table_contains(gui_settings, key);
return false;
}
#ifdef CONFIG_TESTING
GHashTable *test_get_settings()
{
return gui_settings;
}
#endif /* CONFIG_TESTING */

View File

@ -1,105 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/string.h>
#define O_SECONDS (1)
#define O_MINUTES (60)
#define O_HOURS (60 * O_MINUTES)
#define O_DAYS (24 * O_HOURS)
static unsigned int factor[4] = { O_DAYS, O_HOURS, O_MINUTES, O_SECONDS };
static const char *field[4] = { "day", "hour", "minute", "second" };
gchar *string_sec2str(unsigned int sec)
{
return g_strdup_printf("%u:%02u", sec / 60, sec % 60);
}
gchar *string_sec2str_long(unsigned int sec)
{
gchar *tmp;
unsigned int val;
gchar *res = g_strdup("");
for (unsigned int i = 0; i < 4; i++) {
val = sec / factor[i];
sec %= factor[i];
if (val == 0)
continue;
tmp = g_strdup_printf("%s%u %s%s%s", res, val, field[i],
(val > 1) ? "s" : "",
(sec > 0) ? ", " : "");
g_free(res);
res = tmp;
}
return res;
}
gchar *string_tm2str(struct tm *tm)
{
gchar *buf = g_malloc(20 * sizeof(gchar));
strftime(buf, 20, "%Ex", tm);
return buf;
}
int string_compare_tokens(gchar **lhs, gchar **rhs)
{
unsigned int i, cmp;
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 (lhs[i])
return 1;
if (rhs[i])
return -1;
return 0;
}
bool string_match_token(const gchar *prefix, gchar **tokens)
{
unsigned int i;
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;
}
}
g_strfreev(parent);
g_strfreev(child);
return subdir;
}

View File

@ -1,406 +0,0 @@
/*
* 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)
{
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;
}
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)
{
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;
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)
{
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;
}
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));
}
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);
}
static struct album *__album_parse_v0(gchar *line)
{
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)
name = g_strdup("");
album = __album_alloc(artist_get(artist_id),
genre_get(genre_id), name, year);
out:
g_free(line);
return &album->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);
}
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,
};
void album_db_init()
{
db_init(&album_db, "album.db", true, &album_ops, ALBUM_DB_MIN);
db_load(&album_db);
}
void album_db_deinit()
{
db_deinit(&album_db);
}
bool album_db_defrag()
{
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);
struct album *album = ALBUM(db_find(&album_db, key));
g_free(key);
return album;
}
struct album *album_get(const unsigned int index)
{
return ALBUM(db_at(&album_db, index));
}
int album_compare(struct album *lhs, struct album *rhs)
{
return string_compare_tokens(lhs->al_tokens, rhs->al_tokens);
}
int album_compare_year(struct album *lhs, struct album *rhs)
{
if (lhs->al_year - rhs->al_year == 0)
return album_compare(lhs, 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

@ -1,103 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/string.h>
#include <core/tags/artist.h>
static struct database artist_db;
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;
return artist;
}
static struct db_entry *artist_alloc(const gchar *name, unsigned int index)
{
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));
}
static gchar *artist_key(struct db_entry *dbe)
{
return ARTIST(dbe)->ar_name;
}
struct db_entry *artist_read(struct file *file, unsigned int index)
{
return &__artist_alloc(file_readl(file))->ar_dbe;
}
static void artist_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%s", ARTIST(dbe)->ar_name);
}
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,
};
void artist_db_init()
{
db_init(&artist_db, "artist.db", true, &artist_ops, 0);
db_load(&artist_db);
}
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));
}
int artist_compare(struct artist *lhs, struct artist *rhs)
{
return string_compare_tokens(lhs->ar_tokens, rhs->ar_tokens);
}
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

@ -1,92 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/string.h>
#include <core/tags/genre.h>
static struct database genre_db;
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);
return genre;
}
static struct db_entry *genre_alloc(const gchar *name, unsigned int index)
{
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));
}
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)
{
return &__genre_alloc(file_readl(file))->ge_dbe;
}
static void genre_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%s", GENRE(dbe)->ge_name);
}
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,
};
void genre_db_init()
{
db_init(&genre_db, "genre.db", true, &genre_ops, 0);
db_load(&genre_db);
}
void genre_db_deinit()
{
db_deinit(&genre_db);
}
struct genre *genre_find(const gchar *name)
{
return GENRE(db_find(&genre_db, name));
}
struct genre *genre_get(const unsigned int index)
{
return GENRE(db_at(&genre_db, 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);
}
#ifdef CONFIG_TESTING
const struct db_ops *test_genre_ops() { return &genre_ops; }
#endif /* CONFIG_TESTING */

View File

@ -1,122 +0,0 @@
/*
* 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)
{
struct library *library = g_malloc(sizeof(struct library));
dbe_init(&library->li_dbe, library);
library->li_path = path;
library->li_playlist = NULL;
return library;
}
static struct db_entry *library_alloc(const gchar *path, unsigned int index)
{
return &__library_alloc(g_strdup(path))->li_dbe;
}
static void library_free(struct db_entry *dbe)
{
g_free(LIBRARY(dbe));
}
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)
{
gchar *path;
/* Old "enabled" flag */
if (file_version(file) == 0)
file_readd(file);
path = file_readl(file);
return &__library_alloc(path)->li_dbe;
}
static void library_write(struct file *file, struct db_entry *dbe)
{
file_writef(file, "%s", 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,
};
void library_db_init()
{
db_init(&library_db, "library.db", true, &library_ops, LIBRARY_DB_MIN);
db_load(&library_db);
}
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;
}
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;
}
struct library *library_get(const unsigned int index)
{
return LIBRARY(db_at(&library_db, index));
}
void library_remove(struct library *library)
{
if (library)
db_remove(&library_db, &library->li_dbe);
}
gchar *library_file(struct library *library, const gchar *path)
{
return g_strdup_printf("%s/%s", library->li_path, path);
}
#ifdef CONFIG_TESTING
const struct db_ops *test_library_ops() { return &library_ops; }
#endif /* CONFIG_TESTING */

View File

@ -1,50 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/tags/album.h>
#include <core/tags/artist.h>
#include <core/tags/genre.h>
#include <core/tags/library.h>
#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()
{
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()
{
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;
}

View File

@ -1,399 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#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;
static gchar *__track_key(struct library *library, gchar *path)
{
gchar *res;
if (library)
res = g_strdup_printf("%u/%s", library_index(library), path);
else
res = g_strdup("");
g_free(path);
return res;
}
static gchar *__track_path(struct track *track)
{
gchar *path;
sscanf(track->tr_path, "%*u/%m[^\n]", &path);
return path;
}
static struct track *__track_alloc()
{
struct track *track = g_malloc(sizeof(struct track));
dbe_init(&track->tr_dbe, track);
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;
if (!file || !taglib_file_is_valid(file)) {
g_printerr("WARNING: Could not read tags for: %s\n", filepath);
return NULL;
}
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->tr_album = album_find(artist, genre, taglib_tag_album(tag),
taglib_tag_year(tag));
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);
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++;
}
g_free(path);
g_free(fullpath);
return track ? &track->tr_dbe : NULL;
}
static void track_free(struct db_entry *dbe)
{
struct track *track = TRACK(dbe);
play_count -= track->tr_count;
if (track->tr_count == 0)
unplayed_count--;
__track_free(track);
}
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)
{
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);
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_path = __track_key(track->tr_library, file_readl(file));
return &track->tr_dbe;
}
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,
track->tr_length,
track->tr_title,
path);
g_free(path);
}
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,
};
void track_db_init()
{
db_init(&track_db, "track.db", false, &track_ops, TRACK_DB_MIN);
db_load(&track_db);
}
void track_db_deinit()
{
db_deinit(&track_db);
}
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;
}
unsigned int track_db_count_unplayed()
{
return unplayed_count;
}
unsigned int track_db_count_plays()
{
return play_count;
}
unsigned int track_db_average_plays()
{
if (unplayed_count == track_db.db_size)
return 0;
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;
gchar *key = __track_key(library, g_strdup(filepath + offset));
struct track *track = NULL;
if (!db_get(&track_db, key))
track = TRACK(db_insert(&track_db, key));
g_free(key);
return track;
}
void track_remove(struct track *track)
{
db_remove(&track_db, &track->tr_dbe);
}
void track_remove_all(struct library *library)
{
struct db_entry *it, *next;
db_for_each(it, next, &track_db) {
if (TRACK(it)->tr_library == library)
db_remove(&track_db, it);
}
track_db_commit();
}
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);
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);
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);
case COMPARE_TRACK:
return lhs->tr_track - rhs->tr_track;
case COMPARE_YEAR:
return album_compare_year(lhs->tr_album, rhs->tr_album);
}
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;
if (track->tr_library) {
path = __track_path(track);
res = library_file(track->tr_library, path);
g_free(path);
return res;
}
return g_strdup(track->tr_path);
}
void track_played(struct track *track)
{
if (TRACK_IS_EXTERNAL(track))
return;
if (track->tr_count == 0)
unplayed_count--;
track->tr_count++;
play_count++;
date_today(&track->tr_date);
track_db_commit();
}
gchar *track_last_play(struct track *track)
{
if (track->tr_count > 0)
return date_string(&track->tr_date);
return g_strdup("Never");
}
#ifdef CONFIG_TESTING
const struct db_ops *test_track_ops() { return &track_ops; }
#endif /* CONFIG_TESTING */

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 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#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>
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)
{
const gchar *fmt = "<span size='%s'>%s</span>";
gchar *markup = g_markup_printf_escaped(fmt, size, text);
gtk_label_set_markup(label, markup);
g_free(markup);
}
static void __gui_audio_load(struct track *track)
{
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);
}
static void __gui_audio_set_pause_text(int n, GstState state)
{
bool sensitive = true;
gchar *text;
if (n == -1) {
sensitive = false;
if (state == GST_STATE_PLAYING)
text = g_strdup("Keep playing");
else
text = g_strdup("Paused");
} else if (n == 0)
text = g_strdup("Pause after this track");
else if (n == 1)
text = g_strdup("Pause after next track");
else
text = g_strdup_printf("Pause after %d tracks", n);
gtk_widget_set_sensitive(GTK_WIDGET(gui_pause_down()), sensitive);
gtk_entry_set_text(gui_pause_entry(), text);
g_free(text);
}
static void __gui_audio_change_state(GstState state)
{
bool playing = (state == GST_STATE_PLAYING);
gtk_widget_set_visible(GTK_WIDGET(gui_play_button()), !playing);
gtk_widget_set_visible(GTK_WIDGET(gui_pause_button()), playing);
__gui_audio_set_pause_text(audio_get_pause_count(), state);
}
static void __gui_audio_config_pause(int n)
{
__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);
}
}
void __gui_audio_pause_change_text(GtkEntry *entry, gpointer data)
{
const gchar *text = gtk_entry_get_text(entry);
int n = audio_get_pause_count();
unsigned int i;
if (g_str_match_string("Keep", text, true))
n = -1;
else if (g_str_match_string("This", text, true))
n = 0;
else if (g_str_match_string("Next", text, true))
n = 1;
else {
for (i = 0; text[i] != '\0'; i++) {
if (!g_ascii_isdigit(text[i]))
continue;
if (i > 0 && text[i-1] == '-')
i -= 1;
n = g_strtod(text + i, NULL);
break;
}
}
if (!audio_pause_after(n))
__gui_audio_set_pause_text(audio_get_pause_count(), audio_cur_state());
}
void __gui_audio_pause_inc(GtkButton *button, gpointer data)
{
audio_pause_after(audio_get_pause_count() + 1);
}
void __gui_audio_pause_dec(GtkButton *button, gpointer data)
{
audio_pause_after(audio_get_pause_count() - 1);
}
void __gui_audio_pause_popover_popdown(GtkButton *button, gpointer data)
{
gtk_popover_popdown(gui_pause_popover());
#ifdef CONFIG_TESTING
gtk_widget_hide(GTK_WIDGET(gui_pause_popover()));
#endif /* CONFIG_TESTING */
g_source_remove(popover_timeout);
popover_timeout = 0;
}
void __gui_audio_pause_popover_clear(GtkButton *button, gpointer data)
{
audio_pause_after(-1);
__gui_audio_pause_popover_popdown(button, data);
}
void __gui_audio_seek(GtkRange *range, GtkScrollType type,
double value, gpointer data)
{
audio_seek(value * GST_SECOND);
}
void __gui_audio_volume_changed(GtkScaleButton *button, gdouble value,
gpointer data)
{
audio_set_volume((unsigned int)value);
}
gboolean __gui_audio_can_accel(GtkWidget *widget, guint signal_id)
{
g_signal_stop_emission_by_name(widget, "can-activate-accel");
return !GTK_IS_ENTRY(gtk_window_get_focus(gui_window())) &&
gtk_widget_is_visible(widget) &&
gtk_widget_is_sensitive(widget);
}
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);
}
void gui_audio_deinit()
{
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;
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <gui/builder.h>
static GtkBuilder *gui_builder = NULL;
void gui_builder_init(const char *file)
{
gui_builder = gtk_builder_new_from_file(file);
gtk_builder_connect_signals(gui_builder, NULL);
}
void gui_builder_deinit()
{
g_object_unref(G_OBJECT(gui_builder));
gui_builder = NULL;
}
GObject *gui_builder_object(const char *name)
{
if (gui_builder)
return gtk_builder_get_object(gui_builder, name);
return NULL;
}
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()
{
return gui_builder;
}
#endif /* CONFIG_TESTING */

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());
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <gui/idle.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);
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;
}
void gui_idle_enable()
{
idle_id = g_idle_add(__on_idle, NULL);
}
void gui_idle_disable()
{
if (idle_id)
g_source_remove(idle_id);
}

View File

@ -1,383 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/audio.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 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,
};
const GtkTargetEntry gui_model_drag_targets[] = {
{ GUI_DRAG_DATA, GTK_TARGET_SAME_APP, 0 },
};
const unsigned int gui_model_n_targets = G_N_ELEMENTS(gui_model_drag_targets);
static GtkTreeModelFlags __gui_model_get_flags(GtkTreeModel *model)
{
return GTK_TREE_MODEL_LIST_ONLY;
}
static gint __gui_model_get_n_columns(GtkTreeModel *model)
{
return GUI_MODEL_N_COLUMNS;
}
static GType __gui_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];
}
static gboolean __gui_model_get_iter(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreePath *path)
{
gint *indices, depth;
g_assert(path != NULL);
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]);
}
static GtkTreePath *__gui_model_get_path(GtkTreeModel *model, GtkTreeIter *iter)
{
GtkTreePath *path;
unsigned int pos;
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
path = gtk_tree_path_new();
pos = playlist_iter_index(cur_playlist, iter->user_data);
gtk_tree_path_append_index(path, pos);
return path;
}
static void __gui_model_get_value(GtkTreeModel *model, GtkTreeIter *iter,
gint column, GValue *value)
{
struct track *track = playlist_iter_track(iter->user_data);
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_value_init(value, gui_model_columns[column]);
switch (column) {
case GUI_MODEL_TRACK_NR:
g_value_set_uint(value, track->tr_track);
break;
case GUI_MODEL_TITLE:
g_value_set_static_string(value, track->tr_title);
break;
case GUI_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);
break;
case GUI_MODEL_ALBUM:
g_value_set_static_string(value, track->tr_album->al_name);
break;
case GUI_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);
break;
case GUI_MODEL_COUNT:
g_value_set_uint(value, track->tr_count);
break;
case GUI_MODEL_LAST_PLAY:
g_value_take_string(value, track_last_play(track));
break;
case GUI_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));
break;
}
}
static gboolean __gui_model_iter_next(GtkTreeModel *model, GtkTreeIter *iter)
{
g_return_val_if_fail(iter != NULL, FALSE);
g_return_val_if_fail(iter->user_data, FALSE);
iter->user_data = playlist_iter_next(iter->user_data);
return iter->user_data != NULL;
}
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))
return FALSE;
iter->stamp = gui_model->gm_stamp;
iter->user_data = playlist_iter_get(cur_playlist, n);
return iter->user_data != NULL;
}
static gboolean __gui_model_iter_parent(GtkTreeModel *model, GtkTreeIter *iter,
GtkTreeIter *child)
{
return FALSE;
}
static gboolean __gui_model_drag_data_get(GtkTreeDragSource *drag_source,
GtkTreePath *path,
GtkSelectionData *selection_data)
{
struct gui_model_drag_data *data = g_malloc(sizeof(*data));
data->drag_row = gtk_tree_path_get_indices(path)[0];
data->drag_track = gui_model_path_get_track(path);
gtk_selection_data_set(selection_data, gdk_atom_intern(GUI_DRAG_DATA, false),
8 /* bytes */, (void *)data, sizeof(*data));
g_free(data);
return true;
}
static gboolean __gui_model_drag_data_delete(GtkTreeDragSource *drag_source,
GtkTreePath *path)
{
return true;
}
static void __gui_model_init(GuiModel *model)
{
model->gm_stamp = g_random_int();
}
static void __gui_model_finalize(GObject *object)
{
parent_class->finalize(object);
}
static void __gui_model_class_init(GuiModelClass *class)
{
GObjectClass *object_class = (GObjectClass *)class;
object_class->finalize = __gui_model_finalize;
parent_class = g_type_class_peek_parent(class);
}
static void __gui_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;
}
static void __gui_drag_source_init(GtkTreeDragSourceIface *iface)
{
iface->drag_data_get = __gui_model_drag_data_get;
iface->drag_data_delete = __gui_model_drag_data_delete;
}
static const GTypeInfo gui_model_type_info = {
.class_size = sizeof(GuiModelClass),
.base_init = NULL,
.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)
{
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);
gui_model = g_object_new(gui_model_type, NULL);
g_assert(gui_model != NULL);
}
void gui_model_deinit(void)
{
g_object_unref(gui_model);
gui_model_type = 0;
gui_model = NULL;
cur_playlist = NULL;
}
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);
gtk_tree_path_free(path);
__gui_model_set_runtime();
}
void gui_model_remove(struct playlist *playlist, struct track *track,
unsigned int n)
{
GtkTreePath *path;
unsigned int i;
if (cur_playlist != playlist)
return;
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);
}
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)
{
GtkTreeIter iter;
__gui_model_get_iter(GTK_TREE_MODEL(gui_model), &iter, path);
return gui_model_iter_get_track(&iter);
}

View File

@ -1,151 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/core.h>
#include <gui/audio.h>
#include <gui/builder.h>
#include <gui/filter.h>
#include <gui/idle.h>
#include <gui/model.h>
#include <gui/playlist.h>
#include <gui/sidebar.h>
#include <gui/treeview.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 },
};
#ifndef CONFIG_DEBUG
const static gchar *OCARINA_APP = "org.gtk.ocarina";
#else
const static gchar *OCARINA_APP = "org.gtk.ocarina-debug";
#endif
static enum idle_sync_t idle_sync = IDLE_ASYNC;
static int startup_argc;
static char **startup_argv;
static gchar *find_file_path(const gchar *file)
{
gchar *path = g_strjoin("/", "share", "ocarina", file, NULL);
if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
return path;
g_free(path);
return g_strjoin("/", "/usr", "share", "ocarina", file, NULL);
}
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;
}
static void __ocarina_startup(GApplication *application, gpointer data)
{
gchar *ui = find_file_path("ocarina.ui");
gchar *icon = find_file_path("ocarina.png");
gui_builder_init(ui);
core_init(&startup_argc, &startup_argv, &playlist_cb, &audio_cb, idle_sync);
gui_window_init(icon);
gui_model_init();
gui_filter_init();
gui_treeview_init();
gui_sidebar_init();
gui_playlist_init();
gui_audio_init();
gui_idle_enable();
g_free(ui);
g_free(icon);
}
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_builder_deinit();
}
int main(int argc, char **argv)
{
GtkApplication *ocarina = gtk_application_new(OCARINA_APP, 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);
return g_application_run(G_APPLICATION(ocarina), argc, argv);
}

View File

@ -1,259 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#include <core/idle.h>
#include <core/string.h>
#include <gui/filter.h>
#include <gui/model.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,
};
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 inline void __gui_playlist_update_size(struct playlist *playlist)
{
update_size[playlist->pl_type](playlist);
}
static void __gui_playlist_alloc(struct playlist *playlist)
{
if (playlist->pl_type == PL_ARTIST)
gui_pl_artist_add(playlist);
}
static void __gui_playlist_added(struct playlist *playlist, struct track *track)
{
gui_model_add(playlist, track);
gui_filter_refilter(playlist);
__gui_playlist_update_size(playlist);
}
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;
GtkTreeIter iter;
if (gui_sidebar_iter_from_xy(x, y, &iter))
playlist = gui_sidebar_iter_playlist(&iter);
if (!playlist)
playlist = gui_pl_user_add_dialog();
if (!playlist)
goto out;
if (playlist == playlist_lookup(PL_SYSTEM, "Collection") &&
gui_model_get_playlist() == playlist_lookup(PL_SYSTEM, "Hidden"))
__gui_playlist_delete(NULL, NULL);
else if (playlist != playlist_lookup(PL_SYSTEM, "History"))
__gui_playlist_add_selected_to(playlist);
out:
g_signal_stop_emission_by_name(treeview, "drag_data_received");
gtk_drag_finish(context, true, true, time);
}
bool __gui_playlist_init_idle()
{
struct playlist *playlist = playlist_current();
GtkTreeModel *filter = GTK_TREE_MODEL(gui_sidebar_filter());
GtkTreeIter iter;
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;
}
void gui_playlist_init()
{
gui_pl_system_init();
gui_pl_artist_init();
gui_pl_user_init();
gui_pl_library_init();
idle_schedule(IDLE_SYNC, __gui_playlist_init_idle, NULL);
}

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;
}

View File

@ -1,508 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#include <core/settings.h>
#include <core/string.h>
#include <gui/model.h>
#include <gui/sidebar.h>
#include <gui/treeview.h>
enum sidebar_columns {
SB_IMAGE,
SB_NAME,
SB_TYPE,
SB_EDITABLE,
};
const gchar *SIDEBAR_SETTING = "gui.sidebar.pos";
static gchar *__gui_sidebar_size_str(struct playlist *playlist)
{
const gchar *fmt = "%s\n%d track%s";
unsigned int size;
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);
g_free(text);
}
static void __gui_sidebar_add_header(GtkTreeIter *iter, const gchar *name,
const gchar *image)
{
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);
}
static int __gui_sidebar_compare(GtkTreeIter *iter, const gchar *name,
enum playlist_type_t type)
{
gchar *cur;
int ret;
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;
}
static inline void __gui_sidebar_filter_iter_convert(GtkTreeIter *iter,
GtkTreeIter *child)
{
gtk_tree_model_filter_convert_iter_to_child_iter(gui_sidebar_filter(),
child, iter);
}
static gchar *__gui_sidebar_filter_iter_name(GtkTreeIter *iter)
{
GtkTreeIter child;
__gui_sidebar_filter_iter_convert(iter, &child);
return gui_sidebar_iter_name(&child);
}
static gboolean __gui_sidebar_visible_func(GtkTreeModel *model,
GtkTreeIter *iter,
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)
{
GtkTreeIter iter;
if (!gui_sidebar_iter_current(&iter))
return false;
if (playlist_delete(gui_model_get_playlist()))
gtk_tree_store_remove(gui_sidebar_store(), &iter);
return true;
}
bool __gui_sidebar_keypress(GtkTreeView *treeview, GdkEventKey *event,
gpointer data)
{
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;
}
}
bool __gui_sidebar_button_press(GtkTreeView *treeview, GdkEventButton *event,
gpointer data)
{
enum playlist_type_t type = PL_MAX_TYPE;
GtkTreePath *path;
GtkTreeIter iter;
bool ret = true;
if (!gtk_tree_view_get_path_at_pos(treeview, event->x, event->y,
&path, NULL, NULL, NULL))
return false;
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);
}
void gui_sidebar_init()
{
int pos = settings_get(SIDEBAR_SETTING);
GtkTreeSelection *selection;
GtkTreeIter iter;
gtk_tree_view_enable_model_drag_dest(gui_sidebar_treeview(),
gui_model_drag_targets, gui_model_n_targets,
GDK_ACTION_MOVE);
if (!gui_sidebar_iter_first(&iter)) {
__gui_sidebar_add_header(&iter, "Playlists", "emblem-documents");
__gui_sidebar_add_header(&iter, "Dynamic", "emblem-generic");
__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;
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));
out_append:
gui_sidebar_iter_append_child(iter, playlist, image);
}
void gui_sidebar_iter_append_child(GtkTreeIter *iter, struct playlist *playlist,
const gchar *image)
{
GtkTreeIter new;
gtk_tree_store_insert_before(gui_sidebar_store(), &new, iter, NULL);
__gui_sidebar_set_playlist(&new, playlist, image);
}
void gui_sidebar_iter_update_playlist(GtkTreeIter *iter,
struct playlist *playlist)
{
gchar *text;
if (!playlist)
return;
text = __gui_sidebar_size_str(playlist);
gtk_tree_store_set(gui_sidebar_store(), iter, SB_NAME, text, -1);
g_free(text);
}
void gui_sidebar_iter_update(GtkTreeIter *iter)
{
gui_sidebar_iter_update_playlist(iter, gui_sidebar_iter_playlist(iter));
}
void gui_sidebar_iter_select(GtkTreeIter *iter)
{
GtkTreeSelection *selection;
GtkTreeIter filter;
gtk_tree_model_filter_convert_child_iter_to_iter(gui_sidebar_filter(),
&filter, iter);
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;
}

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;
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#include <core/settings.h>
#include <core/version.h>
#include <gui/window.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);
if (event->width != width) {
saved_width = width;
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);
}
return FALSE;
}
gboolean __window_state(GtkWindow *window, GdkEventWindowState *event,
gpointer data)
{
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);
}
return FALSE;
}
void gui_window_init(const gchar *icon)
{
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);
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);
g_free(title);
}
void gui_window_deinit()
{
gtk_widget_destroy(GTK_WIDGET(gui_window()));
}

BIN
images/ocarina.xcf Normal file

Binary file not shown.

BIN
images/ocarina256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
images/ocarina64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,88 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The gst_init() function parses command line options passed to Ocarina
* through argv. Use the command `gst-inspect-1.0 --help-gst` to find
* what options are supported.
*/
#ifndef OCARINA_CORE_AUDIO_H
#define OCARINA_CORE_AUDIO_H
#include <core/tags/track.h>
#include <gst/gst.h>
struct audio_callbacks {
/* Called when a track is loaded. */
void (*audio_cb_load)(struct track *);
/* Called when playback state changes. */
void (*audio_cb_state_change)(GstState);
/* Called when the automatic pause state changes. */
void (*audio_cb_config_pause)(int);
};
/* Called to initialize the audio manager. */
void audio_init(int *, char ***, struct audio_callbacks *);
/* 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. */
bool audio_load(struct track *);
bool audio_load_filepath(const gchar *);
/* Called to get the current track. */
struct track *audio_cur_track();
/* Called to get the current playback state. */
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();
/* Called to pause playback. */
bool audio_pause();
/* Called to seek playback to a specific offset, in nanoseconds. */
bool audio_seek(gint64);
/* Called to find the current playback position, in nanoseconds. */
gint64 audio_position();
/* Called to find the duration of the current track. */
gint64 audio_duration();
/* Called to load the next track. */
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);
#ifdef CONFIG_TESTING
void test_audio_eos();
void test_audio_error(GError *, gchar *);
GstElement *test_audio_pipeline();
#endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_AUDIO_H */

View File

@ -1,20 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#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>
/* Called to initialize all core Ocarina components. */
void core_init(int *, char ***, struct playlist_callbacks *,
struct audio_callbacks *, enum idle_sync_t);
/* Called to deinitialize all core Ocarina componentns. */
void core_deinit();
#endif /* OCARINA_CORE_CORE_H */

View File

@ -1,154 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* Databases are a generic store for information used by Ocarina. Users must
* provide a struct db_ops when initializing a database.
*
* When writing a database to disk, databases will store their size in additon
* to valid bits and values at each index. For example, let's say we have a
* database with the following values: { 1, 2, NULL, 4 }. The resulting file
* would look like:
*
* 4
* 1 1
* 1 2
* 0
* 1 4
* <valid> <data>
*
* The database class will add a newline after each struct db_entry.
*/
#ifndef OCARINA_CORE_CONTAINERS_DATABASE_H
#define OCARINA_CORE_CONTAINERS_DATABASE_H
#include <core/file.h>
struct db_entry {
unsigned int dbe_index; /* The db_entry's position in the database. */
gchar *dbe_key; /* The db_entry's hash key. */
void *dbe_data; /* The db_entry's private data. */
};
static inline void dbe_init(struct db_entry *dbe, void *data)
{
dbe->dbe_index = 0;
dbe->dbe_key = NULL;
dbe->dbe_data = data;
}
static inline void *DBE_DATA(struct db_entry *dbe)
{
if (dbe)
return dbe->dbe_data;
return NULL;
}
struct db_ops {
/* Allocate a new struct db_entry from a given key. */
struct db_entry *(*dbe_alloc)(const gchar *, unsigned int);
/* Free a struct db_entry. */
void (*dbe_free)(struct db_entry *);
/* Return a unique string representing a single struct db_entry. */
gchar *(*dbe_key)(struct db_entry *);
/* Read a single struct db_entry from disk. */
struct db_entry *(*dbe_read)(struct file *, unsigned int);
/* Write a single struct db_entry to disk. */
void (*dbe_write)(struct file *, struct db_entry *);
};
struct database {
unsigned int db_size; /* The database's count of valid entries. */
bool db_autosave; /* The database's automatic save setting. */
struct file db_file; /* The database's associated file object. */
GPtrArray *db_entries; /* The database's backing array. */
GHashTable *db_keys; /* The database's mapping of key -> value. */
const struct db_ops *db_ops; /* The database's operations vector. */
};
#define DB_INIT(fname, autosave, ops, fmin) \
{ \
.db_size = 0, \
.db_autosave = autosave, \
.db_file = FILE_INIT_DATA("", fname, fmin), \
.db_entries = g_ptr_array_new(), \
.db_keys = g_hash_table_new(g_str_hash, g_str_equal), \
.db_ops = ops, \
}
/*
* 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);
/* Called to prevent memory leaks by freeing all remaining database entries. */
void db_deinit(struct database *);
/* Called to write the database to disk. */
void db_save(struct database *);
/* Save the database to disk iff database->db_autosave is set to True */
void db_autosave(struct database *);
/* Called to read the database from disk. */
void db_load(struct database *);
/* Returns the size of the backing std::vector. */
unsigned int db_actual_size(const struct database *);
/*
* Called to add a new item to the database. The caller MUST check if the
* database already contains a similar item before calling this function.
*/
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);
/* Returns the database item with the specified key. */
struct db_entry *db_get(struct database *, const gchar *);
/*
* Similar to db_get(), but allocate and return a new item if the
* database doesn't contain an item with the specified key.
*/
struct db_entry *db_find(struct database *, const gchar *);
/* Returns the first valid database item. */
struct db_entry *db_first(const struct database *);
/* Returns the next valid database item. */
struct db_entry *db_next(const struct database *, struct db_entry *);
#define db_for_each(ent, next, db) \
for (ent = db_first(db), next = db_next(db, ent); \
ent != NULL; \
ent = next, next = db_next(db, ent))
#endif /* OCARINA_CORE_CONTAINERS_DATABASE_H */

View File

@ -1,52 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_DATE_H
#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;
};
};
/* Set the date structure. */
void date_set(struct date *, unsigned int, unsigned int, unsigned int);
/* Set the provided date structure to today's date. */
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.
* This function allocates a new string that MUST be freed with g_free().
*/
gchar *date_string(const struct date *);
/*
* Compare two dates.
*
* if ret < 0: lhs < rhs.
* if ret = 0: lhs == rhs.
* if ret > 0: lhs > rhs.
*/
int date_compare(const struct date *, const struct date *);
#endif /* OCARINA_CORE_DATE_H */

View File

@ -1,152 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* File data is store in the user's home directory according to the
* XDG / freedesktop.org specification, meaning all of our data will
* be stored in a subdirectory of $XDG_DATA_HOME. The actual subdirectory
* changes based on what configuration values the user has set when Ocarina
* is compiled.
*
* Config Value | Ocarina Directory
* ---------------|--------------------
* default | $XDG_DATA_HOME/ocarina/
* CONFIG_DEBUG | $XDG_DATA_HOME/ocarina-debug/
* CONFIG_TESTING | $XDG_DATA_HOME/ocarina-test/
*
* The beginning of every file is the current file version followed by a
* newline. For example:
*
* 42
* <data> <more data>
* <even more data>
*/
#ifndef OCARINA_CORE_FILE_H
#define OCARINA_CORE_FILE_H
#include <core/version.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <stdbool.h>
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. */
};
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. */
};
#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, \
}
/* Initialize a file object. */
void file_init_data(struct file *, const gchar *, const gchar *, unsigned int);
void file_init_cache(struct file *, const gchar *, const gchar *);
/*
* Returns the full path of the file or an empty string if filename is not set.
* NOTE: 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 *);
/* Returns true if the file exists on disk and false otherwise. */
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 writing (OPEN_WRITE / OPEN_WRITE_BINARY):
* - 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).
*
* 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().
*/
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()
*/
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); }
/*
* Write to a file with an fprintf(3) style format string.
* Returns the number of bytes successfully written.
*/
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 */

View File

@ -1,44 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The idle queue is used to schedule function calls to run at a
* later time. It is expected that a higher layer can determine
* when the application is idle and call idle_run_task() accordingly.
*
* The idle layer keeps a count of the number of tasks added to the queue
* since the last time the queue was empty. Whenever a task is scheduled,
* the "queued" count is incremented by 1. When tasks are run, the "serviced"
* count is incremented by 1. These counters are reset to 0 once the queue
* is empty.
*/
#ifndef OCARINA_CORE_IDLE_H
#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 *);
/*
* Called to run the next task on the idle queue.
* Returns true if there are more tasks to run.
*/
bool idle_run_task();
/* Called to find the percentage of idle tasks that have been run. */
float idle_progress();
#endif /* OCARINA_CORE_IDLE_H */

View File

@ -1,81 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*
* The playlist manager is in charge of the various playlists Ocarina
* knows about. This code also manages a special queue used by the GUI
* to display the tracks in each playlist.
*/
#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>
/* Called to initialize the playlist manager. */
void playlist_init(struct playlist_callbacks *);
/* 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 *);
/* Called to remove a track from a playlist. */
bool playlist_remove(struct playlist *, struct track *);
/* Called to check if a specific track is in the playlist. */
bool playlist_has(struct playlist *, struct track *);
/* Called to set the playlist's random flag. */
void playlist_set_random(struct playlist *, bool);
/* Called to change the sort order of the playlist. */
bool playlist_sort(struct playlist *, enum compare_t);
/* Called to manually rearrange the order of the playlist. */
bool playlist_rearrange(struct playlist *, unsigned int, unsigned int);
/* Called to set the playlist's search text */
void playlist_set_search(struct playlist *, const gchar *);
#endif /* OCARINA_CORE_PLAYLIST_H */

View File

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

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2013 Anna Schumaker.
*/
#ifndef OCARINA_CORE_PRINT_H
#define OCARINA_CORE_PRINT_H
#include <cstdio>
#include <cstdarg>
/**
* Print a message to the console
*
* @param fmt Printf-style text format.
* @param ... Var-args matching the specified format.
*/
inline void print(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
#endif /* OCARINA_CORE_PRINT_H */

View File

@ -1,31 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*
* The settings layer is used to store values configured by the user.
*/
#ifndef OCARINA_SETTINGS_H
#define OCARINA_SETTINGS_H
#include <glib.h>
#include <stdbool.h>
/* Called to initialize GUI settings. */
void settings_init();
/* Called to deinitialize GUI settings. */
void settings_deinit();
/* Called to configure a specific setting by key. */
void settings_set(const gchar *, unsigned int);
/* Called to access a specific setting by key. */
unsigned int settings_get(const gchar *);
/* Called to check if a specific settings exists. */
bool settings_has(const gchar *);
#ifdef CONFIG_TESTING
GHashTable *test_get_settings();
#endif /* CONFIG_TESTING */
#endif /* OCARINA_SETTINGS_H */

View File

@ -1,51 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*
* NOTE: All of these functions allocate and return a new string. It is the
* caller's responsibility to free these strings by calling g_free().
*/
#ifndef OCARINA_CORE_STRING_H
#define OCARINA_CORE_STRING_H
#include <glib.h>
#include <string.h>
#include <stdbool.h>
/* Convert number of seconds into a string with format mm:ss. */
gchar *string_sec2str(unsigned int);
/* Convert number of seconds into a long-form time string. */
gchar *string_sec2str_long(unsigned int);
/* Convert a struct tm to a locale-dependant date string. */
gchar *string_tm2str(struct tm *);
/*
* Compare two strings.
*
* if ret < 0: lhs < rhs, or rhs is empty.
* 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;
}
#endif /* OCARINA_CORE_STRING_H */

View File

@ -1,82 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* 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:
*
* ...0 0 1998 Hyrule Symphony
* ...0 0 2006 Twilight Princess
* ...0 0 2011 Skyward Sword
*/
#ifndef OCARINA_CORE_TAGS_ALBUM_H
#define OCARINA_CORE_TAGS_ALBUM_H
#include <core/database.h>
#include <core/tags/artist.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;
struct db_entry al_dbe;
};
#define ALBUM(dbe) ((struct album *)DBE_DATA(dbe))
/* Called to initialize the album database. */
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 get an album tag with a specific index. */
struct album *album_get(const unsigned int);
/* Called to compare two album tags by name. */
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 */
#endif /* OCARINA_CORE_TAGS_ALBUM_H */

View File

@ -1,64 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* The Artist tag is used to store the name of artists
* added to the tag database.
*
* When writing an Artist tag to disk, only write out the
* artist_name field:
*
* ... Koji Kondo
* ... Hajime Wakai
*/
#ifndef OCARINA_CORE_TAGS_ARTIST_H
#define OCARINA_CORE_TAGS_ARTIST_H
#include <core/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. */
struct db_entry ar_dbe;
};
#define ARTIST(dbe) ((struct artist *)DBE_DATA(dbe))
/* Called to initialize the artist database. */
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.
*/
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);
/* 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 */
#endif /* OCARINA_CORE_TAGS_ARTIST_H */

View File

@ -1,55 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* The Genre tag is used to store the name of genres
* added to the tag database.
*
* When writing a Genre tag to disk, nol ywrite out the
* genre_name field:
*
* ... Video Game Music
* ... Game Music
*/
#ifndef OCARINA_CORE_TAGS_GENRE_H
#define OCARINA_CORE_TAGS_GENRE_H
#include <core/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. */
struct db_entry ge_dbe;
};
#define GENRE(dbe) ((struct genre *)DBE_DATA(dbe))
/* Called to initialize the genre database. */
void genre_db_init();
/* Called to clean up the genre database. */
void genre_db_deinit();
/* Called to find a genre tag by name. */
struct genre *genre_find(const gchar *);
/* Called to get a genre tag with a specific index. */
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 */
#endif /* OCARINA_CORE_TAGS_GENRE_H */

View File

@ -1,70 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* The Library tag is used to store a single directory added
* to Ocarina by the user.
*
* When writing a Library tag to disk, write out the library_enabled
* and library_path fields for each tag.
*
* ... true /home/Zelda/Music
* ... false /home/Link/Music
*/
#ifndef OCARINA_CORE_TAGS_LIBRARY_H
#define OCARINA_CORE_TAGS_LIBRARY_H
#include <core/database.h>
struct library {
gchar *li_path; /* This library's root path. */
void *li_playlist; /* This library's associated playlist. */
struct db_entry li_dbe;
};
#define LIBRARY(dbe) ((struct library *)DBE_DATA(dbe))
/* Called to initialize the library database. */
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.
*/
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);
/* 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 find the full path of files under the library directory.
* This function allocates a new string that MUST be freed with g_free().
*/
gchar *library_file(struct library *, const gchar *);
#ifdef CONFIG_TESTING
const struct db_ops *test_library_ops();
#endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_TAGS_LIBRARY_H */

View File

@ -1,17 +0,0 @@
/**
* Copyright 2014 (c) Anna Schumaker.
*/
#ifndef OCARINA_CORE_TAGS_TAGS_H
#define OCARINA_CORE_TAGS_TAGS_H
/* Called to initialize and read all databases from disk. */
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

@ -1,142 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*
* The Track tag is used to store information about tracks
* that have been added to the tag database.
*
* 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
* 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/date.h>
#include <core/tags/album.h>
#include <core/tags/artist.h>
#include <core/tags/genre.h>
#include <core/tags/library.h>
enum compare_t {
COMPARE_ARTIST = 1, /* Compare tracks by artist name. */
COMPARE_ALBUM = 2, /* Compare tracks by album name. */
COMPARE_COUNT = 3, /* Compare tracks by play count. */
COMPARE_GENRE = 4, /* Compare tracks by genre. */
COMPARE_LENGTH = 5, /* Compare tracks by length. */
COMPARE_PLAYED = 6, /* Compare tracks by last played date. */
COMPARE_TITLE = 7, /* Compare tracks by title. */
COMPARE_TRACK = 8, /* Compare tracks by track number. */
COMPARE_YEAR = 9, /* Compare tracks by year. */
};
struct track {
struct album *tr_album; /* This track's associated album. */
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. */
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 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. */
void track_db_init();
/* Called to clean up the track database. */
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();
/* Called to find the number of unplayed tracks in the database. */
unsigned int track_db_count_unplayed();
/* Called to find the total play count of all tracks in the database. */
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 *);
/* Called to remove a specific track tag. */
void track_remove(struct track *);
/* Called to remove all tracks associated with a specific library. */
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().
*/
gchar *track_path(struct track *);
/* Called to mark a track tag as played. */
void track_played(struct track *);
/*
* Called to find the date that this track was last played.
* This function returns a new string that MUST be freed with g_free().
*/
gchar *track_last_play(struct track *);
#ifdef CONFIG_TESTING
const struct db_ops *test_track_ops();
#endif /* CONFIG_TESTING */
#endif /* OCARINA_CORE_TAGS_TRACK_H */

View File

@ -1,27 +0,0 @@
/*
* Copyright 2013 (c) Anna Schumaker.
*/
#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.
*/
static inline const char *get_version()
{
return CONFIG_VERSION;
}
#endif /* OCARINA_CORE_VERSION_H */

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

@ -1,109 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#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;
/* 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"));
}
#endif /* OCARINA_GUI_AUDIO_H */

View File

@ -1,26 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_BUILDER_H
#define OCARINA_GUI_BUILDER_H
#include <gtk/gtk.h>
/* Called to initialize the GTK builder. */
void gui_builder_init(const char *);
/* Called to deinitialize the GTK builder. */
void gui_builder_deinit();
/* Called to get an object from the GTK builder. */
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 */
#endif /* OCARINA_GUI_BUILDER_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 */

View File

@ -1,21 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#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();
/* 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 */

View File

@ -1,99 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_MODEL_H
#define OCARINA_GUI_MODEL_H
#include <core/playlist.h>
#include <gui/builder.h>
#include <gtk/gtk.h>
#define GUI_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \
gui_model_get_type(), GuiModel))
enum gui_model_columns {
GUI_MODEL_TRACK_NR,
GUI_MODEL_TITLE,
GUI_MODEL_LENGTH,
GUI_MODEL_ARTIST,
GUI_MODEL_ALBUM,
GUI_MODEL_YEAR,
GUI_MODEL_GENRE,
GUI_MODEL_COUNT,
GUI_MODEL_LAST_PLAY,
GUI_MODEL_FILE_PATH,
GUI_MODEL_FONT,
GUI_MODEL_N_COLUMNS,
};
struct gui_model {
GObject gm_parent; /* This MUST be the first member. */
gint gm_stamp; /* This is used to check iter validity. */
};
typedef struct gui_model GuiModel;
struct gui_model_class {
GObjectClass parent_class;
};
typedef struct gui_model_class GuiModelClass;
struct gui_model_drag_data {
unsigned int drag_row;
struct track *drag_track;
};
#define GUI_DRAG_DATA "GUI_DRAG_DATA"
extern const GtkTargetEntry gui_model_drag_targets[];
extern const unsigned int gui_model_n_targets;
/* Called to initialize the GuiModel */
void gui_model_init(void);
/* Called to deinitialize the GuiModel */
void gui_model_deinit(void);
/* Called to get the GuiModel */
GuiModel *gui_model_get(void);
/* Called to find the GType of the GuiModel */
GType gui_model_get_type();
/* Called to add a row to the model */
void gui_model_add(struct playlist *, struct track *);
/* Called to remove a row from the model */
void gui_model_remove(struct playlist *, struct track *, unsigned int);
/*
* Called to update a row in the model
* If track is NULL, then all rows will be updated
*/
void gui_model_update(struct playlist *, struct track *);
/* Called to change the queue represented by the model. */
void gui_model_set_playlist(struct playlist *);
/* Called to get the queue currently attached to the model. */
struct playlist *gui_model_get_playlist(void);
/* Called to convert a GtkTreeIter into a struct track */
static inline struct track *gui_model_iter_get_track(GtkTreeIter *iter)
{
g_return_val_if_fail(iter != NULL, NULL);
g_return_val_if_fail(iter->user_data != NULL, NULL);
return playlist_iter_track(iter->user_data);
}
/* Called to convert a GtkTreePath into a struct track */
struct track *gui_model_path_get_track(GtkTreePath *);
/* Called to get the runtime label. */
static inline GtkLabel *gui_model_runtime()
{
return GTK_LABEL(gui_builder_widget("runtime"));
}
#endif /* OCARINA_GUI_MODEL_H */

View File

@ -1,31 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_PLAYLIST_H
#define OCARINA_GUI_PLAYLIST_H
#include <core/playlist.h>
#include <gui/builder.h>
#include <gui/playlists/artist.h>
#include <gui/playlists/library.h>
#include <gui/playlists/system.h>
#include <gui/playlists/user.h>
/* Called to initialize the GUI playlist code. */
void gui_playlist_init();
/* Called to access the right-click menu. */
static inline GtkMenu *gui_rc_menu()
{
return GTK_MENU(gui_builder_widget("rc_menu"));
}
/* Called to acces the "Add to Other Playlist" menu item. */
static inline GtkMenuItem *gui_rc_add_to_other()
{
return GTK_MENU_ITEM(gui_builder_widget("rc_add_to_other"));
}
/* Playlist callbacks passed to core_init() */
extern struct playlist_callbacks playlist_cb;
#endif /* OCARINA_GUI_PLAYLIST_H */

View File

@ -1,19 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_PLAYLISTS_ARTIST_H
#define OCARINA_GUI_PLAYLISTS_ARTIST_H
/* Called to initialize GUI artist playlists. */
void gui_pl_artist_init();
/* Called to add an artist playlist. */
bool gui_pl_artist_add(struct playlist *);
/* Called to update an artist playlist. */
void gui_pl_artist_update(struct playlist *);
/* Called to select an artist playlist. */
void gui_pl_artist_select(struct playlist *);
#endif /* OCARINA_GUI_PLAYLISTS_ARTIST_H */

View File

@ -1,19 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_PLAYLISTS_LIBRARY_H
#define OCARINA_GUI_PLAYLISTS_LIBRARY_H
/* Called to initialize GUI library playlists. */
void gui_pl_library_init();
/* Called to add a library path. */
struct playlist *gui_pl_library_add(const gchar *);
/* Called to update a library path. */
void gui_pl_library_update(struct playlist *);
/* Called to select a library playlist. */
void gui_pl_library_select(struct playlist *);
#endif /* OCARINA_GUI_PLAYLISTS_LIBRARY_H */

View File

@ -1,32 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_PLAYLISTS_SYSTEM_H
#define OCARINA_GUI_PLAYLISTS_SYSTEM_H
#include <gui/builder.h>
/* Called to initialize GUI system playlists. */
void gui_pl_system_init();
/* Called to update a system playlist. */
void gui_pl_system_update(struct playlist *);
/* Called to select a system playlist. */
void gui_pl_system_select(struct playlist *);
/* Called to set favorites and hidden button states. */
void gui_pl_system_track_loaded(struct track *);
/* Called to get the favorite toggle button. */
static inline GtkToggleButton *gui_favorite_button()
{
return GTK_TOGGLE_BUTTON(gui_builder_widget("favorite_button"));
}
/* Called to get the hidden toggle button. */
static inline GtkToggleButton *gui_hide_button()
{
return GTK_TOGGLE_BUTTON(gui_builder_widget("hide_button"));
}
#endif /* OCARINA_GUI_PLAYLISTS_SYSTEM_H */

View File

@ -1,28 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_PLAYLISTS_USER_H
#define OCARINA_GUI_PLAYLISTS_USER_H
/* Called to initialize GUI user playlists. */
void gui_pl_user_init();
/* Called to add a user playlist. */
struct playlist *gui_pl_user_add(const gchar *);
/* Called to add a user playlist (with dialog prompt). */
struct playlist *gui_pl_user_add_dialog(void);
/* Called to update a user playlist. */
void gui_pl_user_update(struct playlist *);
/* Called to select a user playlist. */
void gui_pl_user_select(struct playlist *);
/*
* Called to get a (sorted) list of user playlists.
* Note: The caller is responsible for freeing the list with g_list_free().
*/
GList *gui_pl_user_list(void);
#endif /* OCARINA_GUI_PLAYLISTS_USER_H */

View File

@ -1,124 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_SIDEBAR_H
#define OCARINA_GUI_SIDEBAR_H
#include <core/playlist.h>
#include <gui/builder.h>
/* Called to initialize the sidebar. */
void gui_sidebar_init();
/* Called to set an iterator to the currently displayed playlist. */
gboolean gui_sidebar_iter_current(GtkTreeIter *);
/* Called to set an iterator to the first playlist. */
gboolean gui_sidebar_iter_first(GtkTreeIter *);
/* Called to advance the iterator to the next playlist. */
gboolean gui_sidebar_iter_next(GtkTreeIter *);
/* Called to set an iterator to the first child of the parent iterator. */
gboolean gui_sidebar_iter_down(GtkTreeIter *, GtkTreeIter *);
/*
* Called to find the name of the playlist at the given iterator.
* NOTE: This function returns a new string that must be freed with g_free().
*/
gchar *gui_sidebar_iter_name(GtkTreeIter *);
/* Called to find the type of the playlist at the given iterator. */
enum playlist_type_t gui_sidebar_iter_type(GtkTreeIter *);
/* Called to find the editable state of the playlist at the given iterator. */
bool gui_sidebar_iter_editable(GtkTreeIter *);
/* Called to find the playlist at the given iterator. */
struct playlist *gui_sidebar_iter_playlist(GtkTreeIter *);
/* Called to add a playlist at the current iterator. */
void gui_sidebar_iter_add(GtkTreeIter *, struct playlist *, const gchar *);
/* Called to add a child playlist to the current iterator in sorted order. */
void gui_sidebar_iter_sort_child(GtkTreeIter *, struct playlist *,
const gchar *);
/* Called to append a child playlist to the current iterator. */
void gui_sidebar_iter_append_child(GtkTreeIter *, struct playlist *,
const gchar *);
/* Called to update the playlist at the current iterator. */
void gui_sidebar_iter_update_playlist(GtkTreeIter *, struct playlist *);
void gui_sidebar_iter_update(GtkTreeIter *);
/* Called to select the row at the current iterator. */
void gui_sidebar_iter_select(GtkTreeIter *);
/* Called to set the current iterator as editable. */
bool gui_sidebar_iter_set_editable(GtkTreeIter *, bool);
/* Called to set the playlist at the given iterator as the default. */
void gui_sidebar_filter_path_select(GtkTreePath *);
/* Called to expand or collapse sidebar rows from the user's settings. */
void gui_sidebar_filter_set_expand(GtkTreeIter *);
/* Called when a playlist treeview row is expanded or collapsed. */
void gui_sidebar_filter_row_expanded(GtkTreeIter *, bool);
/*
* Called to find a playlist with the given name and type,
* starting from the current iterator position.
*/
gboolean gui_sidebar_iter_find(GtkTreeIter *, const gchar *,
enum playlist_type_t);
/* Called to set the a GtkTreeIter to the row at path string */
gboolean gui_sidebar_iter_from_string(const gchar *, GtkTreeIter *);
/* Called to set the GtkTreeIter to the row at (x, y) */
gboolean gui_sidebar_iter_from_xy(gint, gint, GtkTreeIter *);
/* Called to get the sidebar widget. */
static inline GtkPaned *gui_sidebar()
{
return GTK_PANED(gui_builder_widget("sidebar"));
}
/* Called to get the sidebar treestore. */
static inline GtkTreeStore *gui_sidebar_store()
{
return GTK_TREE_STORE(gui_builder_object("sidebar_store"));
}
/* Called to get the sidebar treemodel. */
static inline GtkTreeModel *gui_sidebar_model()
{
return GTK_TREE_MODEL(gui_builder_object("sidebar_store"));
}
/* Called to get the sidebar filter model. */
static inline GtkTreeModelFilter *gui_sidebar_filter()
{
return GTK_TREE_MODEL_FILTER(gui_builder_object("sidebar_filter"));
}
/* Called to get the sidebar treeview. */
static inline GtkTreeView *gui_sidebar_treeview()
{
return GTK_TREE_VIEW(gui_builder_widget("sidebar_treeview"));
}
/* Called to get the sidebar right-click menu. */
static inline GtkMenu *gui_sidebar_menu()
{
return GTK_MENU(gui_builder_widget("rc_sidebar"));
}
/* Called to get the random button. */
static inline GtkToggleButton *gui_random_button()
{
return GTK_TOGGLE_BUTTON(gui_builder_widget("random_button"));
}
#endif /* OCARINA_GUI_SIDEBAR_H */

View File

@ -1,47 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_TREEVIEW_H
#define OCARINA_GUI_TREEVIEW_H
#include <gui/builder.h>
/* Called to initialize the treeview widget. */
void gui_treeview_init();
/* Called to deinitialize the treeview layer. */
void gui_treeview_deinit();
/* Called to set the current playlist. */
void gui_treeview_set_playlist(struct playlist *);
/* Called to scroll the treeview to the current track. */
void gui_treeview_scroll();
/* Called to select a path from (x, y) screen coodinates. */
void gui_treeview_select_path_at_pos(unsigned int x, unsigned int y);
/*
* Called to get a list of selected tracks.
* NOTE: The caller is responsible for freeing the list with g_list_free().
*/
GList *gui_treeview_list_selected_tracks(void);
/* Called to access the treeview widget. */
static inline GtkTreeView *gui_treeview()
{
return GTK_TREE_VIEW(gui_builder_widget("treeview"));
}
/* Called to access the treview selection. */
static inline GtkTreeSelection *gui_treeview_selection()
{
return gtk_tree_view_get_selection(gui_treeview());
}
/* Called to access the sorting display widget. */
static inline GtkLabel *gui_sorting()
{
return GTK_LABEL(gui_builder_widget("sorting"));
}
#endif /* OCARINA_GUI_TREEVIEW_H */

View File

@ -1,20 +0,0 @@
/*
* Copyright 2015 (c) Anna Schumaker.
*/
#ifndef OCARINA_GUI_WINDOW_H
#define OCARINA_GUI_WINDOW_H
#include <gui/builder.h>
/* Called to initialize the main window. */
void gui_window_init(const gchar *);
/* Called to deinitialize the main window. */
void gui_window_deinit();
/* Called to get a pointer to the main window. */
static inline GtkWindow *gui_window()
{
return GTK_WINDOW(gui_builder_widget("window"));
}
#endif /* OCARINA_GUI_WINDOW_H */

View File

@ -1,30 +0,0 @@
/*
* Copyright 2016 (c) Anna Schumaker.
*/
#ifndef OCARINA_TESTS_LOOP_H
#define OCARINA_TESTS_LOOP_H
static GMainLoop *__test_main_loop;
static int __test_loop_on_idle(gpointer data)
{
g_main_loop_quit(__test_main_loop);
return G_SOURCE_CONTINUE;
}
static void test_loop_init()
{
__test_main_loop = g_main_loop_new(NULL, FALSE);
g_idle_add(__test_loop_on_idle, NULL);
}
static void test_loop_deinit()
{
g_main_loop_unref(__test_main_loop);
}
static void test_main_loop()
{
g_main_loop_run(__test_main_loop);
}
#endif /* OCARINA_TESTS_LOOP_H */

View File

@ -1,22 +0,0 @@
/*
* Copyright 2014 (c) Anna Schumaker.
*/
#ifndef OCARINA_TESTS_TEST_H
#define OCARINA_TESTS_TEST_H
#include <glib.h>
/*
* Compares two strings, and frees lhs before returning.
* NOTE: We store lhs in a temporary string so that "lhs" only gets
* evaluated once in the case that it is a function.
*/
#define g_assert_cmpstr_free(lhs, cmp, rhs) \
do { \
gchar *__tmp_str = lhs; \
g_assert_cmpstr(__tmp_str, cmp, rhs); \
g_free(__tmp_str); \
} while (0)
#endif /* OCARINA_TESTS_TEST_H */

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<coverage xmlns="http://www.netbeans.org/ns/code-coverage/1" enabled="false"/>
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
</project-private>

View File

@ -0,0 +1,5 @@
java.lib.path=
main.file=core/scion.py
platform.active=default
python.lib.path=/usr/bin/python|
src.dir=src

13
nbproject/project.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.python.project</type>
<configuration>
<data xmlns="http://nbpython.dev.java.net/ns/php-project/1">
<name>scion</name>
<sources>
<root id="src.dir"/>
</sources>
<tests/>
</data>
</configuration>
</project>

View File

@ -1,73 +0,0 @@
{
Suppress GObject Init
Memcheck:Leak
match-leak-kinds: definite,possible
...
fun:call_init.part.0
fun:_dl_init
obj:/usr/lib/ld-2.24.so
}
{
Suppress GObject Type Registration
Memcheck:Leak
match-leak-kinds: possible
...
fun:g_type_register_static
fun:g_*_register_static*
...
}
{
Suppress GStreamer Init
Memcheck:Leak
match-leak-kinds: possible
...
obj:/usr/lib/libgstreamer-*
fun:g_option_context_parse
fun:gst_init_check
fun:gst_init
fun:audio_init
}
{
Suppress GStreamer Plugins
Memcheck:Leak
match-leak-kinds: definite
...
fun:dlopen
fun:g_module_open
obj:/usr/lib/libgstreamer-*
fun:gst_plugin_load_by_name
fun:gst_plugin_feature_load
}
{
Suppress GStreamer Pad Thread Pools
Memcheck:Leak
match-leak-kinds: possible
...
fun:g_thread_pool_push
obj:/usr/lib/libgstreamer-*
fun:gst_task_set_state
fun:gst_pad_start_task
obj:/usr/lib/*gstreamer-*
...
}
{
Suppress GStreamer Type Instantiation
Memcheck:Leak
match-leak-kinds: possible
...
fun:g_type_create_instance
fun:g_param_spec_internal
fun:g_param_spec_string
obj:/usr/lib/libgstreamer-*
...
}
{
Suppress GTK+ Interface Creation
Memcheck:Leak
match-leak-kinds:possible
...
fun:g_type_add_interface_static
...
obj:/usr/lib/libgtk-*
fun:gtk_builder_get_type_from_name
}

View File

@ -1,9 +0,0 @@
[Desktop Entry]
Type=Application
Version=1.0
Name=Ocarina
Comment=A gtkmm and gstreamer based music player
Exec=ocarina
Icon=/usr/share/ocarina/ocarina.png
Terminal=false
Categories=AudioVideo;Audio;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

8
src/clean.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
rm -r ./*/*.pyc
rm -r ./*/*/*.pyc
#rm -r ./*/*.class
#rm -r ./*/*/*.class
#rm -r ./*/*.pyo
#rm -r ./*/*/*.pyo

34
src/core/aliases.py Normal file
View File

@ -0,0 +1,34 @@
#! /usr/bin/python
# To change this template, choose Tools | Templates
# and open the template in the editor.
__author__="bjschuma"
__date__ ="$Jan 23, 2010 2:33:21 PM$"
from bt.file import *
from bt import dict
import session
class Alias(dict.Dict):
def __init__(self):
dict.Dict.__init__(self)
session.events.invite("scion-plugins-loaded",self.load,3)
session.events.invite("scion-end",self.save,90)
def save(self):
import session
write("Saving aliases",1)
file = fopen( join(session.settings["session"],"aliases"), 'w' )
sp = " "
for key in self.keys():
file.write("alias "+key+"="+self[key]+"\n")
def load(self):
import session
path = join(session.settings["session"],"aliases")
write("Alias file: "+path,1)
session.events.invite("scion-plugins-loaded", path )

5
src/core/bt/__init__.py Normal file
View File

@ -0,0 +1,5 @@
# This is the base tools package
# It contains various tools needed by the base layer of ocarina2
__all__ = ["cmd", "dict", "event", "file", "message", "needle", "plugin",
"proc", "scripting", "slist", "sql", "xm"]

101
src/core/bt/cmd.py Normal file
View File

@ -0,0 +1,101 @@
#! /usr/bin/python
# To change this template, choose Tools | Templates
# and open the template in the editor.
__author__="bjschuma"
__date__ ="$Feb 20, 2010 2:19:10 PM$"
from bt.message import *
from session import alias
from session import manager
from session import vars
def runCmd(input):
write("Running command: "+input,2)
# Find command
split = input.split(' ',1)
cmd = split[0]
args=None
if len(split) > 1:
args = split[1]
if (cmd in manager.enabled) == True:
if args == None:
return manager.enabled[cmd].start()
else:
return manager.enabled[cmd].start(args.split())
else:
return input
# Check if we are storing in a variable
def varCheck(cmd):
split = cmd.split('=', 1)
if len(split)==2 and len(split[0].split())==1:
var = split[0].strip()
if not var[0]=="$":
var = "$" + var
write("Using variable: "+var, 2)
return ( var, split[1].strip() )
else:
return (None, cmd)
# Replace variables in the command with their real values
def varReplace(cmd):
for key in vars.keys():
v = "`"+key+"`"
if cmd.find(v) > -1:
new = str(vars[key])
write(key + " => " + new, 2)
cmd = cmd.replace(v, new)
return cmd
# Replace aliases with their real values
def aliasReplace(cmd):
split = cmd.split()
out = ""
for index,word in enumerate(split):
if index > 0:
out += " "
if alias.has(word) == True:
write(word + " => " + alias[word], 2)
out += aliasReplace(alias[word])
else:
out += word
return out
def run(string):
split = string.split(";")
ans = []
for cmd in split:
(var,cmd) = varCheck(cmd)
cmd = varReplace(cmd)
cmd = aliasReplace(cmd)
if var == None:
ans += [ runCmd(cmd) ]
else:
disable()
vars[var] = runCmd(cmd)
enable()
if len(ans) == 1:
return ans[0]
return ans
def call(string):
# disable text printing
disable()
result = run(string)
# enable text printing
enable()
return result

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