Compare commits
No commits in common. "next" and "Scion-1.x" have entirely different histories.
|
@ -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
|
|
|
@ -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/
|
|
|
@ -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
148
CHANGELOG
|
@ -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
|
|
|
@ -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()
|
|
|
@ -1 +0,0 @@
|
||||||
set(CTEST_CUSTOM_PRE_TEST "rm -rf $ENV{HOME}/.local/share/ocarina-test $ENV{HOME}/.cache/ocarina-test")
|
|
339
LICENSE
339
LICENSE
|
@ -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.
|
|
28
PKGBUILD
28
PKGBUILD
|
@ -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/
|
|
||||||
}
|
|
53
README.md
53
README.md
|
@ -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
40
TODO
|
@ -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
|
|
344
core/audio.c
344
core/audio.c
|
@ -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 */
|
|
34
core/core.c
34
core/core.c
|
@ -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();
|
|
||||||
}
|
|
232
core/database.c
232
core/database.c
|
@ -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);
|
|
||||||
}
|
|
68
core/date.c
68
core/date.c
|
@ -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;
|
|
||||||
}
|
|
279
core/file.c
279
core/file.c
|
@ -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;
|
|
||||||
}
|
|
111
core/idle.c
111
core/idle.c
|
@ -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;
|
|
||||||
}
|
|
236
core/playlist.c
236
core/playlist.c
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 */
|
|
105
core/string.c
105
core/string.c
|
@ -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;
|
|
||||||
}
|
|
|
@ -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, ¶m);
|
|
||||||
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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 */
|
|
147
gui/artwork.c
147
gui/artwork.c
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
205
gui/audio.c
205
gui/audio.c
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 */
|
|
142
gui/filter.c
142
gui/filter.c
|
@ -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());
|
|
||||||
}
|
|
31
gui/idle.c
31
gui/idle.c
|
@ -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);
|
|
||||||
}
|
|
383
gui/model.c
383
gui/model.c
|
@ -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);
|
|
||||||
}
|
|
151
gui/ocarina.c
151
gui/ocarina.c
|
@ -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);
|
|
||||||
}
|
|
259
gui/playlist.c
259
gui/playlist.c
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
508
gui/sidebar.c
508
gui/sidebar.c
|
@ -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;
|
|
||||||
}
|
|
289
gui/treeview.c
289
gui/treeview.c
|
@ -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;
|
|
||||||
}
|
|
88
gui/window.c
88
gui/window.c
|
@ -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()));
|
|
||||||
}
|
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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 */
|
|
|
@ -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>
|
|
@ -0,0 +1,5 @@
|
||||||
|
java.lib.path=
|
||||||
|
main.file=core/scion.py
|
||||||
|
platform.active=default
|
||||||
|
python.lib.path=/usr/bin/python|
|
||||||
|
src.dir=src
|
|
@ -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>
|
73
ocarina.supp
73
ocarina.supp
|
@ -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
|
|
||||||
}
|
|
|
@ -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 |
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
rm -r ./*/*.pyc
|
||||||
|
rm -r ./*/*/*.pyc
|
||||||
|
#rm -r ./*/*.class
|
||||||
|
#rm -r ./*/*/*.class
|
||||||
|
#rm -r ./*/*.pyo
|
||||||
|
#rm -r ./*/*/*.pyo
|
|
@ -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 )
|
|
@ -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"]
|
|
@ -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
Loading…
Reference in New Issue