mirror of https://github.com/SuperBFG7/ympd
Compare commits
266 Commits
Author | SHA1 | Date |
---|---|---|
SuperBFG7 | 297691ab5b | |
SuperBFG7 | b41753220f | |
SuperBFG7 | 7a725e648d | |
SuperBFG7 | ac7ad51a0e | |
SuperBFG7 | 3c70301789 | |
SuperBFG7 | f5080c8419 | |
SuperBFG7 | 7b5496e06c | |
SuperBFG7 | 4d30fff4bc | |
SuperBFG7 | 2b461aafa2 | |
SuperBFG7 | fd727cc20b | |
SuperBFG7 | ebf5b3894d | |
SuperBFG7 | 8669750a4b | |
SuperBFG7 | 28844275e6 | |
SuperBFG7 | 86586fbcee | |
SuperBFG7 | 886e390d09 | |
SuperBFG7 | d5294675c4 | |
SuperBFG7 | 883ea7ca06 | |
SuperBFG7 | 61e385a35d | |
Andrew Carr | 9a0cf4a43b | |
Andrew Carr | b35e705066 | |
SuperBFG7 | 9d1a3ccfb2 | |
Andrew Carr | 3fe238b169 | |
SuperBFG7 | d1416d8f34 | |
SuperBFG7 | 5e6d090bd8 | |
SuperBFG7 | bd95651d61 | |
Dafan Zhai | efa4d4443f | |
Daniel Schregenberger | df5a48f99f | |
SuperBFG7 | 2268e0f0f3 | |
Daniel Schregenberger | 91700a2496 | |
Daniel Schregenberger | e6e08d6159 | |
SuperBFG7 | fff184dca2 | |
SuperBFG7 | aeceb91551 | |
Clément Pit-Claudel | 0917b467e8 | |
SuperBFG7 | b9c268b847 | |
SuperBFG7 | 6340dcde72 | |
SuperBFG7 | a3ae8cd802 | |
SuperBFG7 | f89c49e09c | |
Daniel Schregenberger | 9bf518ed07 | |
SuperBFG7 | db72139490 | |
Daniel Schregenberger | 2db7b711f7 | |
Daniel Schregenberger | 5ffdc5e50a | |
SuperBFG7 | b0ec3be02f | |
Daniel Schregenberger | 93ec197f79 | |
Daniel Schregenberger | 3774a35f4c | |
SuperBFG7 | 61ad51ffb7 | |
Daniel Schregenberger | 1e96a921c5 | |
Daniel Schregenberger | 4be2db91e4 | |
Daniel Schregenberger | 86a924cddd | |
Daniel Schregenberger | 1588e21c08 | |
Daniel Schregenberger | 2f4c737f09 | |
Daniel Schregenberger | 68cbe37a84 | |
Daniel Schregenberger | ffe1305c3c | |
Archphile | 870da6f9a1 | |
Emilot | b787c782d1 | |
andreevksya | ac55e8a604 | |
SuperBFG7 | e1cb953e94 | |
SuperBFG7 | ebd357a24a | |
SuperBFG7 | d08092b069 | |
SuperBFG7 | 265e5f76a1 | |
SuperBFG7 | 63be6f0de0 | |
eb041592 | c9e200a3ef | |
eb041592 | 2cb99de852 | |
eb041592 | 18417b9ed5 | |
eb041592 | c226026a51 | |
eb041592 | 84b3ea71a4 | |
eb041592 | 0faf70ad05 | |
eb041592 | 54e334a2a0 | |
Andy | 612f8fc0b2 | |
Andy | 17b97c8f6b | |
Andy | a7d9bf85f5 | |
Andy | 0e58a0d5c7 | |
Andy | eea4a382a4 | |
SuperBFG7 | 2fa653711d | |
SuperBFG7 | 26b2ca3408 | |
Daniel Schregenberger | 5e834e56ff | |
Fabrice Fontaine | f53c0956d4 | |
Archphile | d4865aca9d | |
eb041592 | b4084034d0 | |
eb041592 | bd9775de8e | |
eb041592 | b3037d6117 | |
eb041592 | b091e47d2f | |
eb041592 | 037e7db1da | |
eb041592 | 2e9d477ee6 | |
eb041592 | 73a844eb71 | |
eb041592 | 1b0484a285 | |
eb041592 | 226e3b5999 | |
SuperBFG7 | 17436bff40 | |
SuperBFG7 | 7af873b793 | |
eb041592 | fe0277bfbe | |
eb041592 | 3fe7d9c44c | |
eb041592 | cf572b705e | |
eb041592 | 80834b989f | |
SuperBFG7 | fec13b6937 | |
Daniel Schregenberger | 2a0e4fb22a | |
Daniel Schregenberger | 7a31b441b6 | |
Andrew Karpow | 88b2aa70c8 | |
SuperBFG7 | 839b9aa9de | |
SuperBFG7 | f1495ff712 | |
SuperBFG7 | 94fed08434 | |
SuperBFG7 | 1e93d2dc82 | |
Daniel Schregenberger | 3dac7ce3dc | |
Andrew Karpow | 8c1115dc63 | |
Andrew Karpow | d314ac08df | |
Andrew Karpow | ceee8bd90a | |
Andy | 68429e39d5 | |
Andrew Karpow | b0253a01ab | |
Andrew Karpow | ba4322c684 | |
Andrew Karpow | 95f8ccd7a4 | |
Andrew Karpow | 265577c5ab | |
Andy | 91e3535891 | |
Andy | 393d6063da | |
Andy | a99b0a5ddc | |
Andy | e436fc93b5 | |
Andy | b21fccc240 | |
Andy | f7aade410d | |
Andy | e0050641df | |
Florian Heinemann | bba35bf682 | |
Zachary Boyd | 02ab7df3c6 | |
SuperBFG7 | 5589013a95 | |
SuperBFG7 | 06ded692a5 | |
SuperBFG7 | f118c53ecb | |
SuperBFG7 | d9182dd2f4 | |
SuperBFG7 | 604d54a0bb | |
azrdev | c1f1adbc28 | |
SuperBFG7 | fac60b18a1 | |
SuperBFG7 | eb803d1da5 | |
Galen Alderson | fa4cb48830 | |
SuperBFG7 | 269c55b9c8 | |
SuperBFG7 | dfb9adb78c | |
SuperBFG7 | 6727b8b0ad | |
SuperBFG7 | 4f64061286 | |
SuperBFG7 | 152defeaaa | |
SuperBFG7 | 07c6df23bc | |
SuperBFG7 | afab4e5653 | |
SuperBFG7 | 66ff05e8b5 | |
SuperBFG7 | 6c75d5bc6c | |
SuperBFG7 | 40c335cfca | |
SuperBFG7 | 8abcee0049 | |
unknownln | e5a88b1ffe | |
unknownln | ec74ef463b | |
unknownln | a6ba0d640c | |
unknownln | f79dea44a2 | |
Felix J. Ogris | 40906e2bee | |
Les De Ridder | d9ec70a9da | |
Marc Plano-Lesay | 15edcc645c | |
Govert Buijs | 306840fa1d | |
eolianoe | d2d037fe97 | |
SuperBFG7 | f586db33d1 | |
SuperBFG7 | 92d3806fca | |
SuperBFG7 | 5d9a4831d4 | |
SuperBFG7 | ce689a26de | |
Daniel Schregenberger | b367115850 | |
Daniel Schregenberger | 08bc954917 | |
Andy | ec008a4995 | |
Andrew Karpow | 0059a6dd41 | |
Andy | 9274480b2e | |
Andy | 126f247a08 | |
Andy | 597018542e | |
Andy | 2facf32896 | |
Andrew Karpow | ba40ad4515 | |
Andy | 868f0f622a | |
Andy | b041307637 | |
Andrew Karpow | 336d1e9097 | |
SuperBFG7 | 0591fadb20 | |
SuperBFG7 | ac47c67004 | |
LordMyschkin | f471c961e9 | |
LordMyschkin | aa49b1d352 | |
Nofre | 8ef9e58ab0 | |
Nofre | 0e47d5c4bc | |
SuperBFG7 | 97bdc9aa97 | |
SuperBFG7 | 83dcb137fb | |
Benjamin Häublein | b047a900bc | |
SuperBFG7 | 2526a015f0 | |
SuperBFG7 | d72c10cd8c | |
SuperBFG7 | fd50f3c4ed | |
SuperBFG7 | 6f54187ea9 | |
SuperBFG7 | 5090ca370e | |
SuperBFG7 | 84ad2b06a7 | |
SuperBFG7 | b59c06b922 | |
SuperBFG7 | 2e74427778 | |
SuperBFG7 | a8bcba7fe7 | |
SuperBFG7 | f06885fab7 | |
SuperBFG7 | 8696216d6e | |
SuperBFG7 | 4643f1cdcb | |
SuperBFG7 | ff92b2718f | |
SuperBFG7 | 934fb63e1e | |
SuperBFG7 | 264ce34ae9 | |
Daniel Schregenberger | 03d02e815c | |
SuperBFG7 | 15c29c4b6b | |
SuperBFG7 | 686eb4f5d2 | |
SuperBFG7 | d87c762bb8 | |
SuperBFG7 | e0721fb6c9 | |
SuperBFG7 | bde33d5aa7 | |
Daniel Schregenberger | 2dc771b9c4 | |
SuperBFG7 | 4ce5f736ad | |
Daniel Schregenberger | 18ca9af342 | |
Daniel Schregenberger | 64cdd920e7 | |
Daniel Schregenberger | e051ebff9e | |
SuperBFG7 | cb82047228 | |
SuperBFG7 | 0b2c69c281 | |
SuperBFG7 | 00e58b0fe0 | |
Benjamin Häublein | 5234e97d5e | |
Benjamin Häublein | 9d11aaf829 | |
Benjamin Häublein | ab9c9cf9e2 | |
Andrew Karpow | 4295d15874 | |
Andrew Karpow | 9f06699d3e | |
Andy | 30b0b812f6 | |
Andy | b211543890 | |
Andy | 6dcb2ad758 | |
Andy | 84f3a78a42 | |
Andy | c1b52d819d | |
Daniel Wyatt | 86d2d2ecb4 | |
Benjamin Häublein | 4f138c0986 | |
Nofre | 30bb8161b5 | |
Nofre | 94869bba92 | |
Virgil Dupras | 4e2f300643 | |
Ivor Wanders | d3148056fc | |
Andy | f2164b382a | |
Virgil Dupras | 698f573a65 | |
Andy | 7b05dc1010 | |
Virgil Dupras | fe44fca184 | |
Kurt Van Dijck | 3b72651d7e | |
Andy | 51c371bbc3 | |
Kurt Van Dijck | d125f0a1d3 | |
Jason Pleau | c7f19755bd | |
Andrew Karpow | 770c32a370 | |
Andy | 8c916e2b74 | |
Kurt Van Dijck | c3ddc8318b | |
Kurt Van Dijck | 0c3ded1cf3 | |
Kurt Van Dijck | 66ed2518a2 | |
Jason Pleau | 5abfdfda7b | |
Jason Pleau | 3ef53f3cc2 | |
Jason Pleau | 1035264d66 | |
Andrew Karpow | ade4ee3808 | |
Kurt Van Dijck | d344ec05a9 | |
Andrew Karpow | caddebcec0 | |
Andrew Karpow | 4ba3e0b70f | |
Andrew Karpow | 3701a8510b | |
Andy | 2e350f4a65 | |
Jason Pleau | 19c6d7eb88 | |
Jason Pleau | 7061015486 | |
Andy | 3df5b36b97 | |
warhog | 4d4ff2bdc2 | |
warhog | 1c4e89def8 | |
Andy | ca40c44dd5 | |
Andy | 1268cab3b3 | |
ajs124 | 3eb12e6ecd | |
LaClaro | df3ba1c04c | |
Andrew Karpow | e6e93a08c7 | |
LaClaro | 8b29d44496 | |
LaClaro | f3aef08a7a | |
LaClaro | 838237d854 | |
Ivor Wanders | a5a4ac9461 | |
Andy | aeaac3ecc3 | |
Andy | b9411a7134 | |
Andy | 9728aae69c | |
Jason Pleau | 07b6ac7473 | |
Jason Pleau | 59d971dc1b | |
KAMiKAZOW | d4f45558e9 | |
Andy | 8b29371e4e | |
Jason Pleau | d8610584d4 | |
Andrew Karpow | dc15e3b0d7 | |
KAMiKAZOW | 8b77500850 | |
Christian Krafft | 379a5ffce3 | |
Christian Krafft | 718ac3024e | |
Christian Krafft | 645161bc8a |
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: true
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: true
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
---
|
||||
|
17
.drone.yml
17
.drone.yml
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# Build Artifacts
|
||||
build/
|
|
@ -0,0 +1,13 @@
|
|||
# Used by Prettier to ignore certain files and folders completely.
|
||||
# See https://prettier.io/docs/en/ignore.html for details.
|
||||
|
||||
# Ignore build and system artifacts
|
||||
build/
|
||||
cmake/
|
||||
contrib/
|
||||
|
||||
# Ignore all 3rd party libraries
|
||||
**/bootstrap*
|
||||
**/jquery*
|
||||
**/modernizr*
|
||||
**/sammy*
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.css"],
|
||||
"options": {
|
||||
"tabWidth": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
language: c
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
before_install:
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y libmpdclient-dev cmake
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -D CMAKE_BUILD_TYPE=DEBUG ..
|
||||
|
||||
script: make
|
|
@ -1,62 +1,80 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
|
||||
project (ympd)
|
||||
project (ympd C)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "2")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "3")
|
||||
if(CMAKE_BUILD_TYPE MATCHES RELEASE)
|
||||
set(ASSETS_PATH "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/htdocs")
|
||||
else()
|
||||
set(ASSETS_PATH "${PROJECT_SOURCE_DIR}/htdocs")
|
||||
endif()
|
||||
|
||||
option(WITH_MPD_HOST_CHANGE "Let users of the web frontend change the MPD Host" ON)
|
||||
option(WITH_DYNAMIC_ASSETS "Serve assets dynamically (e.g for development/packaging)" OFF)
|
||||
option(WITH_IPV6 "enable IPv6 support" ON)
|
||||
option(WITH_SSL "enable SSL support" ON)
|
||||
|
||||
find_package(LibMPDClient REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
configure_file(src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ${LIBMPDCLIENT_INCLUDE_DIR})
|
||||
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(CMAKE_C_FLAGS "-std=gnu99 -Wall")
|
||||
set(CMAKE_C_FLAGS_DEBUG "-ggdb -pedantic")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -pedantic")
|
||||
if(WITH_IPV6)
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6)
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_IPV6)
|
||||
endif()
|
||||
if(WITH_SSL)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
# list(APPEND LIB_LIST ${OPENSSL_LIBRARIES})
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS NS_ENABLE_SSL)
|
||||
endif()
|
||||
|
||||
file(GLOB RESOURCES
|
||||
RELATIVE ${PROJECT_SOURCE_DIR}
|
||||
htdocs/js/*
|
||||
htdocs/assets/*
|
||||
htdocs/css/*.min.css
|
||||
htdocs/fonts/*
|
||||
htdocs/index.html
|
||||
)
|
||||
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(MKDATA_EXE ${PROJECT_SOURCE_DIR}/htdocs/mkdata.pl)
|
||||
else()
|
||||
set(MKDATA_EXE $<TARGET_FILE:mkdata>)
|
||||
set(MKDATA_TARGET mkdata)
|
||||
add_executable(mkdata htdocs/mkdata.c)
|
||||
endif()
|
||||
|
||||
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} ${MKDATA_TARGET}
|
||||
RELATIVE ${PROJECT_SOURCE_DIR}
|
||||
htdocs/js/*
|
||||
htdocs/assets/*
|
||||
htdocs/css/*.css
|
||||
htdocs/fonts/*
|
||||
htdocs/index.html
|
||||
htdocs/player.html
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
src/ympd.c
|
||||
src/http_server.c
|
||||
src/mpd_client.c
|
||||
src/mongoose.c
|
||||
src/json_encode.c
|
||||
assets.c
|
||||
)
|
||||
|
||||
if(NOT WITH_DYNAMIC_ASSETS)
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
set(MKDATA_EXE ${PROJECT_SOURCE_DIR}/tools/mkdata.pl)
|
||||
else()
|
||||
set(MKDATA_EXE $<TARGET_FILE:mkdata>)
|
||||
set(MKDATA_TARGET mkdata)
|
||||
add_executable(mkdata tools/mkdata.c)
|
||||
endif()
|
||||
|
||||
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} ${MKDATA_TARGET}
|
||||
)
|
||||
list(APPEND SOURCES src/http_server.c assets.c)
|
||||
endif()
|
||||
|
||||
add_executable(ympd ${SOURCES})
|
||||
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(ympd ${LIBMPDCLIENT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${OPENSSL_LIBRARIES})
|
||||
|
||||
install(TARGETS ympd DESTINATION bin)
|
||||
install(FILES ympd.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1)
|
||||
if(WITH_DYNAMIC_ASSETS)
|
||||
install(DIRECTORY htdocs DESTINATION share/${PROJECT_NAME})
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# Development Notes
|
||||
|
||||
## Code Formatting
|
||||
|
||||
The project has been formatted with [prettier.io](https://prettier.io/) for the HTML, JavaScript, CSS, and Markdown files. See the configuration file [.prettierrc.json](./.prettierrc.json) and the ignore file [.prettierignore](./.prettierignore) for details. If `prettier` is installed globally, there's no need to provide the various `npm`-type dependencies in the project. Various editors may provide plugins that can use this configuration without having to install `npm` and `prettier` manually.
|
||||
|
||||
Manual Usage:
|
||||
|
||||
```bash
|
||||
> npx prettier --write .
|
||||
```
|
||||
|
||||
The C source and header files have been formatted with `clang-format`. There's no easy way to manually execute the formatter on all of the C files at the same time. The clang format is based off of the 'Google' style with ajdustments to make the changes not as disruptive. See [.clang-format](./.clang-format) file for the formatting rules. Various editors should be able to automatically format the source on save.
|
||||
|
||||
The only files formatted are the non-third party library files.
|
||||
|
||||
Manual Usage:
|
||||
|
||||
```bash
|
||||
> clang-format -i -style=file <filename>
|
||||
```
|
||||
|
||||
Manually formatted files:
|
||||
|
||||
- http_server.c
|
||||
- http_server.h
|
||||
- json_encode.h
|
||||
- mpd_client.c
|
||||
- mpd_client.h
|
||||
- ympd.c
|
||||
|
||||
For help with the rules, see [Clang Format Configurator](https://zed0.co.uk/clang-format-configurator/) for an interactive tool and [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) for the rules reference.
|
|
@ -0,0 +1,13 @@
|
|||
FROM alpine:3.5
|
||||
WORKDIR /app/build
|
||||
COPY . /app
|
||||
RUN apk add --no-cache g++ make cmake libmpdclient-dev openssl-dev
|
||||
RUN cmake ..
|
||||
RUN make
|
||||
|
||||
FROM alpine:3.5
|
||||
RUN apk add --no-cache libmpdclient openssl
|
||||
EXPOSE 8080
|
||||
COPY --from=0 /app/build/ympd /usr/bin/ympd
|
||||
COPY --from=0 /app/build/mkdata /usr/bin/mkdata
|
||||
CMD ympd
|
61
README.md
61
README.md
|
@ -1,42 +1,59 @@
|
|||
[![Build Status](https://travis-ci.org/notandy/ympd.svg)](https://travis-ci.org/notandy/ympd)
|
||||
ympd
|
||||
====
|
||||
|
||||
Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
||||
|
||||
|
||||
http://www.ympd.org
|
||||
|
||||
![ScreenShot](http://www.ympd.org/assets/ympd_github.png)
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
|
||||
- cmake 2.6: http://cmake.org/
|
||||
## Dependencies
|
||||
|
||||
Unix Build Instructions
|
||||
-----------------------
|
||||
- libmpdclient 2: http://www.musicpd.org/libs/libmpdclient/
|
||||
- cmake 2.6: http://cmake.org/
|
||||
- OpenSSL: https://www.openssl.org/
|
||||
|
||||
1. install dependencies, cmake and libmpdclient are available from all major distributions.
|
||||
2. create build directory ```cd /path/to/src; mkdir build; cd build```
|
||||
3. create makefile ```cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr```
|
||||
4. build ```make```
|
||||
5. install ```sudo make install``` or just run with ```./ympd```
|
||||
## Unix Build Instructions
|
||||
|
||||
1. install dependencies. cmake, libmpdclient (dev), and OpenSSL (dev) are available from all major distributions.
|
||||
2. create build directory `cd /path/to/src; mkdir build; cd build`
|
||||
3. create makefile `cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/usr`
|
||||
4. build `make`
|
||||
5. install `sudo make install` or just run with `./ympd`
|
||||
|
||||
## Run flags
|
||||
|
||||
Run flags
|
||||
---------
|
||||
```
|
||||
Usage: ./ympd [OPTION]...
|
||||
|
||||
-h, --host <host> connect to mpd at host [localhost]
|
||||
-p, --port <port> connect to mpd at port [6600]
|
||||
-w, --webport [ip:]<port> listen interface/port for webserver [8080]
|
||||
-u, --user <username> drop priviliges to user after socket bind
|
||||
-V, --version get version
|
||||
--help this help
|
||||
-D, --digest <htdigest> path to htdigest file for authorization
|
||||
(realm ympd) [no authorization]
|
||||
-h, --host <host> connect to mpd at host [localhost]
|
||||
-p, --port <port> connect to mpd at port [6600]
|
||||
-w, --webport [ip:]<port> listen interface/port for webserver [8080]
|
||||
-u, --user <username> drop priviliges to user after socket bind
|
||||
-V, --version get version
|
||||
--help this help
|
||||
```
|
||||
|
||||
## SSL Support
|
||||
|
||||
Copyright
|
||||
---------
|
||||
To run ympd with SSL support:
|
||||
|
||||
- create a certificate (key and cert in the same file), example:
|
||||
|
||||
```
|
||||
# openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 1000 -nodes
|
||||
# cat key.pem cert.pem > ssl.pem
|
||||
```
|
||||
|
||||
- tell ympd to use a webport using SSL and where to find the certificate:
|
||||
|
||||
```
|
||||
# ./ympd -w "ssl://8081:/path/to/ssl.pem"
|
||||
```
|
||||
|
||||
## Copyright
|
||||
|
||||
2013-2014 <andy@ndyk.de>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#/bin/sh
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: ympd
|
||||
# Required-Start: $local_fs $mpd
|
||||
# Required-Stop: $local_fs $mpd
|
||||
# Required-Start: $remote_fs mpd
|
||||
# Required-Stop: $remote_fs mpd
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Daemonized version of ympd.
|
||||
|
@ -20,8 +20,13 @@ PIDFILE=/var/run/$NAME.pid
|
|||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
LOG_OUT=/var/log/$NAME.out
|
||||
LOG_ERR=/var/log/$NAME.err
|
||||
YMPD_USER=mpd
|
||||
DAEMON_OPT="--user $YMPD_USER --webport 80"
|
||||
YMPD_USER=nobody
|
||||
MPD_HOST=localhost
|
||||
MPD_PORT=6600
|
||||
WEB_PORT=8080
|
||||
#DIGEST=--digest /path/to/htdigest
|
||||
#LOCALPORT=8080
|
||||
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
@ -32,6 +37,8 @@ DAEMON_OPT="--user $YMPD_USER --webport 80"
|
|||
# Load the VERBOSE setting and other rcS variables
|
||||
[ -f /etc/default/rcS ] && . /etc/default/rcS
|
||||
|
||||
DAEMON_OPT="--user $YMPD_USER --mpdpass '$MPD_PASSWORD' --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT"
|
||||
|
||||
do_start()
|
||||
{
|
||||
start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile \
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
MPD_HOST=localhost
|
||||
MPD_PORT=6600
|
||||
MPD_PASSWORD=
|
||||
WEB_PORT=8080
|
||||
#DIGEST=--digest /path/to/htdigest
|
||||
#LOCALPORT=--localport 8080
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
|
||||
# PROVIDE: ympd
|
||||
# REQUIRE: DAEMON musicpd
|
||||
# KEYWORD: shutdown
|
||||
|
||||
# Add the following line to /etc/rc.conf to enable ympd:
|
||||
#
|
||||
# ympd_enable="YES"
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="ympd"
|
||||
rcvar="${name}_enable"
|
||||
command="/usr/local/bin/${name}"
|
||||
pidfile="/var/run/${name}.pid"
|
||||
start_cmd="ympd_start"
|
||||
|
||||
load_rc_config "${name}"
|
||||
: ${ympd_enable:="NO"}
|
||||
|
||||
ympd_start()
|
||||
{
|
||||
check_startmsgs && echo "Starting ${name}."
|
||||
/usr/sbin/daemon -f -p "${pidfile}" "${command}" "${rc_flags}"
|
||||
}
|
||||
|
||||
run_rc_command "$1"
|
|
@ -3,11 +3,35 @@ Description=ympd server daemon
|
|||
Requires=network.target local-fs.target
|
||||
|
||||
[Service]
|
||||
User=nobody
|
||||
DynamicUser=yes
|
||||
MountAPIVFS=yes
|
||||
RemoveIPC=yes
|
||||
CapabilityBoundingSet=
|
||||
LockPersonality=yes
|
||||
PrivateUsers=yes
|
||||
PrivateTmp=yes
|
||||
PrivateDevices=yes
|
||||
ProtectSystem=strict
|
||||
NoNewPrivileges=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictNamespaces=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
|
||||
Environment=MPD_HOST=localhost
|
||||
Environment=MPD_PORT=6600
|
||||
Environment=MPD_PASSWORD=
|
||||
Environment=WEB_PORT=8080
|
||||
EnvironmentFile=-/etc/conf.d/ympd
|
||||
ExecStart=/usr/bin/ympd -h $MPD_HOST -p $MPD_PORT -w $WEB_PORT
|
||||
Environment=YMPD_USER=nobody
|
||||
Environment=DIGEST=
|
||||
Environment=LOCALPORT=
|
||||
EnvironmentFile=/etc/default/ympd
|
||||
ExecStart=/usr/bin/ympd --user $USER --webport $WEB_PORT --host $MPD_HOST --port $MPD_PORT $DIGEST $LOCALPORT
|
||||
Type=simple
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
#
|
||||
# spec file for package ympd
|
||||
#
|
||||
# Copyright (c) 2014 Markus S. <kamikazow@web.de>
|
||||
#
|
||||
# This file 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Name: ympd
|
||||
Version: 1.2.2
|
||||
Release: 0%{?dist}
|
||||
Summary: ympd is a lightweight MPD (Music Player Daemon) web client
|
||||
Group: Applications/Multimedia
|
||||
License: GPL
|
||||
URL: http://www.ympd.org/
|
||||
|
||||
# For this spec file to work, the ympd sources must be located in a directory
|
||||
# named ympd-1.2.2 (with "1.2.2" being the version number defined above).
|
||||
# If the sources are compressed in another format than ZIP, change the
|
||||
# file extension accordingly.
|
||||
Source0: %{name}-%{version}.zip
|
||||
|
||||
# Package names only verified with Fedora.
|
||||
# Should the packages in your distro be named dirrerently,
|
||||
# see http://en.opensuse.org/openSUSE:Build_Service_cross_distribution_howto
|
||||
# %if 0%{?fedora}
|
||||
BuildRequires: cmake
|
||||
BuildRequires: unzip
|
||||
BuildRequires: libmpdclient-devel
|
||||
Requires: libmpdclient
|
||||
# %endif
|
||||
|
||||
%description
|
||||
ympd is a lightweight MPD (Music Player Daemon) web client that runs without
|
||||
a dedicated webserver or interpreters like PHP, NodeJS or Ruby.
|
||||
It's tuned for minimal resource usage and requires only very litte dependencies.
|
||||
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
mkdir build
|
||||
pushd build
|
||||
%cmake .. -DCMAKE_INSTALL_PREFIX_PATH=%{_prefix}
|
||||
make PREFIX=%{_prefix} %{?_smp_mflags}
|
||||
popd
|
||||
|
||||
%install
|
||||
pushd build
|
||||
%{make_install}
|
||||
popd
|
||||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%doc LICENSE README.md
|
||||
%{_bindir}/%{name}
|
||||
%{_mandir}/man[^3]/*
|
||||
|
||||
|
||||
%changelog
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -9,23 +9,52 @@ body {
|
|||
|
||||
#volumeslider {
|
||||
width: 150px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#volumeslider .progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#volume-icon {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
#volume-number {
|
||||
float: right;
|
||||
margin-top: 2px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#love {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#love > button > span {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#breadcrump {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#breadcrump > li > a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#counter {
|
||||
font-size: 24px;
|
||||
margin-top: -6px;
|
||||
margin-left: 10px;
|
||||
min-width: 50px;
|
||||
font-size: 24px;
|
||||
margin-top: -6px;
|
||||
margin-left: 10px;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
#search {
|
||||
|
@ -33,7 +62,7 @@ body {
|
|||
}
|
||||
|
||||
.btn-group-hover {
|
||||
opacity: 20%;
|
||||
opacity: 20%;
|
||||
}
|
||||
|
||||
.btn:active,
|
||||
|
@ -41,29 +70,77 @@ body {
|
|||
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);
|
||||
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;
|
||||
#btn-responsive-block > .btn {
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
#salamisandwich td:nth-child(3), th:nth-child(3) {
|
||||
h1 {
|
||||
display: block;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td:nth-child(4),
|
||||
th:nth-child(4) {
|
||||
/* This *has* to be placed before
|
||||
any t[dh]:nth-last-child(2) for
|
||||
the override to work. */
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
td:nth-last-child(2),
|
||||
th:nth-last-child(2) {
|
||||
text-align: right;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#salamisandwich td:nth-child(4) span {
|
||||
font-size: 90%;
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
td:nth-child(2),
|
||||
td:nth-child(3) {
|
||||
min-width: 25%;
|
||||
max-width: 10em;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
td:nth-child(2),
|
||||
td:nth-child(3) {
|
||||
min-width: 0;
|
||||
max-width: 0;
|
||||
}
|
||||
td:nth-child(4),
|
||||
th:nth-child(4) {
|
||||
min-width: 10%;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
td:last-child, td:first-child {
|
||||
td:last-child,
|
||||
td:first-child {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
|
@ -72,7 +149,7 @@ td:last-child, td:first-child {
|
|||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Positioning */
|
||||
/* Positioning */
|
||||
.notifications.top-right {
|
||||
right: 10px;
|
||||
top: 60px;
|
||||
|
@ -83,4 +160,30 @@ td:last-child, td:first-child {
|
|||
position: relative;
|
||||
z-index: 9999;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#trashmode span:last-child {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
width: 2.8em;
|
||||
}
|
||||
|
||||
#filter > a.active {
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 55px;
|
||||
z-index: 99;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
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}
|
|
@ -1,246 +1,597 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="ympd - fast and lightweight MPD webclient">
|
||||
<meta name="author" content="andy@ndyk.de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="ympd - fast and lightweight MPD webclient"
|
||||
/>
|
||||
<meta name="author" content="andy@ndyk.de" />
|
||||
|
||||
<title>ympd</title>
|
||||
<title>ympd</title>
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="css/bootstrap.css" rel="stylesheet" />
|
||||
<link href="css/bootstrap-theme.css" rel="stylesheet" />
|
||||
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/mpd.min.css" rel="stylesheet">
|
||||
<link href="assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="css/mpd.css" rel="stylesheet" />
|
||||
<link
|
||||
href="assets/favicon.ico"
|
||||
rel="shortcut icon"
|
||||
type="image/vnd.microsoft.icon"
|
||||
/>
|
||||
<script src="js/modernizr-custom.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button
|
||||
type="button"
|
||||
class="navbar-toggle"
|
||||
data-toggle="collapse"
|
||||
data-target=".navbar-collapse"
|
||||
>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/"
|
||||
><span class="glyphicon glyphicon-play-circle"></span> ympd</a
|
||||
>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul id="nav_links" class="nav navbar-nav">
|
||||
<li id="queue"><a href="#/">Queue</a></li>
|
||||
<li id="browse"><a href="#/browse/0/">Browse</a></li>
|
||||
<li>
|
||||
<a href="#" data-toggle="modal" data-target="#addstream"
|
||||
>Add Stream</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
data-toggle="modal"
|
||||
data-target="#settings"
|
||||
onclick="getHost();"
|
||||
>Settings</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/"><span class="glyphicon glyphicon-play-circle"></span> ympd</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse">
|
||||
|
||||
<ul id="nav_links" class="nav navbar-nav">
|
||||
<li id="queue"><a href="#/">Queue</a></li>
|
||||
<li id="browse"><a href="#/browse/0/">Browse database</a></li>
|
||||
<li><a href="#" data-toggle="modal" data-target="#settings" onclick="getHost();">Settings</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_PREV');">
|
||||
<span class="glyphicon glyphicon-backward"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_STOP');">
|
||||
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" onclick="clickPlay();">
|
||||
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_SET_NEXT');">
|
||||
<span class="glyphicon glyphicon-forward"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-toolbar btn-default">
|
||||
<span id="volume-icon" class="glyphicon glyphicon-volume-up"></span>
|
||||
<div id="volumeslider"></div>
|
||||
<div class="btn-toolbar navbar-btn navbar-right" role="toolbar">
|
||||
<div class="btn-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="socket.send('MPD_API_SET_PREV');"
|
||||
>
|
||||
<span class="glyphicon glyphicon-backward"></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="socket.send('MPD_API_SET_STOP');"
|
||||
>
|
||||
<span id="stop-icon" class="glyphicon glyphicon-stop"></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="clickPlay();"
|
||||
>
|
||||
<span id="play-icon" class="glyphicon glyphicon-pause"></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="socket.send('MPD_API_SET_NEXT');"
|
||||
>
|
||||
<span class="glyphicon glyphicon-forward"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="clickLocalPlay()"
|
||||
>
|
||||
<audio id="player" preload="none"></audio>
|
||||
<span
|
||||
id="localplay-icon"
|
||||
class="glyphicon glyphicon-play"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group" id="volume-group">
|
||||
<div class="btn btn-toolbar btn-default">
|
||||
<span
|
||||
id="volume-icon"
|
||||
class="glyphicon glyphicon-volume-up"
|
||||
></span>
|
||||
<div id="volumeslider"></div>
|
||||
<span id="volume-number"></span>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="container starter-template">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-10 col-xs-12">
|
||||
<div class="notifications top-right"></div>
|
||||
|
||||
<div class="panel panel-primary">
|
||||
<!-- Default panel contents -->
|
||||
<div class="panel-heading"><b id="panel-heading">Queue</b></div>
|
||||
<div class="panel-body">
|
||||
<h1>
|
||||
<span id="track-icon" class="glyphicon glyphicon-play"></span>
|
||||
<span id="currenttrack"></span>
|
||||
</h1>
|
||||
<h4>
|
||||
<span id="album" class="text"></span>
|
||||
<span id="artist" class="text pull-right"></span>
|
||||
</h4>
|
||||
<p id="counter" class="text pull-right"> </p>
|
||||
|
||||
<div id="progressbar"></div>
|
||||
|
||||
|
||||
</div><!-- /.panel-body -->
|
||||
|
||||
<ol id="breadcrump" class="breadcrumb">
|
||||
</ol>
|
||||
|
||||
<!-- Table -->
|
||||
<table id="salamisandwich" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Title</th>
|
||||
<th>Duration</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</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 class="col-md-2 col-xs-12" >
|
||||
<div class="btn-toolbar">
|
||||
<div class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
|
||||
<button id="btnrandom" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-random"></span> Random
|
||||
</button>
|
||||
<button id="btnconsume" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-fire"></span> Consume
|
||||
</button>
|
||||
<button id="btnsingle" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-star"></span> Single
|
||||
</button>
|
||||
<button id="btnrepeat" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-repeat"></span> Repeat
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg">
|
||||
<button type="button" class="btn btn-default" onclick="updateDB();">
|
||||
<span class="glyphicon glyphicon-refresh"></span> Update DB
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" onclick="socket.send('MPD_API_RM_ALL');">
|
||||
<span class="glyphicon glyphicon-trash"></span> Clear queue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="btn-responsive-block" class="btn-group-vertical btn-block btn-group-lg" data-toggle="buttons">
|
||||
<button type="button" class="btn btn-default" id="btnnotify">
|
||||
<span class="glyphicon glyphicon-comment"></span> Notifications
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.col-md-2 -->
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.container -->
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="settings" tabindex="-1" role="dialog" aria-labelledby="settingsLabel" 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="settingsLabel"><span class="glyphicon glyphicon-wrench"></span> Settings</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 webserver 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">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-9">
|
||||
<label class="control-label" for="mpdhost">MPD Host/IP</label>
|
||||
<input type="text" class="form-control" id="mpdhost" />
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label class="control-label" for="mpdport">MPD Port</label>
|
||||
<input type="text" class="form-control" id="mpdport" />
|
||||
</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>
|
||||
|
||||
<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>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-default" onclick="confirmSettings();">Save</button>
|
||||
</div>
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
<!--/.nav-collapse -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 class="container starter-template">
|
||||
<div class="row">
|
||||
<div class="col-md-10 col-xs-12">
|
||||
<div class="notifications top-right"></div>
|
||||
|
||||
<div class="panel panel-primary sticky">
|
||||
<!-- Default panel contents -->
|
||||
<div class="panel-body">
|
||||
<h1>
|
||||
<span
|
||||
id="track-icon"
|
||||
onclick="clickPlay();"
|
||||
class="glyphicon glyphicon-play"
|
||||
></span>
|
||||
<span id="currenttrack"></span>
|
||||
<span id="love">
|
||||
<button
|
||||
id="btnlove"
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="clickLove();"
|
||||
>
|
||||
<span class="glyphicon glyphicon-heart"></span></button
|
||||
></span>
|
||||
</h1>
|
||||
<h4>
|
||||
<span id="artist" class="text"></span>
|
||||
<span id="album" class="text pull-right"></span>
|
||||
</h4>
|
||||
<p id="counter" class="text pull-right"> </p>
|
||||
|
||||
<div id="progressbar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.panel -->
|
||||
<!-- /.panel-body -->
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<b id="panel-heading">Queue</b>
|
||||
<b id="panel-heading-info" class="text pull-right"></b>
|
||||
</div>
|
||||
<ol id="breadcrump" class="breadcrumb"></ol>
|
||||
|
||||
<div class="col-md-12" id="filter"></div>
|
||||
|
||||
<!-- Table -->
|
||||
<table id="salamisandwich" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Artist</th>
|
||||
<th>Album</th>
|
||||
<th>Title</th>
|
||||
<th>Length</th>
|
||||
<th><div style="width: 15px"></div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</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>
|
||||
<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>
|
||||
<!-- /.col-md-10 -->
|
||||
|
||||
<div class="col-md-2 col-xs-12 sticky">
|
||||
<div class="btn-toolbar">
|
||||
<div
|
||||
class="btn-group-vertical btn-block btn-group-lg"
|
||||
data-toggle="buttons"
|
||||
>
|
||||
<button id="btnrandom" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-random"></span> Random
|
||||
</button>
|
||||
<button id="btnconsume" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-fire"></span> Consume
|
||||
</button>
|
||||
<button id="btnsingle" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-star"></span> Single
|
||||
</button>
|
||||
<button id="btncrossfade" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-link"></span> Crossfade
|
||||
</button>
|
||||
<button id="btnrepeat" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-repeat"></span> Repeat
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="btn-outputs-block"
|
||||
class="btn-group-vertical btn-block btn-group-lg"
|
||||
></div>
|
||||
|
||||
<div
|
||||
id="trashmode"
|
||||
class="btn-group-vertical btn-block btn-group-lg"
|
||||
data-toggle="radio"
|
||||
>
|
||||
<button id="btntrashmodeup" type="button" class="btn btn-default">
|
||||
<span class="glyphicon glyphicon-chevron-up"></span>
|
||||
<span class="glyphicon glyphicon-trash"></span> <span>Up</span>
|
||||
</button>
|
||||
<button
|
||||
id="btntrashmodesingle"
|
||||
type="button"
|
||||
class="btn btn-default active"
|
||||
>
|
||||
<span class="glyphicon glyphicon-star-empty"></span>
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
<span>Single</span>
|
||||
</button>
|
||||
<button
|
||||
id="btntrashmodedown"
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
>
|
||||
<span class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span class="glyphicon glyphicon-trash"></span>
|
||||
<span>Down</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="btn-responsive-block"
|
||||
class="btn-group-vertical btn-block btn-group-lg"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="socket.send('MPD_API_RM_ALL');"
|
||||
>
|
||||
<span class="glyphicon glyphicon-trash"></span> Clear Queue
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
data-toggle="modal"
|
||||
data-target="#savequeue"
|
||||
class="btn btn-default"
|
||||
>
|
||||
<span class="glyphicon glyphicon-save"></span> Save Queue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.col-md-2 -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
</div>
|
||||
<!-- /.container -->
|
||||
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="settings"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="settingsLabel"
|
||||
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="settingsLabel">
|
||||
<span class="glyphicon glyphicon-wrench"></span> Settings
|
||||
</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 webserver 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 />
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default btn-block"
|
||||
onclick="updateDB();"
|
||||
>
|
||||
<span class="glyphicon glyphicon-refresh"></span> Update
|
||||
Database
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group col-md-6" data-toggle="buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default btn-block"
|
||||
id="btnnotify"
|
||||
>
|
||||
<span class="glyphicon glyphicon-comment"></span> Enable
|
||||
Notifications
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-9">
|
||||
<label class="control-label" for="mpdhost">MPD Host/IP</label>
|
||||
<input type="text" class="form-control" id="mpdhost" />
|
||||
</div>
|
||||
<div class="form-group col-md-3">
|
||||
<label class="control-label" for="mpdport">MPD Port</label>
|
||||
<input type="text" class="form-control" id="mpdport" />
|
||||
</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="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>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label class="control-label" for="mpdstream"
|
||||
>MPD Stream URL</label
|
||||
>
|
||||
<input type="text" class="form-control" id="mpdstream" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12" data-toggle="buttons">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default btn-block"
|
||||
id="btnautoplay"
|
||||
>
|
||||
<span class="glyphicon glyphicon-play"></span> Autoplay stream
|
||||
in this browser when mpd is playing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="confirmSettings();"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
|
||||
<!-- Modal -->
|
||||
<div
|
||||
class="modal fade"
|
||||
id="addstream"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="addstreamLabel"
|
||||
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="addstreamLabel">
|
||||
<span class="glyphicon glyphicon-wrench"></span> Add Stream
|
||||
</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-12">
|
||||
<label class="control-label" for="streamurl"
|
||||
>Stream URL</label
|
||||
>
|
||||
<input type="text" class="form-control" id="streamurl" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="addStream();"
|
||||
>
|
||||
Add Stream
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</div>
|
||||
<!-- /.modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="savequeue"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="savequeueLabel"
|
||||
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="savequeueLabel">
|
||||
<span class="glyphicon glyphicon-wrench"></span> Save Queue
|
||||
</h2>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="form-group col-md-9">
|
||||
<label class="control-label" for="playlistname"
|
||||
>Playlist Name</label
|
||||
>
|
||||
<input type="text" class="form-control" id="playlistname" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
onclick="saveQueue();"
|
||||
>
|
||||
Save Queue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.modal-content -->
|
||||
</div>
|
||||
<!-- /.modal-dialog -->
|
||||
</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>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="js/jquery-1.10.2.min.js"></script>
|
||||
<script src="js/jquery.cookie.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/bootstrap-notify.js"></script>
|
||||
<script src="js/bootstrap-slider.js"></script>
|
||||
<script src="js/sammy.js"></script>
|
||||
<script src="js/mpd.js"></script>
|
||||
</body>
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="js/jquery-1.10.2.min.js"></script>
|
||||
<script src="js/jquery.cookie.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/bootstrap-notify.js"></script>
|
||||
<script src="js/bootstrap-slider.js"></script>
|
||||
<script src="js/sammy.js"></script>
|
||||
<script src="js/jquery-ui-sortable.min.js"></script>
|
||||
<script src="js/mpd.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1449
htdocs/js/mpd.js
1449
htdocs/js/mpd.js
File diff suppressed because it is too large
Load Diff
|
@ -22,8 +22,8 @@
|
|||
#define YMPD_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
#define YMPD_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR}
|
||||
#define YMPD_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH}
|
||||
#define DATADIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}"
|
||||
#define LOCAL_RESOURCE_PATH DATADIR"/htdocs"
|
||||
#define SRC_PATH "${ASSETS_PATH}"
|
||||
#cmakedefine WITH_MPD_HOST_CHANGE
|
||||
#cmakedefine WITH_DYNAMIC_ASSETS
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -16,24 +16,41 @@
|
|||
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "http_server.h"
|
||||
|
||||
int callback_http(struct mg_connection *c)
|
||||
{
|
||||
#include <openssl/rand.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mpd_client.h"
|
||||
|
||||
int callback_http(struct mg_connection *c) {
|
||||
const struct embedded_file *req_file;
|
||||
|
||||
if(!strcmp(c->uri, "/"))
|
||||
if (!strcmp(c->uri, "/"))
|
||||
req_file = find_embedded_file("/index.html");
|
||||
else
|
||||
req_file = find_embedded_file(c->uri);
|
||||
|
||||
if(req_file)
|
||||
{
|
||||
if (req_file) {
|
||||
mg_send_header(c, "Content-Type", req_file->mimetype);
|
||||
mg_send_data(c, req_file->data, req_file->size);
|
||||
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
if (!strcmp(c->uri, "/wss-auth")) {
|
||||
unsigned char salt[WSS_AUTH_TOKEN_SIZE + 1];
|
||||
|
||||
RAND_bytes(salt, WSS_AUTH_TOKEN_SIZE);
|
||||
for (int i = 0; i <= WSS_AUTH_TOKEN_SIZE; i++) salt[i] = salt[i] % 26 + 65;
|
||||
salt[WSS_AUTH_TOKEN_SIZE] = 0;
|
||||
if (mpd.wss_auth_token)
|
||||
free(mpd.wss_auth_token);
|
||||
mpd.wss_auth_token = strdup((char *)salt);
|
||||
|
||||
mg_send_header(c, "Content-Type", "text/plain");
|
||||
mg_send_data(c, salt, WSS_AUTH_TOKEN_SIZE);
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -22,14 +22,13 @@
|
|||
#include "mongoose.h"
|
||||
|
||||
struct embedded_file {
|
||||
const char *name;
|
||||
const unsigned char *data;
|
||||
const char *mimetype;
|
||||
size_t size;
|
||||
const char *name;
|
||||
const unsigned char *data;
|
||||
const char *mimetype;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
const struct embedded_file *find_embedded_file(const char *name);
|
||||
int callback_http(struct mg_connection *c);
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -15,7 +15,7 @@
|
|||
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__
|
||||
|
||||
|
|
1521
src/mongoose.c
1521
src/mongoose.c
File diff suppressed because it is too large
Load Diff
299
src/mongoose.h
299
src/mongoose.h
|
@ -1,147 +1,152 @@
|
|||
// 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.4"
|
||||
|
||||
#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
|
||||
char local_ip[48]; // 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; // Data 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; // Needed by mg_iterate_over_connections()
|
||||
};
|
||||
|
||||
struct mg_server; // Opaque structure describing server instance
|
||||
enum mg_result { MG_FALSE, MG_TRUE, MG_MORE };
|
||||
enum mg_event {
|
||||
MG_POLL = 100, // Callback return value is ignored
|
||||
MG_CONNECT, // If callback returns MG_FALSE, connect fails
|
||||
MG_AUTH, // If callback returns MG_FALSE, authentication fails
|
||||
MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req
|
||||
MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection
|
||||
MG_CLOSE, // Connection is closed, callback return value is ignored
|
||||
MG_WS_HANDSHAKE, // New websocket connection, handshake request
|
||||
MG_WS_CONNECT, // New websocket connection established
|
||||
MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err
|
||||
};
|
||||
typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event);
|
||||
|
||||
// Websocket opcodes, from http://tools.ietf.org/html/rfc6455
|
||||
enum {
|
||||
WEBSOCKET_OPCODE_CONTINUATION = 0x0,
|
||||
WEBSOCKET_OPCODE_TEXT = 0x1,
|
||||
WEBSOCKET_OPCODE_BINARY = 0x2,
|
||||
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8,
|
||||
WEBSOCKET_OPCODE_PING = 0x9,
|
||||
WEBSOCKET_OPCODE_PONG = 0xa
|
||||
};
|
||||
|
||||
// Server management functions
|
||||
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler);
|
||||
void mg_destroy_server(struct mg_server **);
|
||||
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
|
||||
int mg_poll_server(struct mg_server *, int milliseconds);
|
||||
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 *);
|
||||
struct mg_connection *mg_next(struct mg_server *, struct mg_connection *);
|
||||
void mg_wakeup_server(struct mg_server *);
|
||||
void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...);
|
||||
struct mg_connection *mg_connect(struct mg_server *, const char *, int, int);
|
||||
|
||||
// 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);
|
||||
size_t mg_send_data(struct mg_connection *, const void *data, int data_len);
|
||||
size_t mg_printf_data(struct mg_connection *, const char *format, ...);
|
||||
size_t mg_write(struct mg_connection *, const void *buf, int len);
|
||||
size_t mg_printf(struct mg_connection *conn, const char *fmt, ...);
|
||||
|
||||
size_t mg_websocket_write(struct mg_connection *, int opcode,
|
||||
const char *data, size_t data_len);
|
||||
size_t mg_websocket_printf(struct mg_connection* conn, int opcode,
|
||||
const char *fmt, ...);
|
||||
|
||||
void mg_send_file(struct mg_connection *, const char *path);
|
||||
|
||||
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);
|
||||
int mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len);
|
||||
int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, int);
|
||||
int mg_terminate_ssl(struct mg_connection *c, const char *cert);
|
||||
|
||||
// Templates support
|
||||
struct mg_expansion {
|
||||
const char *keyword;
|
||||
void (*handler)(struct mg_connection *);
|
||||
};
|
||||
void mg_template(struct mg_connection *, const char *text,
|
||||
struct mg_expansion *expansions);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // MONGOOSE_HEADER_INCLUDED
|
||||
// Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
// Copyright (c) 2013-2014 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
//
|
||||
// This software 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 software 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 software under a commercial
|
||||
// license, as set out in <http://cesanta.com/>.
|
||||
|
||||
#ifndef MONGOOSE_HEADER_INCLUDED
|
||||
#define MONGOOSE_HEADER_INCLUDED
|
||||
|
||||
#define MONGOOSE_VERSION "5.6"
|
||||
|
||||
#include <stdio.h> // required for FILE
|
||||
#include <stddef.h> // required for size_t
|
||||
#include <sys/types.h> // required for time_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
|
||||
char local_ip[48]; // 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; // Data 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_create_server()
|
||||
void *connection_param; // Placeholder for connection-specific data
|
||||
void *callback_param;
|
||||
};
|
||||
|
||||
struct mg_server; // Opaque structure describing server instance
|
||||
enum mg_result { MG_FALSE, MG_TRUE, MG_MORE };
|
||||
enum mg_event {
|
||||
MG_POLL = 100, // Callback return value is ignored
|
||||
MG_CONNECT, // If callback returns MG_FALSE, connect fails
|
||||
MG_AUTH, // If callback returns MG_FALSE, authentication fails
|
||||
MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req
|
||||
MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection
|
||||
MG_RECV, // Mongoose has received POST data chunk.
|
||||
// Callback should return a number of bytes to discard from
|
||||
// the receive buffer, or -1 to close the connection.
|
||||
MG_CLOSE, // Connection is closed, callback return value is ignored
|
||||
MG_WS_HANDSHAKE, // New websocket connection, handshake request
|
||||
MG_WS_CONNECT, // New websocket connection established
|
||||
MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err
|
||||
};
|
||||
typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event);
|
||||
|
||||
// Websocket opcodes, from http://tools.ietf.org/html/rfc6455
|
||||
enum {
|
||||
WEBSOCKET_OPCODE_CONTINUATION = 0x0,
|
||||
WEBSOCKET_OPCODE_TEXT = 0x1,
|
||||
WEBSOCKET_OPCODE_BINARY = 0x2,
|
||||
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8,
|
||||
WEBSOCKET_OPCODE_PING = 0x9,
|
||||
WEBSOCKET_OPCODE_PONG = 0xa
|
||||
};
|
||||
|
||||
// Server management functions
|
||||
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler);
|
||||
void mg_destroy_server(struct mg_server **);
|
||||
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
|
||||
time_t mg_poll_server(struct mg_server *, int milliseconds);
|
||||
const char **mg_get_valid_option_names(void);
|
||||
const char *mg_get_option(const struct mg_server *server, const char *name);
|
||||
void mg_copy_listeners(struct mg_server *from, struct mg_server *to);
|
||||
struct mg_connection *mg_next(struct mg_server *, struct mg_connection *);
|
||||
void mg_wakeup_server(struct mg_server *);
|
||||
void mg_wakeup_server_ex(struct mg_server *, mg_handler_t, const char *, ...);
|
||||
struct mg_connection *mg_connect(struct mg_server *, const char *);
|
||||
|
||||
// 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);
|
||||
size_t mg_send_data(struct mg_connection *, const void *data, int data_len);
|
||||
size_t mg_printf_data(struct mg_connection *, const char *format, ...);
|
||||
size_t mg_write(struct mg_connection *, const void *buf, size_t len);
|
||||
size_t mg_printf(struct mg_connection *conn, const char *fmt, ...);
|
||||
|
||||
size_t mg_websocket_write(struct mg_connection *, int opcode,
|
||||
const char *data, size_t data_len);
|
||||
size_t mg_websocket_printf(struct mg_connection* conn, int opcode,
|
||||
const char *fmt, ...);
|
||||
|
||||
void mg_send_file(struct mg_connection *, const char *path, const char *);
|
||||
void mg_send_file_data(struct mg_connection *, int fd);
|
||||
|
||||
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);
|
||||
size_t mg_url_encode(const char *src, size_t s_len, char *dst, size_t dst_len);
|
||||
int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int);
|
||||
int mg_terminate_ssl(struct mg_connection *c, const char *cert);
|
||||
int mg_forward(struct mg_connection *c, const char *addr);
|
||||
void *mg_mmap(FILE *fp, size_t size);
|
||||
void mg_munmap(void *p, size_t size);
|
||||
|
||||
|
||||
// Templates support
|
||||
struct mg_expansion {
|
||||
const char *keyword;
|
||||
void (*handler)(struct mg_connection *);
|
||||
};
|
||||
void mg_template(struct mg_connection *, const char *text,
|
||||
struct mg_expansion *expansions);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif // MONGOOSE_HEADER_INCLUDED
|
||||
|
|
638
src/mpd_client.c
638
src/mpd_client.c
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -16,47 +16,87 @@
|
|||
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include "mpd_client.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include <mpd/client.h>
|
||||
#include <mpd/message.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mpd_client.h"
|
||||
#include "config.h"
|
||||
#include "json_encode.h"
|
||||
|
||||
const char * mpd_cmd_strs[] = {
|
||||
MPD_CMDS(GEN_STR)
|
||||
};
|
||||
/* forward declaration */
|
||||
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev);
|
||||
struct t_mpd mpd;
|
||||
|
||||
static inline enum mpd_cmd_ids get_cmd_id(char *cmd)
|
||||
{
|
||||
for(int i = 0; i < sizeof(mpd_cmd_strs)/sizeof(mpd_cmd_strs[0]); i++)
|
||||
if(!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
|
||||
const char *mpd_cmd_strs[] = {MPD_CMDS(GEN_STR)};
|
||||
|
||||
char *get_arg1(char *p) {
|
||||
return strchr(p, ',') + 1;
|
||||
}
|
||||
|
||||
char *get_arg2(char *p) {
|
||||
return get_arg1(get_arg1(p));
|
||||
}
|
||||
|
||||
static inline enum mpd_cmd_ids get_cmd_id(char *cmd) {
|
||||
for (int i = 0; i < sizeof(mpd_cmd_strs) / sizeof(mpd_cmd_strs[0]); i++)
|
||||
if (!strncmp(cmd, mpd_cmd_strs[i], strlen(mpd_cmd_strs[i])))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int callback_mpd(struct mg_connection *c)
|
||||
{
|
||||
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;
|
||||
char *p_charbuf = NULL, *token;
|
||||
|
||||
if(cmd_id == -1)
|
||||
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 (!s->authorized && (cmd_id != MPD_API_AUTHORIZE)) {
|
||||
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"error\",\"data\":\"not authorized\"}");
|
||||
mg_websocket_write(c, 1, mpd.buf, n);
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
if (cmd_id == -1)
|
||||
return MG_TRUE;
|
||||
|
||||
if(mpd.conn_state != MPD_CONNECTED && cmd_id != MPD_API_SET_MPDHOST &&
|
||||
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_TRUE;
|
||||
|
||||
switch(cmd_id)
|
||||
{
|
||||
switch (cmd_id) {
|
||||
case MPD_API_AUTHORIZE:
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_AUTHORIZE"))
|
||||
goto out_authorize;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_authorize;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
s->auth_token = strdup(get_arg1(p_charbuf));
|
||||
if (!strcmp(mpd.wss_auth_token, s->auth_token))
|
||||
s->authorized = 1;
|
||||
|
||||
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"authorized\", \"data\":\"%s\"}",
|
||||
s->authorized ? "true" : "false");
|
||||
out_authorize:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_UPDATE_DB:
|
||||
mpd_run_update(mpd.conn, NULL);
|
||||
break;
|
||||
|
@ -79,173 +119,296 @@ int callback_mpd(struct mg_connection *c)
|
|||
mpd_run_clear(mpd.conn);
|
||||
break;
|
||||
case MPD_API_RM_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
|
||||
if (sscanf(c->content, "MPD_API_RM_TRACK,%u", &uint_buf))
|
||||
mpd_run_delete_id(mpd.conn, uint_buf);
|
||||
break;
|
||||
case MPD_API_RM_RANGE:
|
||||
if (sscanf(c->content, "MPD_API_RM_RANGE,%u,%u", &uint_buf, &uint_buf_2))
|
||||
mpd_run_delete_range(mpd.conn, uint_buf, uint_buf_2);
|
||||
break;
|
||||
case MPD_API_MOVE_TRACK:
|
||||
if (sscanf(c->content, "MPD_API_MOVE_TRACK,%u,%u", &uint_buf, &uint_buf_2) == 2) {
|
||||
uint_buf -= 1;
|
||||
uint_buf_2 -= 1;
|
||||
mpd_run_move(mpd.conn, uint_buf, uint_buf_2);
|
||||
}
|
||||
break;
|
||||
case MPD_API_PLAY_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
|
||||
if (sscanf(c->content, "MPD_API_PLAY_TRACK,%u", &uint_buf))
|
||||
mpd_run_play_id(mpd.conn, uint_buf);
|
||||
break;
|
||||
case MPD_API_TOGGLE_RANDOM:
|
||||
if(sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
|
||||
if (sscanf(c->content, "MPD_API_TOGGLE_RANDOM,%u", &uint_buf))
|
||||
mpd_run_random(mpd.conn, uint_buf);
|
||||
break;
|
||||
case MPD_API_TOGGLE_REPEAT:
|
||||
if(sscanf(c->content, "MPD_API_TOGGLE_REPEAT,%u", &uint_buf))
|
||||
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))
|
||||
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))
|
||||
if (sscanf(c->content, "MPD_API_TOGGLE_SINGLE,%u", &uint_buf))
|
||||
mpd_run_single(mpd.conn, uint_buf);
|
||||
break;
|
||||
case MPD_API_TOGGLE_CROSSFADE:
|
||||
if (sscanf(c->content, "MPD_API_TOGGLE_CROSSFADE,%u", &uint_buf))
|
||||
mpd_run_crossfade(mpd.conn, uint_buf);
|
||||
break;
|
||||
case MPD_API_GET_OUTPUTS:
|
||||
mpd.buf_size = mpd_put_outputs(mpd.buf, 1);
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
break;
|
||||
case MPD_API_TOGGLE_OUTPUT:
|
||||
if (sscanf(c->content, "MPD_API_TOGGLE_OUTPUT,%u,%u", &uint_buf, &uint_buf_2)) {
|
||||
if (uint_buf_2)
|
||||
mpd_run_enable_output(mpd.conn, uint_buf);
|
||||
else
|
||||
mpd_run_disable_output(mpd.conn, uint_buf);
|
||||
}
|
||||
break;
|
||||
case MPD_API_SET_VOLUME:
|
||||
if(sscanf(c->content, "MPD_API_SET_VOLUME,%ud", &uint_buf) && uint_buf <= 100)
|
||||
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))
|
||||
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))
|
||||
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);
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_GET_BROWSE"))
|
||||
goto out_browse;
|
||||
|
||||
uint_buf = strtoul(strtok(NULL, ","), NULL, 10);
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_browse;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
n = mpd_put_browse(mpd.buf, get_arg2(p_charbuf), uint_buf);
|
||||
out_browse:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_ADD_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_ADD_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
{
|
||||
mpd_run_add(mpd.conn, p_charbuf);
|
||||
free(p_charbuf);
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_TRACK"))
|
||||
goto out_add_track;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_add_track;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
mpd_run_add(mpd.conn, get_arg1(p_charbuf));
|
||||
out_add_track:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_ADD_PLAY_TRACK:
|
||||
if(sscanf(c->content, "MPD_API_ADD_PLAY_TRACK,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
{
|
||||
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);
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAY_TRACK"))
|
||||
goto out_play_track;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_play_track;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
int_buf = mpd_run_add_id(mpd.conn, get_arg1(p_charbuf));
|
||||
if (int_buf != -1)
|
||||
mpd_run_play_id(mpd.conn, int_buf);
|
||||
out_play_track:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_ADD_PLAYLIST:
|
||||
if(sscanf(c->content, "MPD_API_ADD_PLAYLIST,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
{
|
||||
mpd_run_load(mpd.conn, p_charbuf);
|
||||
free(p_charbuf);
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_ADD_PLAYLIST"))
|
||||
goto out_playlist;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_playlist;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
mpd_run_load(mpd.conn, get_arg1(p_charbuf));
|
||||
out_playlist:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_SAVE_QUEUE:
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SAVE_QUEUE"))
|
||||
goto out_save_queue;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_save_queue;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
mpd_run_save(mpd.conn, get_arg1(p_charbuf));
|
||||
out_save_queue:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_SEARCH:
|
||||
if(sscanf(c->content, "MPD_API_SEARCH,%m[^\t\n]", &p_charbuf) && p_charbuf != NULL)
|
||||
{
|
||||
n = mpd_search(mpd.buf, p_charbuf);
|
||||
free(p_charbuf);
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEARCH"))
|
||||
goto out_search;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_search;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(c->content);
|
||||
n = mpd_search(mpd.buf, get_arg1(p_charbuf));
|
||||
out_search:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_SEND_MESSAGE:
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SEND_MESSAGE"))
|
||||
goto out_send_message;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_send_message;
|
||||
|
||||
free(p_charbuf);
|
||||
p_charbuf = strdup(get_arg1(c->content));
|
||||
|
||||
if (strtok(p_charbuf, ",") == NULL)
|
||||
goto out_send_message;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_send_message;
|
||||
|
||||
mpd_run_send_message(mpd.conn, p_charbuf, token);
|
||||
out_send_message:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
case MPD_API_GET_CHANNELS:
|
||||
mpd.buf_size = mpd_put_channels(mpd.buf);
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
break;
|
||||
#ifdef WITH_MPD_HOST_CHANGE
|
||||
/* Commands allowed when disconnected from MPD server */
|
||||
case MPD_API_SET_MPDHOST:
|
||||
int_buf = 0;
|
||||
if(sscanf(c->content, "MPD_API_SET_MPDHOST,%d,%m[^\t\n ]", &int_buf, &p_charbuf) &&
|
||||
p_charbuf != NULL && int_buf > 0)
|
||||
{
|
||||
strncpy(mpd.host, p_charbuf, sizeof(mpd.host));
|
||||
free(p_charbuf);
|
||||
mpd.port = int_buf;
|
||||
mpd.conn_state = MPD_RECONNECT;
|
||||
return MG_TRUE;
|
||||
}
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDHOST"))
|
||||
goto out_host_change;
|
||||
|
||||
if ((int_buf = strtol(strtok(NULL, ","), NULL, 10)) <= 0)
|
||||
goto out_host_change;
|
||||
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_host_change;
|
||||
|
||||
strncpy(mpd.host, token, sizeof(mpd.host));
|
||||
mpd.port = int_buf;
|
||||
mpd.conn_state = MPD_RECONNECT;
|
||||
free(p_charbuf);
|
||||
return MG_TRUE;
|
||||
out_host_change:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
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");
|
||||
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);
|
||||
p_charbuf = strdup(c->content);
|
||||
if (strcmp(strtok(p_charbuf, ","), "MPD_API_SET_MPDPASS"))
|
||||
goto out_set_pass;
|
||||
|
||||
mpd.password = p_charbuf;
|
||||
mpd.conn_state = MPD_RECONNECT;
|
||||
return MG_TRUE;
|
||||
}
|
||||
if ((token = strtok(NULL, ",")) == NULL)
|
||||
goto out_set_pass;
|
||||
|
||||
if (mpd.password)
|
||||
free(mpd.password);
|
||||
|
||||
mpd.password = strdup(token);
|
||||
mpd.conn_state = MPD_RECONNECT;
|
||||
free(p_charbuf);
|
||||
return MG_TRUE;
|
||||
out_set_pass:
|
||||
free(p_charbuf);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
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));
|
||||
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)
|
||||
if (n > 0)
|
||||
mg_websocket_write(c, 1, mpd.buf, n);
|
||||
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
int mpd_close_handler(struct mg_connection *c)
|
||||
{
|
||||
int mpd_close_handler(struct mg_connection *c) {
|
||||
/* Cleanup session data */
|
||||
if(c->connection_param)
|
||||
if (c->connection_param) {
|
||||
struct t_mpd_client_session *s = (struct t_mpd_client_session *)c->connection_param;
|
||||
if (s->auth_token)
|
||||
free(s->auth_token);
|
||||
free(c->connection_param);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) {
|
||||
size_t n;
|
||||
|
||||
if(!c->is_websocket)
|
||||
if (!c->is_websocket)
|
||||
return MG_TRUE;
|
||||
|
||||
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);
|
||||
if (!c->connection_param)
|
||||
return MG_TRUE;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!s->authorized)
|
||||
return MG_TRUE;
|
||||
|
||||
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_TRUE;
|
||||
}
|
||||
|
||||
if (mpd.conn_state != MPD_CONNECTED) {
|
||||
n = snprintf(mpd.buf, MAX_SIZE, "{\"type\":\"disconnected\"}");
|
||||
mg_websocket_write(c, 1, mpd.buf, n);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
mg_websocket_write(c, 1, mpd.buf, mpd.buf_size);
|
||||
|
||||
if(s->song_id != mpd.song_id)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
@ -255,8 +418,7 @@ static int mpd_notify_callback(struct mg_connection *c, enum mg_event ev) {
|
|||
return MG_TRUE;
|
||||
}
|
||||
|
||||
void mpd_poll(struct mg_server *s)
|
||||
{
|
||||
void mpd_poll(struct mg_server *s) {
|
||||
switch (mpd.conn_state) {
|
||||
case MPD_DISCONNECTED:
|
||||
/* Try to connect */
|
||||
|
@ -270,17 +432,20 @@ void mpd_poll(struct mg_server *s)
|
|||
|
||||
if (mpd_connection_get_error(mpd.conn) != MPD_ERROR_SUCCESS) {
|
||||
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));
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
mpd.conn_state = MPD_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
if(mpd.password && !mpd_run_password(mpd.conn, mpd.password))
|
||||
{
|
||||
if (mpd.password && !mpd_run_password(mpd.conn, mpd.password)) {
|
||||
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));
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = (void *)mpd_connection_get_error_message(mpd.conn);
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
mpd.conn_state = MPD_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
@ -288,6 +453,12 @@ void mpd_poll(struct mg_server *s)
|
|||
fprintf(stderr, "MPD connected.\n");
|
||||
mpd_connection_set_timeout(mpd.conn, 10000);
|
||||
mpd.conn_state = MPD_CONNECTED;
|
||||
/* write outputs */
|
||||
mpd.buf_size = mpd_put_outputs(mpd.buf, 1);
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
break;
|
||||
|
||||
case MPD_FAILURE:
|
||||
|
@ -295,7 +466,7 @@ void mpd_poll(struct mg_server *s)
|
|||
|
||||
case MPD_DISCONNECT:
|
||||
case MPD_RECONNECT:
|
||||
if(mpd.conn != NULL)
|
||||
if (mpd.conn != NULL)
|
||||
mpd_connection_free(mpd.conn);
|
||||
mpd.conn = NULL;
|
||||
mpd.conn_state = MPD_DISCONNECTED;
|
||||
|
@ -303,25 +474,69 @@ void mpd_poll(struct mg_server *s)
|
|||
|
||||
case MPD_CONNECTED:
|
||||
mpd.buf_size = mpd_put_state(mpd.buf, &mpd.song_id, &mpd.queue_version);
|
||||
mg_iterate_over_connections(s, mpd_notify_callback, NULL);
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
mpd.buf_size = mpd_put_outputs(mpd.buf, 0);
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
mpd.buf_size = mpd_put_channels(mpd.buf);
|
||||
for (struct mg_connection *c = mg_next(s, NULL); c != NULL; c = mg_next(s, c)) {
|
||||
c->callback_param = NULL;
|
||||
mpd_notify_callback(c, MG_POLL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char* mpd_get_title(struct mpd_song const *song)
|
||||
{
|
||||
char *mpd_get_title(struct mpd_song const *song) {
|
||||
char *str;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
|
||||
{
|
||||
char *mpd_get_album(struct mpd_song const *song) {
|
||||
char *str;
|
||||
|
||||
str = (char *)mpd_song_get_tag(song, MPD_TAG_ALBUM, 0);
|
||||
if (str == NULL) {
|
||||
str = "-";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *mpd_get_artist(struct mpd_song const *song) {
|
||||
char *str;
|
||||
|
||||
str = (char *)mpd_song_get_tag(song, MPD_TAG_ARTIST, 0);
|
||||
if (str == NULL) {
|
||||
str = "-";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
char *mpd_get_year(struct mpd_song const *song) {
|
||||
char *str;
|
||||
|
||||
str = (char *)mpd_song_get_tag(song, MPD_TAG_DATE, 0);
|
||||
if (str == NULL) {
|
||||
str = "-";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version) {
|
||||
struct mpd_status *status;
|
||||
int len;
|
||||
|
||||
|
@ -333,22 +548,18 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
|
|||
}
|
||||
|
||||
len = snprintf(buffer, MAX_SIZE,
|
||||
"{\"type\":\"state\", \"data\":{"
|
||||
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
|
||||
" \"single\":%d, \"consume\":%d, \"random\":%d, "
|
||||
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
|
||||
" \"currentsongid\": %d"
|
||||
"}}",
|
||||
mpd_status_get_state(status),
|
||||
mpd_status_get_volume(status),
|
||||
mpd_status_get_repeat(status),
|
||||
mpd_status_get_single(status),
|
||||
mpd_status_get_consume(status),
|
||||
mpd_status_get_random(status),
|
||||
mpd_status_get_song_pos(status),
|
||||
mpd_status_get_elapsed_time(status),
|
||||
mpd_status_get_total_time(status),
|
||||
mpd_status_get_song_id(status));
|
||||
"{\"type\":\"state\", \"data\":{"
|
||||
" \"state\":%d, \"volume\":%d, \"repeat\":%d,"
|
||||
" \"single\":%d, \"crossfade\":%d, \"consume\":%d, \"random\":%d, "
|
||||
" \"songpos\": %d, \"elapsedTime\": %d, \"totalTime\":%d, "
|
||||
" \"currentsongid\": %d"
|
||||
"}}",
|
||||
mpd_status_get_state(status), mpd_status_get_volume(status),
|
||||
mpd_status_get_repeat(status), mpd_status_get_single(status),
|
||||
mpd_status_get_crossfade(status), mpd_status_get_consume(status),
|
||||
mpd_status_get_random(status), mpd_status_get_song_pos(status),
|
||||
mpd_status_get_elapsed_time(status), mpd_status_get_total_time(status),
|
||||
mpd_status_get_song_id(status));
|
||||
|
||||
*current_song_id = mpd_status_get_song_id(status);
|
||||
*queue_version = mpd_status_get_queue_version(status);
|
||||
|
@ -356,32 +567,81 @@ int mpd_put_state(char *buffer, int *current_song_id, unsigned *queue_version)
|
|||
return len;
|
||||
}
|
||||
|
||||
int mpd_put_current_song(char *buffer)
|
||||
{
|
||||
int mpd_put_outputs(char *buffer, int names) {
|
||||
struct mpd_output *out;
|
||||
int nout;
|
||||
char *str, *strend;
|
||||
|
||||
str = buffer;
|
||||
strend = buffer + MAX_SIZE;
|
||||
str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{",
|
||||
names ? "outputnames" : "outputs");
|
||||
|
||||
mpd_send_outputs(mpd.conn);
|
||||
nout = 0;
|
||||
while ((out = mpd_recv_output(mpd.conn)) != NULL) {
|
||||
if (nout++)
|
||||
*str++ = ',';
|
||||
if (names)
|
||||
str += snprintf(str, strend - str, " \"%d\":\"%s\"", mpd_output_get_id(out),
|
||||
mpd_output_get_name(out));
|
||||
else
|
||||
str += snprintf(str, strend - str, " \"%d\":%d", mpd_output_get_id(out),
|
||||
mpd_output_get_enabled(out));
|
||||
mpd_output_free(out);
|
||||
}
|
||||
if (!mpd_response_finish(mpd.conn)) {
|
||||
fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||
mpd_connection_clear_error(mpd.conn);
|
||||
return 0;
|
||||
}
|
||||
str += snprintf(str, strend - str, " }}");
|
||||
return str - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_channels(char *buffer) {
|
||||
struct mpd_pair *channel;
|
||||
int nchan;
|
||||
char *str, *strend;
|
||||
|
||||
str = buffer;
|
||||
strend = buffer + MAX_SIZE;
|
||||
str += snprintf(str, strend - str, "{\"type\":\"%s\", \"data\":{", "channels");
|
||||
|
||||
mpd_send_channels(mpd.conn);
|
||||
nchan = 0;
|
||||
while ((channel = mpd_recv_channel_pair(mpd.conn)) != NULL) {
|
||||
if (nchan++)
|
||||
*str++ = ',';
|
||||
str += snprintf(str, strend - str, " \"%d\":\"%s\"", nchan, channel->value);
|
||||
mpd_return_pair(mpd.conn, channel);
|
||||
}
|
||||
if (!mpd_response_finish(mpd.conn)) {
|
||||
fprintf(stderr, "MPD outputs: %s\n", mpd_connection_get_error_message(mpd.conn));
|
||||
mpd_connection_clear_error(mpd.conn);
|
||||
return 0;
|
||||
}
|
||||
str += snprintf(str, strend - str, " }}");
|
||||
return str - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_current_song(char *buffer) {
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_song *song;
|
||||
|
||||
song = mpd_run_current_song(mpd.conn);
|
||||
if(song == NULL)
|
||||
if (song == NULL)
|
||||
return 0;
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\": \"song_change\", \"data\":{\"pos\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"title\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_title(song));
|
||||
|
||||
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, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "}}");
|
||||
mpd_song_free(song);
|
||||
|
@ -390,32 +650,44 @@ int mpd_put_current_song(char *buffer)
|
|||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_queue(char *buffer, unsigned int offset)
|
||||
{
|
||||
int mpd_put_queue(char *buffer, unsigned int offset) {
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_entity *entity;
|
||||
unsigned long totalTime = 0;
|
||||
|
||||
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset+MAX_ELEMENTS_PER_PAGE))
|
||||
if (!mpd_send_list_queue_range_meta(mpd.conn, offset, offset + MAX_ELEMENTS_PER_PAGE))
|
||||
RETURN_ERROR_AND_RECOVER("mpd_send_list_queue_meta");
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"queue\",\"data\":[ ");
|
||||
|
||||
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
const struct mpd_song *song;
|
||||
unsigned int drtn;
|
||||
|
||||
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);
|
||||
drtn = mpd_song_get_duration(song);
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"id\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_id(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"pos\":");
|
||||
cur += json_emit_int(cur, end - cur, mpd_song_get_pos(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_int(cur, end - cur, drtn);
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(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, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
|
||||
totalTime += drtn;
|
||||
}
|
||||
mpd_entity_free(entity);
|
||||
}
|
||||
|
@ -423,12 +695,13 @@ int mpd_put_queue(char *buffer, unsigned int offset)
|
|||
/* remove last ',' */
|
||||
cur--;
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "]}");
|
||||
cur += json_emit_raw_str(cur, end - cur, "],\"totalTime\":");
|
||||
cur += json_emit_int(cur, end - cur, totalTime);
|
||||
cur += json_emit_raw_str(cur, end - cur, "}");
|
||||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
||||
{
|
||||
int mpd_put_browse(char *buffer, char *path, unsigned int offset) {
|
||||
char *cur = buffer;
|
||||
const char *end = buffer + MAX_SIZE;
|
||||
struct mpd_entity *entity;
|
||||
|
@ -437,25 +710,22 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
|||
if (!mpd_send_list_meta(mpd.conn, path))
|
||||
RETURN_ERROR_AND_RECOVER("mpd_send_list_meta");
|
||||
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"browse\",\"data\":[ ");
|
||||
|
||||
while((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
while ((entity = mpd_recv_entity(mpd.conn)) != NULL) {
|
||||
const struct mpd_song *song;
|
||||
const struct mpd_directory *dir;
|
||||
const struct mpd_playlist *pl;
|
||||
|
||||
if(offset > entity_count)
|
||||
{
|
||||
if (offset > entity_count) {
|
||||
mpd_entity_free(entity);
|
||||
entity_count++;
|
||||
continue;
|
||||
}
|
||||
else if(offset + MAX_ELEMENTS_PER_PAGE - 1 < entity_count)
|
||||
{
|
||||
} 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_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, "} ");
|
||||
cur += json_emit_raw_str(cur, end - cur, "} ");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -467,6 +737,10 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
|||
song = mpd_entity_get_song(entity);
|
||||
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, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(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\":");
|
||||
|
@ -506,35 +780,41 @@ int mpd_put_browse(char *buffer, char *path, unsigned int offset)
|
|||
return cur - buffer;
|
||||
}
|
||||
|
||||
int mpd_search(char *buffer, char *searchstr)
|
||||
{
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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) {
|
||||
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, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(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, ",\"artist\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_artist(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, ",\"album\":");
|
||||
cur += json_emit_quoted_str(cur, end - cur, mpd_get_album(song));
|
||||
cur += json_emit_raw_str(cur, end - cur, "},");
|
||||
mpd_song_free(song);
|
||||
|
||||
/* Maximum results */
|
||||
if(i++ >= 300)
|
||||
{
|
||||
if (i++ >= 300) {
|
||||
cur += json_emit_raw_str(cur, end - cur, "{\"type\":\"wrap\"},");
|
||||
break;
|
||||
}
|
||||
|
@ -548,9 +828,7 @@ int mpd_search(char *buffer, char *searchstr)
|
|||
return cur - buffer;
|
||||
}
|
||||
|
||||
|
||||
void mpd_disconnect()
|
||||
{
|
||||
void mpd_disconnect() {
|
||||
mpd.conn_state = MPD_DISCONNECT;
|
||||
mpd_poll(NULL);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -15,56 +15,65 @@
|
|||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __MPD_CLIENT_H__
|
||||
#define __MPD_CLIENT_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 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_ELEMENTS_PER_PAGE 512
|
||||
|
||||
#define WSS_AUTH_TOKEN_SIZE 50
|
||||
|
||||
#define GEN_ENUM(X) X,
|
||||
#define GEN_STR(X) #X,
|
||||
#define MPD_CMDS(X) \
|
||||
X(MPD_API_GET_QUEUE) \
|
||||
X(MPD_API_GET_BROWSE) \
|
||||
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_CMDS(X) \
|
||||
X(MPD_API_GET_QUEUE) \
|
||||
X(MPD_API_GET_BROWSE) \
|
||||
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_SAVE_QUEUE) \
|
||||
X(MPD_API_RM_TRACK) \
|
||||
X(MPD_API_RM_RANGE) \
|
||||
X(MPD_API_RM_ALL) \
|
||||
X(MPD_API_MOVE_TRACK) \
|
||||
X(MPD_API_SEARCH) \
|
||||
X(MPD_API_GET_CHANNELS) \
|
||||
X(MPD_API_SEND_MESSAGE) \
|
||||
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_GET_OUTPUTS) \
|
||||
X(MPD_API_TOGGLE_OUTPUT) \
|
||||
X(MPD_API_TOGGLE_RANDOM) \
|
||||
X(MPD_API_TOGGLE_CONSUME) \
|
||||
X(MPD_API_TOGGLE_SINGLE) \
|
||||
X(MPD_API_TOGGLE_CROSSFADE) \
|
||||
X(MPD_API_TOGGLE_REPEAT) \
|
||||
X(MPD_API_AUTHORIZE)
|
||||
|
||||
enum mpd_cmd_ids {
|
||||
MPD_CMDS(GEN_ENUM)
|
||||
};
|
||||
enum mpd_cmd_ids { MPD_CMDS(GEN_ENUM) };
|
||||
|
||||
enum mpd_conn_states {
|
||||
MPD_DISCONNECTED,
|
||||
|
@ -76,8 +85,11 @@ enum mpd_conn_states {
|
|||
|
||||
struct t_mpd {
|
||||
int port;
|
||||
int local_port;
|
||||
char host[128];
|
||||
char *password;
|
||||
char *gpass;
|
||||
char *wss_auth_token;
|
||||
|
||||
struct mpd_connection *conn;
|
||||
enum mpd_conn_states conn_state;
|
||||
|
@ -88,21 +100,26 @@ struct t_mpd {
|
|||
|
||||
int song_id;
|
||||
unsigned queue_version;
|
||||
} mpd;
|
||||
};
|
||||
|
||||
extern struct t_mpd mpd;
|
||||
|
||||
struct t_mpd_client_session {
|
||||
int song_id;
|
||||
unsigned queue_version;
|
||||
int authorized;
|
||||
char *auth_token;
|
||||
};
|
||||
|
||||
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_outputs(char *buffer, int putnames);
|
||||
int mpd_put_channels(char *buffer);
|
||||
int mpd_put_current_song(char *buffer);
|
||||
int mpd_put_queue(char *buffer, unsigned int offset);
|
||||
int mpd_put_browse(char *buffer, char *path, unsigned int offset);
|
||||
int mpd_search(char *buffer, char *searchstr);
|
||||
void mpd_disconnect();
|
||||
#endif
|
||||
|
||||
|
|
115
src/ympd.c
115
src/ympd.c
|
@ -1,7 +1,7 @@
|
|||
/* 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.
|
||||
|
@ -16,119 +16,169 @@
|
|||
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <getopt.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/time.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mongoose.h"
|
||||
#include "http_server.h"
|
||||
#include "mpd_client.h"
|
||||
#include "config.h"
|
||||
#include "http_server.h"
|
||||
#include "mongoose.h"
|
||||
#include "mpd_client.h"
|
||||
|
||||
extern char *optarg;
|
||||
|
||||
int force_exit = 0;
|
||||
|
||||
void bye()
|
||||
{
|
||||
void bye() {
|
||||
force_exit = 1;
|
||||
}
|
||||
|
||||
static int server_callback(struct mg_connection *c, enum mg_event ev) {
|
||||
switch(ev) {
|
||||
int result = MG_FALSE;
|
||||
FILE *fp = NULL;
|
||||
|
||||
switch (ev) {
|
||||
case MG_CLOSE:
|
||||
mpd_close_handler(c);
|
||||
return MG_TRUE;
|
||||
case MG_REQUEST:
|
||||
if (c->is_websocket) {
|
||||
c->content[c->content_len] = '\0';
|
||||
if(c->content_len)
|
||||
if (c->content_len)
|
||||
return callback_mpd(c);
|
||||
else
|
||||
return MG_TRUE;
|
||||
} else
|
||||
#ifdef WITH_DYNAMIC_ASSETS
|
||||
return MG_FALSE;
|
||||
#else
|
||||
return callback_http(c);
|
||||
#endif
|
||||
case MG_AUTH:
|
||||
return MG_TRUE;
|
||||
// no auth for websockets since mobile safari does not support it
|
||||
if ((mpd.gpass == NULL) || (c->is_websocket) ||
|
||||
((mpd.local_port > 0) && (c->local_port == mpd.local_port)))
|
||||
return MG_TRUE;
|
||||
else {
|
||||
if ((fp = fopen(mpd.gpass, "r")) != NULL) {
|
||||
result = mg_authorize_digest(c, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
default:
|
||||
return MG_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int main(int argc, char **argv) {
|
||||
int n, option_index = 0;
|
||||
struct mg_server *server = mg_create_server(NULL, server_callback);
|
||||
unsigned int current_timer = 0, last_timer = 0;
|
||||
char *run_as_user = NULL;
|
||||
char const *error_msg = NULL;
|
||||
char *webport = "8080";
|
||||
|
||||
atexit(bye);
|
||||
error_msg = mg_set_option(server, "listening_port", "8080");
|
||||
#ifdef WITH_DYNAMIC_ASSETS
|
||||
mg_set_option(server, "document_root", SRC_PATH);
|
||||
#endif
|
||||
|
||||
mg_set_option(server, "auth_domain", "ympd");
|
||||
mpd.port = 6600;
|
||||
mpd.local_port = 0;
|
||||
mpd.gpass = NULL;
|
||||
strcpy(mpd.host, "127.0.0.1");
|
||||
|
||||
/* clang-format off */
|
||||
static struct option long_options[] = {
|
||||
{"digest", required_argument, 0, 'D'},
|
||||
{"host", required_argument, 0, 'h'},
|
||||
{"port", required_argument, 0, 'p'},
|
||||
{"localport", required_argument, 0, 'l'},
|
||||
{"webport", required_argument, 0, 'w'},
|
||||
{"user", required_argument, 0, 'u'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{"help", no_argument, 0, 0 },
|
||||
{"mpdpass", required_argument, 0, 'm'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
while((n = getopt_long(argc, argv, "h:p:w:u:v",
|
||||
long_options, &option_index)) != -1) {
|
||||
while ((n = getopt_long(argc, argv, "D:h:p:l:w:u:d:v:m", long_options, &option_index)) != -1) {
|
||||
switch (n) {
|
||||
case 'D':
|
||||
mpd.gpass = strdup(optarg);
|
||||
break;
|
||||
case 'h':
|
||||
strncpy(mpd.host, optarg, sizeof(mpd.host));
|
||||
break;
|
||||
case 'p':
|
||||
mpd.port = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
mpd.local_port = atoi(optarg);
|
||||
break;
|
||||
case 'w':
|
||||
error_msg = mg_set_option(server, "listening_port", optarg);
|
||||
webport = strdup(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
run_as_user = strdup(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
if (strlen(optarg) > 0)
|
||||
mpd.password = strdup(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
fprintf(stdout, "ympd %d.%d.%d\n"
|
||||
fprintf(stdout,
|
||||
"ympd %d.%d.%d\n"
|
||||
"Copyright (C) 2014 Andrew Karpow <andy@ndyk.de>\n"
|
||||
"built " __DATE__ " "__TIME__ " ("__VERSION__")\n",
|
||||
"built " __DATE__
|
||||
" "__TIME__
|
||||
" ("__VERSION__
|
||||
")\n",
|
||||
YMPD_VERSION_MAJOR, YMPD_VERSION_MINOR, YMPD_VERSION_PATCH);
|
||||
return EXIT_SUCCESS;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Usage: %s [OPTION]...\n\n"
|
||||
fprintf(stderr,
|
||||
"Usage: %s [OPTION]...\n\n"
|
||||
" -D, --digest <htdigest>\tpath to htdigest file for authorization\n"
|
||||
" \t(realm ympd) [no authorization]\n"
|
||||
" -h, --host <host>\t\tconnect to mpd at host [localhost]\n"
|
||||
" -p, --port <port>\t\tconnect to mpd at port [6600]\n"
|
||||
" -l, --localport <port>\t\tskip authorization for local port\n"
|
||||
" -w, --webport [ip:]<port>\tlisten interface/port for webserver [8080]\n"
|
||||
" -u, --user <username>\t\tdrop priviliges to user after socket bind\n"
|
||||
" -V, --version\t\t\tget version\n"
|
||||
" --help\t\t\t\tthis help\n"
|
||||
, argv[0]);
|
||||
" -v, --version\t\t\tget version\n"
|
||||
" -m, --mpdpass <password>\tspecifies the password to use when connecting "
|
||||
"to mpd\n"
|
||||
" --help\t\t\t\tthis help\n",
|
||||
argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if(error_msg)
|
||||
{
|
||||
if (error_msg) {
|
||||
fprintf(stderr, "Mongoose error: %s\n", error_msg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
error_msg = mg_set_option(server, "listening_port", webport);
|
||||
if (error_msg) {
|
||||
fprintf(stderr, "Mongoose error: %s\n", error_msg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* drop privilges at last to ensure proper port binding */
|
||||
if(run_as_user != NULL)
|
||||
{
|
||||
if (run_as_user != NULL) {
|
||||
error_msg = mg_set_option(server, "run_as_user", run_as_user);
|
||||
free(run_as_user);
|
||||
if(error_msg)
|
||||
{
|
||||
if (error_msg) {
|
||||
fprintf(stderr, "Mongoose error: %s\n", error_msg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
@ -137,8 +187,7 @@ int main(int argc, char **argv)
|
|||
while (!force_exit) {
|
||||
mg_poll_server(server, 200);
|
||||
current_timer = time(NULL);
|
||||
if(current_timer - last_timer)
|
||||
{
|
||||
if (current_timer - last_timer) {
|
||||
last_timer = current_timer;
|
||||
mpd_poll(server);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
repo="$(git rev-parse --show-toplevel)"
|
||||
|
||||
pushd "$repo"
|
||||
prettier --write .
|
||||
for i in http_server.c http_server.h json_encode.h mpd_client.c mpd_client.h ympd.c; do
|
||||
clang-format -i -style=file "src/$i"
|
||||
done
|
||||
popd
|
5
ympd.1
5
ympd.1
|
@ -1,6 +1,6 @@
|
|||
.\" Manpage for ympd.
|
||||
.\" Contact andy@ympd.org to correct errors or typos.
|
||||
.TH man 8 "18 Mar 2014" "1.2" "ympd man page"
|
||||
.TH man 1 "19 Oct 2014" "1.2.3" "ympd man page"
|
||||
.SH NAME
|
||||
ympd \- Standalone MPD Web GUI written in C, utilizing Websockets and Bootstrap/JS
|
||||
.SH SYNOPSIS
|
||||
|
@ -23,6 +23,9 @@ specifies the port for the webserver to listen to, defaults to 8080
|
|||
\fB\-u\fR, \fB\-\-user username\fR
|
||||
drop privileges to the provided username after socket binding
|
||||
.TP
|
||||
\fB\-m\fR, \fB\-\-mpdpass password\fR
|
||||
specifies the password to use when connecting to mpd
|
||||
.TP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
print version and exit
|
||||
.TP
|
||||
|
|
Loading…
Reference in New Issue