Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9274bc15bc | ||
![]() |
751fff07fb | ||
![]() |
f7d1408a1a | ||
![]() |
e4e14ef6b0 | ||
![]() |
005e691339 | ||
![]() |
61eff1cddf | ||
![]() |
c26703b7e6 | ||
![]() |
db27bb76e2 | ||
![]() |
7cfe929c36 | ||
![]() |
6c06244e83 | ||
![]() |
53448e4633 | ||
![]() |
21adc78713 | ||
![]() |
0340b01392 | ||
![]() |
94aed92e9a | ||
![]() |
6b9966e969 | ||
![]() |
4bc5333995 | ||
![]() |
ff58b8d255 | ||
![]() |
3f3f0af543 | ||
![]() |
850d208b7b | ||
![]() |
da563940b4 | ||
![]() |
282859a62a | ||
![]() |
fbeb5eefdc | ||
![]() |
85bada0505 | ||
![]() |
cf96135125 | ||
![]() |
1ff97783ea | ||
![]() |
2bc42c6445 | ||
![]() |
49372a222f | ||
![]() |
9127afbf3f | ||
![]() |
f2caac595a | ||
![]() |
14d3a7ae83 | ||
![]() |
f37ab5482b | ||
![]() |
ef38dbe5bf | ||
![]() |
54a5491b86 | ||
![]() |
aff070bcbb | ||
![]() |
5af2632d4f | ||
![]() |
44a31357f4 | ||
![]() |
29f78b18b1 | ||
![]() |
147872fe97 | ||
![]() |
38edb58054 | ||
![]() |
98afae2520 | ||
![]() |
ddc85c620f | ||
![]() |
12bc625fe1 | ||
![]() |
6b407356b9 | ||
![]() |
a4e0b52468 | ||
![]() |
98efb4f6d5 | ||
![]() |
36edb4886c | ||
![]() |
76290f786d | ||
![]() |
c6299c26b5 | ||
![]() |
fb5f9baf9c | ||
![]() |
dee591d970 | ||
![]() |
a5cc13b0c5 | ||
![]() |
aaf588aeaa | ||
![]() |
533a3def9f | ||
![]() |
fcf487f4e0 | ||
![]() |
906972973e | ||
![]() |
116edf5fce | ||
![]() |
8581013911 | ||
![]() |
b1e073bacd | ||
![]() |
501e48daba | ||
![]() |
643ecd1edd | ||
![]() |
7393e1cba1 | ||
![]() |
ceee47fda8 | ||
![]() |
6f3c0d0a60 | ||
![]() |
466625f7ad | ||
![]() |
b8259e604a | ||
![]() |
86e2075c63 | ||
![]() |
30900b2fe2 | ||
![]() |
fd7ae7ea4c | ||
![]() |
60d5bf0240 | ||
![]() |
41cdc4e14b | ||
![]() |
87dfca0477 | ||
![]() |
e1ee8e7812 | ||
![]() |
63406efcd8 | ||
![]() |
d5c132fca0 | ||
![]() |
5f082a2739 | ||
![]() |
7d6a762845 | ||
![]() |
8dcb1f805d | ||
![]() |
a8b9e5b9b9 | ||
![]() |
04f928e2b0 | ||
![]() |
c7a803c922 |
96
Makefile.am
96
Makefile.am
@@ -234,6 +234,7 @@ libmpd_a_SOURCES += \
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
CURL_SOURCES = \
|
CURL_SOURCES = \
|
||||||
|
src/lib/curl/Error.hxx \
|
||||||
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
src/lib/curl/Version.cxx src/lib/curl/Version.hxx \
|
||||||
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
src/lib/curl/Global.cxx src/lib/curl/Global.hxx \
|
||||||
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
src/lib/curl/Request.cxx src/lib/curl/Request.hxx \
|
||||||
@@ -275,7 +276,8 @@ libjava_a_SOURCES = \
|
|||||||
noinst_LIBRARIES += libandroid.a
|
noinst_LIBRARIES += libandroid.a
|
||||||
libandroid_a_SOURCES = \
|
libandroid_a_SOURCES = \
|
||||||
src/android/Context.cxx src/android/Context.hxx \
|
src/android/Context.cxx src/android/Context.hxx \
|
||||||
src/android/Environment.cxx src/android/Environment.hxx
|
src/android/Environment.cxx src/android/Environment.hxx \
|
||||||
|
src/android/LogListener.cxx src/android/LogListener.hxx
|
||||||
libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
libandroid_a_CPPFLAGS = $(AM_CPPFLAGS) -Iandroid/build/include
|
||||||
|
|
||||||
noinst_LIBRARIES += libmain.a
|
noinst_LIBRARIES += libmain.a
|
||||||
@@ -290,15 +292,17 @@ clean-local:
|
|||||||
rm -rf android/build
|
rm -rf android/build
|
||||||
|
|
||||||
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
|
libmpd.so: $(filter %.a,$(src_mpd_LDADD)) libmain.a
|
||||||
|
$(AM_V_GEN)
|
||||||
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
|
$(AM_V_CXXLD)$(CXXLD) -shared -Wl,--no-undefined,-shared,-Bsymbolic -llog -lz -o $@ $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) src/libmain_a-Main.o $(src_mpd_LDADD) $(LIBS)
|
||||||
|
|
||||||
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
|
ANDROID_SDK_BUILD_TOOLS_VERSION = 27.0.0
|
||||||
ANDROID_SDK_PLATFORM = android-17
|
ANDROID_SDK_PLATFORM = android-21
|
||||||
|
|
||||||
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
|
ANDROID_BUILD_TOOLS_DIR = $(ANDROID_SDK)/build-tools/$(ANDROID_SDK_BUILD_TOOLS_VERSION)
|
||||||
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
ANDROID_SDK_PLATFORM_DIR = $(ANDROID_SDK)/platforms/$(ANDROID_SDK_PLATFORM)
|
||||||
|
|
||||||
JAVAC = javac
|
JAVAC = javac
|
||||||
|
AIDL = $(ANDROID_BUILD_TOOLS_DIR)/aidl
|
||||||
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
AAPT = $(ANDROID_BUILD_TOOLS_DIR)/aapt
|
||||||
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
DX = $(ANDROID_BUILD_TOOLS_DIR)/dx
|
||||||
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
||||||
@@ -306,18 +310,30 @@ ZIPALIGN = $(ANDROID_BUILD_TOOLS_DIR)/zipalign
|
|||||||
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
ANDROID_XML_RES := $(wildcard $(srcdir)/android/res/*/*.xml)
|
||||||
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
ANDROID_XML_RES_COPIES := $(patsubst $(srcdir)/android/%,android/build/%,$(ANDROID_XML_RES))
|
||||||
|
|
||||||
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java
|
JAVA_SOURCE_NAMES = Bridge.java Loader.java Main.java Settings.java
|
||||||
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
JAVA_SOURCE_PATHS = $(addprefix $(srcdir)/android/src/,$(JAVA_SOURCE_NAMES))
|
||||||
|
|
||||||
JAVA_CLASSFILES_DIR = android/build/classes
|
JAVA_CLASSFILES_DIR = android/build/classes
|
||||||
|
|
||||||
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
AIDL_FILES = $(wildcard $(srcdir)/android/src/*.aidl)
|
||||||
@$(MKDIR_P) $(dir $@)
|
AIDL_JAVA_FILES = $(patsubst $(srcdir)/android/src/%.aidl,android/build/src/org/musicpd/%.java,$(AIDL_FILES))
|
||||||
cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
|
||||||
|
|
||||||
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png
|
android/build/src/org/musicpd/IMain.java: android/build/src/org/musicpd/IMainCallback.java
|
||||||
@$(MKDIR_P) android/build/gen
|
|
||||||
$(AAPT) package -f -m --auto-add-overlay \
|
$(AIDL_JAVA_FILES): android/build/src/org/musicpd/%.java: $(srcdir)/android/src/%.aidl
|
||||||
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||||
|
$(AM_V_at)cp $< $(@D)/
|
||||||
|
$(AM_V_at)$(AIDL) -Iandroid/build/src -oandroid/build/src $(patsubst %.java,%.aidl,$@)
|
||||||
|
|
||||||
|
$(ANDROID_XML_RES_COPIES): $(ANDROID_XML_RES)
|
||||||
|
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||||
|
$(AM_V_at)cp $(patsubst android/build/%,$(srcdir)/android/%,$@) $@
|
||||||
|
|
||||||
|
android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawable/icon.png android/build/res/drawable/notification_icon.png
|
||||||
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)$(MKDIR_P) android/build/gen
|
||||||
|
$(AM_V_at)$(AAPT) package -f -m --auto-add-overlay \
|
||||||
--custom-package org.musicpd \
|
--custom-package org.musicpd \
|
||||||
-M $(srcdir)/android/AndroidManifest.xml \
|
-M $(srcdir)/android/AndroidManifest.xml \
|
||||||
-S android/build/res \
|
-S android/build/res \
|
||||||
@@ -328,40 +344,51 @@ android/build/resources.apk: $(ANDROID_XML_RES_COPIES) android/build/res/drawabl
|
|||||||
# R.java is generated by aapt, when resources.apk is generated
|
# R.java is generated by aapt, when resources.apk is generated
|
||||||
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
android/build/gen/org/musicpd/R.java: android/build/resources.apk
|
||||||
|
|
||||||
android/build/classes.dex: $(JAVA_SOURCE_PATHS) android/build/gen/org/musicpd/R.java
|
android/build/classes.dex: $(JAVA_SOURCE_PATHS) $(AIDL_JAVA_FILES) android/build/gen/org/musicpd/R.java
|
||||||
@$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
$(AM_V_GEN)
|
||||||
$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
$(AM_V_at)$(MKDIR_P) $(JAVA_CLASSFILES_DIR)
|
||||||
|
$(AM_V_at)$(JAVAC) -source 1.6 -target 1.6 -Xlint:-options \
|
||||||
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
-cp $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) \
|
||||||
|
-h android/build/include \
|
||||||
-d $(JAVA_CLASSFILES_DIR) $^
|
-d $(JAVA_CLASSFILES_DIR) $^
|
||||||
$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
$(AM_V_at)$(DX) --dex --output $@ $(JAVA_CLASSFILES_DIR)
|
||||||
|
|
||||||
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
android/build/include/org_musicpd_Bridge.h: android/build/classes.dex
|
||||||
javah -classpath $(ANDROID_SDK_PLATFORM_DIR)/android.jar:$(JAVA_CLASSFILES_DIR) -d $(@D) org.musicpd.Bridge
|
|
||||||
|
|
||||||
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
BUILT_SOURCES = android/build/include/org_musicpd_Bridge.h
|
||||||
|
|
||||||
android/build/lib/armeabi-v7a/libmpd.so: libmpd.so
|
android/build/lib/$(ANDROID_ABI)/libmpd.so: libmpd.so
|
||||||
mkdir -p $(@D)
|
$(AM_V_GEN)
|
||||||
rm -f $@
|
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||||
$(STRIP) -o $@ $<
|
$(AM_V_at)rm -f $@
|
||||||
|
$(AM_V_at)$(STRIP) -o $@ $<
|
||||||
|
|
||||||
android/build/res/drawable/icon.png: mpd.svg
|
android/build/res/drawable/icon.png: mpd.svg
|
||||||
mkdir -p $(@D)
|
$(AM_V_GEN)
|
||||||
rsvg-convert --width=48 --height=48 $< -o $@
|
$(AM_V_at)$(MKDIR_P) $(@D)
|
||||||
|
$(AM_V_at)rsvg-convert --width=48 --height=48 $< -o $@
|
||||||
|
|
||||||
|
android/build/res/drawable/notification_icon.png: android/build/res/drawable/icon.png
|
||||||
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)convert $< -colorspace Gray -gamma 2.2 $@
|
||||||
|
|
||||||
.DELETE_ON_ERROR: android/build/unsigned.apk
|
.DELETE_ON_ERROR: android/build/unsigned.apk
|
||||||
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/armeabi-v7a/libmpd.so
|
android/build/unsigned.apk: android/build/classes.dex android/build/resources.apk android/build/lib/$(ANDROID_ABI)/libmpd.so
|
||||||
cp android/build/resources.apk $@
|
$(AM_V_GEN)
|
||||||
cd $(dir $@) && zip -q -r $(notdir $@) classes.dex lib
|
$(AM_V_at)cp android/build/resources.apk $@
|
||||||
|
$(AM_V_at)cd $(@D) && zip -q -r $(@F) classes.dex lib
|
||||||
|
|
||||||
android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
|
android/build/$(APK_NAME)-debug.apk: android/build/unsigned.apk
|
||||||
jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)jarsigner -keystore $(HOME)/.android/debug.keystore -storepass android -signedjar $@ $< androiddebugkey
|
||||||
|
|
||||||
android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
|
android/build/$(APK_NAME)-release-unaligned.apk: android/build/unsigned.apk
|
||||||
jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)jarsigner -digestalg SHA1 -sigalg MD5withRSA -storepass:env ANDROID_KEYSTORE_PASS -keystore $(ANDROID_KEYSTORE) -signedjar $@ $< $(ANDROID_KEY_ALIAS)
|
||||||
|
|
||||||
android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
|
android/build/$(APK_NAME).apk: android/build/$(APK_NAME)-release-unaligned.apk
|
||||||
$(ZIPALIGN) -f 4 $< $@
|
$(AM_V_GEN)
|
||||||
|
$(AM_V_at)$(ZIPALIGN) -f 4 $< $@
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -444,6 +471,7 @@ libutil_a_SOURCES = \
|
|||||||
src/util/NumberParser.hxx \
|
src/util/NumberParser.hxx \
|
||||||
src/util/MimeType.cxx src/util/MimeType.hxx \
|
src/util/MimeType.cxx src/util/MimeType.hxx \
|
||||||
src/util/StringBuffer.hxx \
|
src/util/StringBuffer.hxx \
|
||||||
|
src/util/StringFormat.hxx \
|
||||||
src/util/StringPointer.hxx \
|
src/util/StringPointer.hxx \
|
||||||
src/util/StringView.cxx src/util/StringView.hxx \
|
src/util/StringView.cxx src/util/StringView.hxx \
|
||||||
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
src/util/AllocatedString.cxx src/util/AllocatedString.hxx \
|
||||||
@@ -712,6 +740,7 @@ NFS_SOURCES = \
|
|||||||
src/lib/nfs/Cancellable.hxx \
|
src/lib/nfs/Cancellable.hxx \
|
||||||
src/lib/nfs/Lease.hxx \
|
src/lib/nfs/Lease.hxx \
|
||||||
src/lib/nfs/Connection.cxx src/lib/nfs/Connection.hxx \
|
src/lib/nfs/Connection.cxx src/lib/nfs/Connection.hxx \
|
||||||
|
src/lib/nfs/Error.cxx src/lib/nfs/Error.hxx \
|
||||||
src/lib/nfs/Manager.cxx src/lib/nfs/Manager.hxx \
|
src/lib/nfs/Manager.cxx src/lib/nfs/Manager.hxx \
|
||||||
src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
|
src/lib/nfs/Glue.cxx src/lib/nfs/Glue.hxx \
|
||||||
src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
|
src/lib/nfs/Base.cxx src/lib/nfs/Base.hxx \
|
||||||
@@ -734,6 +763,8 @@ libstorage_a_SOURCES = \
|
|||||||
src/storage/FileInfo.hxx
|
src/storage/FileInfo.hxx
|
||||||
|
|
||||||
libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||||
|
$(CURL_CFLAGS) \
|
||||||
|
$(EXPAT_CFLAGS) \
|
||||||
$(NFS_CFLAGS) \
|
$(NFS_CFLAGS) \
|
||||||
$(SMBCLIENT_CFLAGS)
|
$(SMBCLIENT_CFLAGS)
|
||||||
|
|
||||||
@@ -946,13 +977,14 @@ libtag_a_SOURCES =\
|
|||||||
src/tag/TagItem.hxx \
|
src/tag/TagItem.hxx \
|
||||||
src/tag/TagHandler.cxx src/tag/TagHandler.hxx \
|
src/tag/TagHandler.cxx src/tag/TagHandler.hxx \
|
||||||
src/tag/Mask.hxx \
|
src/tag/Mask.hxx \
|
||||||
|
src/tag/Fallback.hxx \
|
||||||
|
src/tag/VisitFallback.hxx \
|
||||||
src/tag/Settings.cxx src/tag/Settings.hxx \
|
src/tag/Settings.cxx src/tag/Settings.hxx \
|
||||||
src/tag/TagConfig.cxx src/tag/TagConfig.hxx \
|
src/tag/TagConfig.cxx src/tag/TagConfig.hxx \
|
||||||
src/tag/TagNames.c \
|
src/tag/TagNames.c \
|
||||||
src/tag/TagString.cxx src/tag/TagString.hxx \
|
src/tag/TagString.cxx src/tag/TagString.hxx \
|
||||||
src/tag/TagPool.cxx src/tag/TagPool.hxx \
|
src/tag/TagPool.cxx src/tag/TagPool.hxx \
|
||||||
src/tag/TagTable.cxx src/tag/TagTable.hxx \
|
src/tag/TagTable.cxx src/tag/TagTable.hxx \
|
||||||
src/tag/Set.cxx src/tag/Set.hxx \
|
|
||||||
src/tag/Format.cxx src/tag/Format.hxx \
|
src/tag/Format.cxx src/tag/Format.hxx \
|
||||||
src/tag/VorbisComment.cxx src/tag/VorbisComment.hxx \
|
src/tag/VorbisComment.cxx src/tag/VorbisComment.hxx \
|
||||||
src/tag/ReplayGain.cxx src/tag/ReplayGain.hxx \
|
src/tag/ReplayGain.cxx src/tag/ReplayGain.hxx \
|
||||||
@@ -1292,7 +1324,7 @@ endif
|
|||||||
#
|
#
|
||||||
|
|
||||||
libinput_a_SOURCES = \
|
libinput_a_SOURCES = \
|
||||||
src/input/Domain.cxx src/input/Domain.hxx \
|
src/input/Error.cxx src/input/Error.hxx \
|
||||||
src/input/Init.cxx src/input/Init.hxx \
|
src/input/Init.cxx src/input/Init.hxx \
|
||||||
src/input/Registry.cxx src/input/Registry.hxx \
|
src/input/Registry.cxx src/input/Registry.hxx \
|
||||||
src/input/Open.cxx \
|
src/input/Open.cxx \
|
||||||
@@ -1670,6 +1702,13 @@ FILTER_LIBS = \
|
|||||||
$(PCM_LIBS)
|
$(PCM_LIBS)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Icon
|
||||||
|
#
|
||||||
|
|
||||||
|
iconsdir = $(datadir)/icons/hicolor/scalable/apps
|
||||||
|
icons_DATA = mpd.svg
|
||||||
|
|
||||||
#
|
#
|
||||||
# systemd unit
|
# systemd unit
|
||||||
#
|
#
|
||||||
@@ -2439,6 +2478,7 @@ endif
|
|||||||
#
|
#
|
||||||
|
|
||||||
EXTRA_DIST = $(doc_DATA) autogen.sh \
|
EXTRA_DIST = $(doc_DATA) autogen.sh \
|
||||||
|
mpd.svg \
|
||||||
test/test_archive_bzip2.sh \
|
test/test_archive_bzip2.sh \
|
||||||
test/test_archive_iso9660.sh \
|
test/test_archive_iso9660.sh \
|
||||||
test/test_archive_zzip.sh \
|
test/test_archive_zzip.sh \
|
||||||
|
30
NEWS
30
NEWS
@@ -1,3 +1,33 @@
|
|||||||
|
ver 0.20.22 (2018/10/23)
|
||||||
|
* protocol
|
||||||
|
- add tag fallbacks for AlbumArtistSort, ArtistSort
|
||||||
|
- fix empty string filter on fallback tags
|
||||||
|
- "count group ..." can print an empty group
|
||||||
|
- fix broken command "list ... group"
|
||||||
|
* storage
|
||||||
|
- curl: URL-encode paths
|
||||||
|
* decoder
|
||||||
|
- fluidsynth: adapt to API change in version 2.0
|
||||||
|
* Android
|
||||||
|
- now runs as a service
|
||||||
|
- add button to start/stop MPD
|
||||||
|
- add option to auto-start on boot
|
||||||
|
* work around clang bug leading to crash
|
||||||
|
* install the SVG icon
|
||||||
|
|
||||||
|
ver 0.20.21 (2018/08/17)
|
||||||
|
* database
|
||||||
|
- proxy: add "password" setting
|
||||||
|
- proxy: support tags "ArtistSort", "AlbumArtistSort", "AlbumSort"
|
||||||
|
- simple: allow .mpdignore comments only at start of line
|
||||||
|
* output
|
||||||
|
- httpd: remove broken DLNA support code
|
||||||
|
* playlist
|
||||||
|
- cue: support file type declaration "FLAC" (non-standard)
|
||||||
|
* URI schemes are case insensitive
|
||||||
|
* Android, Windows
|
||||||
|
- enable the "curl" storage plugin
|
||||||
|
|
||||||
ver 0.20.20 (2018/05/22)
|
ver 0.20.20 (2018/05/22)
|
||||||
* protocol
|
* protocol
|
||||||
- fix "modified-since" filter regression
|
- fix "modified-since" filter regression
|
||||||
|
@@ -2,23 +2,32 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="19"
|
android:versionCode="21"
|
||||||
android:versionName="0.20.20">
|
android:versionName="0.20.22">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||||
|
|
||||||
<application android:icon="@drawable/icon" android:label="@string/app_name">
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<activity android:name=".Main"
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
android:label="@string/app_name"
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
android:launchMode="singleInstance">
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
|
<application android:allowBackup="true"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<activity android:name=".Settings"
|
||||||
|
android:label="@string/app_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<receiver android:name=".Receiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
<service android:name=".Main" android:process=":main"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@@ -29,6 +29,15 @@ android_abis = {
|
|||||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'arm64-v8a': {
|
||||||
|
'android_api_level': '21',
|
||||||
|
'arch': 'aarch64-linux-android',
|
||||||
|
'ndk_arch': 'arm64',
|
||||||
|
'toolchain_arch': 'aarch64-linux-android',
|
||||||
|
'llvm_triple': 'aarch64-none-linux-android',
|
||||||
|
'cflags': '',
|
||||||
|
},
|
||||||
|
|
||||||
'x86': {
|
'x86': {
|
||||||
'arch': 'i686-linux-android',
|
'arch': 'i686-linux-android',
|
||||||
'ndk_arch': 'x86',
|
'ndk_arch': 'x86',
|
||||||
@@ -65,7 +74,8 @@ class AndroidNdkToolchain:
|
|||||||
self.build_path = build_path
|
self.build_path = build_path
|
||||||
|
|
||||||
ndk_arch = abi_info['ndk_arch']
|
ndk_arch = abi_info['ndk_arch']
|
||||||
ndk_platform = 'android-14'
|
android_api_level = '21'
|
||||||
|
ndk_platform = 'android-' + android_api_level
|
||||||
|
|
||||||
# select the NDK compiler
|
# select the NDK compiler
|
||||||
gcc_version = '4.9'
|
gcc_version = '4.9'
|
||||||
@@ -106,7 +116,7 @@ class AndroidNdkToolchain:
|
|||||||
self.cppflags = '--sysroot=' + sysroot + \
|
self.cppflags = '--sysroot=' + sysroot + \
|
||||||
' -isystem ' + os.path.join(install_prefix, 'include') + \
|
' -isystem ' + os.path.join(install_prefix, 'include') + \
|
||||||
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
|
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
|
||||||
' -D__ANDROID_API__=14'
|
' -D__ANDROID_API__=' + android_api_level
|
||||||
self.ldflags = '--sysroot=' + sysroot + \
|
self.ldflags = '--sysroot=' + sysroot + \
|
||||||
' -L' + os.path.join(install_prefix, 'lib') + \
|
' -L' + os.path.join(install_prefix, 'lib') + \
|
||||||
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
||||||
@@ -116,18 +126,21 @@ class AndroidNdkToolchain:
|
|||||||
|
|
||||||
self.is_arm = ndk_arch == 'arm'
|
self.is_arm = ndk_arch == 'arm'
|
||||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||||
|
self.is_aarch64 = ndk_arch == 'arm64'
|
||||||
self.is_windows = False
|
self.is_windows = False
|
||||||
|
|
||||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
||||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
||||||
|
|
||||||
libstdcxx_flags = '-stdlib=libc++'
|
libstdcxx_flags = ''
|
||||||
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
||||||
libstdcxx_ldflags = libstdcxx_flags + ' -static-libstdc++ -L' + libcxx_libs_path
|
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
|
||||||
|
libstdcxx_libs = '-lc++_static -lc++abi'
|
||||||
|
|
||||||
if use_cxx:
|
if use_cxx:
|
||||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||||
self.ldflags += ' ' + libstdcxx_ldflags
|
self.ldflags += ' ' + libstdcxx_ldflags
|
||||||
|
self.libs += ' ' + libstdcxx_libs
|
||||||
|
|
||||||
self.env = dict(os.environ)
|
self.env = dict(os.environ)
|
||||||
|
|
||||||
@@ -146,6 +159,7 @@ thirdparty_libs = [
|
|||||||
libid3tag,
|
libid3tag,
|
||||||
ffmpeg,
|
ffmpeg,
|
||||||
curl,
|
curl,
|
||||||
|
libexpat,
|
||||||
libnfs,
|
libnfs,
|
||||||
boost,
|
boost,
|
||||||
]
|
]
|
||||||
|
22
android/res/layout/custom_notification_gb.xml
Normal file
22
android/res/layout/custom_notification_gb.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:padding="10dp" >
|
||||||
|
<ImageView android:id="@+id/image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_marginRight="10dp" />
|
||||||
|
<TextView android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@id/image"
|
||||||
|
style="Custom Notification Title" />
|
||||||
|
<TextView android:id="@+id/text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toRightOf="@id/image"
|
||||||
|
android:layout_below="@id/title"
|
||||||
|
style="Custom Notification Text" />
|
||||||
|
</RelativeLayout>
|
5
android/res/layout/log_item.xml
Normal file
5
android/res/layout/log_item.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:typeface="monospace" />
|
37
android/res/layout/settings.xml
Normal file
37
android/res/layout/settings.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" >
|
||||||
|
|
||||||
|
<ToggleButton
|
||||||
|
android:id="@+id/run"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textOn="@string/toggle_button_run_on"
|
||||||
|
android:textOff="@string/toggle_button_run_off" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/run_on_boot"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/checkbox_run_on_boot" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/wakelock"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/checkbox_wakelock" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/log_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="10dip" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@@ -2,4 +2,10 @@
|
|||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">MPD</string>
|
<string name="app_name">MPD</string>
|
||||||
|
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
|
||||||
|
<string name="notification_text_mpd_running">Touch for MPD options.</string>
|
||||||
|
<string name="toggle_button_run_on">MPD is running</string>
|
||||||
|
<string name="toggle_button_run_off">MPD is not running</string>
|
||||||
|
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
|
||||||
|
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -25,6 +25,12 @@ import android.content.Context;
|
|||||||
* Bridge to native code.
|
* Bridge to native code.
|
||||||
*/
|
*/
|
||||||
public class Bridge {
|
public class Bridge {
|
||||||
public static native void run(Context context);
|
|
||||||
|
/* used by jni */
|
||||||
|
public interface LogListener {
|
||||||
|
public void onLog(int priority, String msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static native void run(Context context, LogListener logListener);
|
||||||
public static native void shutdown();
|
public static native void shutdown();
|
||||||
}
|
}
|
||||||
|
12
android/src/IMain.aidl
Normal file
12
android/src/IMain.aidl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package org.musicpd;
|
||||||
|
import org.musicpd.IMainCallback;
|
||||||
|
|
||||||
|
interface IMain
|
||||||
|
{
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void setWakelockEnabled(boolean enabled);
|
||||||
|
boolean isRunning();
|
||||||
|
void registerCallback(IMainCallback cb);
|
||||||
|
void unregisterCallback(IMainCallback cb);
|
||||||
|
}
|
9
android/src/IMainCallback.aidl
Normal file
9
android/src/IMainCallback.aidl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package org.musicpd;
|
||||||
|
|
||||||
|
interface IMainCallback
|
||||||
|
{
|
||||||
|
void onStarted();
|
||||||
|
void onStopped();
|
||||||
|
void onError(String error);
|
||||||
|
void onLog(int priority, String msg);
|
||||||
|
}
|
@@ -19,57 +19,360 @@
|
|||||||
|
|
||||||
package org.musicpd;
|
package org.musicpd;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.annotation.TargetApi;
|
||||||
import android.os.Bundle;
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.IBinder;
|
||||||
import android.os.Message;
|
import android.os.PowerManager;
|
||||||
import android.widget.TextView;
|
import android.os.RemoteCallbackList;
|
||||||
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
public class Main extends Activity implements Runnable {
|
public class Main extends Service implements Runnable {
|
||||||
private static final String TAG = "MPD";
|
private static final String TAG = "Main";
|
||||||
|
private static final String REMOTE_ERROR = "MPD process was killed";
|
||||||
|
private static final int MAIN_STATUS_ERROR = -1;
|
||||||
|
private static final int MAIN_STATUS_STOPPED = 0;
|
||||||
|
private static final int MAIN_STATUS_STARTED = 1;
|
||||||
|
|
||||||
Thread thread;
|
private static final int MSG_SEND_STATUS = 0;
|
||||||
|
private static final int MSG_SEND_LOG = 1;
|
||||||
|
|
||||||
TextView textView;
|
private Thread mThread = null;
|
||||||
|
private int mStatus = MAIN_STATUS_STOPPED;
|
||||||
|
private boolean mAbort = false;
|
||||||
|
private String mError = null;
|
||||||
|
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
|
||||||
|
private final IBinder mBinder = new MainStub(this);
|
||||||
|
private PowerManager.WakeLock mWakelock = null;
|
||||||
|
|
||||||
final Handler quitHandler = new Handler() {
|
static class MainStub extends IMain.Stub {
|
||||||
public void handleMessage(Message msg) {
|
private Main mService;
|
||||||
textView.setText("Music Player Daemon has quit");
|
MainStub(Main service) {
|
||||||
|
mService = service;
|
||||||
|
}
|
||||||
|
public void start() {
|
||||||
|
mService.start();
|
||||||
|
}
|
||||||
|
public void stop() {
|
||||||
|
mService.stop();
|
||||||
|
}
|
||||||
|
public void setWakelockEnabled(boolean enabled) {
|
||||||
|
mService.setWakelockEnabled(enabled);
|
||||||
|
}
|
||||||
|
public boolean isRunning() {
|
||||||
|
return mService.isRunning();
|
||||||
|
}
|
||||||
|
public void registerCallback(IMainCallback cb) {
|
||||||
|
mService.registerCallback(cb);
|
||||||
|
}
|
||||||
|
public void unregisterCallback(IMainCallback cb) {
|
||||||
|
mService.unregisterCallback(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: what now? restart?
|
private synchronized void sendMessage(int what, int arg1, int arg2, Object obj) {
|
||||||
|
int i = mCallbacks.beginBroadcast();
|
||||||
|
while (i > 0) {
|
||||||
|
i--;
|
||||||
|
final IMainCallback cb = mCallbacks.getBroadcastItem(i);
|
||||||
|
try {
|
||||||
|
switch (what) {
|
||||||
|
case MSG_SEND_STATUS:
|
||||||
|
switch (arg1) {
|
||||||
|
case MAIN_STATUS_ERROR:
|
||||||
|
cb.onError((String)obj);
|
||||||
|
break;
|
||||||
|
case MAIN_STATUS_STOPPED:
|
||||||
|
cb.onStopped();
|
||||||
|
break;
|
||||||
|
case MAIN_STATUS_STARTED:
|
||||||
|
cb.onStarted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MSG_SEND_LOG:
|
||||||
|
cb.onLog(arg1, (String) obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCallbacks.finishBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bridge.LogListener mLogListener = new Bridge.LogListener() {
|
||||||
|
@Override
|
||||||
|
public void onLog(int priority, String msg) {
|
||||||
|
sendMessage(MSG_SEND_LOG, priority, 0, msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override protected void onCreate(Bundle savedInstanceState) {
|
@Override
|
||||||
super.onCreate(savedInstanceState);
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
start();
|
||||||
|
if (intent != null && intent.getBooleanExtra("wakelock", false))
|
||||||
|
setWakelockEnabled(true);
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
if (!Loader.loaded) {
|
if (!Loader.loaded) {
|
||||||
TextView tv = new TextView(this);
|
final String error = "Failed to load the native MPD libary.\n" +
|
||||||
tv.setText("Failed to load the native MPD libary.\n" +
|
|
||||||
"Report this problem to us, and include the following information:\n" +
|
"Report this problem to us, and include the following information:\n" +
|
||||||
"ABI=" + Build.CPU_ABI + "\n" +
|
"SUPPORTED_ABIS=" + String.join(", ", Build.SUPPORTED_ABIS) + "\n" +
|
||||||
"PRODUCT=" + Build.PRODUCT + "\n" +
|
"PRODUCT=" + Build.PRODUCT + "\n" +
|
||||||
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
"FINGERPRINT=" + Build.FINGERPRINT + "\n" +
|
||||||
"error=" + Loader.error);
|
"error=" + Loader.error;
|
||||||
setContentView(tv);
|
setStatus(MAIN_STATUS_ERROR, error);
|
||||||
|
stopSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
synchronized (this) {
|
||||||
if (thread == null || !thread.isAlive()) {
|
if (mAbort)
|
||||||
thread = new Thread(this, "NativeMain");
|
return;
|
||||||
thread.start();
|
setStatus(MAIN_STATUS_STARTED, null);
|
||||||
|
}
|
||||||
|
Bridge.run(this, mLogListener);
|
||||||
|
setStatus(MAIN_STATUS_STOPPED, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
textView = new TextView(this);
|
private synchronized void setStatus(int status, String error) {
|
||||||
textView.setText("Music Player Daemon is running"
|
mStatus = status;
|
||||||
+ "\nCAUTION: this version is EXPERIMENTAL!");
|
mError = error;
|
||||||
setContentView(textView);
|
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void run() {
|
private void start() {
|
||||||
Bridge.run(this);
|
if (mThread != null)
|
||||||
quitHandler.sendMessage(quitHandler.obtainMessage());
|
return;
|
||||||
|
mThread = new Thread(this);
|
||||||
|
mThread.start();
|
||||||
|
|
||||||
|
final Intent mainIntent = new Intent(this, Settings.class);
|
||||||
|
mainIntent.setAction("android.intent.action.MAIN");
|
||||||
|
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||||
|
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||||
|
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
Notification notification = new Notification.Builder(this)
|
||||||
|
.setContentTitle(getText(R.string.notification_title_mpd_running))
|
||||||
|
.setContentText(getText(R.string.notification_text_mpd_running))
|
||||||
|
.setSmallIcon(R.drawable.notification_icon)
|
||||||
|
.setContentIntent(contentIntent)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
startForeground(R.string.notification_title_mpd_running, notification);
|
||||||
|
startService(new Intent(this, Main.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stop() {
|
||||||
|
if (mThread != null) {
|
||||||
|
if (mThread.isAlive()) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mStatus == MAIN_STATUS_STARTED)
|
||||||
|
Bridge.shutdown();
|
||||||
|
else
|
||||||
|
mAbort = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mThread.join();
|
||||||
|
mThread = null;
|
||||||
|
mAbort = false;
|
||||||
|
} catch (InterruptedException ie) {}
|
||||||
|
}
|
||||||
|
setWakelockEnabled(false);
|
||||||
|
stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setWakelockEnabled(boolean enabled) {
|
||||||
|
if (enabled && mWakelock == null) {
|
||||||
|
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
|
||||||
|
mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||||
|
mWakelock.acquire();
|
||||||
|
Log.d(TAG, "Wakelock acquired");
|
||||||
|
} else if (!enabled && mWakelock != null) {
|
||||||
|
mWakelock.release();
|
||||||
|
mWakelock = null;
|
||||||
|
Log.d(TAG, "Wakelock released");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRunning() {
|
||||||
|
return mThread != null && mThread.isAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerCallback(IMainCallback cb) {
|
||||||
|
if (cb != null) {
|
||||||
|
mCallbacks.register(cb);
|
||||||
|
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterCallback(IMainCallback cb) {
|
||||||
|
if (cb != null) {
|
||||||
|
mCallbacks.unregister(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Client that bind the Main Service in order to send commands and receive callback
|
||||||
|
*/
|
||||||
|
public static class Client {
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
public void onStarted();
|
||||||
|
public void onStopped();
|
||||||
|
public void onError(String error);
|
||||||
|
public void onLog(int priority, String msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean mBound = false;
|
||||||
|
private final Context mContext;
|
||||||
|
private Callback mCallback;
|
||||||
|
private IMain mIMain = null;
|
||||||
|
|
||||||
|
private final IMainCallback.Stub mICallback = new IMainCallback.Stub() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopped() throws RemoteException {
|
||||||
|
mCallback.onStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStarted() throws RemoteException {
|
||||||
|
mCallback.onStarted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String error) throws RemoteException {
|
||||||
|
mCallback.onError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLog(int priority, String msg) throws RemoteException {
|
||||||
|
mCallback.onLog(priority, msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
synchronized (this) {
|
||||||
|
mIMain = IMain.Stub.asInterface(service);
|
||||||
|
try {
|
||||||
|
if (mCallback != null)
|
||||||
|
mIMain.registerCallback(mICallback);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
if (mCallback != null)
|
||||||
|
mCallback.onError(REMOTE_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
if (mCallback != null)
|
||||||
|
mCallback.onError(REMOTE_ERROR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public Client(Context context, Callback cb) throws IllegalArgumentException {
|
||||||
|
if (context == null)
|
||||||
|
throw new IllegalArgumentException("Context can't be null");
|
||||||
|
mContext = context;
|
||||||
|
mCallback = cb;
|
||||||
|
mBound = mContext.bindService(new Intent(mContext, Main.class), mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIMain != null) {
|
||||||
|
try {
|
||||||
|
mIMain.start();
|
||||||
|
return true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean stop() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIMain != null) {
|
||||||
|
try {
|
||||||
|
mIMain.stop();
|
||||||
|
return true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setWakelockEnabled(boolean enabled) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIMain != null) {
|
||||||
|
try {
|
||||||
|
mIMain.setWakelockEnabled(enabled);
|
||||||
|
return true;
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIMain != null) {
|
||||||
|
try {
|
||||||
|
return mIMain.isRunning();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
if (mBound) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (mIMain != null && mICallback != null) {
|
||||||
|
try {
|
||||||
|
if (mCallback != null)
|
||||||
|
mIMain.unregisterCallback(mICallback);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBound = false;
|
||||||
|
mContext.unbindService(mServiceConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* start Main service without any callback
|
||||||
|
*/
|
||||||
|
public static void start(Context context, boolean wakelock) {
|
||||||
|
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
android/src/Receiver.java
Normal file
41
android/src/Receiver.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2014 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.musicpd;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class Receiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.d("Receiver", "onReceive: " + intent);
|
||||||
|
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
|
||||||
|
if (Settings.Preferences.getBoolean(context,
|
||||||
|
Settings.Preferences.KEY_RUN_ON_BOOT, false)) {
|
||||||
|
final boolean wakelock = Settings.Preferences.getBoolean(context,
|
||||||
|
Settings.Preferences.KEY_WAKELOCK, false);
|
||||||
|
Main.start(context, wakelock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
android/src/Settings.java
Normal file
254
android/src/Settings.java
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.musicpd;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
|
public class Settings extends Activity {
|
||||||
|
private static final String TAG = "Settings";
|
||||||
|
private Main.Client mClient;
|
||||||
|
private TextView mTextStatus;
|
||||||
|
private ToggleButton mRunButton;
|
||||||
|
private boolean mFirstRun;
|
||||||
|
private LinkedList<String> mLogListArray = new LinkedList<String>();
|
||||||
|
private ListView mLogListView;
|
||||||
|
private ArrayAdapter<String> mLogListAdapter;
|
||||||
|
|
||||||
|
private static final int MAX_LOGS = 500;
|
||||||
|
|
||||||
|
private static final int MSG_ERROR = 0;
|
||||||
|
private static final int MSG_STOPPED = 1;
|
||||||
|
private static final int MSG_STARTED = 2;
|
||||||
|
private static final int MSG_LOG = 3;
|
||||||
|
|
||||||
|
public static class Preferences {
|
||||||
|
public static final String KEY_RUN_ON_BOOT ="run_on_boot";
|
||||||
|
public static final String KEY_WAKELOCK ="wakelock";
|
||||||
|
|
||||||
|
public static SharedPreferences get(Context context) {
|
||||||
|
return context.getSharedPreferences(TAG, MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putBoolean(Context context, String key, boolean value) {
|
||||||
|
final SharedPreferences prefs = get(context);
|
||||||
|
|
||||||
|
if (prefs == null)
|
||||||
|
return;
|
||||||
|
final Editor editor = prefs.edit();
|
||||||
|
editor.putBoolean(key, value);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getBoolean(Context context, String key, boolean defValue) {
|
||||||
|
final SharedPreferences prefs = get(context);
|
||||||
|
|
||||||
|
return prefs != null ? prefs.getBoolean(key, defValue) : defValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler(new Handler.Callback() {
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_ERROR:
|
||||||
|
Log.d(TAG, "onError");
|
||||||
|
|
||||||
|
mClient.release();
|
||||||
|
connectClient();
|
||||||
|
|
||||||
|
mRunButton.setEnabled(false);
|
||||||
|
mRunButton.setChecked(false);
|
||||||
|
|
||||||
|
mTextStatus.setText((String)msg.obj);
|
||||||
|
mFirstRun = true;
|
||||||
|
break;
|
||||||
|
case MSG_STOPPED:
|
||||||
|
Log.d(TAG, "onStopped");
|
||||||
|
mRunButton.setEnabled(true);
|
||||||
|
if (!mFirstRun && Preferences.getBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||||
|
mRunButton.setChecked(true);
|
||||||
|
else
|
||||||
|
mRunButton.setChecked(false);
|
||||||
|
mFirstRun = true;
|
||||||
|
break;
|
||||||
|
case MSG_STARTED:
|
||||||
|
Log.d(TAG, "onStarted");
|
||||||
|
mRunButton.setChecked(true);
|
||||||
|
mFirstRun = true;
|
||||||
|
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
|
||||||
|
break;
|
||||||
|
case MSG_LOG:
|
||||||
|
if (mLogListArray.size() > MAX_LOGS)
|
||||||
|
mLogListArray.remove(0);
|
||||||
|
String priority;
|
||||||
|
switch (msg.arg1) {
|
||||||
|
case Log.DEBUG:
|
||||||
|
priority = "D";
|
||||||
|
break;
|
||||||
|
case Log.ERROR:
|
||||||
|
priority = "E";
|
||||||
|
break;
|
||||||
|
case Log.INFO:
|
||||||
|
priority = "I";
|
||||||
|
break;
|
||||||
|
case Log.VERBOSE:
|
||||||
|
priority = "V";
|
||||||
|
break;
|
||||||
|
case Log.WARN:
|
||||||
|
priority = "W";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
priority = "";
|
||||||
|
}
|
||||||
|
mLogListArray.add(priority + "/ " + (String)msg.obj);
|
||||||
|
mLogListAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private final OnCheckedChangeListener mOnRunChangeListener = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
if (mClient != null) {
|
||||||
|
if (isChecked) {
|
||||||
|
mClient.start();
|
||||||
|
if (Preferences.getBoolean(Settings.this,
|
||||||
|
Preferences.KEY_WAKELOCK, false))
|
||||||
|
mClient.setWakelockEnabled(true);
|
||||||
|
} else {
|
||||||
|
mClient.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final OnCheckedChangeListener mOnRunOnBootChangeListener = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
Preferences.putBoolean(Settings.this, Preferences.KEY_RUN_ON_BOOT, isChecked);
|
||||||
|
if (isChecked && mClient != null && !mRunButton.isChecked())
|
||||||
|
mRunButton.setChecked(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final OnCheckedChangeListener mOnWakelockChangeListener = new OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
Preferences.putBoolean(Settings.this, Preferences.KEY_WAKELOCK, isChecked);
|
||||||
|
if (mClient != null && mClient.isRunning())
|
||||||
|
mClient.setWakelockEnabled(isChecked);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
setContentView(R.layout.settings);
|
||||||
|
mRunButton = (ToggleButton) findViewById(R.id.run);
|
||||||
|
mRunButton.setOnCheckedChangeListener(mOnRunChangeListener);
|
||||||
|
|
||||||
|
mTextStatus = (TextView) findViewById(R.id.status);
|
||||||
|
|
||||||
|
mLogListAdapter = new ArrayAdapter<String>(this, R.layout.log_item, mLogListArray);
|
||||||
|
|
||||||
|
mLogListView = (ListView) findViewById(R.id.log_list);
|
||||||
|
mLogListView.setAdapter(mLogListAdapter);
|
||||||
|
mLogListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
|
||||||
|
|
||||||
|
CheckBox checkbox = (CheckBox) findViewById(R.id.run_on_boot);
|
||||||
|
checkbox.setOnCheckedChangeListener(mOnRunOnBootChangeListener);
|
||||||
|
if (Preferences.getBoolean(this, Preferences.KEY_RUN_ON_BOOT, false))
|
||||||
|
checkbox.setChecked(true);
|
||||||
|
|
||||||
|
checkbox = (CheckBox) findViewById(R.id.wakelock);
|
||||||
|
checkbox.setOnCheckedChangeListener(mOnWakelockChangeListener);
|
||||||
|
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
|
||||||
|
checkbox.setChecked(true);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connectClient() {
|
||||||
|
mClient = new Main.Client(this, new Main.Client.Callback() {
|
||||||
|
|
||||||
|
private void removeMessages() {
|
||||||
|
/* don't remove log messages */
|
||||||
|
mHandler.removeMessages(MSG_STOPPED);
|
||||||
|
mHandler.removeMessages(MSG_STARTED);
|
||||||
|
mHandler.removeMessages(MSG_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopped() {
|
||||||
|
removeMessages();
|
||||||
|
mHandler.sendEmptyMessage(MSG_STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStarted() {
|
||||||
|
removeMessages();
|
||||||
|
mHandler.sendEmptyMessage(MSG_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String error) {
|
||||||
|
removeMessages();
|
||||||
|
mHandler.sendMessage(Message.obtain(mHandler, MSG_ERROR, error));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLog(int priority, String msg) {
|
||||||
|
mHandler.sendMessage(Message.obtain(mHandler, MSG_LOG, priority, 0, msg));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
mFirstRun = false;
|
||||||
|
connectClient();
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
mClient.release();
|
||||||
|
mClient = null;
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
}
|
28
configure.ac
28
configure.ac
@@ -1,10 +1,10 @@
|
|||||||
AC_PREREQ(2.60)
|
AC_PREREQ(2.60)
|
||||||
|
|
||||||
AC_INIT(mpd, 0.20.20, musicpd-dev-team@lists.sourceforge.net)
|
AC_INIT(mpd, 0.20.22, musicpd-dev-team@lists.sourceforge.net)
|
||||||
|
|
||||||
VERSION_MAJOR=0
|
VERSION_MAJOR=0
|
||||||
VERSION_MINOR=20
|
VERSION_MINOR=20
|
||||||
VERSION_REVISION=20
|
VERSION_REVISION=22
|
||||||
VERSION_EXTRA=0
|
VERSION_EXTRA=0
|
||||||
|
|
||||||
AC_CONFIG_SRCDIR([src/Main.cxx])
|
AC_CONFIG_SRCDIR([src/Main.cxx])
|
||||||
@@ -14,9 +14,9 @@ AM_SILENT_RULES
|
|||||||
AC_CONFIG_HEADERS(config.h)
|
AC_CONFIG_HEADERS(config.h)
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
|
|
||||||
AC_DEFINE(PROTOCOL_VERSION, "0.20.0", [The MPD protocol version])
|
AC_DEFINE(PROTOCOL_VERSION, "0.20.22", [The MPD protocol version])
|
||||||
|
|
||||||
GIT_COMMIT=`GIT_DIR="$srcdir/.git" git describe --dirty --always 2>/dev/null`
|
GIT_COMMIT=`cd "$srcdir" && git describe --dirty --always 2>/dev/null`
|
||||||
if test x$GIT_COMMIT != x; then
|
if test x$GIT_COMMIT != x; then
|
||||||
AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
|
AC_DEFINE_UNQUOTED(GIT_COMMIT, ["$GIT_COMMIT"], [The current git commit])
|
||||||
fi
|
fi
|
||||||
@@ -186,6 +186,7 @@ AC_ARG_WITH([android-sdk],
|
|||||||
[Directory for Android SDK]),
|
[Directory for Android SDK]),
|
||||||
[], [with_android_sdk=no])
|
[], [with_android_sdk=no])
|
||||||
|
|
||||||
|
android_abi=""
|
||||||
if test x$host_is_android = xyes; then
|
if test x$host_is_android = xyes; then
|
||||||
if test x$with_android_sdk = xno; then
|
if test x$with_android_sdk = xno; then
|
||||||
AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
|
AC_MSG_ERROR([Android build requires option --with-android-sdk=DIR])
|
||||||
@@ -194,9 +195,15 @@ if test x$host_is_android = xyes; then
|
|||||||
if ! test -x $with_android_sdk/tools/android; then
|
if ! test -x $with_android_sdk/tools/android; then
|
||||||
AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
|
AC_MSG_ERROR([Android SDK not found in $with_android_sdk])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
AS_CASE([$host_cpu],
|
||||||
|
[i686], [android_abi="x86"],
|
||||||
|
[aarch64], [android_abi="arm64-v8a"],
|
||||||
|
[android_abi="armeabi-v7a"])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AC_SUBST(ANDROID_SDK, [$with_android_sdk])
|
AC_SUBST(ANDROID_SDK, [$with_android_sdk])
|
||||||
|
AC_SUBST(ANDROID_ABI, [$android_abi])
|
||||||
|
|
||||||
dnl ---------------------------------------------------------------------------
|
dnl ---------------------------------------------------------------------------
|
||||||
dnl Language Checks
|
dnl Language Checks
|
||||||
@@ -315,7 +322,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
default_enable_daemon=yes
|
default_enable_daemon=yes
|
||||||
if test x$host_is_android = xyes || test x$host_is_android = xyes; then
|
if test x$host_is_android = xyes || test x$host_is_windows = xyes; then
|
||||||
default_enable_daemon=no
|
default_enable_daemon=no
|
||||||
fi
|
fi
|
||||||
AC_ARG_ENABLE(daemon,
|
AC_ARG_ENABLE(daemon,
|
||||||
@@ -402,7 +409,7 @@ AC_ARG_ENABLE(recorder-output,
|
|||||||
|
|
||||||
AC_ARG_ENABLE(sidplay,
|
AC_ARG_ENABLE(sidplay,
|
||||||
AS_HELP_STRING([--enable-sidplay],
|
AS_HELP_STRING([--enable-sidplay],
|
||||||
[enable C64 SID support via libsidplay2]),,
|
[enable C64 SID support via libsidplayfp or libsidplay2]),,
|
||||||
enable_sidplay=auto)
|
enable_sidplay=auto)
|
||||||
|
|
||||||
AC_ARG_ENABLE(shout,
|
AC_ARG_ENABLE(shout,
|
||||||
@@ -700,7 +707,7 @@ MPD_ENABLE_AUTO_PKG_LIB(smbclient, SMBCLIENT, [smbclient >= 0.2],
|
|||||||
[smbclient input plugin], [libsmbclient not found])
|
[smbclient input plugin], [libsmbclient not found])
|
||||||
|
|
||||||
dnl ----------------------------------- NFS -----------------------------
|
dnl ----------------------------------- NFS -----------------------------
|
||||||
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs],
|
MPD_ENABLE_AUTO_PKG(nfs, NFS, [libnfs >= 1.9.5],
|
||||||
[NFS input plugin], [libnfs not found])
|
[NFS input plugin], [libnfs not found])
|
||||||
|
|
||||||
dnl --------------------------------- Soundcloud ------------------------------
|
dnl --------------------------------- Soundcloud ------------------------------
|
||||||
@@ -1005,7 +1012,7 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
|||||||
[found_sidplay=no])
|
[found_sidplay=no])
|
||||||
|
|
||||||
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
MPD_AUTO_PRE(sidplay, [sidplay decoder plugin],
|
||||||
[libsidplay2 not found])
|
[libsidplay2 or libsidutils not found])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
||||||
@@ -1017,10 +1024,11 @@ if test x$enable_sidplay != xno && test x$found_sidplayfp = xno; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if test x$enable_sidplay = xyes; then
|
if test x$enable_sidplay = xyes; then
|
||||||
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplayfp or libsidplay2 support])
|
||||||
AC_DEFINE(ENABLE_SIDPLAY, 1, [Define for libsidplay2 support])
|
|
||||||
if test x$found_sidplayfp = xyes; then
|
if test x$found_sidplayfp = xyes; then
|
||||||
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
|
AC_DEFINE(HAVE_SIDPLAYFP, 1, [Define if libsidplayfp is used instead of libsidplay2])
|
||||||
|
else
|
||||||
|
SIDPLAY_LIBS="$SIDPLAY_LIBS -lresid-builder"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -1599,6 +1599,11 @@ OK
|
|||||||
per-artist counts:
|
per-artist counts:
|
||||||
</para>
|
</para>
|
||||||
<programlisting>count group artist</programlisting>
|
<programlisting>count group artist</programlisting>
|
||||||
|
<para>
|
||||||
|
A group with an empty value contains counts of matching
|
||||||
|
song which don't this group tag. It exists only if at
|
||||||
|
least one such song is found.
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
16
doc/user.xml
16
doc/user.xml
@@ -2087,13 +2087,6 @@ run</programlisting>
|
|||||||
database.
|
database.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
|
||||||
Note that unless overridden by the below settings (e.g. by
|
|
||||||
setting them to a blank value), general curl configuration
|
|
||||||
from environment variables such as http_proxy or specified
|
|
||||||
in ~/.curlrc will be in effect.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<informaltable>
|
<informaltable>
|
||||||
<tgroup cols="2">
|
<tgroup cols="2">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -2121,6 +2114,15 @@ run</programlisting>
|
|||||||
<application>MPD</application> instance.
|
<application>MPD</application> instance.
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>password</varname>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The password used to log in to the "master"
|
||||||
|
<application>MPD</application> instance.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
<row>
|
<row>
|
||||||
<entry>
|
<entry>
|
||||||
<varname>keepalive</varname>
|
<varname>keepalive</varname>
|
||||||
|
@@ -21,6 +21,8 @@ class FfmpegProject(Project):
|
|||||||
|
|
||||||
if toolchain.is_arm:
|
if toolchain.is_arm:
|
||||||
arch = 'arm'
|
arch = 'arm'
|
||||||
|
elif toolchain.is_aarch64:
|
||||||
|
arch = 'aarch64'
|
||||||
else:
|
else:
|
||||||
arch = 'x86'
|
arch = 'x86'
|
||||||
|
|
||||||
|
@@ -9,8 +9,8 @@ from build.ffmpeg import FfmpegProject
|
|||||||
from build.boost import BoostProject
|
from build.boost import BoostProject
|
||||||
|
|
||||||
libmpdclient = MesonProject(
|
libmpdclient = MesonProject(
|
||||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.14.tar.xz',
|
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.16.tar.xz',
|
||||||
'0a84e2791bfe3077cf22ee1784c805d5bb550803dffe56a39aa3690a38061372',
|
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
|
||||||
'lib/libmpdclient.a',
|
'lib/libmpdclient.a',
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
opus = AutotoolsProject(
|
opus = AutotoolsProject(
|
||||||
'https://archive.mozilla.org/pub/opus/opus-1.2.1.tar.gz',
|
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
||||||
'cfafd339ccd9c5ef8d6ab15d7e1a412c054bf4cb4ecbbbcc78c12ef2def70732',
|
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
||||||
'lib/libopus.a',
|
'lib/libopus.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-4.0.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-4.0.2.tar.xz',
|
||||||
'ed945daf40b124e77a685893cc025d086f638bc703183460aff49508edb3a43f',
|
'a95c0cc9eb990e94031d2183f2e6e444cc61c99f6f182d1575c433d62afb2f97',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
curl = AutotoolsProject(
|
curl = AutotoolsProject(
|
||||||
'http://curl.haxx.se/download/curl-7.60.0.tar.xz',
|
'http://curl.haxx.se/download/curl-7.61.1.tar.xz',
|
||||||
'8736ff8ded89ddf7e926eec7b16f82597d029fc1469f3a551f1fafaac164e6a0',
|
'3d5913d6a39bd22e68e34dff697fd6e4c3c81563f580c76fca2009315cd81891',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -364,9 +364,19 @@ curl = AutotoolsProject(
|
|||||||
patches='src/lib/curl/patches',
|
patches='src/lib/curl/patches',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
libexpat = AutotoolsProject(
|
||||||
|
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
||||||
|
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
||||||
|
'lib/libexpat.a',
|
||||||
|
[
|
||||||
|
'--disable-shared', '--enable-static',
|
||||||
|
'--without-docbook',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
libnfs = AutotoolsProject(
|
libnfs = AutotoolsProject(
|
||||||
'https://github.com/sahlberg/libnfs/archive/libnfs-2.0.0.tar.gz',
|
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
|
||||||
'7ea6cd8fa6c461d01091e584d424d28e137d23ff4b65b95d01a3fd0ef95d120e',
|
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
|
||||||
'lib/libnfs.a',
|
'lib/libnfs.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -374,13 +384,15 @@ libnfs = AutotoolsProject(
|
|||||||
|
|
||||||
# work around -Wtautological-compare
|
# work around -Wtautological-compare
|
||||||
'--disable-werror',
|
'--disable-werror',
|
||||||
|
|
||||||
|
'--disable-utils', '--disable-examples',
|
||||||
],
|
],
|
||||||
base='libnfs-libnfs-2.0.0',
|
base='libnfs-libnfs-3.0.0',
|
||||||
autoreconf=True,
|
autoreconf=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
boost = BoostProject(
|
boost = BoostProject(
|
||||||
'http://downloads.sourceforge.net/project/boost/boost/1.66.0/boost_1_66_0.tar.bz2',
|
'http://downloads.sourceforge.net/project/boost/boost/1.68.0/boost_1_68_0.tar.bz2',
|
||||||
'5721818253e6a0989583192f96782c4a98eb6204965316df9f5ad75819225ca9',
|
'7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
@@ -20,6 +20,9 @@ class MesonProject(Project):
|
|||||||
cpu = 'armv7'
|
cpu = 'armv7'
|
||||||
else:
|
else:
|
||||||
cpu = 'armv6'
|
cpu = 'armv6'
|
||||||
|
elif toolchain.is_aarch64:
|
||||||
|
cpu_family = 'aarch64'
|
||||||
|
cpu = 'arm64-v8a'
|
||||||
else:
|
else:
|
||||||
cpu_family = 'x86'
|
cpu_family = 'x86'
|
||||||
if 'x86_64' in toolchain.arch:
|
if 'x86_64' in toolchain.arch:
|
||||||
@@ -51,6 +54,9 @@ c_link_args = %s
|
|||||||
cpp_args = %s
|
cpp_args = %s
|
||||||
cpp_link_args = %s
|
cpp_link_args = %s
|
||||||
|
|
||||||
|
# Keep Meson from executing Android-x86 test binariees
|
||||||
|
needs_exe_wrapper = true
|
||||||
|
|
||||||
[host_machine]
|
[host_machine]
|
||||||
system = '%s'
|
system = '%s'
|
||||||
cpu_family = '%s'
|
cpu_family = '%s'
|
||||||
|
@@ -19,9 +19,9 @@
|
|||||||
|
|
||||||
#include "AudioFormat.hxx"
|
#include "AudioFormat.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
void
|
void
|
||||||
AudioFormat::ApplyMask(AudioFormat mask) noexcept
|
AudioFormat::ApplyMask(AudioFormat mask) noexcept
|
||||||
@@ -44,21 +44,16 @@ AudioFormat::ApplyMask(AudioFormat mask) noexcept
|
|||||||
StringBuffer<24>
|
StringBuffer<24>
|
||||||
ToString(const AudioFormat af) noexcept
|
ToString(const AudioFormat af) noexcept
|
||||||
{
|
{
|
||||||
StringBuffer<24> buffer;
|
|
||||||
|
|
||||||
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
|
||||||
af.sample_rate % 44100 == 0) {
|
af.sample_rate % 44100 == 0) {
|
||||||
/* use shortcuts such as "dsd64" which implies the
|
/* use shortcuts such as "dsd64" which implies the
|
||||||
sample rate */
|
sample rate */
|
||||||
snprintf(buffer.data(), buffer.capacity(), "dsd%u:%u",
|
return StringFormat<24>("dsd%u:%u",
|
||||||
af.sample_rate * 8 / 44100,
|
af.sample_rate * 8 / 44100,
|
||||||
af.channels);
|
af.channels);
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
snprintf(buffer.data(), buffer.capacity(), "%u:%s:%u",
|
return StringFormat<24>("%u:%s:%u",
|
||||||
af.sample_rate, sample_format_to_string(af.format),
|
af.sample_rate, sample_format_to_string(af.format),
|
||||||
af.channels);
|
af.channels);
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,6 @@
|
|||||||
#include "pcm/SampleFormat.hxx"
|
#include "pcm/SampleFormat.hxx"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
@@ -83,7 +83,7 @@ LocateUri(const char *uri, const Client *client
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
/* skip the obsolete "file://" prefix */
|
/* skip the obsolete "file://" prefix */
|
||||||
const char *path_utf8 = StringAfterPrefix(uri, "file://");
|
const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
|
||||||
if (path_utf8 != nullptr) {
|
if (path_utf8 != nullptr) {
|
||||||
if (!PathTraitsUTF8::IsAbsolute(path_utf8))
|
if (!PathTraitsUTF8::IsAbsolute(path_utf8))
|
||||||
throw std::runtime_error("Malformed file:// URI");
|
throw std::runtime_error("Malformed file:// URI");
|
||||||
|
@@ -34,6 +34,8 @@
|
|||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
|
#include "Main.hxx"
|
||||||
|
|
||||||
static int
|
static int
|
||||||
ToAndroidLogLevel(LogLevel log_level)
|
ToAndroidLogLevel(LogLevel log_level)
|
||||||
@@ -177,6 +179,9 @@ Log(const Domain &domain, LogLevel level, const char *msg)
|
|||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||||
"%s: %s", domain.GetName(), msg);
|
"%s: %s", domain.GetName(), msg);
|
||||||
|
if (logListener != nullptr)
|
||||||
|
logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level),
|
||||||
|
"%s: %s", domain.GetName(), msg);
|
||||||
#else
|
#else
|
||||||
|
|
||||||
if (level < log_threshold)
|
if (level < log_threshold)
|
||||||
|
@@ -92,6 +92,7 @@
|
|||||||
#include "java/File.hxx"
|
#include "java/File.hxx"
|
||||||
#include "android/Environment.hxx"
|
#include "android/Environment.hxx"
|
||||||
#include "android/Context.hxx"
|
#include "android/Context.hxx"
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
#include "fs/StandardDirectory.hxx"
|
#include "fs/StandardDirectory.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "org_musicpd_Bridge.h"
|
#include "org_musicpd_Bridge.h"
|
||||||
@@ -128,6 +129,7 @@ static constexpr unsigned DEFAULT_BUFFER_BEFORE_PLAY = 10;
|
|||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
Context *context;
|
Context *context;
|
||||||
|
LogListener *logListener;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Instance *instance;
|
Instance *instance;
|
||||||
@@ -676,16 +678,19 @@ try {
|
|||||||
|
|
||||||
gcc_visibility_default
|
gcc_visibility_default
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context)
|
Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logListener)
|
||||||
{
|
{
|
||||||
Java::Init(env);
|
Java::Init(env);
|
||||||
Java::File::Initialise(env);
|
Java::File::Initialise(env);
|
||||||
Environment::Initialise(env);
|
Environment::Initialise(env);
|
||||||
|
|
||||||
context = new Context(env, _context);
|
context = new Context(env, _context);
|
||||||
|
if (_logListener != nullptr)
|
||||||
|
logListener = new LogListener(env, _logListener);
|
||||||
|
|
||||||
mpd_main(0, nullptr);
|
mpd_main(0, nullptr);
|
||||||
|
|
||||||
|
delete logListener;
|
||||||
delete context;
|
delete context;
|
||||||
Environment::Deinitialise(env);
|
Environment::Deinitialise(env);
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,10 @@ class Context;
|
|||||||
struct Instance;
|
struct Instance;
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
|
#include "android/LogListener.hxx"
|
||||||
|
|
||||||
extern Context *context;
|
extern Context *context;
|
||||||
|
extern LogListener *logListener;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern Instance *instance;
|
extern Instance *instance;
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
#include "db/LightSong.hxx"
|
#include "db/LightSong.hxx"
|
||||||
#include "DetachedSong.hxx"
|
#include "DetachedSong.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
|
#include "tag/Fallback.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
@@ -109,6 +110,24 @@ SongFilter::Item::Match(const Tag &_tag) const noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) {
|
if (tag < TAG_NUM_OF_ITEM_TYPES && !visited_types[tag]) {
|
||||||
|
bool result = false;
|
||||||
|
if (ApplyTagFallback(TagType(tag),
|
||||||
|
[&](TagType tag2) {
|
||||||
|
if (!visited_types[tag2])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const auto &item : _tag) {
|
||||||
|
if (item.type == tag2 &&
|
||||||
|
StringMatch(item.value)) {
|
||||||
|
result = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}))
|
||||||
|
return result;
|
||||||
|
|
||||||
/* If the search critieron was not visited during the
|
/* If the search critieron was not visited during the
|
||||||
sweep through the song's tag, it means this field
|
sweep through the song's tag, it means this field
|
||||||
is absent from the tag or empty. Thus, if the
|
is absent from the tag or empty. Thus, if the
|
||||||
@@ -117,15 +136,6 @@ SongFilter::Item::Match(const Tag &_tag) const noexcept
|
|||||||
true. */
|
true. */
|
||||||
if (value.empty())
|
if (value.empty())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (tag == TAG_ALBUM_ARTIST && visited_types[TAG_ARTIST]) {
|
|
||||||
/* if we're looking for "album artist", but
|
|
||||||
only "artist" exists, use that */
|
|
||||||
for (const auto &item : _tag)
|
|
||||||
if (item.type == TAG_ARTIST &&
|
|
||||||
StringMatch(item.value))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
46
src/android/LogListener.cxx
Normal file
46
src/android/LogListener.cxx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "LogListener.hxx"
|
||||||
|
#include "java/Class.hxx"
|
||||||
|
#include "java/String.hxx"
|
||||||
|
#include "util/AllocatedString.hxx"
|
||||||
|
#include "util/FormatString.hxx"
|
||||||
|
|
||||||
|
void
|
||||||
|
LogListener::OnLog(JNIEnv *env, int priority, const char *fmt, ...) const
|
||||||
|
{
|
||||||
|
assert(env != nullptr);
|
||||||
|
|
||||||
|
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||||
|
|
||||||
|
jmethodID method = env->GetMethodID(cls, "onLog",
|
||||||
|
"(ILjava/lang/String;)V");
|
||||||
|
|
||||||
|
assert(method);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
const auto log = FormatStringV(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
env->CallVoidMethod(Get(), method, priority,
|
||||||
|
Java::String(env, log.c_str()).Get());
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2017 The Music Player Daemon Project
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,11 +17,16 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_INPUT_DOMAIN_HXX
|
#ifndef MPD_ANDROID_LOG_LISTENER_HXX
|
||||||
#define MPD_INPUT_DOMAIN_HXX
|
#define MPD_ANDROID_LOG_LISTENER_HXX
|
||||||
|
|
||||||
class Domain;
|
#include "java/Object.hxx"
|
||||||
|
|
||||||
extern const Domain input_domain;
|
class LogListener : public Java::Object {
|
||||||
|
public:
|
||||||
|
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
||||||
|
|
||||||
|
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
@@ -191,7 +191,7 @@ handle_list(Client &client, Request args, Response &r)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<SongFilter> filter;
|
std::unique_ptr<SongFilter> filter;
|
||||||
tag_mask_t group_mask = 0;
|
TagType group = TAG_NUM_OF_ITEM_TYPES;
|
||||||
|
|
||||||
if (args.size == 1) {
|
if (args.size == 1) {
|
||||||
/* for compatibility with < 0.12.0 */
|
/* for compatibility with < 0.12.0 */
|
||||||
@@ -206,18 +206,16 @@ handle_list(Client &client, Request args, Response &r)
|
|||||||
args.shift()));
|
args.shift()));
|
||||||
}
|
}
|
||||||
|
|
||||||
while (args.size >= 2 &&
|
if (args.size >= 2 &&
|
||||||
StringIsEqual(args[args.size - 2], "group")) {
|
StringIsEqual(args[args.size - 2], "group")) {
|
||||||
const char *s = args[args.size - 1];
|
const char *s = args[args.size - 1];
|
||||||
TagType gt = tag_name_parse_i(s);
|
group = tag_name_parse_i(s);
|
||||||
if (gt == TAG_NUM_OF_ITEM_TYPES) {
|
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||||
r.FormatError(ACK_ERROR_ARG,
|
r.FormatError(ACK_ERROR_ARG,
|
||||||
"Unknown tag type: %s", s);
|
"Unknown tag type: %s", s);
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
group_mask |= tag_mask_t(1) << unsigned(gt);
|
|
||||||
|
|
||||||
args.pop_back();
|
args.pop_back();
|
||||||
args.pop_back();
|
args.pop_back();
|
||||||
}
|
}
|
||||||
@@ -230,14 +228,13 @@ handle_list(Client &client, Request args, Response &r)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagType < TAG_NUM_OF_ITEM_TYPES &&
|
if (tagType < TAG_NUM_OF_ITEM_TYPES && tagType == group) {
|
||||||
group_mask & (tag_mask_t(1) << tagType)) {
|
|
||||||
r.Error(ACK_ERROR_ARG, "Conflicting group");
|
r.Error(ACK_ERROR_ARG, "Conflicting group");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintUniqueTags(r, client.partition,
|
PrintUniqueTags(r, client.partition,
|
||||||
tagType, group_mask, filter.get());
|
tagType, group, filter.get());
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "LightSong.hxx"
|
#include "LightSong.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
|
#include "tag/VisitFallback.hxx"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -72,24 +73,15 @@ stats_visitor_song(SearchStats &stats, const LightSong &song)
|
|||||||
stats.total_duration += duration;
|
stats.total_duration += duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
|
CollectGroupCounts(TagCountMap &map, const Tag &tag,
|
||||||
|
const char *value) noexcept
|
||||||
{
|
{
|
||||||
bool found = false;
|
auto r = map.insert(std::make_pair(value, SearchStats()));
|
||||||
for (const auto &item : tag) {
|
|
||||||
if (item.type == group) {
|
|
||||||
auto r = map.insert(std::make_pair(item.value,
|
|
||||||
SearchStats()));
|
|
||||||
SearchStats &s = r.first->second;
|
SearchStats &s = r.first->second;
|
||||||
++s.n_songs;
|
++s.n_songs;
|
||||||
if (!tag.duration.IsNegative())
|
if (!tag.duration.IsNegative())
|
||||||
s.total_duration += tag.duration;
|
s.total_duration += tag.duration;
|
||||||
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -98,9 +90,10 @@ GroupCountVisitor(TagCountMap &map, TagType group, const LightSong &song)
|
|||||||
assert(song.tag != nullptr);
|
assert(song.tag != nullptr);
|
||||||
|
|
||||||
const Tag &tag = *song.tag;
|
const Tag &tag = *song.tag;
|
||||||
if (!CollectGroupCounts(map, group, tag) && group == TAG_ALBUM_ARTIST)
|
VisitTagWithFallbackOrEmpty(tag, group,
|
||||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
std::bind(CollectGroupCounts, std::ref(map),
|
||||||
CollectGroupCounts(map, TAG_ARTIST, tag);
|
std::cref(tag),
|
||||||
|
std::placeholders::_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -187,22 +187,34 @@ PrintSongURIVisitor(Response &r, Partition &partition, const LightSong &song)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
PrintUniqueTag(Response &r, TagType tag_type,
|
PrintUniqueTags(Response &r, TagType tag_type,
|
||||||
const Tag &tag)
|
const std::set<std::string> &values)
|
||||||
{
|
{
|
||||||
const char *value = tag.GetValue(tag_type);
|
const char *const name = tag_item_names[tag_type];
|
||||||
assert(value != nullptr);
|
for (const auto &i : values)
|
||||||
r.Format("%s: %s\n", tag_item_names[tag_type], value);
|
r.Format("%s: %s\n", name, i.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &item : tag)
|
static void
|
||||||
if (item.type != tag_type)
|
PrintGroupedUniqueTags(Response &r, TagType tag_type, TagType group,
|
||||||
r.Format("%s: %s\n",
|
const std::map<std::string, std::set<std::string>> &groups)
|
||||||
tag_item_names[item.type], item.value);
|
{
|
||||||
|
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||||
|
for (const auto &i : groups)
|
||||||
|
PrintUniqueTags(r, tag_type, i.second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *const group_name = tag_item_names[group];
|
||||||
|
for (const auto &i : groups) {
|
||||||
|
r.Format("%s: %s\n", group_name, i.first.c_str());
|
||||||
|
PrintUniqueTags(r, tag_type, i.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PrintUniqueTags(Response &r, Partition &partition,
|
PrintUniqueTags(Response &r, Partition &partition,
|
||||||
unsigned type, tag_mask_t group_mask,
|
unsigned type, TagType group,
|
||||||
const SongFilter *filter)
|
const SongFilter *filter)
|
||||||
{
|
{
|
||||||
const Database &db = partition.GetDatabaseOrThrow();
|
const Database &db = partition.GetDatabaseOrThrow();
|
||||||
@@ -217,10 +229,9 @@ PrintUniqueTags(Response &r, Partition &partition,
|
|||||||
} else {
|
} else {
|
||||||
assert(type < TAG_NUM_OF_ITEM_TYPES);
|
assert(type < TAG_NUM_OF_ITEM_TYPES);
|
||||||
|
|
||||||
using namespace std::placeholders;
|
PrintGroupedUniqueTags(r, TagType(type), group,
|
||||||
const auto f = std::bind(PrintUniqueTag, std::ref(r),
|
db.CollectUniqueTags(selection,
|
||||||
(TagType)type, _1);
|
TagType(type),
|
||||||
db.VisitUniqueTags(selection, (TagType)type,
|
group));
|
||||||
group_mask, f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#ifndef MPD_DB_PRINT_H
|
#ifndef MPD_DB_PRINT_H
|
||||||
#define MPD_DB_PRINT_H
|
#define MPD_DB_PRINT_H
|
||||||
|
|
||||||
#include "tag/Mask.hxx"
|
#include "tag/TagType.h"
|
||||||
|
|
||||||
class SongFilter;
|
class SongFilter;
|
||||||
struct DatabaseSelection;
|
struct DatabaseSelection;
|
||||||
@@ -44,7 +44,7 @@ db_selection_print(Response &r, Partition &partition,
|
|||||||
|
|
||||||
void
|
void
|
||||||
PrintUniqueTags(Response &r, Partition &partition,
|
PrintUniqueTags(Response &r, Partition &partition,
|
||||||
unsigned type, tag_mask_t group_mask,
|
unsigned type, TagType group,
|
||||||
const SongFilter *filter);
|
const SongFilter *filter);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -22,9 +22,12 @@
|
|||||||
|
|
||||||
#include "Visitor.hxx"
|
#include "Visitor.hxx"
|
||||||
#include "tag/TagType.h"
|
#include "tag/TagType.h"
|
||||||
#include "tag/Mask.hxx"
|
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
struct DatabasePlugin;
|
struct DatabasePlugin;
|
||||||
@@ -99,12 +102,9 @@ public:
|
|||||||
return Visit(selection, VisitDirectory(), visit_song);
|
return Visit(selection, VisitDirectory(), visit_song);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
virtual std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
* Visit all unique tag values.
|
TagType tag_type,
|
||||||
*/
|
TagType group=TAG_NUM_OF_ITEM_TYPES) const = 0;
|
||||||
virtual void VisitUniqueTags(const DatabaseSelection &selection,
|
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
|
||||||
VisitTag visit_tag) const = 0;
|
|
||||||
|
|
||||||
virtual DatabaseStats GetStats(const DatabaseSelection &selection) const = 0;
|
virtual DatabaseStats GetStats(const DatabaseSelection &selection) const = 0;
|
||||||
|
|
||||||
|
@@ -20,34 +20,42 @@
|
|||||||
#include "UniqueTags.hxx"
|
#include "UniqueTags.hxx"
|
||||||
#include "Interface.hxx"
|
#include "Interface.hxx"
|
||||||
#include "LightSong.hxx"
|
#include "LightSong.hxx"
|
||||||
#include "tag/Set.hxx"
|
#include "tag/VisitFallback.hxx"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
static void
|
static void
|
||||||
CollectTags(TagSet &set, TagType tag_type, tag_mask_t group_mask,
|
CollectTags(std::set<std::string> &result,
|
||||||
const LightSong &song)
|
const Tag &tag,
|
||||||
|
TagType tag_type) noexcept
|
||||||
{
|
{
|
||||||
assert(song.tag != nullptr);
|
VisitTagWithFallbackOrEmpty(tag, tag_type, [&result](const char *value){
|
||||||
const Tag &tag = *song.tag;
|
result.emplace(value);
|
||||||
|
});
|
||||||
set.InsertUnique(tag, tag_type, group_mask);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
static void
|
||||||
VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
|
CollectGroupTags(std::map<std::string, std::set<std::string>> &result,
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
const Tag &tag,
|
||||||
VisitTag visit_tag)
|
TagType tag_type,
|
||||||
|
TagType group) noexcept
|
||||||
{
|
{
|
||||||
TagSet set;
|
VisitTagWithFallbackOrEmpty(tag, group, [&](const char *group_name){
|
||||||
|
CollectTags(result[group_name], tag, tag_type);
|
||||||
using namespace std::placeholders;
|
});
|
||||||
const auto f = std::bind(CollectTags, std::ref(set),
|
}
|
||||||
tag_type, group_mask, _1);
|
|
||||||
db.Visit(selection, f);
|
std::map<std::string, std::set<std::string>>
|
||||||
|
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||||
for (const auto &value : set)
|
TagType tag_type, TagType group)
|
||||||
visit_tag(value);
|
{
|
||||||
|
std::map<std::string, std::set<std::string>> result;
|
||||||
|
|
||||||
|
db.Visit(selection, [&result, tag_type, group](const LightSong &song){
|
||||||
|
CollectGroupTags(result, *song.tag, tag_type, group);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
@@ -20,16 +20,19 @@
|
|||||||
#ifndef MPD_DB_UNIQUE_TAGS_HXX
|
#ifndef MPD_DB_UNIQUE_TAGS_HXX
|
||||||
#define MPD_DB_UNIQUE_TAGS_HXX
|
#define MPD_DB_UNIQUE_TAGS_HXX
|
||||||
|
|
||||||
#include "Visitor.hxx"
|
|
||||||
#include "tag/TagType.h"
|
#include "tag/TagType.h"
|
||||||
#include "tag/Mask.hxx"
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class Database;
|
class Database;
|
||||||
struct DatabaseSelection;
|
struct DatabaseSelection;
|
||||||
|
|
||||||
void
|
gcc_pure
|
||||||
VisitUniqueTags(const Database &db, const DatabaseSelection &selection,
|
std::map<std::string, std::set<std::string>>
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
CollectUniqueTags(const Database &db, const DatabaseSelection &selection,
|
||||||
VisitTag visit_tag);
|
TagType tag_type, TagType group);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -82,6 +82,7 @@ class ProxyDatabase final : public Database, SocketMonitor, IdleMonitor {
|
|||||||
DatabaseListener &listener;
|
DatabaseListener &listener;
|
||||||
|
|
||||||
const std::string host;
|
const std::string host;
|
||||||
|
const std::string password;
|
||||||
const unsigned port;
|
const unsigned port;
|
||||||
const bool keepalive;
|
const bool keepalive;
|
||||||
|
|
||||||
@@ -119,9 +120,9 @@ public:
|
|||||||
VisitSong visit_song,
|
VisitSong visit_song,
|
||||||
VisitPlaylist visit_playlist) const override;
|
VisitPlaylist visit_playlist) const override;
|
||||||
|
|
||||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
TagType tag_type,
|
||||||
VisitTag visit_tag) const override;
|
TagType group) const override;
|
||||||
|
|
||||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||||
|
|
||||||
@@ -169,6 +170,13 @@ static constexpr struct {
|
|||||||
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
|
#if LIBMPDCLIENT_CHECK_VERSION(2,10,0)
|
||||||
{ TAG_MUSICBRAINZ_RELEASETRACKID,
|
{ TAG_MUSICBRAINZ_RELEASETRACKID,
|
||||||
MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
|
MPD_TAG_MUSICBRAINZ_RELEASETRACKID },
|
||||||
|
#endif
|
||||||
|
#if LIBMPDCLIENT_CHECK_VERSION(2,11,0)
|
||||||
|
{ TAG_ARTIST_SORT, MPD_TAG_ARTIST_SORT },
|
||||||
|
{ TAG_ALBUM_ARTIST_SORT, MPD_TAG_ALBUM_ARTIST_SORT },
|
||||||
|
#endif
|
||||||
|
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||||
|
{ TAG_ALBUM_SORT, MPD_TAG_ALBUM_SORT },
|
||||||
#endif
|
#endif
|
||||||
{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
|
{ TAG_NUM_OF_ITEM_TYPES, MPD_TAG_COUNT }
|
||||||
};
|
};
|
||||||
@@ -326,28 +334,19 @@ SendConstraints(mpd_connection *connection, const DatabaseSelection &selection)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
SendGroupMask(mpd_connection *connection, tag_mask_t mask)
|
SendGroup(mpd_connection *connection, TagType group)
|
||||||
{
|
{
|
||||||
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
if (group == TAG_NUM_OF_ITEM_TYPES)
|
||||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
return true;
|
||||||
if ((mask & (tag_mask_t(1) << i)) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const auto tag = Convert(TagType(i));
|
#if LIBMPDCLIENT_CHECK_VERSION(2,12,0)
|
||||||
|
const auto tag = Convert(group);
|
||||||
if (tag == MPD_TAG_COUNT)
|
if (tag == MPD_TAG_COUNT)
|
||||||
throw std::runtime_error("Unsupported tag");
|
throw std::runtime_error("Unsupported tag");
|
||||||
|
|
||||||
if (!mpd_search_add_group_tag(connection, tag))
|
return mpd_search_add_group_tag(connection, tag);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
#else
|
#else
|
||||||
(void)connection;
|
(void)connection;
|
||||||
(void)mask;
|
|
||||||
|
|
||||||
if (mask != 0)
|
|
||||||
throw std::runtime_error("Grouping requires libmpdclient 2.12");
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
@@ -359,6 +358,7 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
|||||||
SocketMonitor(_loop), IdleMonitor(_loop),
|
SocketMonitor(_loop), IdleMonitor(_loop),
|
||||||
listener(_listener),
|
listener(_listener),
|
||||||
host(block.GetBlockValue("host", "")),
|
host(block.GetBlockValue("host", "")),
|
||||||
|
password(block.GetBlockValue("password", "")),
|
||||||
port(block.GetBlockValue("port", 0u)),
|
port(block.GetBlockValue("port", 0u)),
|
||||||
keepalive(block.GetBlockValue("keepalive", false))
|
keepalive(block.GetBlockValue("keepalive", false))
|
||||||
{
|
{
|
||||||
@@ -402,6 +402,10 @@ ProxyDatabase::Connect()
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
CheckError(connection);
|
CheckError(connection);
|
||||||
|
|
||||||
|
if (!password.empty() &&
|
||||||
|
!mpd_run_password(connection, password.c_str()))
|
||||||
|
ThrowError(connection);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
mpd_connection_free(connection);
|
mpd_connection_free(connection);
|
||||||
connection = nullptr;
|
connection = nullptr;
|
||||||
@@ -786,11 +790,9 @@ ProxyDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
visit_directory, visit_song, visit_playlist);
|
visit_directory, visit_song, visit_playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
std::map<std::string, std::set<std::string>>
|
||||||
ProxyDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
ProxyDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag_type,
|
TagType tag_type, TagType group) const
|
||||||
tag_mask_t group_mask,
|
|
||||||
VisitTag visit_tag) const
|
|
||||||
try {
|
try {
|
||||||
// TODO: eliminate the const_cast
|
// TODO: eliminate the const_cast
|
||||||
const_cast<ProxyDatabase *>(this)->EnsureConnected();
|
const_cast<ProxyDatabase *>(this)->EnsureConnected();
|
||||||
@@ -801,13 +803,16 @@ try {
|
|||||||
|
|
||||||
if (!mpd_search_db_tags(connection, tag_type2) ||
|
if (!mpd_search_db_tags(connection, tag_type2) ||
|
||||||
!SendConstraints(connection, selection) ||
|
!SendConstraints(connection, selection) ||
|
||||||
!SendGroupMask(connection, group_mask))
|
!SendGroup(connection, group))
|
||||||
ThrowError(connection);
|
ThrowError(connection);
|
||||||
|
|
||||||
if (!mpd_search_commit(connection))
|
if (!mpd_search_commit(connection))
|
||||||
ThrowError(connection);
|
ThrowError(connection);
|
||||||
|
|
||||||
TagBuilder builder;
|
std::map<std::string, std::set<std::string>> result;
|
||||||
|
|
||||||
|
if (group == TAG_NUM_OF_ITEM_TYPES) {
|
||||||
|
auto &values = result[std::string()];
|
||||||
|
|
||||||
while (auto *pair = mpd_recv_pair(connection)) {
|
while (auto *pair = mpd_recv_pair(connection)) {
|
||||||
AtScopeExit(this, pair) {
|
AtScopeExit(this, pair) {
|
||||||
@@ -818,37 +823,36 @@ try {
|
|||||||
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (current_type == tag_type && !builder.IsEmpty()) {
|
if (current_type == tag_type)
|
||||||
try {
|
values.emplace(pair->value);
|
||||||
visit_tag(builder.Commit());
|
|
||||||
} catch (...) {
|
|
||||||
mpd_response_finish(connection);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
std::set<std::string> *current_group = nullptr;
|
||||||
|
|
||||||
|
while (auto *pair = mpd_recv_pair(connection)) {
|
||||||
|
AtScopeExit(this, pair) {
|
||||||
|
mpd_return_pair(connection, pair);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto current_type = tag_name_parse_i(pair->name);
|
||||||
|
if (current_type == TAG_NUM_OF_ITEM_TYPES)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (current_type == tag_type) {
|
||||||
|
if (current_group == nullptr)
|
||||||
|
current_group = &result[std::string()];
|
||||||
|
|
||||||
|
current_group->emplace(pair->value);
|
||||||
|
} else if (current_type == group) {
|
||||||
|
current_group = &result[pair->value];
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AddItem(current_type, pair->value);
|
|
||||||
|
|
||||||
if (!builder.HasType(current_type))
|
|
||||||
/* if no tag item has been added, then the
|
|
||||||
given value was not acceptable
|
|
||||||
(e.g. empty); forcefully insert an empty
|
|
||||||
tag in this case, as the caller expects the
|
|
||||||
given tag type to be present */
|
|
||||||
builder.AddEmptyItem(current_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!builder.IsEmpty()) {
|
|
||||||
try {
|
|
||||||
visit_tag(builder.Commit());
|
|
||||||
} catch (...) {
|
|
||||||
mpd_response_finish(connection);
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mpd_response_finish(connection))
|
if (!mpd_response_finish(connection))
|
||||||
ThrowError(connection);
|
ThrowError(connection);
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
if (connection != nullptr)
|
if (connection != nullptr)
|
||||||
mpd_search_cancel(connection);
|
mpd_search_cancel(connection);
|
||||||
|
@@ -312,12 +312,11 @@ SimpleDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
"No such directory");
|
"No such directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
std::map<std::string, std::set<std::string>>
|
||||||
SimpleDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
SimpleDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
TagType tag_type, TagType group) const
|
||||||
VisitTag visit_tag) const
|
|
||||||
{
|
{
|
||||||
::VisitUniqueTags(*this, selection, tag_type, group_mask, visit_tag);
|
return ::CollectUniqueTags(*this, selection, tag_type, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseStats
|
DatabaseStats
|
||||||
|
@@ -119,9 +119,9 @@ public:
|
|||||||
VisitSong visit_song,
|
VisitSong visit_song,
|
||||||
VisitPlaylist visit_playlist) const override;
|
VisitPlaylist visit_playlist) const override;
|
||||||
|
|
||||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
TagType tag_type,
|
||||||
VisitTag visit_tag) const override;
|
TagType group) const override;
|
||||||
|
|
||||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -47,10 +48,6 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
|||||||
unsigned &didreadp,
|
unsigned &didreadp,
|
||||||
unsigned &totalp) const
|
unsigned &totalp) const
|
||||||
{
|
{
|
||||||
// Create request
|
|
||||||
char ofbuf[100], cntbuf[100];
|
|
||||||
sprintf(ofbuf, "%u", offset);
|
|
||||||
sprintf(cntbuf, "%u", count);
|
|
||||||
// Some devices require an empty SortCriteria, else bad params
|
// Some devices require an empty SortCriteria, else bad params
|
||||||
IXML_Document *request =
|
IXML_Document *request =
|
||||||
MakeActionHelper("Browse", m_serviceType.c_str(),
|
MakeActionHelper("Browse", m_serviceType.c_str(),
|
||||||
@@ -58,8 +55,10 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
|||||||
"BrowseFlag", "BrowseDirectChildren",
|
"BrowseFlag", "BrowseDirectChildren",
|
||||||
"Filter", "*",
|
"Filter", "*",
|
||||||
"SortCriteria", "",
|
"SortCriteria", "",
|
||||||
"StartingIndex", ofbuf,
|
"StartingIndex",
|
||||||
"RequestedCount", cntbuf);
|
StringFormat<32>("%u", offset).c_str(),
|
||||||
|
"RequestedCount",
|
||||||
|
StringFormat<32>("%u", count).c_str());
|
||||||
if (request == nullptr)
|
if (request == nullptr)
|
||||||
throw std::runtime_error("UpnpMakeAction() failed");
|
throw std::runtime_error("UpnpMakeAction() failed");
|
||||||
|
|
||||||
@@ -112,15 +111,13 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
|
|||||||
unsigned offset = 0, total = -1, count;
|
unsigned offset = 0, total = -1, count;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
char ofbuf[100];
|
|
||||||
sprintf(ofbuf, "%d", offset);
|
|
||||||
|
|
||||||
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
|
UniqueIxmlDocument request(MakeActionHelper("Search", m_serviceType.c_str(),
|
||||||
"ContainerID", objectId,
|
"ContainerID", objectId,
|
||||||
"SearchCriteria", ss,
|
"SearchCriteria", ss,
|
||||||
"Filter", "*",
|
"Filter", "*",
|
||||||
"SortCriteria", "",
|
"SortCriteria", "",
|
||||||
"StartingIndex", ofbuf,
|
"StartingIndex",
|
||||||
|
StringFormat<32>("%u", offset).c_str(),
|
||||||
"RequestedCount", "0")); // Setting a value here gets twonky into fits
|
"RequestedCount", "0")); // Setting a value here gets twonky into fits
|
||||||
if (!request)
|
if (!request)
|
||||||
throw std::runtime_error("UpnpMakeAction() failed");
|
throw std::runtime_error("UpnpMakeAction() failed");
|
||||||
|
@@ -87,9 +87,9 @@ public:
|
|||||||
VisitSong visit_song,
|
VisitSong visit_song,
|
||||||
VisitPlaylist visit_playlist) const override;
|
VisitPlaylist visit_playlist) const override;
|
||||||
|
|
||||||
void VisitUniqueTags(const DatabaseSelection &selection,
|
std::map<std::string, std::set<std::string>> CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag_type, tag_mask_t group_mask,
|
TagType tag_type,
|
||||||
VisitTag visit_tag) const override;
|
TagType group) const override;
|
||||||
|
|
||||||
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
DatabaseStats GetStats(const DatabaseSelection &selection) const override;
|
||||||
|
|
||||||
@@ -603,17 +603,15 @@ UpnpDatabase::Visit(const DatabaseSelection &selection,
|
|||||||
visit_directory, visit_song, visit_playlist);
|
visit_directory, visit_song, visit_playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
std::map<std::string, std::set<std::string>>
|
||||||
UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
UpnpDatabase::CollectUniqueTags(const DatabaseSelection &selection,
|
||||||
TagType tag, gcc_unused tag_mask_t group_mask,
|
TagType tag, TagType group) const
|
||||||
VisitTag visit_tag) const
|
|
||||||
{
|
{
|
||||||
// TODO: use group_mask
|
(void)group; // TODO: use group
|
||||||
|
|
||||||
if (!visit_tag)
|
std::map<std::string, std::set<std::string>> result;
|
||||||
return;
|
auto &values = result[std::string()];
|
||||||
|
|
||||||
std::set<std::string> values;
|
|
||||||
for (auto& server : discovery->GetDirectories()) {
|
for (auto& server : discovery->GetDirectories()) {
|
||||||
const auto dirbuf = SearchSongs(server, rootid, selection);
|
const auto dirbuf = SearchSongs(server, rootid, selection);
|
||||||
|
|
||||||
@@ -633,11 +631,7 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& value : values) {
|
return result;
|
||||||
TagBuilder builder;
|
|
||||||
builder.AddItem(tag, value.c_str());
|
|
||||||
visit_tag(builder.Commit());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseStats
|
DatabaseStats
|
||||||
|
@@ -26,8 +26,8 @@
|
|||||||
#include "ExcludeList.hxx"
|
#include "ExcludeList.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "fs/NarrowPath.hxx"
|
#include "fs/NarrowPath.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "input/TextInputStream.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "util/StringUtil.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
@@ -35,35 +35,29 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
bool
|
inline void
|
||||||
ExcludeList::LoadFile(Path path_fs) noexcept
|
ExcludeList::ParseLine(char *line) noexcept
|
||||||
try {
|
{
|
||||||
#ifdef HAVE_CLASS_GLOB
|
char *p = Strip(line);
|
||||||
TextFile file(path_fs);
|
if (*p != 0 && *p != '#')
|
||||||
|
|
||||||
char *line;
|
|
||||||
while ((line = file.ReadLine()) != nullptr) {
|
|
||||||
char *p = strchr(line, '#');
|
|
||||||
if (p != nullptr)
|
|
||||||
*p = 0;
|
|
||||||
|
|
||||||
p = Strip(line);
|
|
||||||
if (*p != 0)
|
|
||||||
patterns.emplace_front(p);
|
patterns.emplace_front(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ExcludeList::Load(InputStreamPtr is)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_CLASS_GLOB
|
||||||
|
TextInputStream tis(std::move(is));
|
||||||
|
|
||||||
|
char *line;
|
||||||
|
while ((line = tis.ReadLine()) != nullptr)
|
||||||
|
ParseLine(line);
|
||||||
#else
|
#else
|
||||||
/* not implemented */
|
/* not implemented */
|
||||||
(void)path_fs;
|
(void)path_fs;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::system_error &e) {
|
|
||||||
if (!IsFileNotFound(e))
|
|
||||||
LogError(e);
|
|
||||||
return false;
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LogError(e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -28,6 +28,7 @@
|
|||||||
#include "check.h"
|
#include "check.h"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
#include "fs/Glob.hxx"
|
#include "fs/Glob.hxx"
|
||||||
|
#include "input/Ptr.hxx"
|
||||||
|
|
||||||
#ifdef HAVE_CLASS_GLOB
|
#ifdef HAVE_CLASS_GLOB
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
@@ -62,13 +63,16 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Loads and parses a .mpdignore file.
|
* Loads and parses a .mpdignore file.
|
||||||
*/
|
*/
|
||||||
bool LoadFile(Path path_fs) noexcept;
|
bool Load(InputStreamPtr is);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether one of the patterns in the .mpdignore file matches
|
* Checks whether one of the patterns in the .mpdignore file matches
|
||||||
* the specified file name.
|
* the specified file name.
|
||||||
*/
|
*/
|
||||||
bool Check(Path name_fs) const noexcept;
|
bool Check(Path name_fs) const noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ParseLine(char *line) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@@ -36,6 +36,9 @@
|
|||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "storage/FileInfo.hxx"
|
#include "storage/FileInfo.hxx"
|
||||||
|
#include "input/InputStream.hxx"
|
||||||
|
#include "input/Error.hxx"
|
||||||
|
#include "thread/Cond.hxx"
|
||||||
#include "util/Alloc.hxx"
|
#include "util/Alloc.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
@@ -345,11 +348,16 @@ UpdateWalk::UpdateDirectory(Directory &directory,
|
|||||||
|
|
||||||
ExcludeList child_exclude_list(exclude_list);
|
ExcludeList child_exclude_list(exclude_list);
|
||||||
|
|
||||||
{
|
try {
|
||||||
const auto exclude_path_fs =
|
Mutex mutex;
|
||||||
storage.MapChildFS(directory.GetPath(), ".mpdignore");
|
Cond cond;
|
||||||
if (!exclude_path_fs.IsNull())
|
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
|
||||||
child_exclude_list.LoadFile(exclude_path_fs);
|
".mpdignore").c_str(),
|
||||||
|
mutex, cond);
|
||||||
|
child_exclude_list.Load(std::move(is));
|
||||||
|
} catch (...) {
|
||||||
|
if (!IsFileNotFound(std::current_exception()))
|
||||||
|
LogError(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!child_exclude_list.IsEmpty())
|
if (!child_exclude_list.IsEmpty())
|
||||||
|
@@ -64,7 +64,12 @@ fluidsynth_level_to_mpd(enum fluid_log_level level)
|
|||||||
* logging library.
|
* logging library.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data)
|
fluidsynth_mpd_log_function(int level,
|
||||||
|
#if FLUIDSYNTH_VERSION_MAJOR >= 2
|
||||||
|
const
|
||||||
|
#endif
|
||||||
|
char *message,
|
||||||
|
void *)
|
||||||
{
|
{
|
||||||
Log(fluidsynth_domain,
|
Log(fluidsynth_domain,
|
||||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/FormatString.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#define SUBTUNE_PREFIX "tune_"
|
#define SUBTUNE_PREFIX "tune_"
|
||||||
|
|
||||||
@@ -191,18 +190,15 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
|||||||
tag_handler_invoke_duration(handler, handler_ctx,
|
tag_handler_invoke_duration(handler, handler_ctx,
|
||||||
SongTime::FromMS(info.play_length));
|
SongTime::FromMS(info.play_length));
|
||||||
|
|
||||||
if (track_count > 1) {
|
if (track_count > 1)
|
||||||
char track[16];
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
|
||||||
sprintf(track, "%u", song_num + 1);
|
StringFormat<16>("%u", song_num + 1));
|
||||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.song != nullptr) {
|
if (info.song != nullptr) {
|
||||||
if (track_count > 1) {
|
if (track_count > 1) {
|
||||||
/* start numbering subtunes from 1 */
|
/* start numbering subtunes from 1 */
|
||||||
char tag_title[1024];
|
const auto tag_title =
|
||||||
snprintf(tag_title, sizeof(tag_title),
|
StringFormat<1024>("%s (%u/%d)",
|
||||||
"%s (%u/%d)",
|
|
||||||
info.song, song_num + 1,
|
info.song, song_num + 1,
|
||||||
track_count);
|
track_count);
|
||||||
tag_handler_invoke_tag(handler, handler_ctx,
|
tag_handler_invoke_tag(handler, handler_ctx,
|
||||||
@@ -297,9 +293,9 @@ gme_container_scan(Path path_fs)
|
|||||||
ScanMusicEmu(emu, i,
|
ScanMusicEmu(emu, i,
|
||||||
add_tag_handler, &tag_builder);
|
add_tag_handler, &tag_builder);
|
||||||
|
|
||||||
char track_name[64];
|
const auto track_name =
|
||||||
snprintf(track_name, sizeof(track_name),
|
StringFormat<64>(SUBTUNE_PREFIX "%03u.%s", i+1,
|
||||||
SUBTUNE_PREFIX "%03u.%s", i+1, subtune_suffix);
|
subtune_suffix);
|
||||||
tail = list.emplace_after(tail, track_name,
|
tail = list.emplace_after(tail, track_name,
|
||||||
tag_builder.Commit());
|
tag_builder.Commit());
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "util/Macros.hxx"
|
#include "util/Macros.hxx"
|
||||||
#include "util/FormatString.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "system/ByteOrder.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -413,9 +413,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
|
|||||||
title = "";
|
title = "";
|
||||||
|
|
||||||
if (n_tracks > 1) {
|
if (n_tracks > 1) {
|
||||||
char tag_title[1024];
|
const auto tag_title =
|
||||||
snprintf(tag_title, sizeof(tag_title),
|
StringFormat<1024>("%s (%u/%u)",
|
||||||
"%s (%u/%u)",
|
|
||||||
title, track, n_tracks);
|
title, track, n_tracks);
|
||||||
tag_handler_invoke_tag(handler, handler_ctx,
|
tag_handler_invoke_tag(handler, handler_ctx,
|
||||||
TAG_TITLE, tag_title);
|
TAG_TITLE, tag_title);
|
||||||
@@ -435,9 +434,8 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
|
|||||||
date);
|
date);
|
||||||
|
|
||||||
/* track */
|
/* track */
|
||||||
char track_buffer[16];
|
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK,
|
||||||
sprintf(track_buffer, "%d", track);
|
StringFormat<16>("%u", track));
|
||||||
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track_buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "FileOutputStream.hxx"
|
#include "FileOutputStream.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
FileOutputStream::FileOutputStream(Path _path, Mode _mode)
|
FileOutputStream::FileOutputStream(Path _path, Mode _mode)
|
||||||
:path(_path), mode(_mode)
|
:path(_path), mode(_mode)
|
||||||
@@ -212,10 +213,9 @@ FileOutputStream::Commit()
|
|||||||
unlink(GetPath().c_str());
|
unlink(GetPath().c_str());
|
||||||
|
|
||||||
/* hard-link the temporary file to the final path */
|
/* hard-link the temporary file to the final path */
|
||||||
char fd_path[64];
|
if (linkat(AT_FDCWD,
|
||||||
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d",
|
StringFormat<64>("/proc/self/fd/%d", fd.Get()),
|
||||||
fd.Get());
|
AT_FDCWD, path.c_str(),
|
||||||
if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(),
|
|
||||||
AT_SYMLINK_FOLLOW) < 0)
|
AT_SYMLINK_FOLLOW) < 0)
|
||||||
throw FormatErrno("Failed to commit %s",
|
throw FormatErrno("Failed to commit %s",
|
||||||
path.c_str());
|
path.c_str());
|
||||||
|
@@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "AsyncInputStream.hxx"
|
#include "AsyncInputStream.hxx"
|
||||||
#include "Domain.hxx"
|
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
#include "IOThread.hxx"
|
#include "IOThread.hxx"
|
||||||
|
53
src/input/Error.cxx
Normal file
53
src/input/Error.cxx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "Error.hxx"
|
||||||
|
#include "system/Error.hxx"
|
||||||
|
|
||||||
|
#ifdef ENABLE_CURL
|
||||||
|
#include "lib/curl/Error.hxx"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_NFS
|
||||||
|
#include "lib/nfs/Error.hxx"
|
||||||
|
#include <nfsc/libnfs-raw-nfs.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool
|
||||||
|
IsFileNotFound(std::exception_ptr ep)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(ep);
|
||||||
|
} catch (const std::system_error &e) {
|
||||||
|
return IsFileNotFound(e);
|
||||||
|
#ifdef ENABLE_CURL
|
||||||
|
} catch (const HttpStatusError &e) {
|
||||||
|
return e.GetStatus() == 404;
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_NFS
|
||||||
|
} catch (const NfsClientError &e) {
|
||||||
|
return e.GetCode() == NFS3ERR_NOENT;
|
||||||
|
#endif
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
45
src/input/Error.hxx
Normal file
45
src/input/Error.hxx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INPUT_ERROR_HXX
|
||||||
|
#define INPUT_ERROR_HXX
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Was this exception thrown because the requested file does not
|
||||||
|
* exist? This function attempts to recognize exceptions thrown by
|
||||||
|
* various input plugins.
|
||||||
|
*/
|
||||||
|
#ifndef __clang__
|
||||||
|
/* the "pure" attribute must be disabled because it triggers a clang
|
||||||
|
bug, wrongfully leading to std::terminate() even though the
|
||||||
|
function catches all exceptions thrown by std::rethrow_exception();
|
||||||
|
this can be reproduced with clang 7 from Android NDK r18b and on
|
||||||
|
clang 6 on FreeBSD
|
||||||
|
(https://github.com/MusicPlayerDaemon/MPD/issues/373) */
|
||||||
|
gcc_pure
|
||||||
|
#endif
|
||||||
|
bool
|
||||||
|
IsFileNotFound(std::exception_ptr e);
|
||||||
|
|
||||||
|
#endif
|
@@ -20,7 +20,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "InputStream.hxx"
|
#include "InputStream.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -77,8 +77,8 @@ gcc_pure
|
|||||||
static bool
|
static bool
|
||||||
ExpensiveSeeking(const char *uri) noexcept
|
ExpensiveSeeking(const char *uri) noexcept
|
||||||
{
|
{
|
||||||
return StringStartsWith(uri, "http://") ||
|
return StringStartsWithCaseASCII(uri, "http://") ||
|
||||||
StringStartsWith(uri, "https://");
|
StringStartsWithCaseASCII(uri, "https://");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -29,4 +29,10 @@
|
|||||||
*/
|
*/
|
||||||
typedef uint64_t offset_type;
|
typedef uint64_t offset_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To format an offset_type with printf(). To use this, include
|
||||||
|
* <cinttypes>.
|
||||||
|
*/
|
||||||
|
#define PRIoffset PRIu64
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -22,7 +22,6 @@
|
|||||||
#include "Registry.hxx"
|
#include "Registry.hxx"
|
||||||
#include "InputPlugin.hxx"
|
#include "InputPlugin.hxx"
|
||||||
#include "LocalOpen.hxx"
|
#include "LocalOpen.hxx"
|
||||||
#include "Domain.hxx"
|
|
||||||
#include "plugins/RewindInputPlugin.hxx"
|
#include "plugins/RewindInputPlugin.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/ReusableArray.hxx"
|
#include "util/ReusableArray.hxx"
|
||||||
|
#include "util/ASCII.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "event/MultiSocketMonitor.hxx"
|
#include "event/MultiSocketMonitor.hxx"
|
||||||
#include "event/DeferredMonitor.hxx"
|
#include "event/DeferredMonitor.hxx"
|
||||||
@@ -147,7 +147,7 @@ private:
|
|||||||
inline InputStream *
|
inline InputStream *
|
||||||
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
|
AlsaInputStream::Create(const char *uri, Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
const char *device = StringAfterPrefix(uri, "alsa://");
|
const char *device = StringAfterPrefixCaseASCII(uri, "alsa://");
|
||||||
if (device == nullptr)
|
if (device == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
#include "../InputStream.hxx"
|
#include "../InputStream.hxx"
|
||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "util/StringUtil.hxx"
|
#include "util/StringUtil.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "system/ByteOrder.hxx"
|
||||||
@@ -128,7 +128,7 @@ struct cdio_uri {
|
|||||||
static bool
|
static bool
|
||||||
parse_cdio_uri(struct cdio_uri *dest, const char *src)
|
parse_cdio_uri(struct cdio_uri *dest, const char *src)
|
||||||
{
|
{
|
||||||
if (!StringStartsWith(src, "cdda://"))
|
if (!StringStartsWithCaseASCII(src, "cdda://"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
src += 7;
|
src += 7;
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "CurlInputPlugin.hxx"
|
#include "CurlInputPlugin.hxx"
|
||||||
|
#include "lib/curl/Error.hxx"
|
||||||
#include "lib/curl/Easy.hxx"
|
#include "lib/curl/Easy.hxx"
|
||||||
#include "lib/curl/Global.hxx"
|
#include "lib/curl/Global.hxx"
|
||||||
#include "lib/curl/Request.hxx"
|
#include "lib/curl/Request.hxx"
|
||||||
@@ -34,12 +35,15 @@
|
|||||||
#include "IOThread.hxx"
|
#include "IOThread.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/StringUtil.hxx"
|
#include "util/StringUtil.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/NumberParser.hxx"
|
#include "util/NumberParser.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "PluginUnavailable.hxx"
|
#include "PluginUnavailable.hxx"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -185,7 +189,9 @@ CurlInputStream::OnHeaders(unsigned status,
|
|||||||
assert(!postponed_exception);
|
assert(!postponed_exception);
|
||||||
|
|
||||||
if (status < 200 || status >= 300)
|
if (status < 200 || status >= 300)
|
||||||
throw FormatRuntimeError("got HTTP status %ld", status);
|
throw HttpStatusError(status,
|
||||||
|
StringFormat<40>("got HTTP status %u",
|
||||||
|
status).c_str());
|
||||||
|
|
||||||
const std::lock_guard<Mutex> protect(mutex);
|
const std::lock_guard<Mutex> protect(mutex);
|
||||||
|
|
||||||
@@ -371,13 +377,10 @@ CurlInputStream::InitEasy()
|
|||||||
if (proxy_port > 0)
|
if (proxy_port > 0)
|
||||||
request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port);
|
request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port);
|
||||||
|
|
||||||
if (proxy_user != nullptr && proxy_password != nullptr) {
|
if (proxy_user != nullptr && proxy_password != nullptr)
|
||||||
char proxy_auth_str[1024];
|
request->SetOption(CURLOPT_PROXYUSERPWD,
|
||||||
snprintf(proxy_auth_str, sizeof(proxy_auth_str),
|
StringFormat<1024>("%s:%s", proxy_user,
|
||||||
"%s:%s",
|
proxy_password).c_str());
|
||||||
proxy_user, proxy_password);
|
|
||||||
request->SetOption(CURLOPT_PROXYUSERPWD, proxy_auth_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
||||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
||||||
@@ -414,16 +417,10 @@ CurlInputStream::SeekInternal(offset_type new_offset)
|
|||||||
|
|
||||||
/* send the "Range" header */
|
/* send the "Range" header */
|
||||||
|
|
||||||
if (offset > 0) {
|
if (offset > 0)
|
||||||
char range[32];
|
request->SetOption(CURLOPT_RANGE,
|
||||||
#ifdef _WIN32
|
StringFormat<40>("%" PRIoffset "-",
|
||||||
// TODO: what can we use on Windows to format 64 bit?
|
offset).c_str());
|
||||||
sprintf(range, "%lu-", (long)offset);
|
|
||||||
#else
|
|
||||||
sprintf(range, "%llu-", (unsigned long long)offset);
|
|
||||||
#endif
|
|
||||||
request->SetOption(CURLOPT_RANGE, range);
|
|
||||||
}
|
|
||||||
|
|
||||||
StartRequest();
|
StartRequest();
|
||||||
}
|
}
|
||||||
@@ -461,8 +458,8 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond)
|
|||||||
static InputStream *
|
static InputStream *
|
||||||
input_curl_open(const char *url, Mutex &mutex, Cond &cond)
|
input_curl_open(const char *url, Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
if (strncmp(url, "http://", 7) != 0 &&
|
if (!StringStartsWithCaseASCII(url, "http://") &&
|
||||||
strncmp(url, "https://", 8) != 0)
|
!StringStartsWithCaseASCII(url, "https://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return CurlInputStream::Open(url, mutex, cond);
|
return CurlInputStream::Open(url, mutex, cond);
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
#include "../InputStream.hxx"
|
#include "../InputStream.hxx"
|
||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "PluginUnavailable.hxx"
|
#include "PluginUnavailable.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavformat/avio.h>
|
#include <libavformat/avio.h>
|
||||||
@@ -85,12 +85,12 @@ static InputStream *
|
|||||||
input_ffmpeg_open(const char *uri,
|
input_ffmpeg_open(const char *uri,
|
||||||
Mutex &mutex, Cond &cond)
|
Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
if (!StringStartsWith(uri, "gopher://") &&
|
if (!StringStartsWithCaseASCII(uri, "gopher://") &&
|
||||||
!StringStartsWith(uri, "rtp://") &&
|
!StringStartsWithCaseASCII(uri, "rtp://") &&
|
||||||
!StringStartsWith(uri, "rtsp://") &&
|
!StringStartsWithCaseASCII(uri, "rtsp://") &&
|
||||||
!StringStartsWith(uri, "rtmp://") &&
|
!StringStartsWithCaseASCII(uri, "rtmp://") &&
|
||||||
!StringStartsWith(uri, "rtmpt://") &&
|
!StringStartsWithCaseASCII(uri, "rtmpt://") &&
|
||||||
!StringStartsWith(uri, "rtmps://"))
|
!StringStartsWithCaseASCII(uri, "rtmps://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
AVIOContext *h;
|
AVIOContext *h;
|
||||||
|
@@ -65,12 +65,9 @@ OpenFileInputStream(Path path,
|
|||||||
throw FormatRuntimeError("Not a regular file: %s",
|
throw FormatRuntimeError("Not a regular file: %s",
|
||||||
path.c_str());
|
path.c_str());
|
||||||
|
|
||||||
#if !defined(__BIONIC__) || __ANDROID_API__ >= 21
|
|
||||||
/* posix_fadvise() requires Android API 21 */
|
|
||||||
#ifdef POSIX_FADV_SEQUENTIAL
|
#ifdef POSIX_FADV_SEQUENTIAL
|
||||||
posix_fadvise(reader.GetFD().Get(), (off_t)0, info.GetSize(),
|
posix_fadvise(reader.GetFD().Get(), (off_t)0, info.GetSize(),
|
||||||
POSIX_FADV_SEQUENTIAL);
|
POSIX_FADV_SEQUENTIAL);
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return InputStreamPtr(new FileInputStream(path.ToUTF8().c_str(),
|
return InputStreamPtr(new FileInputStream(path.ToUTF8().c_str(),
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
#include "input/ThreadInputStream.hxx"
|
#include "input/ThreadInputStream.hxx"
|
||||||
#include "input/InputPlugin.hxx"
|
#include "input/InputPlugin.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#include <libmms/mmsx.h>
|
#include <libmms/mmsx.h>
|
||||||
|
|
||||||
@@ -74,10 +74,10 @@ static InputStream *
|
|||||||
input_mms_open(const char *url,
|
input_mms_open(const char *url,
|
||||||
Mutex &mutex, Cond &cond)
|
Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
if (!StringStartsWith(url, "mms://") &&
|
if (!StringStartsWithCaseASCII(url, "mms://") &&
|
||||||
!StringStartsWith(url, "mmsh://") &&
|
!StringStartsWithCaseASCII(url, "mmsh://") &&
|
||||||
!StringStartsWith(url, "mmst://") &&
|
!StringStartsWithCaseASCII(url, "mmst://") &&
|
||||||
!StringStartsWith(url, "mmsu://"))
|
!StringStartsWithCaseASCII(url, "mmsu://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
auto m = new MmsInputStream(url, mutex, cond);
|
auto m = new MmsInputStream(url, mutex, cond);
|
||||||
|
@@ -23,9 +23,7 @@
|
|||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "lib/nfs/Glue.hxx"
|
#include "lib/nfs/Glue.hxx"
|
||||||
#include "lib/nfs/FileReader.hxx"
|
#include "lib/nfs/FileReader.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not buffer more than this number of bytes. It should be a
|
* Do not buffer more than this number of bytes. It should be a
|
||||||
@@ -219,7 +217,7 @@ static InputStream *
|
|||||||
input_nfs_open(const char *uri,
|
input_nfs_open(const char *uri,
|
||||||
Mutex &mutex, Cond &cond)
|
Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
if (!StringStartsWith(uri, "nfs://"))
|
if (!StringStartsWithCaseASCII(uri, "nfs://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
NfsInputStream *is = new NfsInputStream(uri, mutex, cond);
|
NfsInputStream *is = new NfsInputStream(uri, mutex, cond);
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
#include "../InputPlugin.hxx"
|
#include "../InputPlugin.hxx"
|
||||||
#include "PluginUnavailable.hxx"
|
#include "PluginUnavailable.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#include <libsmbclient.h>
|
#include <libsmbclient.h>
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ static InputStream *
|
|||||||
input_smbclient_open(const char *uri,
|
input_smbclient_open(const char *uri,
|
||||||
Mutex &mutex, Cond &cond)
|
Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
if (!StringStartsWith(uri, "smb://"))
|
if (!StringStartsWithCaseASCII(uri, "smb://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const std::lock_guard<Mutex> protect(smbclient_mutex);
|
const std::lock_guard<Mutex> protect(smbclient_mutex);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright (C) 2010-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
#define JAVA_CLASS_HXX
|
#define JAVA_CLASS_HXX
|
||||||
|
|
||||||
#include "Ref.hxx"
|
#include "Ref.hxx"
|
||||||
|
#include "Exception.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ namespace Java {
|
|||||||
/**
|
/**
|
||||||
* Wrapper for a local "jclass" reference.
|
* Wrapper for a local "jclass" reference.
|
||||||
*/
|
*/
|
||||||
class Class : public Java::LocalRef<jclass> {
|
class Class : public LocalRef<jclass> {
|
||||||
public:
|
public:
|
||||||
Class(JNIEnv *env, jclass cls)
|
Class(JNIEnv *env, jclass cls)
|
||||||
:LocalRef<jclass>(env, cls) {}
|
:LocalRef<jclass>(env, cls) {}
|
||||||
@@ -68,10 +69,8 @@ namespace Java {
|
|||||||
assert(name != nullptr);
|
assert(name != nullptr);
|
||||||
|
|
||||||
jclass cls = env->FindClass(name);
|
jclass cls = env->FindClass(name);
|
||||||
if (cls == nullptr) {
|
if (DiscardException(env))
|
||||||
env->ExceptionClear();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
Set(env, cls);
|
Set(env, cls);
|
||||||
env->DeleteLocalRef(cls);
|
env->DeleteLocalRef(cls);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2010-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010-2011 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2010-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
namespace Java {
|
namespace Java {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright (C) 2016-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -86,6 +86,10 @@ public:
|
|||||||
if (code != CURLE_OK)
|
if (code != CURLE_OK)
|
||||||
throw std::runtime_error(curl_easy_strerror(code));
|
throw std::runtime_error(curl_easy_strerror(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *Escape(const char *string, int length=0) const noexcept {
|
||||||
|
return curl_easy_escape(handle, string, length);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2017 The Music Player Daemon Project
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,8 +17,26 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#ifndef CURL_ERROR_HXX
|
||||||
#include "Domain.hxx"
|
#define CURL_ERROR_HXX
|
||||||
#include "util/Domain.hxx"
|
|
||||||
|
|
||||||
const Domain input_domain("input");
|
#include <stdexcept>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an unsuccessful status was received from the HTTP
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
class HttpStatusError : public std::runtime_error {
|
||||||
|
unsigned status;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<typename M>
|
||||||
|
explicit HttpStatusError(unsigned _status, M &&_msg) noexcept
|
||||||
|
:std::runtime_error(std::forward<M>(_msg)), status(_status) {}
|
||||||
|
|
||||||
|
unsigned GetStatus() const noexcept {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@@ -25,6 +25,7 @@
|
|||||||
#include "Domain.hxx"
|
#include "Domain.hxx"
|
||||||
#include "LogV.hxx"
|
#include "LogV.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavutil/log.h>
|
#include <libavutil/log.h>
|
||||||
@@ -57,9 +58,10 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
|
|||||||
cls = *(const AVClass *const*)ptr;
|
cls = *(const AVClass *const*)ptr;
|
||||||
|
|
||||||
if (cls != nullptr) {
|
if (cls != nullptr) {
|
||||||
char domain[64];
|
const auto domain =
|
||||||
snprintf(domain, sizeof(domain), "%s/%s",
|
StringFormat<64>("%s/%s",
|
||||||
ffmpeg_domain.GetName(), cls->item_name(ptr));
|
ffmpeg_domain.GetName(),
|
||||||
|
cls->item_name(ptr));
|
||||||
const Domain d(domain);
|
const Domain d(domain);
|
||||||
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "Connection.hxx"
|
#include "Connection.hxx"
|
||||||
|
#include "Error.hxx"
|
||||||
#include "Lease.hxx"
|
#include "Lease.hxx"
|
||||||
#include "Callback.hxx"
|
#include "Callback.hxx"
|
||||||
#include "event/Loop.hxx"
|
#include "event/Loop.hxx"
|
||||||
@@ -46,9 +47,9 @@ NfsConnection::CancellableCallback::Stat(nfs_context *ctx,
|
|||||||
{
|
{
|
||||||
assert(connection.GetEventLoop().IsInside());
|
assert(connection.GetEventLoop().IsInside());
|
||||||
|
|
||||||
int result = nfs_stat_async(ctx, path, Callback, this);
|
int result = nfs_stat64_async(ctx, path, Callback, this);
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
throw FormatRuntimeError("nfs_stat_async() failed: %s",
|
throw FormatRuntimeError("nfs_stat64_async() failed: %s",
|
||||||
nfs_get_error(ctx));
|
nfs_get_error(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +140,7 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
|
|||||||
if (err >= 0)
|
if (err >= 0)
|
||||||
cb.OnNfsCallback((unsigned)err, data);
|
cb.OnNfsCallback((unsigned)err, data);
|
||||||
else
|
else
|
||||||
cb.OnNfsError(std::make_exception_ptr(std::runtime_error((const char *)data)));
|
cb.OnNfsError(std::make_exception_ptr(NfsClientError(-err, (const char *)data)));
|
||||||
} else {
|
} else {
|
||||||
if (open) {
|
if (open) {
|
||||||
/* a nfs_open_async() call was cancelled - to
|
/* a nfs_open_async() call was cancelled - to
|
||||||
|
76
src/lib/nfs/Error.cxx
Normal file
76
src/lib/nfs/Error.cxx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2007-2017 Content Management AG
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Error.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <nfsc/libnfs.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static StringBuffer<256>
|
||||||
|
FormatNfsClientError(struct nfs_context *nfs, const char *msg) noexcept
|
||||||
|
{
|
||||||
|
assert(msg != nullptr);
|
||||||
|
|
||||||
|
const char *msg2 = nfs_get_error(nfs);
|
||||||
|
return StringFormat<256>("%s: %s", msg, msg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
NfsClientError::NfsClientError(struct nfs_context *nfs, const char *msg) noexcept
|
||||||
|
:std::runtime_error(FormatNfsClientError(nfs, msg).c_str()),
|
||||||
|
code(0) {}
|
||||||
|
|
||||||
|
static StringBuffer<256>
|
||||||
|
FormatNfsClientError(int err, struct nfs_context *nfs, void *data,
|
||||||
|
const char *msg) noexcept
|
||||||
|
{
|
||||||
|
assert(msg != nullptr);
|
||||||
|
assert(err < 0);
|
||||||
|
|
||||||
|
const char *msg2 = (const char *)data;
|
||||||
|
if (data == nullptr || *(const char *)data == 0) {
|
||||||
|
msg2 = nfs_get_error(nfs);
|
||||||
|
if (msg2 == nullptr)
|
||||||
|
msg2 = strerror(-err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringFormat<256>("%s: %s", msg, msg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
NfsClientError::NfsClientError(int err, struct nfs_context *nfs, void *data,
|
||||||
|
const char *msg) noexcept
|
||||||
|
:std::runtime_error(FormatNfsClientError(err, nfs, data, msg).c_str()),
|
||||||
|
code(-err) {}
|
58
src/lib/nfs/Error.hxx
Normal file
58
src/lib/nfs/Error.hxx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2007-2017 Content Management AG
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* author: Max Kellermann <mk@cm4all.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NFS_ERROR_HXX
|
||||||
|
#define NFS_ERROR_HXX
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
class NfsClientError : public std::runtime_error {
|
||||||
|
int code;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NfsClientError(const char *_msg) noexcept
|
||||||
|
:std::runtime_error(_msg), code(0) {}
|
||||||
|
|
||||||
|
NfsClientError(int _code, const char *_msg) noexcept
|
||||||
|
:std::runtime_error(_msg), code(_code) {}
|
||||||
|
|
||||||
|
NfsClientError(struct nfs_context *nfs, const char *msg) noexcept;
|
||||||
|
|
||||||
|
NfsClientError(int err, struct nfs_context *nfs, void *data,
|
||||||
|
const char *msg) noexcept;
|
||||||
|
|
||||||
|
int GetCode() const noexcept {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@@ -24,7 +24,7 @@
|
|||||||
#include "Connection.hxx"
|
#include "Connection.hxx"
|
||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
#include "IOThread.hxx"
|
#include "IOThread.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ NfsFileReader::Open(const char *uri)
|
|||||||
{
|
{
|
||||||
assert(state == State::INITIAL);
|
assert(state == State::INITIAL);
|
||||||
|
|
||||||
if (!StringStartsWith(uri, "nfs://"))
|
if (!StringStartsWithCaseASCII(uri, "nfs://"))
|
||||||
throw std::runtime_error("Malformed nfs:// URI");
|
throw std::runtime_error("Malformed nfs:// URI");
|
||||||
|
|
||||||
uri += 6;
|
uri += 6;
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/ASCII.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -97,7 +97,7 @@ uri_supported_scheme(const char *uri) noexcept
|
|||||||
assert(uri_has_scheme(uri));
|
assert(uri_has_scheme(uri));
|
||||||
|
|
||||||
while (*urlPrefixes) {
|
while (*urlPrefixes) {
|
||||||
if (StringStartsWith(uri, *urlPrefixes))
|
if (StringStartsWithCaseASCII(uri, *urlPrefixes))
|
||||||
return true;
|
return true;
|
||||||
urlPrefixes++;
|
urlPrefixes++;
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,7 @@
|
|||||||
#include "config/ConfigGlobal.hxx"
|
#include "config/ConfigGlobal.hxx"
|
||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
@@ -36,6 +36,8 @@
|
|||||||
#include <roaraudio.h>
|
#include <roaraudio.h>
|
||||||
#undef new
|
#undef new
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
class RoarOutput {
|
class RoarOutput {
|
||||||
friend struct AudioOutputWrapper<RoarOutput>;
|
friend struct AudioOutputWrapper<RoarOutput>;
|
||||||
|
|
||||||
|
@@ -122,15 +122,6 @@ HttpdClient::HandleLine(const char *line)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
|
|
||||||
/* Send as dlna */
|
|
||||||
dlna_streaming_requested = true;
|
|
||||||
/* metadata is not supported by dlna streaming, so disable it */
|
|
||||||
metadata_supported = false;
|
|
||||||
metadata_requested = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expect more request headers */
|
/* expect more request headers */
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -148,22 +139,7 @@ HttpdClient::SendResponse()
|
|||||||
|
|
||||||
assert(state == RESPONSE);
|
assert(state == RESPONSE);
|
||||||
|
|
||||||
if (dlna_streaming_requested) {
|
if (metadata_requested) {
|
||||||
snprintf(buffer, sizeof(buffer),
|
|
||||||
"HTTP/1.1 206 OK\r\n"
|
|
||||||
"Content-Type: %s\r\n"
|
|
||||||
"Content-Length: 10000\r\n"
|
|
||||||
"Content-RangeX: 0-1000000/1000000\r\n"
|
|
||||||
"transferMode.dlna.org: Streaming\r\n"
|
|
||||||
"Accept-Ranges: bytes\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
|
|
||||||
"contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
|
|
||||||
"\r\n",
|
|
||||||
httpd.content_type);
|
|
||||||
response = buffer;
|
|
||||||
|
|
||||||
} else if (metadata_requested) {
|
|
||||||
allocated =
|
allocated =
|
||||||
icy_server_metadata_header(httpd.name, httpd.genre,
|
icy_server_metadata_header(httpd.name, httpd.genre,
|
||||||
httpd.website,
|
httpd.website,
|
||||||
@@ -202,7 +178,6 @@ HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
|
|||||||
state(REQUEST),
|
state(REQUEST),
|
||||||
queue_size(0),
|
queue_size(0),
|
||||||
head_method(false),
|
head_method(false),
|
||||||
dlna_streaming_requested(false),
|
|
||||||
metadata_supported(_metadata_supported),
|
metadata_supported(_metadata_supported),
|
||||||
metadata_requested(false), metadata_sent(true),
|
metadata_requested(false), metadata_sent(true),
|
||||||
metaint(8192), /*TODO: just a std value */
|
metaint(8192), /*TODO: just a std value */
|
||||||
|
@@ -82,11 +82,6 @@ class HttpdClient final
|
|||||||
*/
|
*/
|
||||||
bool head_method;
|
bool head_method;
|
||||||
|
|
||||||
/**
|
|
||||||
* If DLNA streaming was an option.
|
|
||||||
*/
|
|
||||||
bool dlna_streaming_requested;
|
|
||||||
|
|
||||||
/* ICY */
|
/* ICY */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
#include "SoxrResampler.hxx"
|
#include "SoxrResampler.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
enum class SelectedResampler {
|
enum class SelectedResampler {
|
||||||
|
@@ -32,6 +32,8 @@
|
|||||||
#include "PcmDop.hxx"
|
#include "PcmDop.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
void
|
void
|
||||||
PcmExport::Open(SampleFormat sample_format, unsigned _channels,
|
PcmExport::Open(SampleFormat sample_format, unsigned _channels,
|
||||||
Params params)
|
Params params)
|
||||||
|
@@ -202,6 +202,7 @@ CueParser::Feed2(char *p) noexcept
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (strcmp(type, "WAVE") != 0 &&
|
if (strcmp(type, "WAVE") != 0 &&
|
||||||
|
strcmp(type, "FLAC") != 0 && /* non-standard */
|
||||||
strcmp(type, "MP3") != 0 &&
|
strcmp(type, "MP3") != 0 &&
|
||||||
strcmp(type, "AIFF") != 0) {
|
strcmp(type, "AIFF") != 0) {
|
||||||
state = IGNORE_FILE;
|
state = IGNORE_FILE;
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "config/Block.hxx"
|
#include "config/Block.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "tag/TagBuilder.hxx"
|
#include "tag/TagBuilder.hxx"
|
||||||
|
#include "util/ASCII.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/Alloc.hxx"
|
#include "util/Alloc.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@@ -68,7 +69,7 @@ soundcloud_resolve(const char* uri)
|
|||||||
{
|
{
|
||||||
char *u, *ru;
|
char *u, *ru;
|
||||||
|
|
||||||
if (StringStartsWith(uri, "https://")) {
|
if (StringStartsWithCaseASCII(uri, "https://")) {
|
||||||
u = xstrdup(uri);
|
u = xstrdup(uri);
|
||||||
} else if (StringStartsWith(uri, "soundcloud.com")) {
|
} else if (StringStartsWith(uri, "soundcloud.com")) {
|
||||||
u = xstrcatdup("https://", uri);
|
u = xstrcatdup("https://", uri);
|
||||||
@@ -273,7 +274,7 @@ try {
|
|||||||
static SongEnumerator *
|
static SongEnumerator *
|
||||||
soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
|
soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
|
||||||
{
|
{
|
||||||
assert(strncmp(uri, "soundcloud://", 13) == 0);
|
assert(StringEqualsCaseASCII(uri, "soundcloud://", 13));
|
||||||
uri += 13;
|
uri += 13;
|
||||||
|
|
||||||
char *u = nullptr;
|
char *u = nullptr;
|
||||||
|
@@ -20,9 +20,9 @@
|
|||||||
#ifndef MPD_ACK_H
|
#ifndef MPD_ACK_H
|
||||||
#define MPD_ACK_H
|
#define MPD_ACK_H
|
||||||
|
|
||||||
#include <stdexcept>
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdexcept>
|
||||||
|
|
||||||
class Domain;
|
class Domain;
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ template<typename... Args>
|
|||||||
static inline ProtocolError
|
static inline ProtocolError
|
||||||
FormatProtocolError(enum ack code, const char *fmt, Args&&... args) noexcept
|
FormatProtocolError(enum ack code, const char *fmt, Args&&... args) noexcept
|
||||||
{
|
{
|
||||||
char buffer[256];
|
return ProtocolError(code,
|
||||||
snprintf(buffer, sizeof(buffer), fmt, std::forward<Args>(args)...);
|
StringFormat<256>(fmt,
|
||||||
return ProtocolError(code, buffer);
|
std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -33,8 +33,11 @@
|
|||||||
#include "event/DeferredMonitor.hxx"
|
#include "event/DeferredMonitor.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
|
#include "util/ASCII.hxx"
|
||||||
|
#include "util/IterableSplitString.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/TimeParser.hxx"
|
#include "util/TimeParser.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
|
|
||||||
@@ -77,9 +80,18 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
|
|||||||
if (StringIsEmpty(uri_utf8))
|
if (StringIsEmpty(uri_utf8))
|
||||||
return base;
|
return base;
|
||||||
|
|
||||||
// TODO: escape the given URI
|
CurlEasy easy;
|
||||||
|
std::string path_esc;
|
||||||
|
|
||||||
return PathTraitsUTF8::Build(base.c_str(), uri_utf8);
|
for (auto elt: IterableSplitString(uri_utf8, '/')) {
|
||||||
|
char *elt_esc = easy.Escape(elt.data, elt.size);
|
||||||
|
if (!path_esc.empty())
|
||||||
|
path_esc.push_back('/');
|
||||||
|
path_esc += elt_esc;
|
||||||
|
curl_free(elt_esc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
@@ -296,9 +308,7 @@ public:
|
|||||||
{
|
{
|
||||||
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
|
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
|
||||||
|
|
||||||
char buffer[40];
|
request_headers.Append(StringFormat<40>("depth: %u", depth));
|
||||||
sprintf(buffer, "depth: %u", depth);
|
|
||||||
request_headers.Append(buffer);
|
|
||||||
|
|
||||||
request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
request.SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||||
|
|
||||||
@@ -591,8 +601,8 @@ CurlStorage::OpenDirectory(const char *uri_utf8)
|
|||||||
static Storage *
|
static Storage *
|
||||||
CreateCurlStorageURI(EventLoop &event_loop, const char *uri)
|
CreateCurlStorageURI(EventLoop &event_loop, const char *uri)
|
||||||
{
|
{
|
||||||
if (strncmp(uri, "http://", 7) != 0 &&
|
if (!StringStartsWithCaseASCII(uri, "http://") &&
|
||||||
strncmp(uri, "https://", 8) != 0)
|
!StringStartsWithCaseASCII(uri, "https://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
return new CurlStorage(event_loop, uri);
|
return new CurlStorage(event_loop, uri);
|
||||||
|
@@ -35,6 +35,7 @@
|
|||||||
#include "event/Call.hxx"
|
#include "event/Call.hxx"
|
||||||
#include "event/DeferredMonitor.hxx"
|
#include "event/DeferredMonitor.hxx"
|
||||||
#include "event/TimeoutMonitor.hxx"
|
#include "event/TimeoutMonitor.hxx"
|
||||||
|
#include "util/ASCII.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -245,19 +246,19 @@ NfsStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
Copy(StorageFileInfo &info, const struct stat &st)
|
Copy(StorageFileInfo &info, const struct nfs_stat_64 &st)
|
||||||
{
|
{
|
||||||
if (S_ISREG(st.st_mode))
|
if (S_ISREG(st.nfs_mode))
|
||||||
info.type = StorageFileInfo::Type::REGULAR;
|
info.type = StorageFileInfo::Type::REGULAR;
|
||||||
else if (S_ISDIR(st.st_mode))
|
else if (S_ISDIR(st.nfs_mode))
|
||||||
info.type = StorageFileInfo::Type::DIRECTORY;
|
info.type = StorageFileInfo::Type::DIRECTORY;
|
||||||
else
|
else
|
||||||
info.type = StorageFileInfo::Type::OTHER;
|
info.type = StorageFileInfo::Type::OTHER;
|
||||||
|
|
||||||
info.size = st.st_size;
|
info.size = st.nfs_size;
|
||||||
info.mtime = st.st_mtime;
|
info.mtime = st.nfs_mtime;
|
||||||
info.device = st.st_dev;
|
info.device = st.nfs_dev;
|
||||||
info.inode = st.st_ino;
|
info.inode = st.nfs_ino;
|
||||||
}
|
}
|
||||||
|
|
||||||
class NfsGetInfoOperation final : public BlockingNfsOperation {
|
class NfsGetInfoOperation final : public BlockingNfsOperation {
|
||||||
@@ -278,7 +279,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HandleResult(gcc_unused unsigned status, void *data) override {
|
void HandleResult(gcc_unused unsigned status, void *data) override {
|
||||||
Copy(info, *(const struct stat *)data);
|
Copy(info, *(const struct nfs_stat_64 *)data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -401,11 +402,10 @@ NfsStorage::OpenDirectory(const char *uri_utf8)
|
|||||||
static Storage *
|
static Storage *
|
||||||
CreateNfsStorageURI(EventLoop &event_loop, const char *base)
|
CreateNfsStorageURI(EventLoop &event_loop, const char *base)
|
||||||
{
|
{
|
||||||
if (strncmp(base, "nfs://", 6) != 0)
|
const char *p = StringAfterPrefixCaseASCII(base, "nfs://");
|
||||||
|
if (p == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const char *p = base + 6;
|
|
||||||
|
|
||||||
const char *mount = strchr(p, '/');
|
const char *mount = strchr(p, '/');
|
||||||
if (mount == nullptr)
|
if (mount == nullptr)
|
||||||
throw std::runtime_error("Malformed nfs:// URI");
|
throw std::runtime_error("Malformed nfs:// URI");
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
|
#include "util/ASCII.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
|
||||||
@@ -182,7 +183,7 @@ SmbclientDirectoryReader::GetInfo(gcc_unused bool follow)
|
|||||||
static Storage *
|
static Storage *
|
||||||
CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base)
|
CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base)
|
||||||
{
|
{
|
||||||
if (strncmp(base, "smb://", 6) != 0)
|
if (!StringStartsWithCaseASCII(base, "smb://"))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
SmbclientInit();
|
SmbclientInit();
|
||||||
|
@@ -22,21 +22,6 @@
|
|||||||
#include "EPollFD.hxx"
|
#include "EPollFD.hxx"
|
||||||
#include "FatalError.hxx"
|
#include "FatalError.hxx"
|
||||||
|
|
||||||
#if defined(__BIONIC__) && __ANDROID_API__ < 21
|
|
||||||
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
|
|
||||||
#define EPOLL_CLOEXEC O_CLOEXEC
|
|
||||||
|
|
||||||
static inline int
|
|
||||||
epoll_create1(int flags)
|
|
||||||
{
|
|
||||||
return syscall(__NR_epoll_create1, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
EPollFD::EPollFD()
|
EPollFD::EPollFD()
|
||||||
:fd(::epoll_create1(EPOLL_CLOEXEC))
|
:fd(::epoll_create1(EPOLL_CLOEXEC))
|
||||||
{
|
{
|
||||||
|
53
src/tag/Fallback.hxx
Normal file
53
src/tag/Fallback.hxx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_TAG_FALLBACK_HXX
|
||||||
|
#define MPD_TAG_FALLBACK_HXX
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
bool
|
||||||
|
ApplyTagFallback(TagType type, F &&f) noexcept
|
||||||
|
{
|
||||||
|
if (type == TAG_ALBUM_ARTIST_SORT) {
|
||||||
|
/* fall back to "AlbumArtist", "ArtistSort" and
|
||||||
|
"Artist" if no "AlbumArtistSort" was found */
|
||||||
|
if (f(TAG_ALBUM_ARTIST))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return ApplyTagFallback(TAG_ARTIST_SORT, std::forward<F>(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == TAG_ALBUM_ARTIST || type == TAG_ARTIST_SORT)
|
||||||
|
/* fall back to "Artist" if no
|
||||||
|
"AlbumArtist"/"ArtistSort" was found */
|
||||||
|
return f(TAG_ARTIST);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
bool
|
||||||
|
ApplyTagWithFallback(TagType type, F &&f) noexcept
|
||||||
|
{
|
||||||
|
return f(type) || ApplyTagFallback(type, std::forward<F>(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
117
src/tag/Set.cxx
117
src/tag/Set.cxx
@@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2017 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.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; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "Set.hxx"
|
|
||||||
#include "TagBuilder.hxx"
|
|
||||||
#include "Settings.hxx"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy all tag items of the specified type.
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
CopyTagItem(TagBuilder &dest, TagType dest_type,
|
|
||||||
const Tag &src, TagType src_type)
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
for (const auto &item : src) {
|
|
||||||
if (item.type == src_type) {
|
|
||||||
dest.AddItem(dest_type, item.value);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy all tag items of the specified type. Fall back to "Artist" if
|
|
||||||
* there is no "AlbumArtist".
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
CopyTagItem(TagBuilder &dest, const Tag &src, TagType type)
|
|
||||||
{
|
|
||||||
if (!CopyTagItem(dest, type, src, type) &&
|
|
||||||
type == TAG_ALBUM_ARTIST)
|
|
||||||
CopyTagItem(dest, type, src, TAG_ARTIST);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy all tag items of the types in the mask.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
CopyTagMask(TagBuilder &dest, const Tag &src, tag_mask_t mask)
|
|
||||||
{
|
|
||||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
|
||||||
if ((mask & (tag_mask_t(1) << i)) != 0)
|
|
||||||
CopyTagItem(dest, src, TagType(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
TagSet::InsertUnique(const Tag &src, TagType type, const char *value,
|
|
||||||
tag_mask_t group_mask) noexcept
|
|
||||||
{
|
|
||||||
TagBuilder builder;
|
|
||||||
if (value == nullptr)
|
|
||||||
builder.AddEmptyItem(type);
|
|
||||||
else
|
|
||||||
builder.AddItem(type, value);
|
|
||||||
CopyTagMask(builder, src, group_mask);
|
|
||||||
#if CLANG_OR_GCC_VERSION(4,8)
|
|
||||||
emplace(builder.Commit());
|
|
||||||
#else
|
|
||||||
insert(builder.Commit());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
TagSet::CheckUnique(TagType dest_type,
|
|
||||||
const Tag &tag, TagType src_type,
|
|
||||||
tag_mask_t group_mask) noexcept
|
|
||||||
{
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
for (const auto &item : tag) {
|
|
||||||
if (item.type == src_type) {
|
|
||||||
InsertUnique(tag, dest_type, item.value, group_mask);
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
TagSet::InsertUnique(const Tag &tag,
|
|
||||||
TagType type, tag_mask_t group_mask) noexcept
|
|
||||||
{
|
|
||||||
static_assert(sizeof(group_mask) * 8 >= TAG_NUM_OF_ITEM_TYPES,
|
|
||||||
"Mask is too small");
|
|
||||||
|
|
||||||
assert((group_mask & (tag_mask_t(1) << unsigned(type))) == 0);
|
|
||||||
|
|
||||||
if (!CheckUnique(type, tag, type, group_mask) &&
|
|
||||||
(type != TAG_ALBUM_ARTIST ||
|
|
||||||
!IsTagEnabled(TAG_ALBUM_ARTIST) ||
|
|
||||||
/* fall back to "Artist" if no "AlbumArtist" was found */
|
|
||||||
!CheckUnique(type, tag, TAG_ARTIST, group_mask)))
|
|
||||||
InsertUnique(tag, type, nullptr, group_mask);
|
|
||||||
}
|
|
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2017 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.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; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MPD_TAG_SET_HXX
|
|
||||||
#define MPD_TAG_SET_HXX
|
|
||||||
|
|
||||||
#include "Compiler.h"
|
|
||||||
#include "Tag.hxx"
|
|
||||||
#include "Mask.hxx"
|
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for #TagSet which compares two #Tag objects.
|
|
||||||
*/
|
|
||||||
struct TagLess {
|
|
||||||
gcc_pure
|
|
||||||
bool operator()(const Tag &a, const Tag &b) const noexcept {
|
|
||||||
if (a.num_items != b.num_items)
|
|
||||||
return a.num_items < b.num_items;
|
|
||||||
|
|
||||||
const unsigned n = a.num_items;
|
|
||||||
for (unsigned i = 0; i < n; ++i) {
|
|
||||||
const TagItem &ai = *a.items[i];
|
|
||||||
const TagItem &bi = *b.items[i];
|
|
||||||
if (ai.type != bi.type)
|
|
||||||
return unsigned(ai.type) < unsigned(bi.type);
|
|
||||||
|
|
||||||
const int cmp = strcmp(ai.value, bi.value);
|
|
||||||
if (cmp != 0)
|
|
||||||
return cmp < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A set of #Tag objects.
|
|
||||||
*/
|
|
||||||
class TagSet : public std::set<Tag, TagLess> {
|
|
||||||
public:
|
|
||||||
void InsertUnique(const Tag &tag,
|
|
||||||
TagType type, tag_mask_t group_mask) noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void InsertUnique(const Tag &src, TagType type, const char *value,
|
|
||||||
tag_mask_t group_mask) noexcept;
|
|
||||||
|
|
||||||
bool CheckUnique(TagType dest_type,
|
|
||||||
const Tag &tag, TagType src_type,
|
|
||||||
tag_mask_t group_mask) noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@@ -188,6 +188,16 @@ TagBuilder::Complement(const Tag &other)
|
|||||||
tag_pool_lock.unlock();
|
tag_pool_lock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TagBuilder::AddItemUnchecked(TagType type, StringView value) noexcept
|
||||||
|
{
|
||||||
|
tag_pool_lock.lock();
|
||||||
|
auto i = tag_pool_get_item(type, value);
|
||||||
|
tag_pool_lock.unlock();
|
||||||
|
|
||||||
|
items.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
inline void
|
inline void
|
||||||
TagBuilder::AddItemInternal(TagType type, StringView value)
|
TagBuilder::AddItemInternal(TagType type, StringView value)
|
||||||
{
|
{
|
||||||
@@ -197,13 +207,9 @@ TagBuilder::AddItemInternal(TagType type, StringView value)
|
|||||||
if (!f.IsNull())
|
if (!f.IsNull())
|
||||||
value = { f.data, f.size };
|
value = { f.data, f.size };
|
||||||
|
|
||||||
tag_pool_lock.lock();
|
AddItemUnchecked(type, value);
|
||||||
auto i = tag_pool_get_item(type, value);
|
|
||||||
tag_pool_lock.unlock();
|
|
||||||
|
|
||||||
free(f.data);
|
free(f.data);
|
||||||
|
|
||||||
items.push_back(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -229,11 +235,7 @@ TagBuilder::AddItem(TagType type, const char *value)
|
|||||||
void
|
void
|
||||||
TagBuilder::AddEmptyItem(TagType type)
|
TagBuilder::AddEmptyItem(TagType type)
|
||||||
{
|
{
|
||||||
tag_pool_lock.lock();
|
AddItemUnchecked(type, StringView::Empty());
|
||||||
auto i = tag_pool_get_item(type, StringView::Empty());
|
|
||||||
tag_pool_lock.unlock();
|
|
||||||
|
|
||||||
items.push_back(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@@ -132,6 +132,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void Complement(const Tag &other);
|
void Complement(const Tag &other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variant of AddItem() which does not attempt to fix up the
|
||||||
|
* value and does not check whether the tag type is disabled.
|
||||||
|
*/
|
||||||
|
void AddItemUnchecked(TagType type, StringView value) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a new tag item.
|
* Appends a new tag item.
|
||||||
*
|
*
|
||||||
|
@@ -21,8 +21,8 @@
|
|||||||
#include "TagHandler.hxx"
|
#include "TagHandler.hxx"
|
||||||
#include "TagBuilder.hxx"
|
#include "TagBuilder.hxx"
|
||||||
#include "util/ASCII.hxx"
|
#include "util/ASCII.hxx"
|
||||||
|
#include "util/StringFormat.hxx"
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -42,11 +42,8 @@ add_tag_tag(TagType type, const char *value, void *ctx)
|
|||||||
/* filter out this extra data and leading zeroes */
|
/* filter out this extra data and leading zeroes */
|
||||||
char *end;
|
char *end;
|
||||||
unsigned n = strtoul(value, &end, 10);
|
unsigned n = strtoul(value, &end, 10);
|
||||||
if (value != end) {
|
if (value != end)
|
||||||
char s[21];
|
tag.AddItem(type, StringFormat<21>("%u", n));
|
||||||
if (snprintf(s, 21, "%u", n) > 0)
|
|
||||||
tag.AddItem(type, s);
|
|
||||||
}
|
|
||||||
} else
|
} else
|
||||||
tag.AddItem(type, value);
|
tag.AddItem(type, value);
|
||||||
}
|
}
|
||||||
|
60
src/tag/VisitFallback.hxx
Normal file
60
src/tag/VisitFallback.hxx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.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; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_TAG_VISIT_FALLBACK_HXX
|
||||||
|
#define MPD_TAG_VISIT_FALLBACK_HXX
|
||||||
|
|
||||||
|
#include "Fallback.hxx"
|
||||||
|
#include "Tag.hxx"
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
bool
|
||||||
|
VisitTagType(const Tag &tag, TagType type, F &&f) noexcept
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (const auto &item : tag) {
|
||||||
|
if (item.type == type) {
|
||||||
|
found = true;
|
||||||
|
f(item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
bool
|
||||||
|
VisitTagWithFallback(const Tag &tag, TagType type, F &&f) noexcept
|
||||||
|
{
|
||||||
|
return ApplyTagWithFallback(type,
|
||||||
|
[&](TagType type2) {
|
||||||
|
return VisitTagType(tag, type2, f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename F>
|
||||||
|
void
|
||||||
|
VisitTagWithFallbackOrEmpty(const Tag &tag, TagType type, F &&f) noexcept
|
||||||
|
{
|
||||||
|
if (!VisitTagWithFallback(tag, type, f))
|
||||||
|
f("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -31,7 +31,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_THREAD_NAME
|
#ifdef HAVE_THREAD_NAME
|
||||||
# include <stdio.h>
|
#include "util/StringFormat.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -59,9 +59,7 @@ static inline void
|
|||||||
FormatThreadName(const char *fmt, gcc_unused Args&&... args)
|
FormatThreadName(const char *fmt, gcc_unused Args&&... args)
|
||||||
{
|
{
|
||||||
#ifdef HAVE_THREAD_NAME
|
#ifdef HAVE_THREAD_NAME
|
||||||
char buffer[16];
|
SetThreadName(StringFormat<16>(fmt, args...));
|
||||||
snprintf(buffer, sizeof(buffer), fmt, args...);
|
|
||||||
SetThreadName(buffer);
|
|
||||||
#else
|
#else
|
||||||
(void)fmt;
|
(void)fmt;
|
||||||
#endif
|
#endif
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2013 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright (C) 2013-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
#ifndef ASCII_HXX
|
#ifndef ASCII_HXX
|
||||||
#define ASCII_HXX
|
#define ASCII_HXX
|
||||||
|
|
||||||
|
#include "StringView.hxx"
|
||||||
#include "Compiler.h"
|
#include "Compiler.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -69,4 +70,20 @@ StringEqualsCaseASCII(const char *a, const char *b, size_t n) noexcept
|
|||||||
return strncasecmp(a, b, n) == 0;
|
return strncasecmp(a, b, n) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure gcc_nonnull_all
|
||||||
|
static inline bool
|
||||||
|
StringStartsWithCaseASCII(const char *haystack, StringView needle) noexcept
|
||||||
|
{
|
||||||
|
return StringEqualsCaseASCII(haystack, needle.data, needle.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
gcc_pure gcc_nonnull_all
|
||||||
|
static inline const char *
|
||||||
|
StringAfterPrefixCaseASCII(const char *haystack, StringView needle) noexcept
|
||||||
|
{
|
||||||
|
return StringStartsWithCaseASCII(haystack, needle)
|
||||||
|
? haystack + needle.size
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -23,14 +23,9 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <string.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AllocatedString<>
|
AllocatedString<>
|
||||||
FormatStringV(const char *fmt, va_list args)
|
FormatStringV(const char *fmt, va_list args)
|
||||||
{
|
{
|
||||||
#ifndef _WIN32
|
|
||||||
va_list tmp;
|
va_list tmp;
|
||||||
va_copy(tmp, args);
|
va_copy(tmp, args);
|
||||||
const int length = vsnprintf(NULL, 0, fmt, tmp);
|
const int length = vsnprintf(NULL, 0, fmt, tmp);
|
||||||
@@ -43,22 +38,6 @@ FormatStringV(const char *fmt, va_list args)
|
|||||||
char *buffer = new char[length + 1];
|
char *buffer = new char[length + 1];
|
||||||
vsnprintf(buffer, length + 1, fmt, args);
|
vsnprintf(buffer, length + 1, fmt, args);
|
||||||
return AllocatedString<>::Donate(buffer);
|
return AllocatedString<>::Donate(buffer);
|
||||||
#else
|
|
||||||
/* On mingw32, snprintf() expects a 64 bit integer instead of
|
|
||||||
a "long int" for "%li". This is not consistent with our
|
|
||||||
expectation, so we're using plain sprintf() here, hoping
|
|
||||||
the static buffer is large enough. Sorry for this hack,
|
|
||||||
but WIN32 development is so painful, I'm not in the mood to
|
|
||||||
do it properly now. */
|
|
||||||
|
|
||||||
char buffer[16384];
|
|
||||||
vsprintf(buffer, fmt, args);
|
|
||||||
|
|
||||||
const size_t length = strlen(buffer);
|
|
||||||
char *p = new char[length + 1];
|
|
||||||
memcpy(p, buffer, length + 1);
|
|
||||||
return AllocatedString<>::Donate(p);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocatedString<>
|
AllocatedString<>
|
||||||
|
69
src/util/StringFormat.hxx
Normal file
69
src/util/StringFormat.hxx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010-2015 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STRING_FORMAT_HXX
|
||||||
|
#define STRING_FORMAT_HXX
|
||||||
|
|
||||||
|
#include "StringBuffer.hxx"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
static inline void
|
||||||
|
StringFormat(char *buffer, size_t size,
|
||||||
|
const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
snprintf(buffer, size, fmt, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t CAPACITY, typename... Args>
|
||||||
|
static inline void
|
||||||
|
StringFormat(StringBuffer<CAPACITY> &buffer,
|
||||||
|
const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
StringFormat(buffer.data(), buffer.capacity(), fmt, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t CAPACITY, typename... Args>
|
||||||
|
static inline StringBuffer<CAPACITY>
|
||||||
|
StringFormat(const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
StringBuffer<CAPACITY> result;
|
||||||
|
StringFormat(result, fmt, args...);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
static inline void
|
||||||
|
StringFormatUnsafe(char *buffer, const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
sprintf(buffer, fmt, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "UriUtil.hxx"
|
#include "UriUtil.hxx"
|
||||||
#include "StringCompare.hxx"
|
#include "ASCII.hxx"
|
||||||
#include "CharUtil.hxx"
|
#include "CharUtil.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -169,7 +169,7 @@ SkipUriScheme(const char *uri) noexcept
|
|||||||
{
|
{
|
||||||
const char *const schemes[] = { "http://", "https://", "ftp://" };
|
const char *const schemes[] = { "http://", "https://", "ftp://" };
|
||||||
for (auto scheme : schemes) {
|
for (auto scheme : schemes) {
|
||||||
auto result = StringAfterPrefix(uri, scheme);
|
auto result = StringAfterPrefixCaseASCII(uri, scheme);
|
||||||
if (result != nullptr)
|
if (result != nullptr)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@@ -65,6 +65,7 @@ class CrossGccToolchain:
|
|||||||
|
|
||||||
self.is_arm = arch.startswith('arm')
|
self.is_arm = arch.startswith('arm')
|
||||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||||
|
self.is_aarch64 = arch == 'aarch64'
|
||||||
self.is_windows = 'mingw32' in arch
|
self.is_windows = 'mingw32' in arch
|
||||||
|
|
||||||
self.env = dict(os.environ)
|
self.env = dict(os.environ)
|
||||||
@@ -86,6 +87,7 @@ thirdparty_libs = [
|
|||||||
liblame,
|
liblame,
|
||||||
ffmpeg,
|
ffmpeg,
|
||||||
curl,
|
curl,
|
||||||
|
libexpat,
|
||||||
libnfs,
|
libnfs,
|
||||||
boost,
|
boost,
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user