mirror of
https://github.com/SuperBFG7/ympd
synced 2024-11-29 16:09:55 +00:00
Merge branch 'mongoose'
This commit is contained in:
commit
14e807850c
17
.drone.yml
Normal file
17
.drone.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
image: gcc4.8
|
||||||
|
script:
|
||||||
|
- sudo apt-get install -y libmpdclient-dev &> /dev/null
|
||||||
|
- cmake . -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
- make VERBOSE=1
|
||||||
|
notify:
|
||||||
|
email:
|
||||||
|
recipients:
|
||||||
|
- andy@ndyk.de
|
||||||
|
irc:
|
||||||
|
server: irc.freenode.org
|
||||||
|
nick: droneBot
|
||||||
|
channel: '#nicotest'
|
||||||
|
on_started: true
|
||||||
|
on_success: true
|
||||||
|
on_failure: true
|
||||||
|
|
@ -3,55 +3,51 @@ cmake_minimum_required(VERSION 2.6)
|
|||||||
project (ympd)
|
project (ympd)
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
|
||||||
set(CPACK_PACKAGE_VERSION_MAJOR "1")
|
set(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||||
set(CPACK_PACKAGE_VERSION_MINOR "0")
|
set(CPACK_PACKAGE_VERSION_MINOR "2")
|
||||||
set(CPACK_PACKAGE_VERSION_PATCH "0")
|
set(CPACK_PACKAGE_VERSION_PATCH "0")
|
||||||
set(CPACK_GENERATOR "DEB;RPM;TGZ")
|
|
||||||
set(CPACK_SOURCE_GENERATOR "TBZ2")
|
|
||||||
set(DEBIAN_PACKAGE_SECTION "web")
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MPD web client based on Websockets and Bootstrap")
|
|
||||||
set(CPACK_PACKAGE_CONTACT "Andrew Karpow <andy@ympd.org>")
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "andy@ndyk.de")
|
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.0.0,libmpdclient2")
|
|
||||||
|
|
||||||
option(WITH_STATIC_WEBSOCKETS "Build with static libwebsockets library" ON)
|
|
||||||
option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
|
option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
|
||||||
|
|
||||||
find_package(LibMPDClient REQUIRED)
|
find_package(LibMPDClient REQUIRED)
|
||||||
find_package(LibWebSockets REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
if(WITH_STATIC_WEBSOCKETS)
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
find_package(ZLIB REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in
|
configure_file(src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||||
${PROJECT_BINARY_DIR}/config.h)
|
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR})
|
||||||
include_directories(${PROJECT_BINARY_DIR} ${LIBWEBSOCKETS_INCLUDE_DIR})
|
|
||||||
|
|
||||||
include(CheckCSourceCompiles)
|
include(CheckCSourceCompiles)
|
||||||
include(CPack)
|
|
||||||
|
|
||||||
set(CMAKE_C_FLAGS "-std=gnu99 -Wall")
|
set(CMAKE_C_FLAGS "-std=gnu99 -Wall")
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic")
|
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic")
|
||||||
|
|
||||||
|
file(GLOB RESOURCES
|
||||||
|
RELATIVE ${PROJECT_SOURCE_DIR}
|
||||||
|
htdocs/js/*
|
||||||
|
htdocs/assets/*
|
||||||
|
htdocs/css/*.min.css
|
||||||
|
htdocs/fonts/*
|
||||||
|
htdocs/index.html
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(mkdata htdocs/mkdata.c)
|
||||||
|
get_target_property(MKDATA_EXE mkdata LOCATION)
|
||||||
|
|
||||||
|
add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/assets.c
|
||||||
|
COMMAND ${MKDATA_EXE} ${RESOURCES} > ${PROJECT_BINARY_DIR}/assets.c
|
||||||
|
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||||
|
DEPENDS ${RESOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
src/ympd.c
|
src/ympd.c
|
||||||
src/http_server.c
|
src/http_server.c
|
||||||
src/mpd_client.c
|
src/mpd_client.c
|
||||||
|
src/mongoose.c
|
||||||
|
src/json_encode.c
|
||||||
|
assets.c
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(ympd ${SOURCES})
|
add_executable(ympd ${SOURCES})
|
||||||
|
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||||
# TODO: use generator expressions introduced to CMake 2.8.12, too fresh yet
|
|
||||||
if(WITH_STATIC_WEBSOCKETS)
|
|
||||||
find_library(LIBWEBSOCKETS_LIBRARY_STATIC libwebsockets.a)
|
|
||||||
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY}
|
|
||||||
${LIBWEBSOCKETS_LIBRARY_STATIC} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES})
|
|
||||||
else()
|
|
||||||
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY}
|
|
||||||
${LIBWEBSOCKETS_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
install(TARGETS ympd DESTINATION bin)
|
install(TARGETS ympd DESTINATION bin)
|
||||||
install(FILES ympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
|
install(FILES ympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
|
||||||
install(DIRECTORY htdocs DESTINATION share/${PROJECT_NAME})
|
|
||||||
|
356
LICENSE
356
LICENSE
@ -1,22 +1,340 @@
|
|||||||
Redistribution and use in source and binary forms, with or without
|
GNU GENERAL PUBLIC LICENSE
|
||||||
modification, are permitted provided that the following conditions
|
Version 2, June 1991
|
||||||
are met:
|
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
notice, this list of conditions and the following disclaimer.
|
51 Franklin St, 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.
|
||||||
|
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
Preamble
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
The licenses for most software are designed to take away your
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
License is intended to guarantee your freedom to share and change free
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
software--to make sure the software is free for all its users. This
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
General Public License applies to most of the Free Software
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
Foundation's software and to any other program whose authors commit to
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
your programs, too.
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
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 St, 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 Library General
|
||||||
|
Public License instead of this License.
|
||||||
|
@ -1,26 +1,27 @@
|
|||||||
ympd
|
ympd
|
||||||
====
|
====
|
||||||
|
[![Build Status](http://ci.ympd.org/github.com/notandy/ympd/status.png?branch=mongoose)](https://ci.ympd.org/github.com/notandy/ympd)
|
||||||
|
|
||||||
Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
||||||
|
|
||||||
|
|
||||||
http://www.ympd.org
|
http://www.ympd.org
|
||||||
|
|
||||||
|
![ScreenShot](http://www.ympd.org/assets/ympd_github.png)
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
- libwebsockets master branch: http://git.libwebsockets.org/cgi-bin/cgit/libwebsockets
|
|
||||||
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
|
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
|
||||||
- cmake 2.6: http://cmake.org/
|
- cmake 2.6: http://cmake.org/
|
||||||
- Optional: OpenSSL for SSL support in libwebsockets webserver
|
|
||||||
|
|
||||||
Unix Build Instructions
|
Unix Build Instructions
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
1. Install Dependencies, cmake, openssl and libmpdclient are available from all major distributions.
|
1. Install Dependencies, cmake and libmpdclient are available from all major distributions.
|
||||||
2. create build directory ```cd /path/to/src; mkdir build; cd build```
|
2. create build directory ```cd /path/to/src; mkdir build; cd build```
|
||||||
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr```
|
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr```
|
||||||
4. build ```make```
|
4. build ```make```
|
||||||
5. install ```sudo make install``` or build debian package ```cpack -G DEB; sudo dpkg -i ympd*.deb```
|
5. install ```sudo make install``` or just run with ```./ympd```
|
||||||
|
|
||||||
Copyright
|
Copyright
|
||||||
---------
|
---------
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
# This module tries to find libWebsockets library and include files
|
|
||||||
#
|
|
||||||
# LIBWEBSOCKETS_FOUND, If false, do not try to use libWebSockets
|
|
||||||
# LIBWEBSOCKETS_INCLUDE_DIR, path where to find libwebsockets.h
|
|
||||||
# LIBWEBSOCKETS_LIBRARY_DIR, path where to find libwebsockets.so
|
|
||||||
# LIBWEBSOCKETS_LIBRARIES, the library to link against
|
|
||||||
#
|
|
||||||
# This currently works probably only for Linux
|
|
||||||
|
|
||||||
find_package(PkgConfig)
|
|
||||||
pkg_check_modules(PC_LIBWEBSOCKETS QUIET libwebsockets)
|
|
||||||
set(LIBWEBSOCKETS_DEFINITIONS ${PC_LIBWEBSOCKETS_CFLAGS_OTHER})
|
|
||||||
|
|
||||||
find_path(LIBWEBSOCKETS_INCLUDE_DIR libwebsockets.h
|
|
||||||
HINTS ${PC_LIBWEBSOCKETS_INCLUDEDIR} ${PC_LIBWEBSOCKETS_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
find_library(LIBWEBSOCKETS_LIBRARY websockets
|
|
||||||
HINTS ${PC_LIBWEBSOCKETS_LIBDIR} ${PC_LIBWEBSOCKETS_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(LIBWEBSOCKETS_LIBRARIES ${LIBWEBSOCKETS_LIBRARY})
|
|
||||||
set(LIBWEBSOCKETS_INCLUDE_DIRS ${LIBWEBSOCKETS_INCLUDE_DIR})
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
# handle the QUIETLY and REQUIRED arguments and set LIBWEBSOCKETS_FOUND to TRUE
|
|
||||||
# if all listed variables are TRUE
|
|
||||||
find_package_handle_standard_args(LibWebSockets DEFAULT_MSG
|
|
||||||
LIBWEBSOCKETS_LIBRARY LIBWEBSOCKETS_INCLUDE_DIR
|
|
||||||
)
|
|
||||||
|
|
||||||
mark_as_advanced(
|
|
||||||
LIBWEBSOCKETS_LIBRARY
|
|
||||||
LIBWEBSOCKETS_INCLUDE_DIR
|
|
||||||
)
|
|
@ -21,8 +21,7 @@ SCRIPTNAME=/etc/init.d/$NAME
|
|||||||
LOG_OUT=/var/log/$NAME.out
|
LOG_OUT=/var/log/$NAME.out
|
||||||
LOG_ERR=/var/log/$NAME.err
|
LOG_ERR=/var/log/$NAME.err
|
||||||
YMPD_USER=mpd
|
YMPD_USER=mpd
|
||||||
YMPD_GROUP=mpd
|
DAEMON_OPT="--user $YMPD_USER --webport 80"
|
||||||
DAEMON_OPT="--uid $YMPD_USER --gid $YMPD_GROUP --webport 80"
|
|
||||||
|
|
||||||
# Exit if the package is not installed
|
# Exit if the package is not installed
|
||||||
[ -x "$DAEMON" ] || exit 0
|
[ -x "$DAEMON" ] || exit 0
|
||||||
|
@ -28,6 +28,10 @@ body {
|
|||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#search {
|
||||||
|
margin-right: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-group-hover {
|
.btn-group-hover {
|
||||||
opacity: 20%;
|
opacity: 20%;
|
||||||
}
|
}
|
||||||
|
1
htdocs/css/mpd.min.css
vendored
Normal file
1
htdocs/css/mpd.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
body{padding-top:50px;padding-bottom:50px}.starter-template{padding:40px 15px}#volumeslider{width:150px}#volumeslider .progress{margin-bottom:0}#volume-icon{float:left;margin-right:10px;margin-top:2px}#counter{font-size:24px;margin-top:-6px;margin-left:10px;min-width:50px}#search{margin-right:-10px}.btn-group-hover{opacity:20%}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);color:#428bca;background-color:#fdfdfd;border-color:#adadad}@media(max-width:1199px){#btn-responsive-block>.btn{padding:6px 12px;font-size:14px;border-radius:4px}}#salamisandwich td:nth-child(3),th:nth-child(3){text-align:right}tbody{cursor:pointer}td:last-child,td:first-child{width:30px}.notifications{position:fixed;z-index:9999}.notifications.top-right{right:10px;top:60px}.notifications>div{position:relative;z-index:9999;margin:5px 0}
|
@ -10,10 +10,11 @@
|
|||||||
<title>ympd</title>
|
<title>ympd</title>
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
<link href="css/bootstrap.css" rel="stylesheet">
|
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Custom styles for this template -->
|
<!-- Custom styles for this template -->
|
||||||
<link href="css/mpd.css" rel="stylesheet">
|
<link href="css/mpd.min.css" rel="stylesheet">
|
||||||
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
|
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
|
||||||
|
|
||||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
@ -37,9 +38,8 @@
|
|||||||
<div class="collapse navbar-collapse">
|
<div class="collapse navbar-collapse">
|
||||||
|
|
||||||
<ul id="nav_links" class="nav navbar-nav">
|
<ul id="nav_links" class="nav navbar-nav">
|
||||||
<li id="playlist"><a href="#/">Playlist</a></li>
|
<li id="queue"><a href="#/">Queue</a></li>
|
||||||
<li id="browse"><a href="#/browse/">Browse database</a></li>
|
<li id="browse"><a href="#/browse/0/">Browse database</a></li>
|
||||||
<li><a href="#" data-toggle="modal" data-target="#about" onclick="getVersion();">About</a></li>
|
|
||||||
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
|
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -65,6 +65,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<form id="search" class="navbar-form navbar-right" role="search">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" class="form-control" placeholder="Search">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div><!--/.nav-collapse -->
|
</div><!--/.nav-collapse -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +82,7 @@
|
|||||||
|
|
||||||
<div class="panel panel-primary">
|
<div class="panel panel-primary">
|
||||||
<!-- Default panel contents -->
|
<!-- Default panel contents -->
|
||||||
<div id="panel-heading" class="panel-heading">Playlist</div>
|
<div class="panel-heading"><b id="panel-heading">Queue</b></div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<h1>
|
<h1>
|
||||||
<span id="track-icon" class="glyphicon glyphicon-play"></span>
|
<span id="track-icon" class="glyphicon glyphicon-play"></span>
|
||||||
@ -111,6 +116,10 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div><!-- /.panel -->
|
</div><!-- /.panel -->
|
||||||
|
<ul class="pager">
|
||||||
|
<li id="prev" class="page-btn hide"><a href="">Previous</a></li>
|
||||||
|
<li id="next" class="page-btn"><a href="">Next</a></li>
|
||||||
|
</ul>
|
||||||
</div><!-- /.col-md-10 -->
|
</div><!-- /.col-md-10 -->
|
||||||
|
|
||||||
<div class="col-md-2 col-xs-12" >
|
<div class="col-md-2 col-xs-12" >
|
||||||
@ -149,34 +158,6 @@
|
|||||||
</div><!-- /.row -->
|
</div><!-- /.row -->
|
||||||
</div><!-- /.container -->
|
</div><!-- /.container -->
|
||||||
|
|
||||||
<!-- Modal -->
|
|
||||||
<div class="modal fade" id="about" tabindex="-1" role="dialog" aria-labelledby="aboutLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
|
||||||
<h2 class="modal-title" id="aboutLabel">About</h2>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<h4><a href="http://www.ympd.org"><span class="glyphicon glyphicon-play-circle"></span> ympd</a> <small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
|
|
||||||
<p>
|
|
||||||
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
|
|
||||||
<p class="text-muted">
|
|
||||||
ympd <span id="ympd_version"></span><br/>
|
|
||||||
libmpdclient <span id="mpd_version"></span><br/>
|
|
||||||
</p>
|
|
||||||
<h5>ympd uses following excellent software:</h5>
|
|
||||||
<h6><a href="http://libwebsockets.org">libWebSockets</a> <small>LGPL2.1 + static link exception</small></h6>
|
|
||||||
<h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
|
|
||||||
<br/>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div><!-- /.modal-content -->
|
|
||||||
</div><!-- /.modal-dialog -->
|
|
||||||
</div><!-- /.modal -->
|
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade" id="settings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
|
<div class="modal fade" id="settings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@ -186,6 +167,13 @@
|
|||||||
<h2 class="modal-title" id="settingsLabel"><span class="glyphicon glyphicon-wrench"></span> Settings</h2>
|
<h2 class="modal-title" id="settingsLabel"><span class="glyphicon glyphicon-wrench"></span> Settings</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<h4><a href="http://www.ympd.org"><span class="glyphicon glyphicon-play-circle"></span> ympd</a> <small>MPD Web GUI - written in C, utilizing Websockets and Bootstrap/JS</small></h4>
|
||||||
|
<p>
|
||||||
|
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.</p>
|
||||||
|
<h5>ympd uses following excellent software:</h5>
|
||||||
|
<h6><a href="http://cesanta.com/docs.html">Mongoose</a> <small>GPLv2</small></h6>
|
||||||
|
<h6><a href="http://www.musicpd.org/libs/libmpdclient/">libMPDClient</a> <small>BSD License</small></h6>
|
||||||
|
<hr />
|
||||||
<form role="form">
|
<form role="form">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-md-9">
|
<div class="form-group col-md-9">
|
||||||
@ -197,16 +185,51 @@
|
|||||||
<input type="text" class="form-control" id="mpdport" />
|
<input type="text" class="form-control" id="mpdport" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label class="control-label" for="mpd_pw">MPD Password</label>
|
||||||
|
<input type="password" class="form-control" id="mpd_pw" placeholder="Password"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label class="control-label" for="mpd_pw_con">MPD Password (Confirmation)</label>
|
||||||
|
<input type="password" class="form-control" id="mpd_pw_con" placeholder="Password confirmation"
|
||||||
|
data-placement="right" data-toggle="popover" data-content="Password does not match!"
|
||||||
|
data-trigger="manual" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<div id="mpd_password_set" class="hide alert alert-info">
|
||||||
|
<button type="button" class="close" aria-hidden="true">×</button>
|
||||||
|
MPD Password is set
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal" onclick="setHost();">Save</button>
|
<button type="button" class="btn btn-default" onclick="confirmSettings();">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /.modal-content -->
|
</div><!-- /.modal-content -->
|
||||||
</div><!-- /.modal-dialog -->
|
</div><!-- /.modal-dialog -->
|
||||||
</div><!-- /.modal -->
|
</div><!-- /.modal -->
|
||||||
|
|
||||||
|
<div class="modal fade bs-example-modal-sm" id="wait" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1>Searching...</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="progress progress-striped active">
|
||||||
|
<div class="progress-bar" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 100%">
|
||||||
|
<span class="sr-only">Please Wait</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Bootstrap core JavaScript
|
<!-- Bootstrap core JavaScript
|
||||||
|
318
htdocs/js/mpd.js
318
htdocs/js/mpd.js
@ -1,62 +1,67 @@
|
|||||||
/* libmpdclient
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var socket;
|
var socket;
|
||||||
var last_state;
|
var last_state;
|
||||||
var current_app;
|
var current_app;
|
||||||
|
var pagination = 0;
|
||||||
|
var browsepath;
|
||||||
var lastSongTitle = "";
|
var lastSongTitle = "";
|
||||||
var current_song = new Object();
|
var current_song = new Object();
|
||||||
|
var MAX_ELEMENTS_PER_PAGE = 512;
|
||||||
|
|
||||||
var app = $.sammy(function() {
|
var app = $.sammy(function() {
|
||||||
this.before('/', function(e, data) {
|
|
||||||
$('#nav_links > li').removeClass('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.get('#/', function() {
|
function runBrowse() {
|
||||||
current_app = 'playlist';
|
current_app = 'queue';
|
||||||
|
|
||||||
$('#breadcrump').addClass('hide');
|
$('#breadcrump').addClass('hide');
|
||||||
$('#salamisandwich').find("tr:gt(0)").remove();
|
$('#salamisandwich').find("tr:gt(0)").remove();
|
||||||
$.get( "/api/get_playlist", socket.onmessage);
|
socket.send('MPD_API_GET_QUEUE,'+pagination);
|
||||||
|
|
||||||
$('#panel-heading').text("Playlist");
|
$('#panel-heading').text("Queue");
|
||||||
$('#playlist').addClass('active');
|
$('#queue').addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
$('#nav_links > li').removeClass('active');
|
||||||
|
$('.page-btn').addClass('hide');
|
||||||
|
pagination = 0;
|
||||||
|
browsepath = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get(/\#\/(\d+)/, function() {
|
||||||
|
prepare();
|
||||||
|
pagination = parseInt(this.params['splat'][0]);
|
||||||
|
runBrowse();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get(/\#\/browse\/(.*)/, function() {
|
this.get(/\#\/browse\/(\d+)\/(.*)/, function() {
|
||||||
|
prepare();
|
||||||
|
browsepath = this.params['splat'][1];
|
||||||
|
pagination = parseInt(this.params['splat'][0]);
|
||||||
current_app = 'browse';
|
current_app = 'browse';
|
||||||
$('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/\">root</a></li>");
|
$('#breadcrump').removeClass('hide').empty().append("<li><a href=\"#/browse/0/\">root</a></li>");
|
||||||
$('#salamisandwich').find("tr:gt(0)").remove();
|
$('#salamisandwich').find("tr:gt(0)").remove();
|
||||||
var path = this.params['splat'][0];
|
socket.send('MPD_API_GET_BROWSE,'+pagination+','+(browsepath ? browsepath : "/"));
|
||||||
|
|
||||||
$.get( "/api/get_browse/" + encodeURIComponent(path), socket.onmessage);
|
$('#panel-heading').text("Browse database: "+browsepath);
|
||||||
|
var path_array = browsepath.split('/');
|
||||||
$('#panel-heading').text("Browse database: "+path+"");
|
|
||||||
var path_array = path.split('/');
|
|
||||||
var full_path = "";
|
var full_path = "";
|
||||||
$.each(path_array, function(index, chunk) {
|
$.each(path_array, function(index, chunk) {
|
||||||
if(path_array.length - 1 == index) {
|
if(path_array.length - 1 == index) {
|
||||||
@ -65,14 +70,25 @@ var app = $.sammy(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
full_path = full_path + chunk;
|
full_path = full_path + chunk;
|
||||||
$('#breadcrump').append("<li><a href=\"#/browse/" + full_path + "\">"+chunk+"</a></li>");
|
$('#breadcrump').append("<li><a href=\"#/browse/0/" + full_path + "\">"+chunk+"</a></li>");
|
||||||
full_path += "/";
|
full_path += "/";
|
||||||
});
|
});
|
||||||
$('#browse').addClass('active');
|
$('#browse').addClass('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get(/\#\/search\/(.*)/, function() {
|
||||||
|
current_app = 'search';
|
||||||
|
$('#salamisandwich').find("tr:gt(0)").remove();
|
||||||
|
var searchstr = this.params['splat'][0];
|
||||||
|
|
||||||
|
$('#search > div > input').val(searchstr);
|
||||||
|
socket.send('MPD_API_SEARCH,' + searchstr);
|
||||||
|
|
||||||
|
$('#panel-heading').text("Search: "+searchstr);
|
||||||
|
});
|
||||||
|
|
||||||
this.get("/", function(context) {
|
this.get("/", function(context) {
|
||||||
context.redirect("#/");
|
context.redirect("#/0");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,13 +116,14 @@ $(document).ready(function(){
|
|||||||
|
|
||||||
function webSocketConnect() {
|
function webSocketConnect() {
|
||||||
if (typeof MozWebSocket != "undefined") {
|
if (typeof MozWebSocket != "undefined") {
|
||||||
socket = new MozWebSocket(get_appropriate_ws_url(), "ympd-client");
|
socket = new MozWebSocket(get_appropriate_ws_url());
|
||||||
} else {
|
} else {
|
||||||
socket = new WebSocket(get_appropriate_ws_url(), "ympd-client");
|
socket = new WebSocket(get_appropriate_ws_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket.onopen = function() {
|
socket.onopen = function() {
|
||||||
|
console.log("connected");
|
||||||
$('.top-right').notify({
|
$('.top-right').notify({
|
||||||
message:{text:"Connected to ympd"},
|
message:{text:"Connected to ympd"},
|
||||||
fadeOut: { enabled: true, delay: 500 }
|
fadeOut: { enabled: true, delay: 500 }
|
||||||
@ -115,19 +132,15 @@ function webSocketConnect() {
|
|||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage =function got_packet(msg) {
|
socket.onmessage = function got_packet(msg) {
|
||||||
if(msg instanceof MessageEvent) {
|
if(msg.data === last_state || msg.data.length == 0)
|
||||||
if(msg.data === last_state)
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
var obj = JSON.parse(msg.data);
|
var obj = JSON.parse(msg.data);
|
||||||
} else {
|
|
||||||
var obj = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case "playlist":
|
case "queue":
|
||||||
if(current_app !== 'playlist')
|
if(current_app !== 'queue')
|
||||||
break;
|
break;
|
||||||
|
|
||||||
$('#salamisandwich > tbody').empty();
|
$('#salamisandwich > tbody').empty();
|
||||||
@ -142,6 +155,11 @@ function webSocketConnect() {
|
|||||||
"</td><td></td></tr>");
|
"</td><td></td></tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(obj.data[obj.data.length-1].pos + 1 >= pagination + MAX_ELEMENTS_PER_PAGE)
|
||||||
|
$('#next').removeClass('hide');
|
||||||
|
if(pagination > 0)
|
||||||
|
$('#prev').removeClass('hide');
|
||||||
|
|
||||||
$('#salamisandwich > tbody > tr').on({
|
$('#salamisandwich > tbody > tr').on({
|
||||||
mouseover: function(){
|
mouseover: function(){
|
||||||
if($(this).children().last().has("a").length == 0)
|
if($(this).children().last().has("a").length == 0)
|
||||||
@ -161,8 +179,10 @@ function webSocketConnect() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "search":
|
||||||
|
$('#wait').modal('hide');
|
||||||
case "browse":
|
case "browse":
|
||||||
if(current_app !== 'browse')
|
if(current_app !== 'browse' && current_app !== 'search')
|
||||||
break;
|
break;
|
||||||
|
|
||||||
for (var item in obj.data) {
|
for (var item in obj.data) {
|
||||||
@ -170,24 +190,47 @@ function webSocketConnect() {
|
|||||||
case "directory":
|
case "directory":
|
||||||
$('#salamisandwich > tbody').append(
|
$('#salamisandwich > tbody').append(
|
||||||
"<tr uri=\"" + obj.data[item].dir + "\" class=\"dir\">" +
|
"<tr uri=\"" + obj.data[item].dir + "\" class=\"dir\">" +
|
||||||
"<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" +
|
"<td><span class=\"glyphicon glyphicon-folder-open\"></span></td>" +
|
||||||
"<td><a>" + basename(obj.data[item].dir) + "</a></td>" +
|
"<td><a>" + basename(obj.data[item].dir) + "</a></td>" +
|
||||||
"<td></td><td></td></tr>");
|
"<td></td><td></td></tr>"
|
||||||
break;
|
);
|
||||||
case "song":
|
break;
|
||||||
var minutes = Math.floor(obj.data[item].duration / 60);
|
case "playlist":
|
||||||
var seconds = obj.data[item].duration - minutes * 60;
|
$('#salamisandwich > tbody').append(
|
||||||
|
"<tr uri=\"" + obj.data[item].plist + "\" class=\"plist\">" +
|
||||||
|
"<td><span class=\"glyphicon glyphicon-list\"></span></td>" +
|
||||||
|
"<td><a>" + basename(obj.data[item].plist) + "</a></td>" +
|
||||||
|
"<td></td><td></td></tr>"
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "song":
|
||||||
|
var minutes = Math.floor(obj.data[item].duration / 60);
|
||||||
|
var seconds = obj.data[item].duration - minutes * 60;
|
||||||
|
|
||||||
$('#salamisandwich > tbody').append(
|
$('#salamisandwich > tbody').append(
|
||||||
"<tr uri=\"" + obj.data[item].uri + "\" class=\"song\">" +
|
"<tr uri=\"" + obj.data[item].uri + "\" class=\"song\">" +
|
||||||
"<td><span class=\"glyphicon glyphicon-music\"></span></td>" +
|
"<td><span class=\"glyphicon glyphicon-music\"></span></td>" +
|
||||||
"<td>" + obj.data[item].title +"</td>" +
|
"<td>" + obj.data[item].title +"</td>" +
|
||||||
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +"</td><td></td></tr>");
|
"<td>"+ minutes + ":" + (seconds < 10 ? '0' : '') + seconds +
|
||||||
|
"</td><td></td></tr>"
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
case "wrap":
|
||||||
case "playlist":
|
if(current_app == 'browse') {
|
||||||
|
$('#next').removeClass('hide');
|
||||||
|
} else {
|
||||||
|
$('#salamisandwich > tbody').append(
|
||||||
|
"<tr><td><span class=\"glyphicon glyphicon-remove\"></span></td>" +
|
||||||
|
"<td>Too many results, please refine your search!</td>" +
|
||||||
|
"<td></td><td></td></tr>"
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(pagination > 0)
|
||||||
|
$('#prev').removeClass('hide');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendClickableIcon(appendTo, onClickAction, glyphicon) {
|
function appendClickableIcon(appendTo, onClickAction, glyphicon) {
|
||||||
@ -212,16 +255,27 @@ function webSocketConnect() {
|
|||||||
appendClickableIcon($(this), 'MPD_API_ADD_PLAY_TRACK', 'play');
|
appendClickableIcon($(this), 'MPD_API_ADD_PLAY_TRACK', 'play');
|
||||||
},
|
},
|
||||||
click: function() {
|
click: function() {
|
||||||
if($(this).is(".song")) {
|
switch($(this).attr('class')) {
|
||||||
socket.send("MPD_API_ADD_TRACK," + $(this).attr("uri"));
|
case 'dir':
|
||||||
$('.top-right').notify({
|
app.setLocation("#/browse/0/"+$(this).attr("uri"));
|
||||||
message:{
|
break;
|
||||||
text: $('td:nth-child(2)', this).text() + " added"
|
case 'song':
|
||||||
}
|
socket.send("MPD_API_ADD_TRACK," + $(this).attr("uri"));
|
||||||
}).show();
|
$('.top-right').notify({
|
||||||
|
message:{
|
||||||
} else
|
text: $('td:nth-child(2)', this).text() + " added"
|
||||||
app.setLocation("#/browse/"+$(this).attr("uri"));
|
}
|
||||||
|
}).show();
|
||||||
|
break;
|
||||||
|
case 'plist':
|
||||||
|
socket.send("MPD_API_ADD_PLAYLIST," + $(this).attr("uri"));
|
||||||
|
$('.top-right').notify({
|
||||||
|
message:{
|
||||||
|
text: "Playlist " + $('td:nth-child(2)', this).text() + " added"
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mouseleave: function(){
|
mouseleave: function(){
|
||||||
$(this).children().last().find("a").stop().remove();
|
$(this).children().last().find("a").stop().remove();
|
||||||
@ -286,9 +340,9 @@ function webSocketConnect() {
|
|||||||
fadeOut: { enabled: true, delay: 1000 },
|
fadeOut: { enabled: true, delay: 1000 },
|
||||||
}).show();
|
}).show();
|
||||||
break;
|
break;
|
||||||
case "update_playlist":
|
case "update_queue":
|
||||||
if(current_app === 'playlist')
|
if(current_app === 'queue')
|
||||||
$.get( "/api/get_playlist", socket.onmessage);
|
socket.send('MPD_API_GET_QUEUE,'+pagination);
|
||||||
break;
|
break;
|
||||||
case "song_change":
|
case "song_change":
|
||||||
$('#currenttrack').text(" " + obj.data.title);
|
$('#currenttrack').text(" " + obj.data.title);
|
||||||
@ -304,7 +358,7 @@ function webSocketConnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($.cookie("notification") === "true")
|
if ($.cookie("notification") === "true")
|
||||||
songNotify(obj.data.title, obj.data.artist + " " + obj.data.album );
|
songNotify(obj.data.title, obj.data.artist, obj.data.album );
|
||||||
else
|
else
|
||||||
$('.top-right').notify({
|
$('.top-right').notify({
|
||||||
message:{html: notification},
|
message:{html: notification},
|
||||||
@ -315,6 +369,8 @@ function webSocketConnect() {
|
|||||||
case "mpdhost":
|
case "mpdhost":
|
||||||
$('#mpdhost').val(obj.data.host);
|
$('#mpdhost').val(obj.data.host);
|
||||||
$('#mpdport').val(obj.data.port);
|
$('#mpdport').val(obj.data.port);
|
||||||
|
if(obj.data.passwort_set)
|
||||||
|
$('#mpd_password_set').removeClass('hide');
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
$('.top-right').notify({
|
$('.top-right').notify({
|
||||||
@ -328,6 +384,7 @@ function webSocketConnect() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
socket.onclose = function(){
|
socket.onclose = function(){
|
||||||
|
console.log("disconnected");
|
||||||
$('.top-right').notify({
|
$('.top-right').notify({
|
||||||
message:{text:"Connection to ympd lost, retrying in 3 seconds "},
|
message:{text:"Connection to ympd lost, retrying in 3 seconds "},
|
||||||
type: "danger",
|
type: "danger",
|
||||||
@ -448,47 +505,104 @@ $('#btnnotify').on('click', function (e) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getVersion()
|
|
||||||
{
|
|
||||||
$.get( "/api/get_version", function(response) {
|
|
||||||
$('#ympd_version').text(response.data.ympd_version);
|
|
||||||
$('#mpd_version').text(response.data.mpd_version);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHost() {
|
function getHost() {
|
||||||
socket.send('MPD_API_GET_MPDHOST');
|
socket.send('MPD_API_GET_MPDHOST');
|
||||||
|
|
||||||
function onEnter(event) {
|
function onEnter(event) {
|
||||||
if ( event.which == 13 ) {
|
if ( event.which == 13 ) {
|
||||||
setHost();
|
confirmSettings();
|
||||||
$('#settings').modal('hide');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#mpdhost').keypress(onEnter);
|
$('#mpdhost').keypress(onEnter);
|
||||||
$('#mpdport').keypress(onEnter);
|
$('#mpdport').keypress(onEnter);
|
||||||
|
$('#mpd_pw').keypress(onEnter);
|
||||||
|
$('#mpd_pw_con').keypress(onEnter);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHost() {
|
$('#search').submit(function () {
|
||||||
|
app.setLocation("#/search/"+$('#search > div > input').val());
|
||||||
|
$('#wait').modal('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#wait').modal('hide');
|
||||||
|
}, 10000);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.page-btn').on('click', function (e) {
|
||||||
|
|
||||||
|
switch ($(this).text()) {
|
||||||
|
case "Next":
|
||||||
|
pagination += MAX_ELEMENTS_PER_PAGE;
|
||||||
|
break;
|
||||||
|
case "Previous":
|
||||||
|
pagination -= MAX_ELEMENTS_PER_PAGE;
|
||||||
|
if(pagination <= 0)
|
||||||
|
pagination = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(current_app) {
|
||||||
|
case "queue":
|
||||||
|
app.setLocation('#/'+pagination);
|
||||||
|
break;
|
||||||
|
case "browse":
|
||||||
|
app.setLocation('#/browse/'+pagination+'/'+browsepath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmSettings() {
|
||||||
|
if($('#mpd_pw').val().length + $('#mpd_pw_con').val().length > 0) {
|
||||||
|
if ($('#mpd_pw').val() !== $('#mpd_pw_con').val())
|
||||||
|
{
|
||||||
|
$('#mpd_pw_con').popover('show');
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#mpd_pw_con').popover('hide');
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
socket.send('MPD_API_SET_MPDPASS,'+$('#mpd_pw').val());
|
||||||
|
}
|
||||||
socket.send('MPD_API_SET_MPDHOST,'+$('#mpdport').val()+','+$('#mpdhost').val());
|
socket.send('MPD_API_SET_MPDHOST,'+$('#mpdport').val()+','+$('#mpdhost').val());
|
||||||
|
$('#settings').modal('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$('#mpd_password_set > button').on('click', function (e) {
|
||||||
|
socket.send('MPD_API_SET_MPDPASS,');
|
||||||
|
$('#mpd_pw').val("");
|
||||||
|
$('#mpd_pw_con').val("");
|
||||||
|
$('#mpd_password_set').addClass('hide');
|
||||||
|
})
|
||||||
|
|
||||||
function notificationsSupported() {
|
function notificationsSupported() {
|
||||||
return "webkitNotifications" in window;
|
return "webkitNotifications" in window;
|
||||||
}
|
}
|
||||||
|
|
||||||
function songNotify(artist, title) {
|
function songNotify(title, artist, album) {
|
||||||
if (!notificationsSupported())
|
/*var opt = {
|
||||||
return;
|
type: "list",
|
||||||
|
title: title,
|
||||||
if (window.webkitNotifications.checkPermission() == 0) {
|
message: title,
|
||||||
var notification = window.webkitNotifications.createNotification("assets/favicon.ico", artist, title);
|
items: []
|
||||||
notification.show();
|
|
||||||
setTimeout(function(notification) {
|
|
||||||
notification.cancel();
|
|
||||||
}, 3000, notification);
|
|
||||||
} else {
|
|
||||||
window.webkitNotifications.requestPermission();
|
|
||||||
}
|
}
|
||||||
|
if(artist.length > 0)
|
||||||
|
opt.items.push({title: "Artist", message: artist});
|
||||||
|
if(album.length > 0)
|
||||||
|
opt.items.push({title: "Album", message: album});
|
||||||
|
*/
|
||||||
|
//chrome.notifications.create(id, options, creationCallback);
|
||||||
|
|
||||||
|
var textNotification = "";
|
||||||
|
if(artist.length > 0)
|
||||||
|
textNotification += " " + artist;
|
||||||
|
if(album.length > 0)
|
||||||
|
textNotification += "\n " + album;
|
||||||
|
|
||||||
|
var notification = window.webkitNotifications.createNotification("assets/favicon.ico", textNotification, title);
|
||||||
|
notification.show();
|
||||||
|
setTimeout(function(notification) {
|
||||||
|
notification.cancel();
|
||||||
|
}, 3000, notification);
|
||||||
}
|
}
|
||||||
|
94
htdocs/mkdata.c
Normal file
94
htdocs/mkdata.c
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/* This program is used to embed arbitrary data into a C binary. It takes
|
||||||
|
* a list of files as an input, and produces a .c data file that contains
|
||||||
|
* contents of all these files as collection of char arrays.
|
||||||
|
*
|
||||||
|
* Usage: ./mkdata <this_file> <file1> [file2, ...] > embedded_data.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <error.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
const char* header =
|
||||||
|
"#include <stddef.h>\n"
|
||||||
|
"#include <string.h>\n"
|
||||||
|
"#include <sys/types.h>\n"
|
||||||
|
"#include \"src/http_server.h\"\n"
|
||||||
|
"\n"
|
||||||
|
"static const struct embedded_file embedded_files[] = {\n";
|
||||||
|
|
||||||
|
const char* footer =
|
||||||
|
" {NULL, NULL, NULL, 0}\n"
|
||||||
|
"};\n"
|
||||||
|
"\n"
|
||||||
|
"const struct embedded_file *find_embedded_file(const char *name) {\n"
|
||||||
|
" const struct embedded_file *p;\n"
|
||||||
|
" for (p = embedded_files; p->name != NULL; p++)\n"
|
||||||
|
" if (!strcmp(p->name, name))\n"
|
||||||
|
" return p;\n"
|
||||||
|
" return NULL;\n"
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
static const char* get_mime(char* filename)
|
||||||
|
{
|
||||||
|
const char *extension = strrchr(filename, '.');
|
||||||
|
if(!strcmp(extension, ".js"))
|
||||||
|
return "application/javascript";
|
||||||
|
if(!strcmp(extension, ".css"))
|
||||||
|
return "text/css";
|
||||||
|
if(!strcmp(extension, ".ico"))
|
||||||
|
return "image/vnd.microsoft.icon";
|
||||||
|
if(!strcmp(extension, ".woff"))
|
||||||
|
return "application/font-woff";
|
||||||
|
if(!strcmp(extension, ".ttf"))
|
||||||
|
return "application/x-font-ttf";
|
||||||
|
if(!strcmp(extension, ".eot"))
|
||||||
|
return "application/octet-stream";
|
||||||
|
if(!strcmp(extension, ".svg"))
|
||||||
|
return "image/svg+xml";
|
||||||
|
if(!strcmp(extension, ".html"))
|
||||||
|
return "text/html";
|
||||||
|
return "text/plain";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
int i, j, buf;
|
||||||
|
FILE *fd;
|
||||||
|
|
||||||
|
if(argc <= 1)
|
||||||
|
error(EXIT_FAILURE, 0, "Usage: ./%s <this_file> <file1> [file2, ...] > embedded_data.c", argv[0]);
|
||||||
|
|
||||||
|
for(i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
printf("static const unsigned char v%d[] = {", i);
|
||||||
|
|
||||||
|
fd = fopen(argv[i], "r");
|
||||||
|
if(!fd)
|
||||||
|
error(EXIT_FAILURE, errno, "Failed open file %s", argv[i]);
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
while((buf = fgetc(fd)) != EOF)
|
||||||
|
{
|
||||||
|
if(!(j % 12))
|
||||||
|
putchar('\n');
|
||||||
|
|
||||||
|
printf(" %#04x, ", buf);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
printf(" 0x00\n};\n\n");
|
||||||
|
fclose(fd);
|
||||||
|
}
|
||||||
|
fputs(header, stdout);
|
||||||
|
|
||||||
|
for(i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
printf(" {\"%s\", v%d, \"%s\", sizeof(v%d) - 1}, \n",
|
||||||
|
argv[i]+6, i, get_mime(argv[i]), i);
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs(footer, stdout);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
@ -1,29 +1,19 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __CONFIG_H__
|
#ifndef __CONFIG_H__
|
||||||
|
@ -1,184 +1,43 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libwebsockets.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <mpd/client.h>
|
|
||||||
|
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "mpd_client.h"
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
char *resource_path = LOCAL_RESOURCE_PATH;
|
int callback_http(struct mg_connection *c)
|
||||||
extern enum mpd_conn_states mpd_conn_state;
|
|
||||||
|
|
||||||
struct serveable {
|
|
||||||
const char *urlpath;
|
|
||||||
const char *mimetype;
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct serveable whitelist[] = {
|
|
||||||
{ "/css/bootstrap.css", "text/css" },
|
|
||||||
{ "/css/mpd.css", "text/css" },
|
|
||||||
|
|
||||||
{ "/js/bootstrap.min.js", "text/javascript" },
|
|
||||||
{ "/js/mpd.js", "text/javascript" },
|
|
||||||
{ "/js/jquery-1.10.2.min.js", "text/javascript" },
|
|
||||||
{ "/js/jquery.cookie.js", "text/javascript" },
|
|
||||||
{ "/js/bootstrap-slider.js", "text/javascript" },
|
|
||||||
{ "/js/bootstrap-notify.js", "text/javascript" },
|
|
||||||
{ "/js/sammy.js", "text/javascript" },
|
|
||||||
|
|
||||||
{ "/fonts/glyphicons-halflings-regular.woff", "application/x-font-woff"},
|
|
||||||
{ "/fonts/glyphicons-halflings-regular.svg", "image/svg+xml"},
|
|
||||||
{ "/fonts/glyphicons-halflings-regular.ttf", "application/x-font-ttf"},
|
|
||||||
{ "/fonts/glyphicons-halflings-regular.eot", "application/vnd.ms-fontobject"},
|
|
||||||
|
|
||||||
{ "/assets/favicon.ico", "image/vnd.microsoft.icon" },
|
|
||||||
|
|
||||||
/* last one is the default served if no match */
|
|
||||||
{ "/index.html", "text/html" },
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char http_header[] = "HTTP/1.0 200 OK\x0d\x0a"
|
|
||||||
"Server: libwebsockets\x0d\x0a"
|
|
||||||
"Content-Type: application/json\x0d\x0a"
|
|
||||||
"Content-Length: 000000\x0d\x0a\x0d\x0a";
|
|
||||||
|
|
||||||
/* Converts a hex character to its integer value */
|
|
||||||
char from_hex(char ch) {
|
|
||||||
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns a url-decoded version of str */
|
|
||||||
/* IMPORTANT: be sure to free() the returned string after use */
|
|
||||||
char *url_decode(char *str) {
|
|
||||||
char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
|
|
||||||
while (*pstr) {
|
|
||||||
if (*pstr == '%') {
|
|
||||||
if (pstr[1] && pstr[2]) {
|
|
||||||
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
|
|
||||||
pstr += 2;
|
|
||||||
}
|
|
||||||
} else if (*pstr == '+') {
|
|
||||||
*pbuf++ = ' ';
|
|
||||||
} else {
|
|
||||||
*pbuf++ = *pstr;
|
|
||||||
}
|
|
||||||
pstr++;
|
|
||||||
}
|
|
||||||
*pbuf = '\0';
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
int callback_http(struct libwebsocket_context *context,
|
|
||||||
struct libwebsocket *wsi,
|
|
||||||
enum libwebsocket_callback_reasons reason, void *user,
|
|
||||||
void *in, size_t len)
|
|
||||||
{
|
{
|
||||||
char *response_buffer, *p;
|
const struct embedded_file *req_file;
|
||||||
char buf[64];
|
|
||||||
size_t n, response_size = 0;
|
|
||||||
|
|
||||||
switch (reason) {
|
if(!strcmp(c->uri, "/"))
|
||||||
case LWS_CALLBACK_HTTP:
|
req_file = find_embedded_file("/index.html");
|
||||||
if(in && strncmp((const char *)in, "/api/", 5) == 0)
|
else
|
||||||
{
|
req_file = find_embedded_file(c->uri);
|
||||||
|
|
||||||
p = (char *)malloc(MAX_SIZE + 100);
|
if(req_file)
|
||||||
memcpy(p, http_header, sizeof(http_header) - 1);
|
{
|
||||||
response_buffer = p + sizeof(http_header) - 1;
|
mg_send_header(c, "Content-Type", req_file->mimetype);
|
||||||
|
mg_send_data(c, req_file->data, req_file->size);
|
||||||
/* put content length and payload to buffer */
|
|
||||||
if(mpd_conn_state != MPD_CONNECTED) {}
|
return MG_REQUEST_PROCESSED;
|
||||||
else if(strncmp((const char *)in, "/api/get_browse", 15) == 0)
|
|
||||||
{
|
|
||||||
char *url;
|
|
||||||
if(sscanf(in, "/api/get_browse/%m[^\t\n]", &url) == 1)
|
|
||||||
{
|
|
||||||
char *url_decoded = url_decode(url);
|
|
||||||
response_size = mpd_put_browse(response_buffer, url_decoded);
|
|
||||||
free(url_decoded);
|
|
||||||
free(url);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
response_size = mpd_put_browse(response_buffer, "/");
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(strncmp((const char *)in, "/api/get_playlist", 17) == 0)
|
|
||||||
response_size = mpd_put_playlist(response_buffer);
|
|
||||||
else if(strncmp((const char *)in, "/api/get_version", 16) == 0)
|
|
||||||
response_size = snprintf(response_buffer, MAX_SIZE,
|
|
||||||
"{\"type\":\"version\",\"data\":{"
|
|
||||||
"\"ympd_version\":\"%d.%d.%d\","
|
|
||||||
"\"mpd_version\":\"%d.%d.%d\""
|
|
||||||
"}}",
|
|
||||||
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH,
|
|
||||||
LIBMPDCLIENT_MAJOR_VERSION, LIBMPDCLIENT_MINOR_VERSION,
|
|
||||||
LIBMPDCLIENT_PATCH_VERSION);
|
|
||||||
|
|
||||||
/* Copy size to content-length field */
|
|
||||||
sprintf(buf, "%6zu", response_size);
|
|
||||||
memcpy(p + sizeof(http_header) - 11, buf, 6);
|
|
||||||
|
|
||||||
n = libwebsocket_write(wsi, (unsigned char *)p,
|
|
||||||
sizeof(http_header) - 1 + response_size, LWS_WRITE_HTTP);
|
|
||||||
|
|
||||||
free(p);
|
|
||||||
/*
|
|
||||||
* book us a LWS_CALLBACK_HTTP_WRITEABLE callback
|
|
||||||
*/
|
|
||||||
libwebsocket_callback_on_writable(context, wsi);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
|
|
||||||
if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
|
|
||||||
|
|
||||||
if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype, NULL))
|
|
||||||
return -1; /* through completion or error, close the socket */
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
|
|
||||||
/* kill the connection after we sent one file */
|
|
||||||
return -1;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
mg_send_status(c, 404);
|
||||||
|
mg_printf_data(c, "Not Found");
|
||||||
|
return MG_REQUEST_PROCESSED;
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,35 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __HTTP_SERVER_H__
|
#ifndef __HTTP_SERVER_H__
|
||||||
#define __HTTP_SERVER_H__
|
#define __HTTP_SERVER_H__
|
||||||
|
|
||||||
#include <libwebsockets.h>
|
#include "mongoose.h"
|
||||||
|
|
||||||
struct per_session_data__http {
|
struct embedded_file {
|
||||||
int fd;
|
const char *name;
|
||||||
|
const unsigned char *data;
|
||||||
|
const char *mimetype;
|
||||||
|
size_t size;
|
||||||
};
|
};
|
||||||
int callback_http(struct libwebsocket_context *context,
|
|
||||||
struct libwebsocket *wsi,
|
const struct embedded_file *find_embedded_file(const char *name);
|
||||||
enum libwebsocket_callback_reasons reason, void *user,
|
int callback_http(struct mg_connection *c);
|
||||||
void *in, size_t len);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
58
src/json_encode.c
Normal file
58
src/json_encode.c
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
|
// Copyright (c) 2013 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// This library is dual-licensed: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation. For the terms of this
|
||||||
|
// license, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// You are free to use this library under the terms of the GNU General
|
||||||
|
// Public License, 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.
|
||||||
|
//
|
||||||
|
// Alternatively, you can license this library under a commercial
|
||||||
|
// license, as set out in <http://cesanta.com/products.html>.
|
||||||
|
|
||||||
|
// json encoder 'frozen' from https://github.com/cesanta/frozen
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "json_encode.h"
|
||||||
|
|
||||||
|
int json_emit_int(char *buf, int buf_len, long int value) {
|
||||||
|
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%ld", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int json_emit_double(char *buf, int buf_len, double value) {
|
||||||
|
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%g", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int json_emit_quoted_str(char *buf, int buf_len, const char *str) {
|
||||||
|
int i = 0, j = 0, ch;
|
||||||
|
|
||||||
|
#define EMIT(x) do { if (j < buf_len) buf[j++] = x; } while (0)
|
||||||
|
|
||||||
|
EMIT('"');
|
||||||
|
while ((ch = str[i++]) != '\0' && j < buf_len) {
|
||||||
|
switch (ch) {
|
||||||
|
case '"': EMIT('\\'); EMIT('"'); break;
|
||||||
|
case '\\': EMIT('\\'); EMIT('\\'); break;
|
||||||
|
case '\b': EMIT('\\'); EMIT('b'); break;
|
||||||
|
case '\f': EMIT('\\'); EMIT('f'); break;
|
||||||
|
case '\n': EMIT('\\'); EMIT('n'); break;
|
||||||
|
case '\r': EMIT('\\'); EMIT('r'); break;
|
||||||
|
case '\t': EMIT('\\'); EMIT('t'); break;
|
||||||
|
default: EMIT(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EMIT('"');
|
||||||
|
EMIT(0);
|
||||||
|
|
||||||
|
return j == 0 ? 0 : j - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int json_emit_raw_str(char *buf, int buf_len, const char *str) {
|
||||||
|
return buf_len <= 0 ? 0 : snprintf(buf, buf_len, "%s", str);
|
||||||
|
}
|
27
src/json_encode.h
Normal file
27
src/json_encode.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* ympd
|
||||||
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
|
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.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __JSON_ENCODE_H__
|
||||||
|
#define __JSON_ENCODE_H__
|
||||||
|
|
||||||
|
int json_emit_int(char *buf, int buf_len, long int value);
|
||||||
|
int json_emit_double(char *buf, int buf_len, double value);
|
||||||
|
int json_emit_quoted_str(char *buf, int buf_len, const char *str);
|
||||||
|
int json_emit_raw_str(char *buf, int buf_len, const char *str);
|
||||||
|
|
||||||
|
#endif
|
4296
src/mongoose.c
Normal file
4296
src/mongoose.c
Normal file
File diff suppressed because it is too large
Load Diff
125
src/mongoose.h
Normal file
125
src/mongoose.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||||
|
// Copyright (c) 2013-2014 Cesanta Software Limited
|
||||||
|
// All rights reserved
|
||||||
|
//
|
||||||
|
// This library is dual-licensed: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License version 2 as
|
||||||
|
// published by the Free Software Foundation. For the terms of this
|
||||||
|
// license, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// You are free to use this library under the terms of the GNU General
|
||||||
|
// Public License, 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.
|
||||||
|
//
|
||||||
|
// Alternatively, you can license this library under a commercial
|
||||||
|
// license, as set out in <http://cesanta.com/>.
|
||||||
|
//
|
||||||
|
// NOTE: Detailed API documentation is at http://cesanta.com/#docs
|
||||||
|
|
||||||
|
#ifndef MONGOOSE_HEADER_INCLUDED
|
||||||
|
#define MONGOOSE_HEADER_INCLUDED
|
||||||
|
|
||||||
|
#define MONGOOSE_VERSION "5.3"
|
||||||
|
|
||||||
|
#include <stdio.h> // required for FILE
|
||||||
|
#include <stddef.h> // required for size_t
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
// This structure contains information about HTTP request.
|
||||||
|
struct mg_connection {
|
||||||
|
const char *request_method; // "GET", "POST", etc
|
||||||
|
const char *uri; // URL-decoded URI
|
||||||
|
const char *http_version; // E.g. "1.0", "1.1"
|
||||||
|
const char *query_string; // URL part after '?', not including '?', or NULL
|
||||||
|
|
||||||
|
char remote_ip[48]; // Max IPv6 string length is 45 characters
|
||||||
|
const char *local_ip; // Local IP address
|
||||||
|
unsigned short remote_port; // Client's port
|
||||||
|
unsigned short local_port; // Local port number
|
||||||
|
|
||||||
|
int num_headers; // Number of HTTP headers
|
||||||
|
struct mg_header {
|
||||||
|
const char *name; // HTTP header name
|
||||||
|
const char *value; // HTTP header value
|
||||||
|
} http_headers[30];
|
||||||
|
|
||||||
|
char *content; // POST (or websocket message) data, or NULL
|
||||||
|
size_t content_len; // content length
|
||||||
|
|
||||||
|
int is_websocket; // Connection is a websocket connection
|
||||||
|
int status_code; // HTTP status code for HTTP error handler
|
||||||
|
int wsbits; // First byte of the websocket frame
|
||||||
|
void *server_param; // Parameter passed to mg_add_uri_handler()
|
||||||
|
void *connection_param; // Placeholder for connection-specific data
|
||||||
|
void *callback_param; // Used by mg_iterate_over_connections()
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mg_server; // Opaque structure describing server instance
|
||||||
|
typedef int (*mg_handler_t)(struct mg_connection *);
|
||||||
|
|
||||||
|
// Server management functions
|
||||||
|
struct mg_server *mg_create_server(void *server_param);
|
||||||
|
void mg_destroy_server(struct mg_server **);
|
||||||
|
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
|
||||||
|
unsigned int mg_poll_server(struct mg_server *, int milliseconds);
|
||||||
|
void mg_set_request_handler(struct mg_server *, mg_handler_t);
|
||||||
|
void mg_set_http_close_handler(struct mg_server *, mg_handler_t);
|
||||||
|
void mg_set_http_error_handler(struct mg_server *, mg_handler_t);
|
||||||
|
void mg_set_auth_handler(struct mg_server *, mg_handler_t);
|
||||||
|
const char **mg_get_valid_option_names(void);
|
||||||
|
const char *mg_get_option(const struct mg_server *server, const char *name);
|
||||||
|
void mg_set_listening_socket(struct mg_server *, int sock);
|
||||||
|
int mg_get_listening_socket(struct mg_server *);
|
||||||
|
void mg_iterate_over_connections(struct mg_server *, mg_handler_t, void *);
|
||||||
|
|
||||||
|
// Connection management functions
|
||||||
|
void mg_send_status(struct mg_connection *, int status_code);
|
||||||
|
void mg_send_header(struct mg_connection *, const char *name, const char *val);
|
||||||
|
void mg_send_data(struct mg_connection *, const void *data, int data_len);
|
||||||
|
void mg_printf_data(struct mg_connection *, const char *format, ...);
|
||||||
|
|
||||||
|
int mg_websocket_write(struct mg_connection *, int opcode,
|
||||||
|
const char *data, size_t data_len);
|
||||||
|
|
||||||
|
// Deprecated in favor of mg_send_* interface
|
||||||
|
int mg_write(struct mg_connection *, const void *buf, int len);
|
||||||
|
int mg_printf(struct mg_connection *conn, const char *fmt, ...);
|
||||||
|
|
||||||
|
const char *mg_get_header(const struct mg_connection *, const char *name);
|
||||||
|
const char *mg_get_mime_type(const char *name, const char *default_mime_type);
|
||||||
|
int mg_get_var(const struct mg_connection *conn, const char *var_name,
|
||||||
|
char *buf, size_t buf_len);
|
||||||
|
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
|
||||||
|
int mg_parse_multipart(const char *buf, int buf_len,
|
||||||
|
char *var_name, int var_name_len,
|
||||||
|
char *file_name, int file_name_len,
|
||||||
|
const char **data, int *data_len);
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
void *mg_start_thread(void *(*func)(void *), void *param);
|
||||||
|
char *mg_md5(char buf[33], ...);
|
||||||
|
int mg_authorize_digest(struct mg_connection *c, FILE *fp);
|
||||||
|
|
||||||
|
// Callback function return codes
|
||||||
|
enum { MG_REQUEST_NOT_PROCESSED, MG_REQUEST_PROCESSED, MG_REQUEST_CALL_AGAIN };
|
||||||
|
enum { MG_AUTH_FAIL, MG_AUTH_OK };
|
||||||
|
enum { MG_ERROR_NOT_PROCESSED, MG_ERROR_PROCESSED };
|
||||||
|
enum { MG_CLIENT_CONTINUE, MG_CLIENT_CLOSE };
|
||||||
|
|
||||||
|
// HTTP client events
|
||||||
|
enum {
|
||||||
|
MG_CONNECT_SUCCESS, MG_CONNECT_FAILURE,
|
||||||
|
MG_DOWNLOAD_SUCCESS, MG_DOWNLOAD_FAILURE
|
||||||
|
};
|
||||||
|
int mg_connect(struct mg_server *, const char *host, int port, int use_ssl,
|
||||||
|
mg_handler_t handler, void *param);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // MONGOOSE_HEADER_INCLUDED
|
719
src/mpd_client.c
719
src/mpd_client.c
@ -1,29 +1,19 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -31,275 +21,301 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
|
|
||||||
#include <mpd/client.h>
|
#include <mpd/client.h>
|
||||||
#include <mpd/status.h>
|
|
||||||
#include <mpd/song.h>
|
|
||||||
#include <mpd/entity.h>
|
|
||||||
#include <mpd/search.h>
|
|
||||||
#include <mpd/tag.h>
|
|
||||||
|
|
||||||
#include "mpd_client.h"
|
#include "mpd_client.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "json_encode.h"
|
||||||
|
|
||||||
struct mpd_connection *conn = NULL;
|
const char * mpd_cmd_strs[] = {
|
||||||
enum mpd_conn_states mpd_conn_state = MPD_DISCONNECTED;
|
MPD_CMDS(GEN_STR)
|
||||||
enum mpd_state mpd_play_state = MPD_STATE_UNKNOWN;
|
};
|
||||||
|
|
||||||
int callback_ympd(struct libwebsocket_context *context,
|
static inline enum mpd_cmd_ids get_cmd_id(char *cmd)
|
||||||
struct libwebsocket *wsi,
|
|
||||||
enum libwebsocket_callback_reasons reason,
|
|
||||||
void *user, void *in, size_t len)
|
|
||||||
{
|
{
|
||||||
size_t n, m = -1;
|
for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++)
|
||||||
char *buf = NULL, *p;
|
if(!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
|
||||||
struct per_session_data__ympd *pss = (struct per_session_data__ympd *)user;
|
return i;
|
||||||
|
|
||||||
switch (reason) {
|
return -1;
|
||||||
case LWS_CALLBACK_ESTABLISHED:
|
}
|
||||||
lwsl_info("mpd_client: "
|
|
||||||
"LWS_CALLBACK_ESTABLISHED\n");
|
int callback_mpd(struct mg_connection *c)
|
||||||
|
{
|
||||||
|
enum mpd_cmd_ids cmd_id = get_cmd_id(c->content);
|
||||||
|
size_t n = 0;
|
||||||
|
unsigned int uint_buf, uint_buf_2;
|
||||||
|
int int_buf;
|
||||||
|
char *p_charbuf = NULL;
|
||||||
|
|
||||||
|
if(cmd_id == -1)
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
|
|
||||||
|
if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
|
||||||
|
cmd_id != MPD_API_GET_MPDHOST && cmd_id != MPD_API_SET_MPDPASS)
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
|
|
||||||
|
switch(cmd_id)
|
||||||
|
{
|
||||||
|
case MPD_API_UPDATE_DB:
|
||||||
|
mpd_run_update(mpd.conn, NULL);
|
||||||
break;
|
break;
|
||||||
|
case MPD_API_SET_PAUSE:
|
||||||
case LWS_CALLBACK_SERVER_WRITEABLE:
|
mpd_run_toggle_pause(mpd.conn);
|
||||||
buf = (char *)malloc(MAX_SIZE + LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING);
|
|
||||||
if(buf == NULL) {
|
|
||||||
lwsl_err("ERROR Failed allocating memory\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
|
|
||||||
|
|
||||||
if(pss->do_send & DO_SEND_ERROR) {
|
|
||||||
n = snprintf(p, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
|
|
||||||
mpd_connection_get_error_message(conn));
|
|
||||||
pss->do_send &= ~DO_SEND_ERROR;
|
|
||||||
|
|
||||||
/* Try to recover error */
|
|
||||||
if (!mpd_connection_clear_error(conn))
|
|
||||||
mpd_conn_state = MPD_FAILURE;
|
|
||||||
}
|
|
||||||
else if(mpd_conn_state != MPD_CONNECTED) {
|
|
||||||
n = snprintf(p, MAX_SIZE, "{\"type\":\"disconnected\"}");
|
|
||||||
}
|
|
||||||
else if(pss->do_send & DO_SEND_PLAYLIST) {
|
|
||||||
/*n = mpd_put_playlist(p);*/
|
|
||||||
n = snprintf(p, MAX_SIZE, "{\"type\":\"update_playlist\"}");
|
|
||||||
pss->do_send &= ~DO_SEND_PLAYLIST;
|
|
||||||
}
|
|
||||||
else if(pss->do_send & DO_SEND_TRACK_INFO) {
|
|
||||||
n = mpd_put_current_song(p);
|
|
||||||
pss->do_send &= ~DO_SEND_TRACK_INFO;
|
|
||||||
}
|
|
||||||
else if(pss->do_send & DO_SEND_BROWSE) {
|
|
||||||
n = mpd_put_browse(p, pss->browse_path);
|
|
||||||
pss->do_send &= ~DO_SEND_BROWSE;
|
|
||||||
free(pss->browse_path);
|
|
||||||
}
|
|
||||||
else if(pss->do_send & DO_SEND_MPDHOST) {
|
|
||||||
n = snprintf(p, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
|
|
||||||
"{\"host\" : \"%s\", \"port\": \"%d\"}"
|
|
||||||
"}", mpd_host, mpd_port);
|
|
||||||
pss->do_send &= ~DO_SEND_MPDHOST;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Default Action */
|
|
||||||
int current_song_id;
|
|
||||||
unsigned queue_version;
|
|
||||||
|
|
||||||
n = mpd_put_state(p, ¤t_song_id, &queue_version);
|
|
||||||
if(current_song_id != pss->current_song_id)
|
|
||||||
{
|
|
||||||
pss->current_song_id = current_song_id;
|
|
||||||
pss->do_send |= DO_SEND_TRACK_INFO;
|
|
||||||
libwebsocket_callback_on_writable(context, wsi);
|
|
||||||
}
|
|
||||||
else if(pss->queue_version != queue_version) {
|
|
||||||
pss->queue_version = queue_version;
|
|
||||||
pss->do_send |= DO_SEND_PLAYLIST;
|
|
||||||
libwebsocket_callback_on_writable(context, wsi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n > 0)
|
|
||||||
m = libwebsocket_write(wsi, (unsigned char *)p, n, LWS_WRITE_TEXT);
|
|
||||||
|
|
||||||
if (m < n) {
|
|
||||||
lwsl_err("ERROR %d writing to socket\n", n, m);
|
|
||||||
free(buf);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
break;
|
break;
|
||||||
|
case MPD_API_SET_PREV:
|
||||||
case LWS_CALLBACK_RECEIVE:
|
mpd_run_previous(mpd.conn);
|
||||||
if(!strcmp((const char *)in, MPD_API_GET_PLAYLIST))
|
break;
|
||||||
pss->do_send |= DO_SEND_PLAYLIST;
|
case MPD_API_SET_NEXT:
|
||||||
else if(!strcmp((const char *)in, MPD_API_GET_TRACK_INFO))
|
mpd_run_next(mpd.conn);
|
||||||
pss->do_send |= DO_SEND_TRACK_INFO;
|
break;
|
||||||
else if(!strcmp((const char *)in, MPD_API_UPDATE_DB))
|
case MPD_API_SET_PLAY:
|
||||||
mpd_run_update(conn, NULL);
|
mpd_run_play(mpd.conn);
|
||||||
else if(!strcmp((const char *)in, MPD_API_SET_PAUSE))
|
break;
|
||||||
mpd_run_toggle_pause(conn);
|
case MPD_API_SET_STOP:
|
||||||
else if(!strcmp((const char *)in, MPD_API_SET_PREV))
|
mpd_run_stop(mpd.conn);
|
||||||
mpd_run_previous(conn);
|
break;
|
||||||
else if(!strcmp((const char *)in, MPD_API_SET_NEXT))
|
case MPD_API_RM_ALL:
|
||||||
mpd_run_next(conn);
|
mpd_run_clear(mpd.conn);
|
||||||
else if(!strcmp((const char *)in, MPD_API_SET_PLAY))
|
break;
|
||||||
mpd_run_play(conn);
|
case MPD_API_RM_TRACK:
|
||||||
else if(!strcmp((const char *)in, MPD_API_SET_STOP))
|
if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
|
||||||
mpd_run_stop(conn);
|
mpd_run_delete_id(mpd.conn, uint_buf);
|
||||||
else if(!strcmp((const char *)in, MPD_API_RM_ALL))
|
break;
|
||||||
mpd_run_clear(conn);
|
case MPD_API_PLAY_TRACK:
|
||||||
else if(!strncmp((const char *)in, MPD_API_RM_TRACK, sizeof(MPD_API_RM_TRACK)-1)) {
|
if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
|
||||||
unsigned id;
|
mpd_run_play_id(mpd.conn, uint_buf);
|
||||||
if(sscanf(in, "MPD_API_RM_TRACK,%u", &id))
|
break;
|
||||||
mpd_run_delete_id(conn, id);
|
case MPD_API_TOGGLE_RANDOM:
|
||||||
|
if(sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
|
||||||
libwebsocket_callback_on_writable(context, wsi);
|
mpd_run_random(mpd.conn, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_TOGGLE_REPEAT:
|
||||||
|
if(sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf))
|
||||||
|
mpd_run_repeat(mpd.conn, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_TOGGLE_CONSUME:
|
||||||
|
if(sscanf(c->content, "MPD_API_TOGGLE_CONSUME,%u", &uint_buf))
|
||||||
|
mpd_run_consume(mpd.conn, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_TOGGLE_SINGLE:
|
||||||
|
if(sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
|
||||||
|
mpd_run_single(mpd.conn, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_SET_VOLUME:
|
||||||
|
if(sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
|
||||||
|
mpd_run_set_volume(mpd.conn, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_SET_SEEK:
|
||||||
|
if(sscanf(c->content, "MPD_API_SET_SEEK,%u,%u", &uint_buf, &uint_buf_2))
|
||||||
|
mpd_run_seek_id(mpd.conn, uint_buf, uint_buf_2);
|
||||||
|
break;
|
||||||
|
case MPD_API_GET_QUEUE:
|
||||||
|
if(sscanf(c->content, "MPD_API_GET_QUEUE,%u", &uint_buf))
|
||||||
|
n = mpd_put_queue(mpd.buf, uint_buf);
|
||||||
|
break;
|
||||||
|
case MPD_API_GET_BROWSE:
|
||||||
|
if(sscanf(c->content, "MPD_API_GET_BROWSE,%u,%m[^\t\n]", &uint_buf, &p_charbuf) && p_charbuf != NULL)
|
||||||
|
{
|
||||||
|
n = mpd_put_browse(mpd.buf, p_charbuf, uint_buf);
|
||||||
|
free(p_charbuf);
|
||||||
}
|
}
|
||||||
else if(!strncmp((const char *)in, MPD_API_PLAY_TRACK, sizeof(MPD_API_PLAY_TRACK)-1)) {
|
break;
|
||||||
unsigned id;
|
case MPD_API_ADD_TRACK:
|
||||||
if(sscanf(in, "MPD_API_PLAY_TRACK,%u", &id))
|
if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||||
mpd_run_play_id(conn, id);
|
{
|
||||||
|
mpd_run_add(mpd.conn, p_charbuf);
|
||||||
|
free(p_charbuf);
|
||||||
}
|
}
|
||||||
else if(!strncmp((const char *)in, MPD_API_TOGGLE_RANDOM, sizeof(MPD_API_TOGGLE_RANDOM)-1)) {
|
break;
|
||||||
unsigned random;
|
case MPD_API_ADD_PLAY_TRACK:
|
||||||
if(sscanf(in, "MPD_API_TOGGLE_RANDOM,%u", &random))
|
if(sscanf(c->content, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||||
mpd_run_random(conn, random);
|
{
|
||||||
|
int_buf = mpd_run_add_id(mpd.conn, p_charbuf);
|
||||||
|
if(int_buf != -1)
|
||||||
|
mpd_run_play_id(mpd.conn, int_buf);
|
||||||
|
free(p_charbuf);
|
||||||
}
|
}
|
||||||
else if(!strncmp((const char *)in, MPD_API_TOGGLE_REPEAT, sizeof(MPD_API_TOGGLE_REPEAT)-1)) {
|
break;
|
||||||
unsigned repeat;
|
case MPD_API_ADD_PLAYLIST:
|
||||||
if(sscanf(in, "MPD_API_TOGGLE_REPEAT,%u", &repeat))
|
if(sscanf(c->content, "MPD_API_ADD_PLAYLIST,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||||
mpd_run_repeat(conn, repeat);
|
{
|
||||||
|
mpd_run_load(mpd.conn, p_charbuf);
|
||||||
|
free(p_charbuf);
|
||||||
}
|
}
|
||||||
else if(!strncmp((const char *)in, MPD_API_TOGGLE_CONSUME, sizeof(MPD_API_TOGGLE_CONSUME)-1)) {
|
break;
|
||||||
unsigned consume;
|
case MPD_API_SEARCH:
|
||||||
if(sscanf(in, "MPD_API_TOGGLE_CONSUME,%u", &consume))
|
if(sscanf(c->content, "MPD_API_SEARCH,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||||
mpd_run_consume(conn, consume);
|
{
|
||||||
}
|
n = mpd_search(mpd.buf, p_charbuf);
|
||||||
else if(!strncmp((const char *)in, MPD_API_TOGGLE_SINGLE, sizeof(MPD_API_TOGGLE_SINGLE)-1)) {
|
free(p_charbuf);
|
||||||
unsigned single;
|
|
||||||
if(sscanf(in, "MPD_API_TOGGLE_SINGLE,%u", &single))
|
|
||||||
mpd_run_single(conn, single);
|
|
||||||
}
|
|
||||||
else if(!strncmp((const char *)in, MPD_API_SET_VOLUME, sizeof(MPD_API_SET_VOLUME)-1)) {
|
|
||||||
unsigned int volume;
|
|
||||||
if(sscanf(in, "MPD_API_SET_VOLUME,%ud", &volume) && volume <= 100)
|
|
||||||
mpd_run_set_volume(conn, volume);
|
|
||||||
}
|
|
||||||
else if(!strncmp((const char *)in, MPD_API_SET_SEEK, sizeof(MPD_API_SET_SEEK)-1)) {
|
|
||||||
unsigned int seek, songid;
|
|
||||||
if(sscanf(in, "MPD_API_SET_SEEK,%u,%u", &songid, &seek)) {
|
|
||||||
mpd_run_seek_id(conn, songid, seek);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(!strncmp((const char *)in, MPD_API_GET_BROWSE, sizeof(MPD_API_GET_BROWSE)-1)) {
|
|
||||||
char *dir;
|
|
||||||
if(sscanf(in, "MPD_API_GET_BROWSE,%m[^\t\n]", &dir) && dir != NULL) {
|
|
||||||
pss->do_send |= DO_SEND_BROWSE;
|
|
||||||
pss->browse_path = dir;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(!strncmp((const char *)in, MPD_API_ADD_TRACK, sizeof(MPD_API_ADD_TRACK)-1)) {
|
|
||||||
char *uri;
|
|
||||||
if(sscanf(in, "MPD_API_ADD_TRACK,%m[^\t\n]", &uri) && uri != NULL) {
|
|
||||||
mpd_run_add(conn, uri);
|
|
||||||
free(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(!strncmp((const char *)in, MPD_API_ADD_PLAY_TRACK, sizeof(MPD_API_ADD_PLAY_TRACK)-1)) {
|
|
||||||
char *uri;
|
|
||||||
if(sscanf(in, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &uri) && uri != NULL) {
|
|
||||||
int added_song = mpd_run_add_id(conn, uri);
|
|
||||||
if(added_song != -1)
|
|
||||||
mpd_run_play_id(conn, added_song);
|
|
||||||
free(uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
#ifdef WITH_MPD_HOST_CHANGE
|
#ifdef WITH_MPD_HOST_CHANGE
|
||||||
else if(!strncmp((const char *)in, MPD_API_SET_MPDHOST, sizeof(MPD_API_SET_MPDHOST)-1)) {
|
/* Commands allowed when disconnected from MPD server */
|
||||||
char *host;
|
case MPD_API_SET_MPDHOST:
|
||||||
int port = 0;
|
int_buf = 0;
|
||||||
if(sscanf(in, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &port, &host) && host != NULL && port > 0) {
|
if(sscanf(c->content, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &int_buf, &p_charbuf) &&
|
||||||
strncpy(mpd_host, host, sizeof(mpd_host));
|
p_charbuf != NULL && int_buf > 0)
|
||||||
free(host);
|
{
|
||||||
mpd_port = port;
|
strncpy(mpd.host, p_charbuf, sizeof(mpd.host));
|
||||||
mpd_conn_state = MPD_RECONNECT;
|
free(p_charbuf);
|
||||||
break;
|
mpd.port = int_buf;
|
||||||
}
|
mpd.conn_state = MPD_RECONNECT;
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
}
|
}
|
||||||
else if(!strncmp((const char *)in, MPD_API_GET_MPDHOST, sizeof(MPD_API_GET_MPDHOST)-1)) {
|
break;
|
||||||
pss->do_send |= DO_SEND_MPDHOST;
|
case MPD_API_GET_MPDHOST:
|
||||||
|
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"mpdhost\", \"data\": "
|
||||||
|
"{\"host\" : \"%s\", \"port\": \"%d\", \"passwort_set\": %s}"
|
||||||
|
"}", mpd.host, mpd.port, mpd.password ? "true" : "false");
|
||||||
|
break;
|
||||||
|
case MPD_API_SET_MPDPASS:
|
||||||
|
if(sscanf(c->content, "MPD_API_SET_MPDPASS,%m[^\t\n ]", &p_charbuf))
|
||||||
|
{
|
||||||
|
if(mpd.password)
|
||||||
|
free(mpd.password);
|
||||||
|
|
||||||
|
mpd.password = p_charbuf;
|
||||||
|
mpd.conn_state = MPD_RECONNECT;
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(mpd_conn_state == MPD_CONNECTED && mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS)
|
|
||||||
pss->do_send |= DO_SEND_ERROR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mpd.conn_state == MPD_CONNECTED && mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\", \"data\": \"%s\"}",
|
||||||
|
mpd_connection_get_error_message(mpd.conn));
|
||||||
|
|
||||||
|
/* Try to recover error */
|
||||||
|
if (!mpd_connection_clear_error(mpd.conn))
|
||||||
|
mpd.conn_state = MPD_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(n > 0)
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, n);
|
||||||
|
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mpd_close_handler(struct mg_connection *c)
|
||||||
|
{
|
||||||
|
/* Cleanup session data */
|
||||||
|
if(c->connection_param)
|
||||||
|
free(c->connection_param);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mpd_loop()
|
static int mpd_notify_callback(struct mg_connection *c) {
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
if(!c->is_websocket)
|
||||||
|
return MG_REQUEST_PROCESSED;
|
||||||
|
|
||||||
|
if(c->callback_param)
|
||||||
|
{
|
||||||
|
/* error message? */
|
||||||
|
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"%s\"}",
|
||||||
|
(const char *)c->callback_param);
|
||||||
|
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, n);
|
||||||
|
return MG_REQUEST_PROCESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!c->connection_param)
|
||||||
|
c->connection_param = calloc(1, sizeof(struct t_mpd_client_session));
|
||||||
|
|
||||||
|
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
|
||||||
|
|
||||||
|
if(mpd.conn_state != MPD_CONNECTED) {
|
||||||
|
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
|
||||||
|
|
||||||
|
if(s->song_id != mpd.song_id)
|
||||||
|
{
|
||||||
|
n = mpd_put_current_song(mpd.buf);
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, n);
|
||||||
|
s->song_id = mpd.song_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s->queue_version != mpd.queue_version)
|
||||||
|
{
|
||||||
|
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"update_queue\"}");
|
||||||
|
mg_websocket_write(c, 1, mpd.buf, n);
|
||||||
|
s->queue_version = mpd.queue_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MG_REQUEST_PROCESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mpd_poll(struct mg_server *s)
|
||||||
{
|
{
|
||||||
switch (mpd_conn_state) {
|
switch (mpd.conn_state) {
|
||||||
case MPD_DISCONNECTED:
|
case MPD_DISCONNECTED:
|
||||||
/* Try to connect */
|
/* Try to connect */
|
||||||
lwsl_notice("MPD Connecting to %s:%d\n", mpd_host, mpd_port);
|
fprintf(stdout, "MPD Connecting to %s:%d\n", mpd.host, mpd.port);
|
||||||
conn = mpd_connection_new(mpd_host, mpd_port, 3000);
|
mpd.conn = mpd_connection_new(mpd.host, mpd.port, 3000);
|
||||||
if (conn == NULL) {
|
if (mpd.conn == NULL) {
|
||||||
lwsl_err("Out of memory.");
|
fprintf(stderr, "Out of memory.");
|
||||||
mpd_conn_state = MPD_FAILURE;
|
mpd.conn_state = MPD_FAILURE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) {
|
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
|
||||||
lwsl_err("MPD connection: %s\n", mpd_connection_get_error_message(conn));
|
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||||
mpd_conn_state = MPD_FAILURE;
|
mg_iterate_over_connections(s, mpd_notify_callback,
|
||||||
|
(void *)mpd_connection_get_error_message(mpd.conn));
|
||||||
|
mpd.conn_state = MPD_FAILURE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lwsl_notice("MPD connected.\n");
|
if(mpd.password && !mpd_run_password(mpd.conn, mpd.password))
|
||||||
mpd_conn_state = MPD_CONNECTED;
|
{
|
||||||
|
fprintf(stderr, "MPD connection: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||||
|
mg_iterate_over_connections(s, mpd_notify_callback,
|
||||||
|
(void *)mpd_connection_get_error_message(mpd.conn));
|
||||||
|
mpd.conn_state = MPD_FAILURE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "MPD connected.\n");
|
||||||
|
mpd.conn_state = MPD_CONNECTED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPD_FAILURE:
|
case MPD_FAILURE:
|
||||||
lwsl_err("MPD connection failed.\n");
|
fprintf(stderr, "MPD connection failed.\n");
|
||||||
|
|
||||||
|
case MPD_DISCONNECT:
|
||||||
case MPD_RECONNECT:
|
case MPD_RECONNECT:
|
||||||
if(conn != NULL)
|
if(mpd.conn != NULL)
|
||||||
mpd_connection_free(conn);
|
mpd_connection_free(mpd.conn);
|
||||||
conn = NULL;
|
mpd.conn = NULL;
|
||||||
mpd_conn_state = MPD_DISCONNECTED;
|
mpd.conn_state = MPD_DISCONNECTED;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPD_CONNECTED:
|
case MPD_CONNECTED:
|
||||||
/* Nothing to do */
|
mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version);
|
||||||
|
mg_iterate_over_connections(s, mpd_notify_callback, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char* mpd_get_title(struct mpd_song const *song)
|
char* mpd_get_title(struct mpd_song const *song)
|
||||||
{
|
{
|
||||||
char *str, *ptr;
|
char *str;
|
||||||
|
|
||||||
str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
|
str = (char *)mpd_song_get_tag(song, MPD_TAG_TITLE, 0);
|
||||||
if(str == NULL){
|
if(str == NULL){
|
||||||
str = basename((char *)mpd_song_get_uri(song));
|
str = basename((char *)mpd_song_get_uri(song));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(str == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
ptr = str;
|
|
||||||
while(*ptr++ != '\0')
|
|
||||||
if(*ptr=='"')
|
|
||||||
*ptr='\'';
|
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,30 +324,30 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
|
|||||||
struct mpd_status *status;
|
struct mpd_status *status;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
status = mpd_run_status(conn);
|
status = mpd_run_status(mpd.conn);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
lwsl_err("MPD mpd_run_status: %s\n", mpd_connection_get_error_message(conn));
|
fprintf(stderr, "MPD mpd_run_status: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||||
mpd_conn_state = MPD_FAILURE;
|
mpd.conn_state = MPD_FAILURE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
len = snprintf(buffer, MAX_SIZE,
|
len = snprintf(buffer, MAX_SIZE,
|
||||||
"{\"type\":\"state\", \"data\":{"
|
"{\"type\":\"state\", \"data\":{"
|
||||||
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
|
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
|
||||||
" \"single\":%d, \"consume\":%d, \"random\":%d, "
|
" \"single\":%d, \"consume\":%d, \"random\":%d, "
|
||||||
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
|
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
|
||||||
" \"currentsongid\": %d"
|
" \"currentsongid\": %d"
|
||||||
"}}",
|
"}}",
|
||||||
mpd_status_get_state(status),
|
mpd_status_get_state(status),
|
||||||
mpd_status_get_volume(status),
|
mpd_status_get_volume(status),
|
||||||
mpd_status_get_repeat(status),
|
mpd_status_get_repeat(status),
|
||||||
mpd_status_get_single(status),
|
mpd_status_get_single(status),
|
||||||
mpd_status_get_consume(status),
|
mpd_status_get_consume(status),
|
||||||
mpd_status_get_random(status),
|
mpd_status_get_random(status),
|
||||||
mpd_status_get_song_pos(status),
|
mpd_status_get_song_pos(status),
|
||||||
mpd_status_get_elapsed_time(status),
|
mpd_status_get_elapsed_time(status),
|
||||||
mpd_status_get_total_time(status),
|
mpd_status_get_total_time(status),
|
||||||
mpd_status_get_song_id(status));
|
mpd_status_get_song_id(status));
|
||||||
|
|
||||||
*current_song_id = mpd_status_get_song_id(status);
|
*current_song_id = mpd_status_get_song_id(status);
|
||||||
*queue_version = mpd_status_get_queue_version(status);
|
*queue_version = mpd_status_get_queue_version(status);
|
||||||
@ -345,132 +361,195 @@ int mpd_put_current_song(char *buffer)
|
|||||||
const char *end = buffer + MAX_SIZE;
|
const char *end = buffer + MAX_SIZE;
|
||||||
struct mpd_song *song;
|
struct mpd_song *song;
|
||||||
|
|
||||||
song = mpd_run_current_song(conn);
|
song = mpd_run_current_song(mpd.conn);
|
||||||
if(song == NULL)
|
if(song == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
cur += snprintf(cur, end - cur, "{\"type\": \"song_change\", \"data\":"
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
|
||||||
"{\"pos\":%d, \"title\":\"%s\"",
|
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
|
||||||
mpd_song_get_pos(song),
|
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||||
mpd_get_title(song));
|
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||||
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
|
|
||||||
cur += snprintf(cur, end - cur, ", \"artist\":\"%s\"",
|
|
||||||
mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
|
|
||||||
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
|
|
||||||
cur += snprintf(cur, end - cur, ", \"album\":\"%s\"",
|
|
||||||
mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
|
|
||||||
|
|
||||||
cur += snprintf(cur, end - cur, "}}");
|
if(mpd_song_get_tag(song, MPD_TAG_ARTIST, 0) != NULL)
|
||||||
|
{
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
|
||||||
|
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ARTIST, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mpd_song_get_tag(song, MPD_TAG_ALBUM, 0) != NULL)
|
||||||
|
{
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||||
|
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_tag(song, MPD_TAG_ALBUM, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "}}");
|
||||||
mpd_song_free(song);
|
mpd_song_free(song);
|
||||||
mpd_response_finish(conn);
|
mpd_response_finish(mpd.conn);
|
||||||
|
|
||||||
return cur - buffer;
|
return cur - buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mpd_put_playlist(char *buffer)
|
int mpd_put_queue(char *buffer, unsigned int offset)
|
||||||
{
|
{
|
||||||
char *cur = buffer;
|
char *cur = buffer;
|
||||||
const char *end = buffer + MAX_SIZE;
|
const char *end = buffer + MAX_SIZE;
|
||||||
struct mpd_entity *entity;
|
struct mpd_entity *entity;
|
||||||
|
|
||||||
if (!mpd_send_list_queue_meta(conn)) {
|
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
|
||||||
lwsl_err("MPD mpd_send_list_queue_meta: %s\n", mpd_connection_get_error_message(conn));
|
RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
|
||||||
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}",
|
|
||||||
mpd_connection_get_error_message(conn));
|
|
||||||
|
|
||||||
if (!mpd_connection_clear_error(conn))
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
|
||||||
mpd_conn_state = MPD_FAILURE;
|
|
||||||
return cur - buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
cur += snprintf(cur, end - cur, "{\"type\": \"playlist\", \"data\": [ ");
|
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||||
|
|
||||||
while((entity = mpd_recv_entity(conn)) != NULL) {
|
|
||||||
const struct mpd_song *song;
|
const struct mpd_song *song;
|
||||||
|
|
||||||
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
|
if(mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
|
||||||
song = mpd_entity_get_song(entity);
|
song = mpd_entity_get_song(entity);
|
||||||
cur += snprintf(cur, end - cur,
|
|
||||||
"{\"id\":%d, \"pos\":%d, \"duration\":%d, \"title\":\"%s\"},",
|
cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
|
||||||
mpd_song_get_id(song),
|
cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
|
||||||
mpd_song_get_pos(song),
|
cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
|
||||||
mpd_song_get_duration(song),
|
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
|
||||||
mpd_get_title(song)
|
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||||
);
|
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||||
|
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||||
}
|
}
|
||||||
mpd_entity_free(entity);
|
mpd_entity_free(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove last ',' */
|
/* remove last ',' */
|
||||||
cur--;
|
cur--;
|
||||||
cur += snprintf(cur, end - cur, "] }");
|
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||||
return cur - buffer;
|
return cur - buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mpd_put_browse(char *buffer, char *path)
|
int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
||||||
{
|
{
|
||||||
char *cur = buffer;
|
char *cur = buffer;
|
||||||
const char *end = buffer + MAX_SIZE;
|
const char *end = buffer + MAX_SIZE;
|
||||||
struct mpd_entity *entity;
|
struct mpd_entity *entity;
|
||||||
|
unsigned int entity_count = 0;
|
||||||
|
|
||||||
if (!mpd_send_list_meta(conn, path)) {
|
if (!mpd_send_list_meta(mpd.conn, path))
|
||||||
lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn));
|
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
|
||||||
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}",
|
|
||||||
mpd_connection_get_error_message(conn));
|
|
||||||
|
|
||||||
if (!mpd_connection_clear_error(conn))
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
||||||
mpd_conn_state = MPD_FAILURE;
|
|
||||||
return cur - buffer;
|
|
||||||
}
|
|
||||||
cur += snprintf(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
|
||||||
|
|
||||||
while((entity = mpd_recv_entity(conn)) != NULL) {
|
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||||
const struct mpd_song *song;
|
const struct mpd_song *song;
|
||||||
const struct mpd_directory *dir;
|
const struct mpd_directory *dir;
|
||||||
const struct mpd_playlist *pl;
|
const struct mpd_playlist *pl;
|
||||||
|
|
||||||
|
if(offset > entity_count)
|
||||||
|
{
|
||||||
|
mpd_entity_free(entity);
|
||||||
|
entity_count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
|
||||||
|
{
|
||||||
|
mpd_entity_free(entity);
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\",\"count\":");
|
||||||
|
cur += json_emit_int(cur, end - cur, entity_count);
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "} ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (mpd_entity_get_type(entity)) {
|
switch (mpd_entity_get_type(entity)) {
|
||||||
case MPD_ENTITY_TYPE_UNKNOWN:
|
case MPD_ENTITY_TYPE_UNKNOWN:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPD_ENTITY_TYPE_SONG:
|
case MPD_ENTITY_TYPE_SONG:
|
||||||
song = mpd_entity_get_song(entity);
|
song = mpd_entity_get_song(entity);
|
||||||
cur += snprintf(cur, end - cur,
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
|
||||||
"{\"type\":\"song\",\"uri\":\"%s\",\"duration\":%d,\"title\":\"%s\"},",
|
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
|
||||||
mpd_song_get_uri(song),
|
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||||
mpd_song_get_duration(song),
|
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||||
mpd_get_title(song)
|
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||||
);
|
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPD_ENTITY_TYPE_DIRECTORY:
|
case MPD_ENTITY_TYPE_DIRECTORY:
|
||||||
dir = mpd_entity_get_directory(entity);
|
dir = mpd_entity_get_directory(entity);
|
||||||
cur += snprintf(cur, end - cur,
|
|
||||||
"{\"type\":\"directory\",\"dir\":\"%s\", \"basename\":\"%s\"},",
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"directory\",\"dir\":");
|
||||||
mpd_directory_get_path(dir),
|
cur += json_emit_quoted_str(cur, end - cur, mpd_directory_get_path(dir));
|
||||||
basename((char *)mpd_directory_get_path(dir))
|
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MPD_ENTITY_TYPE_PLAYLIST:
|
case MPD_ENTITY_TYPE_PLAYLIST:
|
||||||
pl = mpd_entity_get_playlist(entity);
|
pl = mpd_entity_get_playlist(entity);
|
||||||
cur += snprintf(cur, end - cur,
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"playlist\",\"plist\":");
|
||||||
"{\"type\":\"playlist\",\"plist\":\"%s\"},",
|
cur += json_emit_quoted_str(cur, end - cur, mpd_playlist_get_path(pl));
|
||||||
mpd_playlist_get_path(pl)
|
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mpd_entity_free(entity);
|
mpd_entity_free(entity);
|
||||||
|
entity_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(conn)) {
|
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS || !mpd_response_finish(mpd.conn)) {
|
||||||
lwsl_err("MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(conn));
|
fprintf(stderr, "MPD mpd_send_list_meta: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||||
mpd_conn_state = MPD_FAILURE;
|
mpd.conn_state = MPD_FAILURE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remove last ',' */
|
/* remove last ',' */
|
||||||
cur--;
|
cur--;
|
||||||
cur += snprintf(cur, end - cur, "] }");
|
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||||
return cur - buffer;
|
return cur - buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mpd_search(char *buffer, char *searchstr)
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
char *cur = buffer;
|
||||||
|
const char *end = buffer + MAX_SIZE;
|
||||||
|
struct mpd_song *song;
|
||||||
|
|
||||||
|
if(mpd_search_db_songs(mpd.conn, false) == false)
|
||||||
|
RETURN_ERROR_AND_RECOVER("mpd_search_db_songs");
|
||||||
|
else if(mpd_search_add_any_tag_constraint(mpd.conn, MPD_OPERATOR_DEFAULT, searchstr) == false)
|
||||||
|
RETURN_ERROR_AND_RECOVER("mpd_search_add_any_tag_constraint");
|
||||||
|
else if(mpd_search_commit(mpd.conn) == false)
|
||||||
|
RETURN_ERROR_AND_RECOVER("mpd_search_commit");
|
||||||
|
else {
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"search\",\"data\":[ ");
|
||||||
|
|
||||||
|
while((song = mpd_recv_song(mpd.conn)) != NULL) {
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"song\",\"uri\":");
|
||||||
|
cur += json_emit_quoted_str(cur, end - cur, mpd_song_get_uri(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, ",\"duration\":");
|
||||||
|
cur += json_emit_int(cur, end - cur, mpd_song_get_duration(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||||
|
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||||
|
mpd_song_free(song);
|
||||||
|
|
||||||
|
/* Maximum results */
|
||||||
|
if(i++ >= 300)
|
||||||
|
{
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove last ',' */
|
||||||
|
cur--;
|
||||||
|
|
||||||
|
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||||
|
}
|
||||||
|
return cur - buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void mpd_disconnect()
|
||||||
|
{
|
||||||
|
mpd.conn_state = MPD_DISCONNECT;
|
||||||
|
mpd_poll(NULL);
|
||||||
|
}
|
||||||
|
156
src/mpd_client.h
156
src/mpd_client.h
@ -1,96 +1,108 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __MPD_CLIENT_H__
|
#ifndef __MPD_CLIENT_H__
|
||||||
#define __MPD_CLIENT_H__
|
#define __MPD_CLIENT_H__
|
||||||
|
|
||||||
#include <libwebsockets.h>
|
#include "mongoose.h"
|
||||||
|
|
||||||
|
#define RETURN_ERROR_AND_RECOVER(X) do { \
|
||||||
|
fprintf(stderr, "MPD X: %s\n", mpd_connection_get_error_message(mpd.conn)); \
|
||||||
|
cur += snprintf(cur, end - cur, "{\"type\":\"error\",\"data\":\"%s\"}", \
|
||||||
|
mpd_connection_get_error_message(mpd.conn)); \
|
||||||
|
if (!mpd_connection_clear_error(mpd.conn)) \
|
||||||
|
mpd.conn_state = MPD_FAILURE; \
|
||||||
|
return cur - buffer; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
|
||||||
#define MAX_SIZE 1024 * 100
|
#define MAX_SIZE 1024 * 100
|
||||||
|
#define MAX_ELEMENTS_PER_PAGE 512
|
||||||
|
|
||||||
#define DO_SEND_STATE (1 << 0)
|
#define GEN_ENUM(X) X,
|
||||||
#define DO_SEND_PLAYLIST (1 << 1)
|
#define GEN_STR(X) #X,
|
||||||
#define DO_SEND_TRACK_INFO (1 << 2)
|
#define MPD_CMDS(X) \
|
||||||
#define DO_SEND_BROWSE (1 << 3)
|
X(MPD_API_GET_QUEUE) \
|
||||||
#define DO_SEND_ERROR (1 << 4)
|
X(MPD_API_GET_BROWSE) \
|
||||||
#define DO_SEND_MPDHOST (1 << 5)
|
X(MPD_API_GET_MPDHOST) \
|
||||||
|
X(MPD_API_ADD_TRACK) \
|
||||||
|
X(MPD_API_ADD_PLAY_TRACK) \
|
||||||
|
X(MPD_API_ADD_PLAYLIST) \
|
||||||
|
X(MPD_API_PLAY_TRACK) \
|
||||||
|
X(MPD_API_RM_TRACK) \
|
||||||
|
X(MPD_API_RM_ALL) \
|
||||||
|
X(MPD_API_SEARCH) \
|
||||||
|
X(MPD_API_SET_VOLUME) \
|
||||||
|
X(MPD_API_SET_PAUSE) \
|
||||||
|
X(MPD_API_SET_PLAY) \
|
||||||
|
X(MPD_API_SET_STOP) \
|
||||||
|
X(MPD_API_SET_SEEK) \
|
||||||
|
X(MPD_API_SET_NEXT) \
|
||||||
|
X(MPD_API_SET_PREV) \
|
||||||
|
X(MPD_API_SET_MPDHOST) \
|
||||||
|
X(MPD_API_SET_MPDPASS) \
|
||||||
|
X(MPD_API_UPDATE_DB) \
|
||||||
|
X(MPD_API_TOGGLE_RANDOM) \
|
||||||
|
X(MPD_API_TOGGLE_CONSUME) \
|
||||||
|
X(MPD_API_TOGGLE_SINGLE) \
|
||||||
|
X(MPD_API_TOGGLE_REPEAT)
|
||||||
|
|
||||||
#define MPD_API_GET_SEEK "MPD_API_GET_SEEK"
|
enum mpd_cmd_ids {
|
||||||
#define MPD_API_GET_PLAYLIST "MPD_API_GET_PLAYLIST"
|
MPD_CMDS(GEN_ENUM)
|
||||||
#define MPD_API_GET_TRACK_INFO "MPD_API_GET_TRACK_INFO"
|
|
||||||
#define MPD_API_GET_BROWSE "MPD_API_GET_BROWSE"
|
|
||||||
#define MPD_API_GET_MPDHOST "MPD_API_GET_MPDHOST"
|
|
||||||
#define MPD_API_ADD_TRACK "MPD_API_ADD_TRACK"
|
|
||||||
#define MPD_API_ADD_PLAY_TRACK "MPD_API_ADD_PLAY_TRACK"
|
|
||||||
#define MPD_API_PLAY_TRACK "MPD_API_PLAY_TRACK"
|
|
||||||
#define MPD_API_RM_TRACK "MPD_API_RM_TRACK"
|
|
||||||
#define MPD_API_RM_ALL "MPD_API_RM_ALL"
|
|
||||||
#define MPD_API_SET_VOLUME "MPD_API_SET_VOLUME"
|
|
||||||
#define MPD_API_SET_PAUSE "MPD_API_SET_PAUSE"
|
|
||||||
#define MPD_API_SET_PLAY "MPD_API_SET_PLAY"
|
|
||||||
#define MPD_API_SET_STOP "MPD_API_SET_STOP"
|
|
||||||
#define MPD_API_SET_SEEK "MPD_API_SET_SEEK"
|
|
||||||
#define MPD_API_SET_NEXT "MPD_API_SET_PREV"
|
|
||||||
#define MPD_API_SET_PREV "MPD_API_SET_NEXT"
|
|
||||||
#define MPD_API_SET_MPDHOST "MPD_API_SET_MPDHOST"
|
|
||||||
#define MPD_API_UPDATE_DB "MPD_API_UPDATE_DB"
|
|
||||||
#define MPD_API_TOGGLE_RANDOM "MPD_API_TOGGLE_RANDOM"
|
|
||||||
#define MPD_API_TOGGLE_CONSUME "MPD_API_TOGGLE_CONSUME"
|
|
||||||
#define MPD_API_TOGGLE_SINGLE "MPD_API_TOGGLE_SINGLE"
|
|
||||||
#define MPD_API_TOGGLE_REPEAT "MPD_API_TOGGLE_REPEAT"
|
|
||||||
|
|
||||||
struct per_session_data__ympd {
|
|
||||||
int do_send;
|
|
||||||
unsigned queue_version;
|
|
||||||
int current_song_id;
|
|
||||||
char *browse_path;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum mpd_conn_states {
|
enum mpd_conn_states {
|
||||||
MPD_FAILURE,
|
|
||||||
MPD_DISCONNECTED,
|
MPD_DISCONNECTED,
|
||||||
|
MPD_FAILURE,
|
||||||
MPD_CONNECTED,
|
MPD_CONNECTED,
|
||||||
MPD_RECONNECT
|
MPD_RECONNECT,
|
||||||
|
MPD_DISCONNECT
|
||||||
};
|
};
|
||||||
|
|
||||||
void *mpd_idle_connection(void *_data);
|
struct t_mpd {
|
||||||
int callback_ympd(struct libwebsocket_context *context,
|
int port;
|
||||||
struct libwebsocket *wsi,
|
char host[128];
|
||||||
enum libwebsocket_callback_reasons reason,
|
char *password;
|
||||||
void *user, void *in, size_t len);
|
|
||||||
void mpd_loop();
|
struct mpd_connection *conn;
|
||||||
|
enum mpd_conn_states conn_state;
|
||||||
|
|
||||||
|
/* Reponse Buffer */
|
||||||
|
char buf[MAX_SIZE];
|
||||||
|
size_t buf_size;
|
||||||
|
|
||||||
|
int song_id;
|
||||||
|
unsigned queue_version;
|
||||||
|
} mpd;
|
||||||
|
|
||||||
|
struct t_mpd_client_session {
|
||||||
|
int song_id;
|
||||||
|
unsigned queue_version;
|
||||||
|
};
|
||||||
|
|
||||||
|
void mpd_poll(struct mg_server *s);
|
||||||
|
int callback_mpd(struct mg_connection *c);
|
||||||
|
int mpd_close_handler(struct mg_connection *c);
|
||||||
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version);
|
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version);
|
||||||
int mpd_put_current_song(char *buffer);
|
int mpd_put_current_song(char *buffer);
|
||||||
int mpd_put_playlist(char *buffer);
|
int mpd_put_queue(char *buffer, unsigned int offset);
|
||||||
int mpd_put_browse(char *buffer, char *path);
|
int mpd_put_browse(char *buffer, char *path, unsigned int offset);
|
||||||
|
int mpd_search(char *buffer, char *searchstr);
|
||||||
int mpd_port;
|
void mpd_disconnect();
|
||||||
char mpd_host[255];
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
from http://www.geekhideout.com/urlcode.shtml
|
|
||||||
public domain
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/* Converts a hex character to its integer value */
|
|
||||||
char from_hex(char ch) {
|
|
||||||
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Converts an integer value to its hex character*/
|
|
||||||
char to_hex(char code) {
|
|
||||||
static char hex[] = "0123456789abcdef";
|
|
||||||
return hex[code & 15];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns a url-decoded version of str */
|
|
||||||
/* IMPORTANT: be sure to free() the returned string after use */
|
|
||||||
char *url_decode(char *str) {
|
|
||||||
char *pstr = str, *buf = malloc(strlen(str) + 1), *pbuf = buf;
|
|
||||||
while (*pstr) {
|
|
||||||
if (*pstr == '%') {
|
|
||||||
if (pstr[1] && pstr[2]) {
|
|
||||||
*pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]);
|
|
||||||
pstr += 2;
|
|
||||||
}
|
|
||||||
} else if (*pstr == '+') {
|
|
||||||
*pbuf++ = ' ';
|
|
||||||
} else {
|
|
||||||
*pbuf++ = *pstr;
|
|
||||||
}
|
|
||||||
pstr++;
|
|
||||||
}
|
|
||||||
*pbuf = '\0';
|
|
||||||
return buf;
|
|
||||||
}
|
|
209
src/ympd.c
209
src/ympd.c
@ -1,64 +1,35 @@
|
|||||||
/* ympd
|
/* ympd
|
||||||
(c) 2013-2014 Andrew Karpow <andy@ympd.org>
|
(c) 2013-2014 Andrew Karpow <andy@ndyk.de>
|
||||||
This project's homepage is: http://www.ympd.org
|
This project's homepage is: http://www.ympd.org
|
||||||
|
|
||||||
|
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; version 2 of the License.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
This program is distributed in the hope that it will be useful,
|
||||||
modification, are permitted provided that the following conditions
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
are met:
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
- Redistributions of source code must retain the above copyright
|
You should have received a copy of the GNU General Public License along
|
||||||
notice, this list of conditions and the following disclaimer.
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
- Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
||||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libwebsockets.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "mongoose.h"
|
||||||
#include "http_server.h"
|
#include "http_server.h"
|
||||||
#include "mpd_client.h"
|
#include "mpd_client.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
extern char *optarg;
|
extern char *optarg;
|
||||||
extern char *resource_path;
|
|
||||||
|
|
||||||
struct libwebsocket_protocols protocols[] = {
|
|
||||||
/* first protocol must always be HTTP handler */
|
|
||||||
{
|
|
||||||
"http-only", /* name */
|
|
||||||
callback_http, /* callback */
|
|
||||||
sizeof (struct per_session_data__http), /* per_session_data_size */
|
|
||||||
0, /* max frame size / rx buffer */
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ympd-client",
|
|
||||||
callback_ympd,
|
|
||||||
sizeof(struct per_session_data__ympd),
|
|
||||||
255,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
|
|
||||||
{ NULL, NULL, 0, 0, 0 } /* terminator */
|
|
||||||
};
|
|
||||||
|
|
||||||
int force_exit = 0;
|
int force_exit = 0;
|
||||||
|
|
||||||
@ -67,149 +38,87 @@ void bye()
|
|||||||
force_exit = 1;
|
force_exit = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int server_callback(struct mg_connection *c) {
|
||||||
|
if (c->is_websocket)
|
||||||
|
{
|
||||||
|
c->content[c->content_len] = '\0';
|
||||||
|
if(c->content_len)
|
||||||
|
return callback_mpd(c);
|
||||||
|
else
|
||||||
|
return MG_CLIENT_CONTINUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return callback_http(c);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int n, gid = -1, uid = -1;
|
int n, option_index = 0;
|
||||||
int option_index = 0;
|
struct mg_server *server = mg_create_server(NULL);
|
||||||
unsigned int oldus = 0;
|
unsigned int current_timer = 0, last_timer = 0;
|
||||||
struct libwebsocket_context *context;
|
|
||||||
struct lws_context_creation_info info;
|
|
||||||
const char *cert_filepath = NULL;
|
|
||||||
const char *private_key_filepath = NULL;
|
|
||||||
const char *iface = NULL;
|
|
||||||
|
|
||||||
atexit(bye);
|
atexit(bye);
|
||||||
memset(&info, 0, sizeof info);
|
mg_set_option(server, "listening_port", "8080");
|
||||||
info.port = 8080;
|
mpd.port = 6600;
|
||||||
strcpy(mpd_host, "127.0.0.1");
|
strcpy(mpd.host, "127.0.0.1");
|
||||||
mpd_port = 6600;
|
|
||||||
lws_set_log_level(LLL_ERR | LLL_WARN, NULL);
|
|
||||||
|
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"host", required_argument, 0, 'h'},
|
{"host", required_argument, 0, 'h'},
|
||||||
{"port", required_argument, 0, 'p'},
|
{"port", required_argument, 0, 'p'},
|
||||||
{"interface", required_argument, 0, 'i'},
|
|
||||||
{"webport", required_argument, 0, 'w'},
|
{"webport", required_argument, 0, 'w'},
|
||||||
{"resourcepath", required_argument, 0, 'r'},
|
{"user", required_argument, 0, 'u'},
|
||||||
{"ssl_cert", required_argument, 0, 'c'},
|
{"version", no_argument, 0, 'v'},
|
||||||
{"ssl_key", required_argument, 0, 'k'},
|
|
||||||
{"gid", required_argument, 0, 'g'},
|
|
||||||
{"uid", required_argument, 0, 'u'},
|
|
||||||
{"verbose", optional_argument, 0, 'v'},
|
|
||||||
{"version", no_argument, 0, 'V'},
|
|
||||||
{"help", no_argument, 0, 0 },
|
{"help", no_argument, 0, 0 },
|
||||||
{0, 0, 0, 0 }
|
{0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
while((n = getopt_long(argc, argv, "h:p:i:w:r:c:k:g:u:v::V",
|
while((n = getopt_long(argc, argv, "h:p:w:u:v",
|
||||||
long_options, &option_index)) != -1) {
|
long_options, &option_index)) != -1) {
|
||||||
switch (n) {
|
switch (n) {
|
||||||
case 'h':
|
case 'h':
|
||||||
strncpy(mpd_host, optarg, sizeof(mpd_host));
|
strncpy(mpd.host, optarg, sizeof(mpd.host));
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
mpd_port = atoi(optarg);
|
mpd.port = atoi(optarg);
|
||||||
case 'i':
|
|
||||||
iface = optarg;
|
|
||||||
break;
|
|
||||||
case 'w':
|
case 'w':
|
||||||
info.port = atoi(optarg);
|
mg_set_option(server, "listening_port", optarg);
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
resource_path = optarg;
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
cert_filepath = optarg;
|
|
||||||
break;
|
|
||||||
case 'k':
|
|
||||||
private_key_filepath = optarg;
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
gid = atoi(optarg);
|
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
uid = atoi(optarg);
|
mg_set_option(server, "run_as_user", optarg);
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
if(optarg)
|
|
||||||
lws_set_log_level(strtol(optarg, NULL, 10), NULL);
|
|
||||||
else
|
|
||||||
lws_set_log_level(LLL_ERR | LLL_WARN |
|
|
||||||
LLL_NOTICE | LLL_INFO, NULL);
|
|
||||||
break;
|
|
||||||
case 'V':
|
|
||||||
fprintf(stdout, "ympd %d.%d.%d\n"
|
fprintf(stdout, "ympd %d.%d.%d\n"
|
||||||
"Copyright (C) 2014 Andrew Karpow <andy@ympd.org>\n"
|
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
|
||||||
"Resource Path: "LOCAL_RESOURCE_PATH"\n"
|
|
||||||
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
|
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
|
||||||
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
|
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "Usage: %s [OPTION]...\n\n"
|
fprintf(stderr, "Usage: %s [OPTION]...\n\n"
|
||||||
"\t-h, --host <host>\t\tconnect to mpd at host [localhost]\n"
|
" -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
|
||||||
"\t-p, --port <port>\t\tconnect to mpd at port [6600]\n"
|
" -p, --port <port>\t\tconnect to mpd at port [6600]\n"
|
||||||
"\t-i, --interface <interface>\tlisten interface for webserver [all]\n"
|
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n"
|
||||||
"\t-w, --webport <port>\t\tlisten port for webserver [8080]\n"
|
" -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
|
||||||
"\t-r, --resourcepath <path>\tresourcepath for webserver [" LOCAL_RESOURCE_PATH "]\n"
|
" -V, --version\t\t\tget version\n"
|
||||||
"\t-c, --ssl_cert <filepath>\tssl certificate ssl_private_key_filepath\n"
|
" --help\t\t\t\tthis help\n"
|
||||||
"\t-k, --ssl_key <filepath>\tssl private key filepath\n"
|
|
||||||
"\t-u, --uid <id>\t\t\tuser-id after socket bind\n"
|
|
||||||
"\t-g, --gid <id>\t\t\tgroup-id after socket bind\n"
|
|
||||||
"\t-v, --verbose[<level>]\t\tverbosity level\n"
|
|
||||||
"\t-V, --version\t\t\tget version\n"
|
|
||||||
"\t--help\t\t\t\tthis help\n"
|
|
||||||
, argv[0]);
|
, argv[0]);
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cert_filepath != NULL && private_key_filepath == NULL) {
|
mg_set_http_close_handler(server, mpd_close_handler);
|
||||||
lwsl_err("private key filepath needed\n");
|
mg_set_request_handler(server, server_callback);
|
||||||
return EXIT_FAILURE;
|
while (!force_exit) {
|
||||||
}
|
current_timer = mg_poll_server(server, 200);
|
||||||
|
if(current_timer - last_timer)
|
||||||
if(private_key_filepath != NULL && cert_filepath == NULL) {
|
{
|
||||||
lwsl_err("public cert filepath needed\n");
|
last_timer = current_timer;
|
||||||
return EXIT_FAILURE;
|
mpd_poll(server);
|
||||||
}
|
|
||||||
|
|
||||||
info.ssl_cert_filepath = cert_filepath;
|
|
||||||
info.ssl_private_key_filepath = private_key_filepath;
|
|
||||||
info.iface = iface;
|
|
||||||
info.protocols = protocols;
|
|
||||||
info.extensions = libwebsocket_get_internal_extensions();
|
|
||||||
info.gid = gid;
|
|
||||||
info.uid = uid;
|
|
||||||
info.options = 0;
|
|
||||||
|
|
||||||
context = libwebsocket_create_context(&info);
|
|
||||||
if (context == NULL) {
|
|
||||||
lwsl_err("libwebsocket init failed\n");
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
n = 0;
|
|
||||||
while (n >= 0 && !force_exit) {
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
|
|
||||||
* live websocket connection using the DUMB_INCREMENT protocol,
|
|
||||||
* as soon as it can take more packets (usually immediately)
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (((unsigned int)tv.tv_usec - oldus) > 1000 * 500) {
|
|
||||||
mpd_loop();
|
|
||||||
libwebsocket_callback_on_writable_all_protocol(&protocols[1]);
|
|
||||||
oldus = tv.tv_usec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n = libwebsocket_service(context, 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
libwebsocket_context_destroy(context);
|
mpd_disconnect();
|
||||||
return 0;
|
mg_destroy_server(&server);
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
28
ympd.1
28
ympd.1
@ -1,6 +1,6 @@
|
|||||||
.\" Manpage for ympd.
|
.\" Manpage for ympd.
|
||||||
.\" Contact andy@ympd.org to correct errors or typos.
|
.\" Contact andy@ympd.org to correct errors or typos.
|
||||||
.TH man 8 "17 Jan 2014" "1.0" "ympd man page"
|
.TH man 8 "08 Mar 2014" "1.2" "ympd man page"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ympd \- Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
ympd \- Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -8,7 +8,7 @@ ympd [OPTION]...
|
|||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.
|
ympd is a lightweight MPD (Music Player Daemon) web client that runs without a dedicated werbserver or interpreters like PHP, NodeJS or Ruby. It's tuned for minimal resource usage and requires only very litte dependencies.
|
||||||
|
|
||||||
ympd is based in part on the work of the libwebsockets project (http://libwebsockets.org)
|
ympd is based in part on the work of the mongoose http server (https://github.com/cesanta/mongoose)
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
\fB\-h\fR, \fB\-\-host HOST\fR
|
\fB\-h\fR, \fB\-\-host HOST\fR
|
||||||
@ -17,29 +17,11 @@ connect to mpd at host, defaults to localhost
|
|||||||
\fB\-p\fR, \fB\-\-port PORT\fR
|
\fB\-p\fR, \fB\-\-port PORT\fR
|
||||||
connect to mpd at port, defaults to 6600
|
connect to mpd at port, defaults to 6600
|
||||||
.TP
|
.TP
|
||||||
\fB\-i\fR, \fB\-\-interface INTERFACE\fR
|
|
||||||
specifies the interface to listen to
|
|
||||||
.TP
|
|
||||||
\fB\-w\fR, \fB\-\-webport PORT\fR
|
\fB\-w\fR, \fB\-\-webport PORT\fR
|
||||||
specifies the port for the webserver to listen to, defaults to 8080
|
specifies the port for the webserver to listen to, defaults to 8080
|
||||||
.TP
|
.TP
|
||||||
\fB\-r\fR, \fB\-\-resourcepath PATH\fR
|
\fB\-u\fR, \fB\-\-user username\fR
|
||||||
resource path to htdocs directory, defaults to the supplied htdocs directory
|
drop privileges to the provided username after socket binding
|
||||||
.TP
|
|
||||||
\fB\-c\fR, \fB\-\-ssl_cert PATH\fR
|
|
||||||
path the the ssl certificate
|
|
||||||
.TP
|
|
||||||
\fB\-k\fR, \fB\-\-ssl_key PATH\fR
|
|
||||||
path the the private ssl key
|
|
||||||
.TP
|
|
||||||
\fB\-u\fR, \fB\-\-uid UID\fR
|
|
||||||
drop privileges to the provided uid after socket binding
|
|
||||||
.TP
|
|
||||||
\fB\-g\fR, \fB\-\-gid GID\fR
|
|
||||||
drop privileges to the provided gid after socket binding
|
|
||||||
.TP
|
|
||||||
\fB\-v\fR, \fB\-\-verbose [LEVEL]\fR
|
|
||||||
sets the verbository level
|
|
||||||
.TP
|
.TP
|
||||||
\fB\-V\fR, \fB\-\-version\fR
|
\fB\-V\fR, \fB\-\-version\fR
|
||||||
print version and exit
|
print version and exit
|
||||||
@ -51,4 +33,4 @@ No known bugs.
|
|||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Andrew Karpow (andy@ympd.org)
|
Andrew Karpow (andy@ympd.org)
|
||||||
|
|
||||||
http://ympd.org
|
https://www.ympd.org
|
||||||
|
Loading…
Reference in New Issue
Block a user