Compare commits
269 Commits
Author | SHA1 | Date | |
---|---|---|---|
8842650c33 | |||
d5bf128cee | |||
5cd86e272f | |||
740cbe9e02 | |||
ed890a273a | |||
068cd559e1 | |||
dc127f39a7 | |||
7a99a7008c | |||
70b451db7b | |||
2ab03a0914 | |||
2fa8c7d2db | |||
7c759ba8b0 | |||
6d9b452fde | |||
f7eb1c9a83 | |||
2d22e6dee4 | |||
4587bf759d | |||
e1e37cfe3c | |||
381934985a | |||
a8042885ac | |||
a71e68db50 | |||
1417578b3d | |||
96befa138c | |||
16a99804de | |||
75a39ed279 | |||
4d357ab77c | |||
d4f3dd49b4 | |||
4ec6d0555a | |||
a6a1182c4c | |||
a59c9c602b | |||
0c4d824d64 | |||
a5281856c9 | |||
0206a46d39 | |||
9475ef2202 | |||
edae00e719 | |||
fb695bc55f | |||
23a5b8fd3c | |||
273a93cfcf | |||
d105985d78 | |||
f8cfeb39e9 | |||
d5d3982d3c | |||
47341107ea | |||
90eaa87a4d | |||
b09a54b2c2 | |||
10aec174d5 | |||
d32ed194e8 | |||
70d0fbd715 | |||
302432e157 | |||
4ab8a677dc | |||
52e4a4c904 | |||
a0f6932ebe | |||
6e700dab69 | |||
35eaed7206 | |||
e7c963f2ce | |||
949d72e368 | |||
8d2a184658 | |||
c877a32d97 | |||
541468f0ca | |||
d2797effa3 | |||
1170fb1e1e | |||
65b9b3195c | |||
258830e913 | |||
d91da96798 | |||
b3897df682 | |||
3cacb56bb7 | |||
15a1973e28 | |||
ad7d47a8ba | |||
0948c607b6 | |||
60d04052c5 | |||
c1780ac657 | |||
e49cf0ec38 | |||
e1d641f684 | |||
4efd0a9f77 | |||
f6f8751332 | |||
abb28593ce | |||
115693b046 | |||
e4b055eb6d | |||
9866adff95 | |||
a8b0c55818 | |||
cac88e8be5 | |||
e9f6a3482c | |||
5d2e80f188 | |||
cfd4d5b13e | |||
06514aec63 | |||
4ded1ae67b | |||
1da974e3fa | |||
94f06f0946 | |||
d9eec8a455 | |||
eaecbcafb2 | |||
73b5d0a9b9 | |||
c2d0f35e7a | |||
ab99a57997 | |||
c8ebaf3521 | |||
52d00f7e30 | |||
309491a6d8 | |||
e7bfd32ccc | |||
6f283b52ab | |||
32bddfabea | |||
1944c826bc | |||
619bb60b26 | |||
c549e16ed1 | |||
01c9c4507f | |||
8c9d7bf07e | |||
44ef34db88 | |||
5781f223f6 | |||
e4c8ebe056 | |||
76b25a1377 | |||
ccc3ee663b | |||
0626661764 | |||
31db04a3ca | |||
0c7163b9db | |||
7d78cad8af | |||
912530ed20 | |||
d3f37199b9 | |||
a4748d84b0 | |||
8f847ec381 | |||
3a70f09dd3 | |||
568f63100b | |||
3e25916b37 | |||
5f9438dae6 | |||
99e65c58ce | |||
df71b07e9d | |||
2694195215 | |||
66450d1f3c | |||
76efea3aa7 | |||
7ab0dfc8ce | |||
15ff7c4cad | |||
9ab9b97f20 | |||
88d92aceab | |||
a2ce4352c8 | |||
84f43ccde8 | |||
38704c9cf3 | |||
910d0ec92b | |||
3b05c89765 | |||
e77b3fa46f | |||
12147f6d58 | |||
40bc60d6ae | |||
7778210269 | |||
6229210d51 | |||
5d0d5b5d97 | |||
1aa3c1e543 | |||
b90e32fe4e | |||
1f4df2a64d | |||
2efc1db6a9 | |||
e2d4654e20 | |||
2b8f1170a6 | |||
5c4743441e | |||
cb288439a4 | |||
69f741e8a6 | |||
4b4f47002b | |||
615c301961 | |||
dc07180e48 | |||
d3b235bab5 | |||
7c920ddebe | |||
bbc088ae4e | |||
fe195257d8 | |||
57d5df8118 | |||
59792cb0b8 | |||
cc557c4d60 | |||
956c5faebb | |||
cd0396c1f1 | |||
79f9b268bb | |||
b45f3c8deb | |||
f8a8de87e4 | |||
2183f0553c | |||
1f28790476 | |||
c8dae95eff | |||
547a084c7e | |||
493677ff81 | |||
6b430ba271 | |||
bc6924d303 | |||
02b00f9146 | |||
e807ed5870 | |||
f08944253b | |||
792d6584b9 | |||
7b45d01462 | |||
5c17b2966a | |||
0c54f29446 | |||
9c3cf39fdd | |||
d2fb229685 | |||
f55bc6682f | |||
6857286b42 | |||
c0d5bd2048 | |||
666e5d7904 | |||
3613407ac5 | |||
c32dceb4d4 | |||
5573e78364 | |||
807a19889f | |||
df7242de91 | |||
d62426f168 | |||
1714cf3417 | |||
1080c917be | |||
8eb3164878 | |||
915c5442d1 | |||
be0360d5e8 | |||
4d6ae6ffdd | |||
ecee6f415b | |||
47680f936b | |||
2d7181105d | |||
9bdc75524b | |||
2f6ceb4949 | |||
cd933aa35f | |||
138738075b | |||
2ee57f9b0d | |||
5a5655b790 | |||
b88d1e6820 | |||
19d2864c34 | |||
29e3a17f26 | |||
252e9f736f | |||
5d08988dda | |||
47ca4246aa | |||
f8338d4f00 | |||
5cf6032c90 | |||
8d8b77412d | |||
fd9114e7e2 | |||
a3fba2f8f7 | |||
e2b671f1b2 | |||
2a35fbe29e | |||
81cde72fd0 | |||
bf9ffba4f7 | |||
c975d8b943 | |||
2730f91872 | |||
97ca85e155 | |||
39bb4c5871 | |||
bdceb90c59 | |||
8bd1b5228c | |||
a009e95afd | |||
32aafb3572 | |||
b577783cf0 | |||
aa7b872a14 | |||
c6f7f57776 | |||
106ad08cd2 | |||
0341ca1b6a | |||
7581ea55db | |||
fc9cee38d8 | |||
b175e4128d | |||
97b07798b0 | |||
112fcd206d | |||
11d1f56062 | |||
bd840d4638 | |||
c3d393f214 | |||
f88fc0ca1a | |||
fb8d8242ab | |||
f2a3dfd700 | |||
85f9863e0a | |||
83572701f4 | |||
fa7d7e9187 | |||
f818cde32c | |||
9da93cd887 | |||
026e7ea32a | |||
9659d19718 | |||
50d35c9677 | |||
4260e78861 | |||
7342ae2e33 | |||
35dbc1a90c | |||
c7a4355153 | |||
33a84a8ca2 | |||
1d04490ed3 | |||
4a30c2d79c | |||
83072d6b9c | |||
c779fc37eb | |||
e08c13ad7e | |||
2c82a6b2e0 | |||
3929f17aef | |||
ee39af3419 | |||
3882a5a263 | |||
ac06088948 | |||
a757eebfbb | |||
2be4f89555 | |||
4a5c7d8261 |
.github
NEWSandroid
doc
meson.buildpython/build
src
CommandLine.cxxCommandLine.hxxLog.hxxLogInit.cxxMain.cxxPartition.cxxPartition.hxxPlaylistFile.cxxRemoteTagCache.hxxSongPrint.cxxSongSave.cxxSongSave.hxxStateFile.cxxStateFileConfig.cxxTagPrint.cxxTimePrint.cxx
android
apple
archive
client
command
DatabaseCommands.cxxFileCommands.cxxOtherCommands.cxxOutputCommands.cxxPlayerCommands.cxxStorageCommands.cxx
db
decoder
encoder
event
filter
fs
input
AsyncInputStream.cxx
plugins
io
java
lib
crypto
curl
Adapter.cxxAdapter.hxxDelegate.cxxDelegate.hxxEasy.hxxForm.cxxForm.hxxHandler.hxxHeaders.hxxRequest.cxxRequest.hxxSetup.cxxSetup.hxxmeson.build
patches
dbus
ffmpeg
icu
upnp
xiph
mixer
output
storage
system
tag
time
unix
util
subprojects
systemd
test
win32
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: MaxK
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -18,5 +18,9 @@ about: Create a bug report
|
||||
<!-- Paste the output of "mpd --version" here -->
|
||||
|
||||
|
||||
## Configuration
|
||||
<!-- Paste your MPD configuration here -->
|
||||
|
||||
|
||||
## Log
|
||||
<!-- Paste relevant portions of the log file here (--verbose) -->
|
||||
|
9
.github/ISSUE_TEMPLATE/question.md
vendored
9
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about MPD
|
||||
---
|
||||
|
||||
<!-- Before you ask a question on GitHub, please read MPD's
|
||||
documentation. A copy is available at
|
||||
https://www.musicpd.org/doc/html/ -->
|
||||
## Question
|
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@ -41,7 +41,8 @@ jobs:
|
||||
key: linux
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt install -y --no-install-recommends \
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
g++-10 libfmt-dev libboost-dev \
|
||||
libgtest-dev \
|
||||
libpcre2-dev \
|
||||
@ -73,19 +74,30 @@ jobs:
|
||||
libgcrypt20-dev
|
||||
|
||||
- name: Full Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Mini Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
action: build
|
||||
directory: output/mini
|
||||
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
build-macos:
|
||||
@ -124,10 +136,20 @@ jobs:
|
||||
wavpack \
|
||||
libmpdclient
|
||||
|
||||
- name: Meson Build
|
||||
- name: Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
101
NEWS
101
NEWS
@ -1,3 +1,104 @@
|
||||
ver 0.23.13 (2023/05/22)
|
||||
* input
|
||||
- curl: fix busy loop after connection failed
|
||||
- curl: hide "404" log messages for non-existent ".mpdignore" files
|
||||
* archive
|
||||
- zzip: fix crash bug
|
||||
* database
|
||||
- simple: reveal hidden songs after deleting containing CUE
|
||||
* decoder
|
||||
- ffmpeg: reorder to a lower priority than "gme"
|
||||
- gme: require GME 0.6 or later
|
||||
* output
|
||||
- pipewire: fix corruption bug due to missing lock
|
||||
* Linux
|
||||
- shut down if parent process dies in --no-daemon mode
|
||||
- determine systemd unit directories via pkg-config
|
||||
* support libfmt 10
|
||||
|
||||
ver 0.23.12 (2023/01/17)
|
||||
* input
|
||||
- curl: require CURL 7.55.0 or later
|
||||
* decoder
|
||||
- mad: fix integer underflow with very small files
|
||||
* tags
|
||||
- fix crash bug due to race condition
|
||||
* output
|
||||
- pipewire: adjust to PipeWire 0.3.64 API change
|
||||
* fix build failures with GCC 13
|
||||
|
||||
ver 0.23.11 (2022/11/28)
|
||||
* database
|
||||
- simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
|
||||
- simple: default "cache_directory" to ~/.cache/mpd/mounts
|
||||
* macOS: fix build failure "no archive members specified"
|
||||
* Windows
|
||||
- fix crash bug (stack buffer overflow) after I/O errors
|
||||
- fix path traversal bug because backslash was allowed in playlist names
|
||||
* Android/Windows
|
||||
- update OpenSSL to 3.0.7
|
||||
- re-enable CURL's verbose error strings
|
||||
|
||||
ver 0.23.10 (2022/10/14)
|
||||
* storage
|
||||
- curl: fix file time stamps
|
||||
* decoder
|
||||
- ffmpeg: fix libfmt 9 compiler warning
|
||||
* encoder
|
||||
- flac: fix failure when libFLAC is built without Ogg support
|
||||
* output
|
||||
- alsa: fix crash bug
|
||||
* Windows
|
||||
- log to stdout by default, don't require "log_file" setting
|
||||
|
||||
ver 0.23.9 (2022/08/18)
|
||||
* input
|
||||
- cdio_paranoia: add options "mode" and "skip"
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 5.1
|
||||
* filter
|
||||
- replay gain: fix delayed volume display with handler=mixer
|
||||
* output
|
||||
- pipewire: set app icon
|
||||
* fix bogus volume levels with multiple partitions
|
||||
* improve iconv detection
|
||||
* macOS: fix macOS 10 build problem (0.23.8 regression)
|
||||
* Android
|
||||
- load mpd.conf from app data directory
|
||||
|
||||
ver 0.23.8 (2022/07/09)
|
||||
* storage
|
||||
- curl: fix crash if web server does not understand WebDAV
|
||||
* input
|
||||
- cdio_paranoia: fix crash if no drive was found
|
||||
- cdio_paranoia: faster cancellation
|
||||
- cdio_paranoia: don't scan for replay gain tags
|
||||
- pipewire: fix playback of very short tracks
|
||||
- pipewire: drop all buffers before manual song change
|
||||
- pipewire: fix stuttering after manual song change
|
||||
- snapcast: fix busy loop while paused
|
||||
- snapcast: fix stuttering after resuming playback
|
||||
* mixer
|
||||
- better error messages
|
||||
- alsa: fix setting volume before playback starts
|
||||
- pipewire: fix crash bug
|
||||
- pipewire: fix volume change events with PipeWire 0.3.53
|
||||
- pipewire: don't force initial volume=100%
|
||||
* support libfmt 9
|
||||
|
||||
ver 0.23.7 (2022/05/09)
|
||||
* database
|
||||
- upnp: support pupnp 1.14
|
||||
* decoder
|
||||
- ffmpeg: fix HLS seeking
|
||||
- opus: fix missing song length on high-latency files
|
||||
* output
|
||||
- shout: require at least libshout 2.4.0
|
||||
* mixer
|
||||
- pipewire: fix volume restore
|
||||
- software: update volume of disabled outputs
|
||||
* support libiconv
|
||||
|
||||
ver 0.23.6 (2022/03/14)
|
||||
* protocol
|
||||
- support filename "cover.webp" for "albumart" command
|
||||
|
@ -2,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="65"
|
||||
android:versionName="0.23.5">
|
||||
android:versionCode="71"
|
||||
android:versionName="0.23.12">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
@ -19,6 +19,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:debuggable="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:banner="@drawable/icon"
|
||||
|
@ -12,18 +12,30 @@ unsigned_apk = custom_target(
|
||||
],
|
||||
)
|
||||
|
||||
aligned_apk = custom_target(
|
||||
'mpd-aligned.apk',
|
||||
output: 'mpd-aligned.apk',
|
||||
input: unsigned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
],
|
||||
)
|
||||
|
||||
if get_option('android_debug_keystore') != ''
|
||||
debug_apk = custom_target(
|
||||
'mpd-debug.apk',
|
||||
output: 'mpd-debug.apk',
|
||||
input: unsigned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-keystore', get_option('android_debug_keystore'),
|
||||
'-storepass', 'android',
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'androiddebugkey',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'--debuggable-apk-permitted',
|
||||
'-ks', get_option('android_debug_keystore'),
|
||||
'--ks-key-alias', 'androiddebugkey',
|
||||
'--ks-pass', 'pass:android',
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
@ -31,29 +43,16 @@ endif
|
||||
|
||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
||||
unaligned_apk = custom_target(
|
||||
'mpd-unaligned.apk',
|
||||
output: 'mpd-unaligned.apk',
|
||||
input: unsigned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
|
||||
'-keystore', get_option('android_keystore'),
|
||||
'-storepass', get_option('android_keypass'),
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
get_option('android_keyalias'),
|
||||
],
|
||||
)
|
||||
|
||||
apk = custom_target(
|
||||
'mpd.apk',
|
||||
output: 'mpd.apk',
|
||||
input: unaligned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'-ks', get_option('android_keystore'),
|
||||
'--ks-key-alias', get_option('android_keyalias'),
|
||||
'--ks-pass', 'pass:' + get_option('android_keypass'),
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
endif
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
@ -100,6 +100,7 @@ class AndroidNdkToolchain:
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
self.ar = os.path.join(llvm_bin, 'llvm-ar')
|
||||
self.arflags = 'rcs'
|
||||
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
|
||||
self.nm = os.path.join(llvm_bin, 'llvm-nm')
|
||||
self.strip = os.path.join(llvm_bin, 'llvm-strip')
|
||||
|
54
android/gdb.sh
Executable file
54
android/gdb.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script need the following modification in ANDROID_NDK in order to attach
|
||||
# to the good :main pid
|
||||
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||
#@@ -669,7 +669,7 @@
|
||||
# log("Sleeping for {} seconds.".format(args.delay))
|
||||
# time.sleep(args.delay)
|
||||
#
|
||||
#- pids = gdbrunner.get_pids(device, pkg_name)
|
||||
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
|
||||
# if len(pids) == 0:
|
||||
# error("Failed to find running process '{}'".format(pkg_name))
|
||||
# if len(pids) > 1:
|
||||
|
||||
SCRIPT_PATH=$(dirname $0)
|
||||
BUILD_PATH="`pwd`"
|
||||
TMP_PATH="$BUILD_PATH/gdb"
|
||||
NDK_GDB_ARGS="--force"
|
||||
ANDROID_NDK=$1
|
||||
|
||||
if [ ! -f $ANDROID_NDK/source.properties ];then
|
||||
echo "usage: $0 ANDROID_NDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f $BUILD_PATH/libmpd.so ];then
|
||||
echo "This script need to be executed from the android build directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$TMP_PATH"
|
||||
mkdir -p "$TMP_PATH"
|
||||
|
||||
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
|
||||
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
|
||||
|
||||
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
|
||||
echo "Invalid manifest/ABI, did you try building first ?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_PATH"/jni
|
||||
touch "$TMP_PATH"/jni/Android.mk
|
||||
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
|
||||
|
||||
DEST=obj/local/$ABI
|
||||
mkdir -p "$TMP_PATH/$DEST"
|
||||
|
||||
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
|
||||
cp "$ANDROID_MANIFEST" "$TMP_PATH"
|
||||
|
||||
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)
|
@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
|
||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
||||
|
||||
javac = find_program('javac')
|
||||
jarsigner = find_program('jarsigner')
|
||||
apksigner = find_program('apksigner')
|
||||
rsvg_convert = find_program('rsvg-convert')
|
||||
convert = find_program('convert')
|
||||
zip = find_program('zip')
|
||||
|
@ -38,7 +38,10 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.23.6'
|
||||
with open('../meson.build') as f:
|
||||
import re
|
||||
version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
|
||||
f.read(4096)).group(1)
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
#release = version + '~git'
|
||||
|
||||
@ -47,7 +50,7 @@ version = '0.23.6'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
|
@ -11,6 +11,12 @@ Music Player Daemon
|
||||
client
|
||||
protocol
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: man pages:
|
||||
|
||||
mpd.1
|
||||
mpd.conf.5
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@ -181,7 +181,7 @@
|
||||
#
|
||||
#database {
|
||||
# plugin "simple"
|
||||
# path "~/.local/share/mpd/db
|
||||
# path "~/.local/share/mpd/db"
|
||||
# cache_directory "~/.local/share/mpd/cache"
|
||||
#}
|
||||
#
|
||||
|
@ -206,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
|
||||
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
|
||||
* - **speed N**
|
||||
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
|
||||
* - **mode disable|overlap|full**
|
||||
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
|
||||
performs overlapped reads, and ``full`` enables all options.
|
||||
* - **skip yes|no**
|
||||
- If set to ``no``, then never skip failed reads.
|
||||
|
||||
curl
|
||||
----
|
||||
@ -214,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
|
||||
|
||||
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 :file:`~/.curlrc`
|
||||
will be in effect.
|
||||
variables such as ``http_proxy`` will be in effect.
|
||||
|
||||
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
@ -545,6 +545,13 @@ Playback options
|
||||
Sets repeat state to ``STATE``,
|
||||
``STATE`` should be 0 or 1.
|
||||
|
||||
If enabled, MPD keeps repeating the whole queue (:ref:`single mode
|
||||
<command_single>` disabled) or the current song (:ref:`single mode
|
||||
<command_single>` enabled).
|
||||
|
||||
If :ref:`random mode <command_random>` is also enabled, the
|
||||
playback order will be shuffled each time the queue gets repeated.
|
||||
|
||||
.. _command_setvol:
|
||||
|
||||
:command:`setvol {VOL}`
|
||||
|
29
doc/user.rst
29
doc/user.rst
@ -36,7 +36,9 @@ Installing on Android
|
||||
|
||||
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
|
||||
|
||||
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function).
|
||||
If you need to tweak the configuration, you can create a file called
|
||||
:file:`mpd.conf` in MPD's data directory on the external storage
|
||||
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
|
||||
|
||||
ALSA is not available on Android; only the :ref:`OpenSL ES
|
||||
<sles_output>` output plugin can be used for local playback.
|
||||
@ -197,7 +199,7 @@ Compiling for Android
|
||||
You need:
|
||||
|
||||
* Android SDK
|
||||
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_
|
||||
* `Android NDK r25b <https://developer.android.com/ndk/downloads>`_
|
||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* cmake
|
||||
@ -301,7 +303,7 @@ Configuring neighbor plugins
|
||||
----------------------------
|
||||
|
||||
All neighbor plugins are disabled by default to avoid unwanted
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
|
||||
block to :file:`mpd.conf`:
|
||||
|
||||
.. code-block:: none
|
||||
@ -538,7 +540,7 @@ The following table lists the playlist_plugin options valid for all plugins:
|
||||
|
||||
* - Name
|
||||
- Description
|
||||
* - **plugin**
|
||||
* - **name**
|
||||
- The name of the plugin
|
||||
* - **enabled yes|no**
|
||||
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
|
||||
@ -609,6 +611,11 @@ If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
|
||||
set to a value (in dB) between ``-15`` and ``15``. This is the gain
|
||||
applied to songs with ReplayGain tags.
|
||||
|
||||
On songs without ReplayGain tags, the setting
|
||||
``replaygain_missing_preamp`` is used instead. If this setting is not
|
||||
configured, then no ReplayGain is applied to such songs, and they will
|
||||
appear too loud.
|
||||
|
||||
ReplayGain is usually implemented with a software volume filter (which
|
||||
prevents `Bit-perfect playback`_). To use a hardware mixer, set
|
||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
|
||||
@ -1063,7 +1070,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
|
||||
|
||||
Depending on the size of your music collection and the speed of the storage, this can take a while.
|
||||
|
||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
|
||||
To exclude a file from the update, create a file called
|
||||
:file:`.mpdignore` in its parent directory. Each line of that file
|
||||
may contain a list of shell wildcards. Matching files (or
|
||||
directories) in the current directory and all subdirectories are
|
||||
excluded. Example::
|
||||
|
||||
*.opus
|
||||
99*
|
||||
|
||||
Subject to pattern matching is the file/directory name. It is (not
|
||||
yet) possible to match nested path names, e.g. something like
|
||||
``foo/*.flac`` is not possible.
|
||||
|
||||
|
||||
Mounting other storages into the music directory
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
17
meson.build
17
meson.build
@ -1,7 +1,7 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.23.6',
|
||||
version: '0.23.13',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@ -73,6 +73,9 @@ test_common_flags = [
|
||||
# clang specific warning options:
|
||||
'-Wunreachable-code-aggressive',
|
||||
'-Wused-but-marked-unused',
|
||||
|
||||
# suppress bogus GCC12 warnings in libfmt headers
|
||||
'-Wno-stringop-overflow',
|
||||
]
|
||||
|
||||
test_global_cxxflags = test_global_common_flags + [
|
||||
@ -202,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
|
||||
conf.set('ENABLE_DAEMON', enable_daemon)
|
||||
|
||||
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||
|
||||
@ -248,6 +250,14 @@ endif
|
||||
|
||||
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
|
||||
|
||||
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
|
||||
fmt_dep = declare_dependency(
|
||||
dependencies: fmt_dep,
|
||||
# suppress bogus clang 14 warning (the version in Android NDK r25b)
|
||||
compile_args: ['-Wno-unused-local-typedef'],
|
||||
)
|
||||
endif
|
||||
|
||||
log = static_library(
|
||||
'log',
|
||||
'src/Log.cxx',
|
||||
@ -349,7 +359,7 @@ sources = [
|
||||
'src/TagStream.cxx',
|
||||
'src/TagAny.cxx',
|
||||
'src/TimePrint.cxx',
|
||||
'src/mixer/Volume.cxx',
|
||||
'src/mixer/Memento.cxx',
|
||||
'src/PlaylistFile.cxx',
|
||||
]
|
||||
|
||||
@ -379,6 +389,7 @@ endif
|
||||
|
||||
if enable_database
|
||||
sources += [
|
||||
'src/storage/StorageState.cxx',
|
||||
'src/queue/PlaylistUpdate.cxx',
|
||||
'src/command/StorageCommands.cxx',
|
||||
'src/command/DatabaseCommands.cxx',
|
||||
|
@ -45,14 +45,27 @@ class AutotoolsProject(MakeProject):
|
||||
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
|
||||
'LIBS=' + toolchain.libs + ' ' + self.libs,
|
||||
'AR=' + toolchain.ar,
|
||||
'ARFLAGS=' + toolchain.arflags,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'STRIP=' + toolchain.strip,
|
||||
'--host=' + toolchain.arch,
|
||||
'--prefix=' + toolchain.install_prefix,
|
||||
'--enable-silent-rules',
|
||||
'--disable-silent-rules',
|
||||
] + self.configure_args
|
||||
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
try:
|
||||
print(configure)
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
except subprocess.CalledProcessError:
|
||||
# dump config.log after a failed configure run
|
||||
try:
|
||||
with open(os.path.join(build, 'config.log')) as f:
|
||||
sys.stdout.write(f.read())
|
||||
except:
|
||||
pass
|
||||
# re-raise the exception
|
||||
raise
|
||||
|
||||
return build
|
||||
|
||||
def _build(self, toolchain):
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
@ -25,13 +26,33 @@ set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
|
||||
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
|
||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
|
||||
|
||||
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
""")
|
||||
__write_cmake_compiler(f, 'C', toolchain.cc)
|
||||
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
if cmake_system_name == 'Darwin':
|
||||
# On macOS, cmake forcibly adds an "-isysroot" flag even if
|
||||
# one is already present in the flags variable; this breaks
|
||||
# cross-compiling for iOS, and can be worked around by setting
|
||||
# the CMAKE_OSX_SYSROOT variable
|
||||
# (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
|
||||
m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
|
||||
if m:
|
||||
sysroot = m.group(1)
|
||||
|
||||
print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
|
||||
|
||||
# search libraries and headers only in the sysroot, not on
|
||||
# the build host
|
||||
f.write(f"""
|
||||
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
""")
|
||||
|
||||
def configure(toolchain, src, build, args=(), env=None):
|
||||
cross_args = []
|
||||
|
||||
if toolchain.is_windows:
|
||||
@ -61,15 +82,23 @@ def configure(toolchain, src, build, args=()):
|
||||
'-GNinja',
|
||||
] + cross_args + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||
if env is None:
|
||||
env = toolchain.env
|
||||
else:
|
||||
env = {**toolchain.env, **env}
|
||||
|
||||
print(configure)
|
||||
subprocess.check_call(configure, env=env, cwd=build)
|
||||
|
||||
class CmakeProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
windows_configure_args=[],
|
||||
env=None,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.windows_configure_args = windows_configure_args
|
||||
self.env = env
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
@ -77,10 +106,10 @@ class CmakeProject(Project):
|
||||
configure_args = self.configure_args
|
||||
if toolchain.is_windows:
|
||||
configure_args = configure_args + self.windows_configure_args
|
||||
configure(toolchain, src, build, configure_args)
|
||||
configure(toolchain, src, build, configure_args, self.env)
|
||||
return build
|
||||
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -43,20 +43,22 @@ opus = AutotoolsProject(
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.4.tar.xz',
|
||||
'8ff0607e75a322dd7cd6ec48f4f225471404ae2730d0ea945127b1355155e737',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz',
|
||||
'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-stack-smash-protection',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
'--disable-programs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
'http://zlib.net/zlib-1.2.11.tar.xz',
|
||||
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066',
|
||||
'http://zlib.net/zlib-1.2.13.tar.xz',
|
||||
'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98',
|
||||
'lib/libz.a',
|
||||
)
|
||||
|
||||
@ -112,30 +114,31 @@ libmodplug = AutotoolsProject(
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.12+release.autotools.tar.gz',
|
||||
'892aea7a599b5d21842bebf463b5aafdad5711be7008dd84401920c6234820af',
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
|
||||
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--disable-examples',
|
||||
'--disable-tests',
|
||||
'--disable-doxygen-doc',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
'--without-flac',
|
||||
],
|
||||
base='libopenmpt-0.5.12+release.autotools',
|
||||
base='libopenmpt-0.6.6+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
|
||||
'6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
|
||||
'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
|
||||
'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.4',
|
||||
name='wildmidi',
|
||||
version='0.4.4',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
@ -151,8 +154,8 @@ gme = CmakeProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-5.0.tar.xz',
|
||||
'51e919f7d205062c0fd4fae6243a84850391115104ccf1efc451733bc0ac7298',
|
||||
'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
|
||||
'57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -166,17 +169,21 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-swscale',
|
||||
'--disable-postproc',
|
||||
'--disable-avfilter',
|
||||
'--disable-lzo',
|
||||
'--disable-faan',
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
'--disable-encoders',
|
||||
'--disable-hwaccels',
|
||||
'--disable-muxers',
|
||||
'--disable-protocols',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-sdl2',
|
||||
'--disable-vulkan',
|
||||
'--disable-xlib',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
'--disable-parser=dvbsub',
|
||||
@ -189,17 +196,22 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-parser=h263',
|
||||
'--disable-parser=h264',
|
||||
'--disable-parser=hevc',
|
||||
'--disable-parser=jpeg2000',
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=qoi',
|
||||
'--disable-parser=rv30',
|
||||
'--disable-parser=rv40',
|
||||
'--disable-parser=vc1',
|
||||
'--disable-parser=vp3',
|
||||
'--disable-parser=vp8',
|
||||
'--disable-parser=vp9',
|
||||
'--disable-parser=png',
|
||||
'--disable-parser=pnm',
|
||||
'--disable-parser=webp',
|
||||
'--disable-parser=xma',
|
||||
|
||||
'--disable-demuxer=aqtitle',
|
||||
@ -215,6 +227,42 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=h264',
|
||||
'--disable-demuxer=ico',
|
||||
'--disable-demuxer=image2',
|
||||
'--disable-demuxer=image2pipe',
|
||||
'--disable-demuxer=image_bmp_pipe',
|
||||
'--disable-demuxer=image_cri_pipe',
|
||||
'--disable-demuxer=image_dds_pipe',
|
||||
'--disable-demuxer=image_dpx_pipe',
|
||||
'--disable-demuxer=image_exr_pipe',
|
||||
'--disable-demuxer=image_gem_pipe',
|
||||
'--disable-demuxer=image_gif_pipe',
|
||||
'--disable-demuxer=image_j2k_pipe',
|
||||
'--disable-demuxer=image_jpeg_pipe',
|
||||
'--disable-demuxer=image_jpegls_pipe',
|
||||
'--disable-demuxer=image_jpegxl_pipe',
|
||||
'--disable-demuxer=image_pam_pipe',
|
||||
'--disable-demuxer=image_pbm_pipe',
|
||||
'--disable-demuxer=image_pcx_pipe',
|
||||
'--disable-demuxer=image_pfm_pipe',
|
||||
'--disable-demuxer=image_pgm_pipe',
|
||||
'--disable-demuxer=image_pgmyuv_pipe',
|
||||
'--disable-demuxer=image_pgx_pipe',
|
||||
'--disable-demuxer=image_phm_pipe',
|
||||
'--disable-demuxer=image_photocd_pipe',
|
||||
'--disable-demuxer=image_pictor_pipe',
|
||||
'--disable-demuxer=image_png_pipe',
|
||||
'--disable-demuxer=image_ppm_pipe',
|
||||
'--disable-demuxer=image_psd_pipe',
|
||||
'--disable-demuxer=image_qdraw_pipe',
|
||||
'--disable-demuxer=image_qoi_pipe',
|
||||
'--disable-demuxer=image_sgi_pipe',
|
||||
'--disable-demuxer=image_sunrast_pipe',
|
||||
'--disable-demuxer=image_svg_pipe',
|
||||
'--disable-demuxer=image_tiff_pipe',
|
||||
'--disable-demuxer=image_vbn_pipe',
|
||||
'--disable-demuxer=image_webp_pipe',
|
||||
'--disable-demuxer=image_xbm_pipe',
|
||||
'--disable-demuxer=image_xpm_pipe',
|
||||
'--disable-demuxer=image_xwd_pipe',
|
||||
'--disable-demuxer=jacosub',
|
||||
'--disable-demuxer=lrc',
|
||||
'--disable-demuxer=microdvd',
|
||||
@ -237,6 +285,7 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=tedcaptions',
|
||||
'--disable-demuxer=vobsub',
|
||||
'--disable-demuxer=vplayer',
|
||||
'--disable-demuxer=webm_dash_manifest',
|
||||
'--disable-demuxer=webvtt',
|
||||
'--disable-demuxer=yuv4mpegpipe',
|
||||
|
||||
@ -266,78 +315,179 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=qdmc',
|
||||
|
||||
# disable lots of image and video codecs
|
||||
'--disable-decoder=acelp_kelvin',
|
||||
'--disable-decoder=agm',
|
||||
'--disable-decoder=aic',
|
||||
'--disable-decoder=alias_pix',
|
||||
'--disable-decoder=ansi',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=arbc',
|
||||
'--disable-decoder=argo',
|
||||
'--disable-decoder=ass',
|
||||
'--disable-decoder=asv1',
|
||||
'--disable-decoder=asv2',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=aura',
|
||||
'--disable-decoder=aura2',
|
||||
'--disable-decoder=avrn',
|
||||
'--disable-decoder=avrp',
|
||||
'--disable-decoder=avui',
|
||||
'--disable-decoder=ayuv',
|
||||
'--disable-decoder=bethsoftvid',
|
||||
'--disable-decoder=bfi',
|
||||
'--disable-decoder=bink',
|
||||
'--disable-decoder=bintext',
|
||||
'--disable-decoder=bitpacked',
|
||||
'--disable-decoder=bmp',
|
||||
'--disable-decoder=bmv_video',
|
||||
'--disable-decoder=brender_pix',
|
||||
'--disable-decoder=c93',
|
||||
'--disable-decoder=cavs',
|
||||
'--disable-decoder=ccaption',
|
||||
'--disable-decoder=cdgraphics',
|
||||
'--disable-decoder=cdtoons',
|
||||
'--disable-decoder=cdxl',
|
||||
'--disable-decoder=cfhd',
|
||||
'--disable-decoder=cinepak',
|
||||
'--disable-decoder=clearvideo',
|
||||
'--disable-decoder=cljr',
|
||||
'--disable-decoder=cllc',
|
||||
'--disable-decoder=cpia',
|
||||
'--disable-decoder=cscd',
|
||||
'--disable-decoder=cyuv',
|
||||
'--disable-decoder=dds',
|
||||
'--disable-decoder=dirac',
|
||||
'--disable-decoder=dnxhd',
|
||||
'--disable-decoder=dpx',
|
||||
'--disable-decoder=dsicinvideo',
|
||||
'--disable-decoder=dvbsub',
|
||||
'--disable-decoder=dvdsub',
|
||||
'--disable-decoder=dvvideo',
|
||||
'--disable-decoder=dxa',
|
||||
'--disable-decoder=dxtory',
|
||||
'--disable-decoder=dxv',
|
||||
'--disable-decoder=eacmv',
|
||||
'--disable-decoder=eamad',
|
||||
'--disable-decoder=eatgq',
|
||||
'--disable-decoder=eatgv',
|
||||
'--disable-decoder=eatqi',
|
||||
'--disable-decoder=eightbps',
|
||||
'--disable-decoder=escape124',
|
||||
'--disable-decoder=escape130',
|
||||
'--disable-decoder=exr',
|
||||
'--disable-decoder=ffv1',
|
||||
'--disable-decoder=ffvhuff',
|
||||
'--disable-decoder=ffwavesynth',
|
||||
'--disable-decoder=fic',
|
||||
'--disable-decoder=fits',
|
||||
'--disable-decoder=flashsv',
|
||||
'--disable-decoder=flashsv2',
|
||||
'--disable-decoder=flic',
|
||||
'--disable-decoder=flv',
|
||||
'--disable-decoder=fmvc',
|
||||
'--disable-decoder=fraps',
|
||||
'--disable-decoder=fourxm',
|
||||
'--disable-decoder=frwu',
|
||||
'--disable-decoder=g2m',
|
||||
'--disable-decoder=gdv',
|
||||
'--disable-decoder=gem',
|
||||
'--disable-decoder=gif',
|
||||
'--disable-decoder=h261',
|
||||
'--disable-decoder=h263',
|
||||
'--disable-decoder=h263i',
|
||||
'--disable-decoder=h263p',
|
||||
'--disable-decoder=h264',
|
||||
'--disable-decoder=hap',
|
||||
'--disable-decoder=hevc',
|
||||
'--disable-decoder=hnm4_video',
|
||||
'--disable-decoder=hq_hqa',
|
||||
'--disable-decoder=hqx',
|
||||
'--disable-decoder=huffyuv',
|
||||
'--disable-decoder=hymt',
|
||||
'--disable-decoder=idcin',
|
||||
'--disable-decoder=idf',
|
||||
'--disable-decoder=iff_ilbm',
|
||||
'--disable-decoder=imm4',
|
||||
'--disable-decoder=indeo2',
|
||||
'--disable-decoder=indeo3',
|
||||
'--disable-decoder=indeo4',
|
||||
'--disable-decoder=indeo5',
|
||||
'--disable-decoder=interplay_video',
|
||||
'--disable-decoder=ipu',
|
||||
'--disable-decoder=jacosub',
|
||||
'--disable-decoder=jpeg2000',
|
||||
'--disable-decoder=jpegls',
|
||||
'--disable-decoder=jv',
|
||||
'--disable-decoder=kgv1',
|
||||
'--disable-decoder=kmvc',
|
||||
'--disable-decoder=lagarith',
|
||||
'--disable-decoder=loco',
|
||||
'--disable-decoder=lscr',
|
||||
'--disable-decoder=m101',
|
||||
'--disable-decoder=magicyuv',
|
||||
'--disable-decoder=mdec',
|
||||
'--disable-decoder=microdvd',
|
||||
'--disable-decoder=mimic',
|
||||
'--disable-decoder=mjpeg',
|
||||
'--disable-decoder=mmvideo',
|
||||
'--disable-decoder=mpl2',
|
||||
'--disable-decoder=mobiclip',
|
||||
'--disable-decoder=motionpixels',
|
||||
'--disable-decoder=movtext',
|
||||
'--disable-decoder=mpeg1video',
|
||||
'--disable-decoder=mpeg2video',
|
||||
'--disable-decoder=mpeg4',
|
||||
'--disable-decoder=mpegvideo',
|
||||
'--disable-decoder=msa1',
|
||||
'--disable-decoder=mscc',
|
||||
'--disable-decoder=msmpeg4_crystalhd',
|
||||
'--disable-decoder=msmpeg4v1',
|
||||
'--disable-decoder=msmpeg4v2',
|
||||
'--disable-decoder=msmpeg4v3',
|
||||
'--disable-decoder=msp2',
|
||||
'--disable-decoder=msrle',
|
||||
'--disable-decoder=mss1',
|
||||
'--disable-decoder=msvideo1',
|
||||
'--disable-decoder=mszh',
|
||||
'--disable-decoder=mts2',
|
||||
'--disable-decoder=mv30',
|
||||
'--disable-decoder=mvc1',
|
||||
'--disable-decoder=mvc2',
|
||||
'--disable-decoder=mvdv',
|
||||
'--disable-decoder=mvha',
|
||||
'--disable-decoder=mwsc',
|
||||
'--disable-decoder=notchlc',
|
||||
'--disable-decoder=nuv',
|
||||
'--disable-decoder=on2avc',
|
||||
'--disable-decoder=paf_video',
|
||||
'--disable-decoder=pam',
|
||||
'--disable-decoder=pbm',
|
||||
'--disable-decoder=pcx',
|
||||
'--disable-decoder=pgm',
|
||||
'--disable-decoder=pgmyuv',
|
||||
'--disable-decoder=pgssub',
|
||||
'--disable-decoder=pgx',
|
||||
'--disable-decoder=phm',
|
||||
'--disable-decoder=photocd',
|
||||
'--disable-decoder=png',
|
||||
'--disable-decoder=pictor',
|
||||
'--disable-decoder=pixlet',
|
||||
'--disable-decoder=pjs',
|
||||
'--disable-decoder=ppm',
|
||||
'--disable-decoder=prores',
|
||||
'--disable-decoder=prosumer',
|
||||
'--disable-decoder=psd',
|
||||
'--disable-decoder=ptx',
|
||||
'--disable-decoder=qdraw',
|
||||
'--disable-decoder=qoi',
|
||||
'--disable-decoder=qpeg',
|
||||
'--disable-decoder=qtrle',
|
||||
'--disable-decoder=rawvideo',
|
||||
'--disable-decoder=r10k',
|
||||
'--disable-decoder=r210',
|
||||
'--disable-decoder=rasc',
|
||||
'--disable-decoder=realtext',
|
||||
'--disable-decoder=rl2',
|
||||
'--disable-decoder=rpza',
|
||||
'--disable-decoder=roq',
|
||||
'--disable-decoder=roq_dpcm',
|
||||
'--disable-decoder=rscc',
|
||||
@ -346,53 +496,120 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=rv30',
|
||||
'--disable-decoder=rv40',
|
||||
'--disable-decoder=sami',
|
||||
'--disable-decoder=sanm',
|
||||
'--disable-decoder=scpr',
|
||||
'--disable-decoder=screenpresso',
|
||||
'--disable-decoder=sga',
|
||||
'--disable-decoder=sgi',
|
||||
'--disable-decoder=sgirle',
|
||||
'--disable-decoder=sheervideo',
|
||||
'--disable-decoder=simbiosis_imx',
|
||||
'--disable-decoder=smc',
|
||||
'--disable-decoder=snow',
|
||||
'--disable-decoder=speedhq',
|
||||
'--disable-decoder=srgc',
|
||||
'--disable-decoder=srt',
|
||||
'--disable-decoder=ssa',
|
||||
'--disable-decoder=stl',
|
||||
'--disable-decoder=subrip',
|
||||
'--disable-decoder=subviewer',
|
||||
'--disable-decoder=subviewer1',
|
||||
'--disable-decoder=sunrast',
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=targa',
|
||||
'--disable-decoder=targa_y216',
|
||||
'--disable-decoder=text',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=tmv',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
'--disable-decoder=tscc',
|
||||
'--disable-decoder=tscc2',
|
||||
'--disable-decoder=twinvq',
|
||||
'--disable-decoder=txd',
|
||||
'--disable-decoder=ulti',
|
||||
'--disable-decoder=utvideo',
|
||||
'--disable-decoder=v210',
|
||||
'--disable-decoder=v210x',
|
||||
'--disable-decoder=v308',
|
||||
'--disable-decoder=v408',
|
||||
'--disable-decoder=v410',
|
||||
'--disable-decoder=vb',
|
||||
'--disable-decoder=vble',
|
||||
'--disable-decoder=vbn',
|
||||
'--disable-decoder=vc1',
|
||||
'--disable-decoder=vcr1',
|
||||
'--disable-decoder=vmdvideo',
|
||||
'--disable-decoder=vmnc',
|
||||
'--disable-decoder=vp3',
|
||||
'--disable-decoder=vp5',
|
||||
'--disable-decoder=vp6',
|
||||
'--disable-decoder=vp7',
|
||||
'--disable-decoder=vp8',
|
||||
'--disable-decoder=vp9',
|
||||
'--disable-decoder=vplayer',
|
||||
'--disable-decoder=vqa',
|
||||
'--disable-decoder=webvtt',
|
||||
'--disable-decoder=wcmv',
|
||||
'--disable-decoder=wmv1',
|
||||
'--disable-decoder=wmv2',
|
||||
'--disable-decoder=wmv3',
|
||||
'--disable-decoder=wnv1',
|
||||
'--disable-decoder=wrapped_avframe',
|
||||
'--disable-decoder=xan_wc3',
|
||||
'--disable-decoder=xan_wc4',
|
||||
'--disable-decoder=xbin',
|
||||
'--disable-decoder=xbm',
|
||||
'--disable-decoder=xface',
|
||||
'--disable-decoder=xl',
|
||||
'--disable-decoder=xpm',
|
||||
'--disable-decoder=xsub',
|
||||
'--disable-decoder=xwd',
|
||||
'--disable-decoder=y41p',
|
||||
'--disable-decoder=ylc',
|
||||
'--disable-decoder=yop',
|
||||
'--disable-decoder=yuv4',
|
||||
'--disable-decoder=zero12v',
|
||||
'--disable-decoder=zerocodec',
|
||||
'--disable-decoder=zlib',
|
||||
'--disable-decoder=zmbv',
|
||||
|
||||
'--disable-bsf=av1_frame_merge',
|
||||
'--disable-bsf=av1_frame_split',
|
||||
'--disable-bsf=av1_metadata',
|
||||
'--disable-bsf=dts2pts',
|
||||
'--disable-bsf=h264_metadata',
|
||||
'--disable-bsf=h264_mp4toannexb',
|
||||
'--disable-bsf=h264_redundant_pps',
|
||||
'--disable-bsf=hevc_metadata',
|
||||
'--disable-bsf=hevc_mp4toannexb',
|
||||
'--disable-bsf=mjpeg2jpeg',
|
||||
'--disable-bsf=opus_metadata',
|
||||
'--disable-bsf=pgs_frame_merge',
|
||||
'--disable-bsf=text2movsub',
|
||||
'--disable-bsf=vp9_metadata',
|
||||
'--disable-bsf=vp9_raw_reorder',
|
||||
'--disable-bsf=vp9_superframe',
|
||||
'--disable-bsf=vp9_superframe_split',
|
||||
],
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.1.tar.gz',
|
||||
'c311ad853353bce796edad01a862c50a8a587f62e7e2100ef465ab53ec9b06d1',
|
||||
'https://www.openssl.org/source/openssl-3.1.0.tar.gz',
|
||||
'aaa925ad9828745c4cad9d9efeb273deca820f2cdcf2c3ac7d7c1212b7c497b4',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = CmakeProject(
|
||||
'https://curl.se/download/curl-7.82.0.tar.xz',
|
||||
'0aaa12d7bd04b0966254f2703ce80dd5c38dbbd76af0297d3d690cdce58a583c',
|
||||
'https://curl.se/download/curl-8.0.1.tar.xz',
|
||||
'0a381cd82f4d00a9a334438b8ca239afea5bfefcfa9a1025f2bf118e79e0b5f0',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'-DBUILD_CURL_EXE=OFF',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
|
||||
'-DCURL_DISABLE_LDAP=ON',
|
||||
'-DCURL_DISABLE_TELNET=ON',
|
||||
'-DCURL_DISABLE_DICT=ON',
|
||||
@ -421,8 +638,8 @@ curl = CmakeProject(
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.1.tar.gz',
|
||||
'7ef445410b42f36b9bad426608b53ccb9ccca4101e545c383f564c11db672ca8',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
|
||||
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -433,7 +650,7 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-5.0.1',
|
||||
base='libnfs-libnfs-5.0.2',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
@ -444,7 +661,7 @@ jack = JackProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.78.0/source/boost_1_78_0.tar.bz2',
|
||||
'8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc',
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2',
|
||||
'71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import subprocess
|
||||
import subprocess, multiprocessing
|
||||
|
||||
from build.project import Project
|
||||
|
||||
@ -10,7 +10,12 @@ class MakeProject(Project):
|
||||
self.install_target = install_target
|
||||
|
||||
def get_simultaneous_jobs(self):
|
||||
return 12
|
||||
try:
|
||||
# use twice as many simultaneous jobs as we have CPU cores
|
||||
return multiprocessing.cpu_count() * 2
|
||||
except NotImplementedError:
|
||||
# default to 12, if multiprocessing.cpu_count() is not implemented
|
||||
return 12
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
|
||||
@ -19,7 +24,7 @@ class MakeProject(Project):
|
||||
return ['--quiet', self.install_target]
|
||||
|
||||
def make(self, toolchain, wd, args):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
subprocess.check_call(['make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build_make(self, toolchain, wd, install=True):
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os.path, subprocess, sys
|
||||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
from build.project import Project
|
||||
@ -82,8 +83,8 @@ endian = '{endian}'
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_file = make_cross_file(toolchain)
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
'meson', 'setup',
|
||||
build, src,
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
@ -115,5 +116,5 @@ class MesonProject(Project):
|
||||
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -14,13 +14,14 @@ class Project:
|
||||
if base is None:
|
||||
basename = os.path.basename(url)
|
||||
m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
|
||||
if not m: raise
|
||||
if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
|
||||
self.base = m.group(1)
|
||||
else:
|
||||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
|
||||
if not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
@ -55,8 +56,8 @@ class Project:
|
||||
parent_path = toolchain.src_path
|
||||
else:
|
||||
parent_path = toolchain.build_path
|
||||
path = untar(self.download(toolchain), parent_path, self.base)
|
||||
|
||||
path = untar(self.download(toolchain), parent_path, self.base,
|
||||
lazy=out_of_tree and self.patches is None)
|
||||
if self.patches is not None:
|
||||
push_all(toolchain, path, self.patches)
|
||||
|
||||
@ -71,8 +72,10 @@ class Project:
|
||||
|
||||
return path
|
||||
|
||||
def make_build_path(self, toolchain):
|
||||
def make_build_path(self, toolchain, lazy=False):
|
||||
path = os.path.join(toolchain.build_path, self.base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
|
@ -1,14 +1,16 @@
|
||||
import os, shutil, subprocess
|
||||
|
||||
def untar(tarball_path, parent_path, base):
|
||||
def untar(tarball_path, parent_path, base, lazy=False):
|
||||
path = os.path.join(parent_path, base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.makedirs(parent_path, exist_ok=True)
|
||||
try:
|
||||
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
|
||||
subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
|
||||
except FileNotFoundError:
|
||||
import tarfile
|
||||
tar = tarfile.open(tarball_path)
|
||||
|
@ -1,22 +1,32 @@
|
||||
import os.path, subprocess
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
from build.makeproject import MakeProject
|
||||
|
||||
class ZlibProject(Project):
|
||||
class ZlibProject(MakeProject):
|
||||
def __init__(self, url, md5, installed,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
return MakeProject.get_make_args(self, toolchain) + [
|
||||
'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
|
||||
'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
|
||||
'AR=' + toolchain.ar,
|
||||
'ARFLAGS=' + toolchain.arflags,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'LDSHARED=' + toolchain.cc + ' -shared',
|
||||
'libz.a'
|
||||
]
|
||||
|
||||
def get_make_install_args(self, toolchain):
|
||||
return [
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
self.install_target
|
||||
]
|
||||
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet',
|
||||
'-f', 'win32/Makefile.gcc',
|
||||
'PREFIX=' + toolchain.arch + '-',
|
||||
'-j12',
|
||||
'install',
|
||||
'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
|
||||
'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
|
||||
'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
|
||||
],
|
||||
cwd=src, env=toolchain.env)
|
||||
subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
|
||||
cwd=src, env=toolchain.env)
|
||||
self.build_make(toolchain, src)
|
||||
|
@ -352,12 +352,16 @@ ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
break;
|
||||
|
||||
case OPTION_NO_DAEMON:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case OPTION_SYSTEMD:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
options.systemd = true;
|
||||
break;
|
||||
#endif
|
||||
|
@ -20,11 +20,18 @@
|
||||
#ifndef MPD_COMMAND_LINE_HXX
|
||||
#define MPD_COMMAND_LINE_HXX
|
||||
|
||||
#include "config.h" // for ENABLE_DAEMON
|
||||
|
||||
struct ConfigData;
|
||||
|
||||
struct CommandLineOptions {
|
||||
bool kill = false;
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
bool daemon = true;
|
||||
#else
|
||||
static constexpr bool daemon = false;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
bool systemd = false;
|
||||
|
@ -45,7 +45,10 @@ void
|
||||
LogFmt(LogLevel level, const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return LogVFmt(level, domain, format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return LogVFmt(level, domain, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
|
@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
|
||||
getenv("NOTIFY_SOCKET") != nullptr) {
|
||||
/* if MPD was started as a systemd
|
||||
service, default to journal (which
|
||||
is connected to fd=2) */
|
||||
is connected to stdout&stderr) */
|
||||
out_fd = STDOUT_FILENO;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifndef HAVE_SYSLOG
|
||||
#ifdef _WIN32
|
||||
/* default to stdout on Windows */
|
||||
out_fd = STDOUT_FILENO;
|
||||
#elif !defined(HAVE_SYSLOG)
|
||||
throw std::runtime_error("config parameter 'log_file' not found");
|
||||
#endif
|
||||
#ifdef HAVE_SYSLOG
|
||||
|
53
src/Main.cxx
53
src/Main.cxx
@ -482,7 +482,10 @@ MainConfigured(const CommandLineOptions &options,
|
||||
#ifndef ANDROID
|
||||
setup_log_output();
|
||||
|
||||
const ScopeSignalHandlersInit signal_handlers_init(instance);
|
||||
const ScopeSignalHandlersInit signal_handlers_init{
|
||||
instance,
|
||||
options.daemon,
|
||||
};
|
||||
#endif
|
||||
|
||||
instance.io_thread.Start();
|
||||
@ -590,19 +593,46 @@ MainConfigured(const CommandLineOptions &options,
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
/**
|
||||
* Wrapper for ReadConfigFile() which returns false if the file was
|
||||
* not found.
|
||||
*/
|
||||
static bool
|
||||
TryReadConfigFile(ConfigData &config, Path path)
|
||||
{
|
||||
if (!FileExists(path))
|
||||
return false;
|
||||
|
||||
ReadConfigFile(config, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
AndroidMain()
|
||||
LoadConfigFile(JNIEnv *env, ConfigData &config)
|
||||
{
|
||||
/* try loading mpd.conf from
|
||||
"Android/data/org.musicpd/files/mpd.conf" (the app specific
|
||||
data directory) first */
|
||||
if (const auto dir = context->GetExternalFilesDir(env);
|
||||
!dir.IsNull() &&
|
||||
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
|
||||
return;
|
||||
|
||||
/* if that fails, attempt to load "mpd.conf" from the root of
|
||||
the SD card (pre-0.23.9, ceases to work since Android
|
||||
12) */
|
||||
if (const auto dir = Environment::getExternalStorageDirectory(env);
|
||||
!dir.IsNull())
|
||||
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
|
||||
}
|
||||
|
||||
static void
|
||||
AndroidMain(JNIEnv *env)
|
||||
{
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
if (!sdcard.IsNull()) {
|
||||
const auto config_path =
|
||||
sdcard / Path::FromFS("mpd.conf");
|
||||
if (FileExists(config_path))
|
||||
ReadConfigFile(raw_config, config_path);
|
||||
}
|
||||
LoadConfigFile(env, raw_config);
|
||||
|
||||
MainConfigured(options, raw_config);
|
||||
}
|
||||
@ -614,9 +644,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
Java::Init(env);
|
||||
Java::Object::Initialise(env);
|
||||
Java::File::Initialise(env);
|
||||
|
||||
Environment::Initialise(env);
|
||||
AtScopeExit(env) { Environment::Deinitialise(env); };
|
||||
|
||||
Context::Initialise(env);
|
||||
|
||||
context = new Context(env, _context);
|
||||
AtScopeExit() { delete context; };
|
||||
|
||||
@ -625,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
AtScopeExit() { delete logListener; };
|
||||
|
||||
try {
|
||||
AndroidMain();
|
||||
AndroidMain(env);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "Log.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "client/Listener.hxx"
|
||||
#include "client/Client.hxx"
|
||||
@ -206,7 +205,7 @@ Partition::OnBorderPause() noexcept
|
||||
void
|
||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
||||
{
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
|
||||
/* notify clients */
|
||||
EmitIdle(IDLE_MIXER);
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "queue/Listener.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "player/Control.hxx"
|
||||
#include "player/Listener.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
@ -76,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
|
||||
MultipleOutputs outputs;
|
||||
|
||||
MixerMemento mixer_memento;
|
||||
|
||||
PlayerControl pc;
|
||||
|
||||
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
||||
|
@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
|
||||
*/
|
||||
|
||||
return std::strchr(name_utf8, '/') == nullptr &&
|
||||
#ifdef _WIN32
|
||||
std::strchr(name_utf8, '\\') == nullptr &&
|
||||
#endif
|
||||
std::strchr(name_utf8, '\n') == nullptr &&
|
||||
std::strchr(name_utf8, '\r') == nullptr;
|
||||
}
|
||||
|
@ -28,7 +28,11 @@
|
||||
#include <boost/intrusive/list.hpp>
|
||||
#include <boost/intrusive/unordered_set.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class RemoteTagCacheHandler;
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
@ -93,7 +94,7 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
|
||||
time_print(r, "Last-Modified", song.mtime);
|
||||
|
||||
if (song.audio_format.IsDefined())
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format));
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), song.audio_format);
|
||||
|
||||
tag_print_values(r, song.tag);
|
||||
|
||||
@ -116,7 +117,7 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
|
||||
time_print(r, "Last-Modified", song.GetLastModified());
|
||||
|
||||
if (const auto &f = song.GetAudioFormat(); f.IsDefined())
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), ToString(f));
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), f);
|
||||
|
||||
tag_print_values(r, song.GetTag());
|
||||
|
||||
|
@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
|
||||
if (song.audio_format.IsDefined())
|
||||
os.Format("Format: %s\n", ToString(song.audio_format).c_str());
|
||||
|
||||
if (song.in_playlist)
|
||||
os.Write("InPlaylist: yes\n");
|
||||
|
||||
if (!IsNegative(song.mtime))
|
||||
os.Format(SONG_MTIME ": %li\n",
|
||||
(long)std::chrono::system_clock::to_time_t(song.mtime));
|
||||
@ -86,7 +89,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
|
||||
|
||||
DetachedSong
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r)
|
||||
std::string *target_r, bool *in_playlist_r)
|
||||
{
|
||||
DetachedSong song(uri);
|
||||
|
||||
@ -132,6 +135,9 @@ song_load(LineReader &file, const char *uri,
|
||||
|
||||
song.SetStartTime(SongTime::FromMS(start_ms));
|
||||
song.SetEndTime(SongTime::FromMS(end_ms));
|
||||
} else if (StringIsEqual(line, "InPlaylist")) {
|
||||
if (in_playlist_r != nullptr)
|
||||
*in_playlist_r = StringIsEqual(value, "yes");
|
||||
} else {
|
||||
throw FormatRuntimeError("unknown line in db: %s", line);
|
||||
}
|
||||
|
@ -44,6 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
|
||||
*/
|
||||
DetachedSong
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r=nullptr);
|
||||
std::string *target_r=nullptr, bool *in_playlist_r=nullptr);
|
||||
|
||||
#endif
|
||||
|
@ -27,7 +27,6 @@
|
||||
#include "storage/StorageState.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
|
||||
void
|
||||
StateFile::RememberVersions() noexcept
|
||||
{
|
||||
prev_volume_version = sw_volume_state_get_hash();
|
||||
prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
|
||||
prev_output_version = audio_output_state_get_version();
|
||||
prev_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
|
||||
bool
|
||||
StateFile::IsModified() const noexcept
|
||||
{
|
||||
return prev_volume_version != sw_volume_state_get_hash() ||
|
||||
return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
|
||||
prev_output_version != audio_output_state_get_version() ||
|
||||
prev_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||
partition.pc)
|
||||
@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
|
||||
inline void
|
||||
StateFile::Write(BufferedOutputStream &os)
|
||||
{
|
||||
save_sw_volume_state(os);
|
||||
partition.mixer_memento.SaveSoftwareVolumeState(os);
|
||||
audio_output_state_save(os, partition.outputs);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
@ -125,7 +124,7 @@ try {
|
||||
|
||||
const char *line;
|
||||
while ((line = file.ReadLine()) != nullptr) {
|
||||
success = read_sw_volume_state(line, partition.outputs) ||
|
||||
success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
|
||||
audio_output_state_read(line, partition.outputs) ||
|
||||
playlist_state_restore(config, line, file, song_loader,
|
||||
partition.playlist,
|
||||
|
@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (path.IsNull()) {
|
||||
const auto cache_dir = GetUserCacheDir();
|
||||
const auto cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
return;
|
||||
|
||||
|
@ -35,8 +35,9 @@ tag_print_types(Response &r) noexcept
|
||||
}
|
||||
|
||||
void
|
||||
tag_print(Response &r, TagType type, StringView value) noexcept
|
||||
tag_print(Response &r, TagType type, StringView _value) noexcept
|
||||
{
|
||||
const std::string_view value{_value};
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
|
||||
}
|
||||
|
||||
|
@ -36,5 +36,5 @@ time_print(Response &r, const char *name,
|
||||
return;
|
||||
}
|
||||
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), name, s);
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), name, s.c_str());
|
||||
}
|
||||
|
@ -26,19 +26,30 @@
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
|
||||
static jmethodID getExternalFilesDir_method,
|
||||
getCacheDir_method,
|
||||
getSystemService_method;
|
||||
|
||||
void
|
||||
Context::Initialise(JNIEnv *env) noexcept
|
||||
{
|
||||
assert(_type != nullptr);
|
||||
Java::Class cls{env, "android/content/Context"};
|
||||
|
||||
Java::Class cls{env, env->GetObjectClass(Get())};
|
||||
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
assert(method);
|
||||
getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
getCacheDir_method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()Ljava/io/File;");
|
||||
getSystemService_method = env->GetMethodID(cls, "getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
Java::String type{env, _type};
|
||||
AllocatedPath
|
||||
Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept
|
||||
{
|
||||
assert(type != nullptr);
|
||||
|
||||
jobject file = env->CallObjectMethod(Get(), method, type.Get());
|
||||
jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method,
|
||||
Java::String::Optional(env, type).Get());
|
||||
if (Java::DiscardException(env) || file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@ -50,12 +61,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getCacheDir",
|
||||
"()Ljava/io/File;");
|
||||
assert(method);
|
||||
|
||||
jobject file = env->CallObjectMethod(Get(), method);
|
||||
jobject file = env->CallObjectMethod(Get(), getCacheDir_method);
|
||||
if (Java::DiscardException(env) || file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
@ -67,13 +73,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
jmethodID method = env->GetMethodID(cls, "getSystemService",
|
||||
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
assert(method);
|
||||
|
||||
Java::String name(env, "audio");
|
||||
jobject am = env->CallObjectMethod(Get(), method, name.Get());
|
||||
jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get());
|
||||
if (Java::DiscardException(env) || am == nullptr)
|
||||
return nullptr;
|
||||
|
||||
|
@ -27,12 +27,21 @@ class AudioManager;
|
||||
|
||||
class Context : public Java::GlobalObject {
|
||||
public:
|
||||
/**
|
||||
* Global initialisation. Looks up the methods of the
|
||||
* Context Java class.
|
||||
*/
|
||||
static void Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
Context(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
|
||||
/**
|
||||
* @param type the subdirectory name; may be nullptr
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
||||
const char *type) noexcept;
|
||||
const char *type=nullptr) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||
|
@ -25,13 +25,13 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
namespace Environment {
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
}
|
||||
|
||||
static Java::TrivialClass cls;
|
||||
static jmethodID getExternalStorageDirectory_method;
|
||||
static jmethodID getExternalStoragePublicDirectory_method;
|
||||
|
||||
void
|
||||
Environment::Initialise(JNIEnv *env) noexcept
|
||||
Initialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Find(env, "android/os/Environment");
|
||||
|
||||
@ -45,16 +45,14 @@ Environment::Initialise(JNIEnv *env) noexcept
|
||||
}
|
||||
|
||||
void
|
||||
Environment::Deinitialise(JNIEnv *env) noexcept
|
||||
Deinitialise(JNIEnv *env) noexcept
|
||||
{
|
||||
cls.Clear(env);
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStorageDirectory() noexcept
|
||||
getExternalStorageDirectory(JNIEnv *env) noexcept
|
||||
{
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
jobject file =
|
||||
env->CallStaticObjectMethod(cls,
|
||||
getExternalStorageDirectory_method);
|
||||
@ -65,20 +63,20 @@ Environment::getExternalStorageDirectory() noexcept
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Environment::getExternalStoragePublicDirectory(const char *type) noexcept
|
||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept
|
||||
{
|
||||
if (getExternalStoragePublicDirectory_method == nullptr)
|
||||
/* needs API level 8 */
|
||||
return nullptr;
|
||||
|
||||
JNIEnv *env = Java::GetEnv();
|
||||
|
||||
Java::String type2(env, type);
|
||||
jobject file = env->CallStaticObjectMethod(Environment::cls,
|
||||
Environment::getExternalStoragePublicDirectory_method,
|
||||
jobject file = env->CallStaticObjectMethod(cls,
|
||||
getExternalStoragePublicDirectory_method,
|
||||
type2.Get());
|
||||
if (file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return Java::File::ToAbsolutePath(env, file);
|
||||
}
|
||||
|
||||
} // namespace Environment
|
||||
|
@ -17,27 +17,29 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ANDROID_ENVIRONMENT_HXX
|
||||
#define MPD_ANDROID_ENVIRONMENT_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
class AllocatedPath;
|
||||
|
||||
namespace Environment {
|
||||
void Initialise(JNIEnv *env) noexcept;
|
||||
void Deinitialise(JNIEnv *env) noexcept;
|
||||
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStorageDirectory() noexcept;
|
||||
void
|
||||
Initialise(JNIEnv *env) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
|
||||
}
|
||||
void
|
||||
Deinitialise(JNIEnv *env) noexcept;
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
getExternalStorageDirectory(JNIEnv *env) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept;
|
||||
|
||||
} // namespace Environment
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "ErrorRef.hxx"
|
||||
#include "StringRef.hxx"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Apple {
|
||||
@ -57,8 +58,8 @@ ThrowOSStatus(OSStatus status, const char *_msg)
|
||||
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||
|
||||
char msg[1024];
|
||||
strcpy(msg, _msg);
|
||||
size_t length = strlen(msg);
|
||||
std::strcpy(msg, _msg);
|
||||
size_t length = std::strlen(msg);
|
||||
|
||||
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||
throw std::runtime_error(msg);
|
||||
|
@ -166,7 +166,7 @@ class Iso9660InputStream final : public InputStream {
|
||||
assert(fill <= data.size());
|
||||
assert(position <= fill);
|
||||
|
||||
return {&data[position], &data[fill]};
|
||||
return {data.data() + position, data.data() + fill};
|
||||
}
|
||||
|
||||
void Consume(size_t nbytes) noexcept {
|
||||
|
@ -22,6 +22,10 @@ if libzzip_dep.found()
|
||||
found_archive_plugin = true
|
||||
endif
|
||||
|
||||
if not found_archive_plugin
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
archive_plugins = static_library(
|
||||
'archive_plugins',
|
||||
archive_plugins_sources,
|
||||
|
@ -82,7 +82,10 @@ public:
|
||||
|
||||
template<typename S, typename... Args>
|
||||
bool Fmt(const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return VFmt(format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return VFmt(fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
@ -109,7 +112,10 @@ public:
|
||||
template<typename S, typename... Args>
|
||||
void FmtError(enum ack code,
|
||||
const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
#if FMT_VERSION >= 90000
|
||||
return VFmtError(code, format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif FMT_VERSION >= 70000
|
||||
return VFmtError(code, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
|
@ -41,6 +41,8 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <limits.h> // for UINT_MAX
|
||||
|
||||
CommandResult
|
||||
handle_listfiles_db(Client &client, Response &r, const char *uri)
|
||||
{
|
||||
|
@ -100,10 +100,6 @@ handle_listfiles_local(Response &r, Path path_fs)
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
gcc_pure
|
||||
static bool
|
||||
IsValidName(const StringView s) noexcept
|
||||
@ -130,7 +126,8 @@ public:
|
||||
explicit PrintCommentHandler(Response &_response) noexcept
|
||||
:NullTagHandler(WANT_PAIR), response(_response) {}
|
||||
|
||||
void OnPair(StringView key, StringView value) noexcept override {
|
||||
void OnPair(StringView _key, StringView _value) noexcept override {
|
||||
const std::string_view key{_key}, value{_value};
|
||||
if (IsValidName(key) && IsValidValue(value))
|
||||
response.Fmt(FMT_STRING("{}: {}\n"), key, value);
|
||||
}
|
||||
|
@ -33,7 +33,6 @@
|
||||
#include "TimePrint.hxx"
|
||||
#include "decoder/DecoderPrint.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/StringAPI.hxx"
|
||||
@ -325,7 +324,7 @@ handle_getvol(Client &client, Request, Response &r)
|
||||
{
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
const auto volume = volume_level_get(partition.outputs);
|
||||
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||
if (volume >= 0)
|
||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||
|
||||
@ -333,15 +332,13 @@ handle_getvol(Client &client, Request, Response &r)
|
||||
}
|
||||
|
||||
CommandResult
|
||||
handle_setvol(Client &client, Request args, Response &r)
|
||||
handle_setvol(Client &client, Request args, Response &)
|
||||
{
|
||||
unsigned level = args.ParseUnsigned(0, 100);
|
||||
|
||||
if (!volume_level_change(client.GetPartition().outputs, level)) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
||||
auto &partition = client.GetPartition();
|
||||
partition.mixer_memento.SetVolume(partition.outputs, level);
|
||||
partition.EmitIdle(IDLE_MIXER);
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
@ -350,9 +347,11 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
{
|
||||
int relative = args.ParseInt(0, -100, 100);
|
||||
|
||||
auto &outputs = client.GetPartition().outputs;
|
||||
auto &partition = client.GetPartition();
|
||||
auto &outputs = partition.outputs;
|
||||
auto &mixer_memento = partition.mixer_memento;
|
||||
|
||||
const int old_volume = volume_level_get(outputs);
|
||||
const int old_volume = mixer_memento.GetVolume(outputs);
|
||||
if (old_volume < 0) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "No mixer");
|
||||
return CommandResult::ERROR;
|
||||
@ -364,10 +363,9 @@ handle_volume(Client &client, Request args, Response &r)
|
||||
else if (new_volume > 100)
|
||||
new_volume = 100;
|
||||
|
||||
if (new_volume != old_volume &&
|
||||
!volume_level_change(outputs, new_volume)) {
|
||||
r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
|
||||
return CommandResult::ERROR;
|
||||
if (new_volume != old_volume) {
|
||||
mixer_memento.SetVolume(outputs, new_volume);
|
||||
partition.EmitIdle(IDLE_MIXER);
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
|
@ -33,7 +33,11 @@ handle_enableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
unsigned device = args.ParseUnsigned(0);
|
||||
|
||||
if (!audio_output_enable_index(client.GetPartition().outputs, device)) {
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
if (!audio_output_enable_index(partition.outputs,
|
||||
partition.mixer_memento,
|
||||
device)) {
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@ -47,7 +51,11 @@ handle_disableoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
unsigned device = args.ParseUnsigned(0);
|
||||
|
||||
if (!audio_output_disable_index(client.GetPartition().outputs, device)) {
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
if (!audio_output_disable_index(partition.outputs,
|
||||
partition.mixer_memento,
|
||||
device)) {
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
@ -61,7 +69,11 @@ handle_toggleoutput(Client &client, Request args, Response &r)
|
||||
assert(args.size == 1);
|
||||
unsigned device = args.ParseUnsigned(0);
|
||||
|
||||
if (!audio_output_toggle_index(client.GetPartition().outputs, device)) {
|
||||
auto &partition = client.GetPartition();
|
||||
|
||||
if (!audio_output_toggle_index(partition.outputs,
|
||||
partition.mixer_memento,
|
||||
device)) {
|
||||
r.Error(ACK_ERROR_NO_EXIST, "No such audio output");
|
||||
return CommandResult::ERROR;
|
||||
}
|
||||
|
@ -25,10 +25,10 @@
|
||||
#include "SingleMode.hxx"
|
||||
#include "client/Client.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/Exception.hxx"
|
||||
@ -131,7 +131,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
||||
|
||||
const auto &playlist = partition.playlist;
|
||||
|
||||
const auto volume = volume_level_get(partition.outputs);
|
||||
const auto volume = partition.mixer_memento.GetVolume(partition.outputs);
|
||||
if (volume >= 0)
|
||||
r.Fmt(FMT_STRING("volume: {}\n"), volume);
|
||||
|
||||
@ -186,7 +186,7 @@ handle_status(Client &client, [[maybe_unused]] Request args, Response &r)
|
||||
|
||||
if (player_status.audio_format.IsDefined())
|
||||
r.Fmt(FMT_STRING(COMMAND_STATUS_AUDIO ": {}\n"),
|
||||
ToString(player_status.audio_format));
|
||||
player_status.audio_format);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
@ -83,10 +83,6 @@ handle_listfiles_storage(Response &r, StorageDirectoryReader &reader)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && GCC_CHECK_VERSION(4,6)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
CommandResult
|
||||
handle_listfiles_storage(Response &r, Storage &storage, const char *uri)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "config/Param.hxx"
|
||||
#include "config/Block.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
@ -51,17 +52,30 @@ CreateConfiguredDatabase(const ConfigData &config,
|
||||
} else {
|
||||
/* if there is no override, use the cache directory */
|
||||
|
||||
const AllocatedPath cache_dir = GetUserCacheDir();
|
||||
const AllocatedPath cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
return nullptr;
|
||||
|
||||
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("mpd.db"));
|
||||
const auto db_file = cache_dir / Path::FromFS(PATH_LITERAL("db"));
|
||||
auto db_file_utf8 = db_file.ToUTF8();
|
||||
if (db_file_utf8.empty())
|
||||
return nullptr;
|
||||
|
||||
ConfigBlock block;
|
||||
block.AddBlockParam("path", std::move(db_file_utf8), -1);
|
||||
|
||||
{
|
||||
const auto mounts_dir = cache_dir
|
||||
/ Path::FromFS(PATH_LITERAL("mounts"));
|
||||
CreateDirectoryNoThrow(mounts_dir);
|
||||
|
||||
if (auto mounts_dir_utf8 = mounts_dir.ToUTF8();
|
||||
!mounts_dir_utf8.empty())
|
||||
block.AddBlockParam("cache_directory",
|
||||
std::move(mounts_dir_utf8),
|
||||
-1);
|
||||
}
|
||||
|
||||
return DatabaseGlobalInit(main_event_loop, io_event_loop,
|
||||
listener, block);
|
||||
}
|
||||
|
@ -126,6 +126,18 @@ Directory::LookupTargetSong(std::string_view _target) noexcept
|
||||
return lr.directory->FindSong(lr.rest);
|
||||
}
|
||||
|
||||
void
|
||||
Directory::ClearInPlaylist() noexcept
|
||||
{
|
||||
assert(holding_db_lock());
|
||||
|
||||
for (auto &child : children)
|
||||
child.ClearInPlaylist();
|
||||
|
||||
for (auto &song : songs)
|
||||
song.in_playlist = false;
|
||||
}
|
||||
|
||||
void
|
||||
Directory::PruneEmpty() noexcept
|
||||
{
|
||||
|
@ -287,6 +287,14 @@ public:
|
||||
*/
|
||||
SongPtr RemoveSong(Song *song) noexcept;
|
||||
|
||||
/**
|
||||
* Recursively walk through the whole tree and set all
|
||||
* `Song::in_playlist` fields to `false`.
|
||||
*
|
||||
* Caller must lock the #db_mutex.
|
||||
*/
|
||||
void ClearInPlaylist() noexcept;
|
||||
|
||||
/**
|
||||
* Caller must lock the #db_mutex.
|
||||
*/
|
||||
|
@ -168,12 +168,14 @@ directory_load(LineReader &file, Directory &directory)
|
||||
throw FormatRuntimeError("Duplicate song '%s'", name);
|
||||
|
||||
std::string target;
|
||||
bool in_playlist = false;
|
||||
auto detached_song = song_load(file, name,
|
||||
&target);
|
||||
&target, &in_playlist);
|
||||
|
||||
auto song = std::make_unique<Song>(std::move(detached_song),
|
||||
directory);
|
||||
song->target = std::move(target);
|
||||
song->in_playlist = in_playlist;
|
||||
|
||||
directory.AddSong(std::move(song));
|
||||
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
|
||||
|
@ -51,6 +51,15 @@ LockFindSong(Directory &directory, std::string_view name) noexcept
|
||||
return directory.FindSong(name);
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static bool
|
||||
IsAcceptableFilename(std::string_view name) noexcept
|
||||
{
|
||||
return !name.empty() &&
|
||||
/* newlines cannot be represented in MPD's protocol */
|
||||
name.find('\n') == name.npos;
|
||||
}
|
||||
|
||||
void
|
||||
UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
const char *name) noexcept
|
||||
@ -58,6 +67,9 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
const char *tmp = std::strchr(name, '/');
|
||||
if (tmp) {
|
||||
const std::string_view child_name(name, tmp - name);
|
||||
if (!IsAcceptableFilename(child_name))
|
||||
return;
|
||||
|
||||
//add dir is not there already
|
||||
Directory *subdir = LockMakeChild(directory, child_name);
|
||||
subdir->device = DEVICE_INARCHIVE;
|
||||
@ -65,11 +77,8 @@ UpdateWalk::UpdateArchiveTree(ArchiveFile &archive, Directory &directory,
|
||||
//create directories first
|
||||
UpdateArchiveTree(archive, *subdir, tmp + 1);
|
||||
} else {
|
||||
if (StringIsEmpty(name)) {
|
||||
LogWarning(update_domain,
|
||||
"archive returned directory only");
|
||||
if (!IsAcceptableFilename(name))
|
||||
return;
|
||||
}
|
||||
|
||||
//add file
|
||||
Song *song = LockFindSong(directory, name);
|
||||
|
@ -531,6 +531,7 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
||||
|
||||
{
|
||||
const ScopeDatabaseLock protect;
|
||||
root.ClearInPlaylist();
|
||||
PurgeDanglingFromPlaylists(root);
|
||||
}
|
||||
|
||||
|
@ -257,6 +257,12 @@ public:
|
||||
return HasFailed();
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
bool LockIsReplayGainEnabled() const noexcept {
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return replay_gain_mode != ReplayGainMode::OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition this obejct from DecoderState::START to
|
||||
* DecoderState::DECODE.
|
||||
|
@ -114,11 +114,11 @@ constexpr const struct DecoderPlugin *decoder_plugins[] = {
|
||||
#ifdef ENABLE_ADPLUG
|
||||
&adplug_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FFMPEG
|
||||
&ffmpeg_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_GME
|
||||
&gme_decoder_plugin,
|
||||
#endif
|
||||
#ifdef ENABLE_FFMPEG
|
||||
&ffmpeg_decoder_plugin,
|
||||
#endif
|
||||
&pcm_decoder_plugin,
|
||||
nullptr
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "thread/Name.hxx"
|
||||
#include "tag/ApeReplayGain.hxx"
|
||||
#include "Log.hxx"
|
||||
@ -261,12 +262,16 @@ LoadReplayGain(DecoderClient &client, InputStream &is)
|
||||
static void
|
||||
MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is)
|
||||
{
|
||||
{
|
||||
const std::scoped_lock<Mutex> protect(bridge.dc.mutex);
|
||||
if (bridge.dc.replay_gain_mode == ReplayGainMode::OFF)
|
||||
/* ReplayGain is disabled */
|
||||
return;
|
||||
}
|
||||
if (!bridge.dc.LockIsReplayGainEnabled())
|
||||
/* ReplayGain is disabled */
|
||||
return;
|
||||
|
||||
if (is.HasMimeType() &&
|
||||
StringStartsWith(is.GetMimeType(), "audio/x-mpd-"))
|
||||
/* skip for (virtual) files (e.g. from the
|
||||
cdio_paranoia input plugin) which cannot possibly
|
||||
contain tags */
|
||||
return;
|
||||
|
||||
LoadReplayGain(bridge, is);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "lib/ffmpeg/Format.hxx"
|
||||
#include "lib/ffmpeg/Codec.hxx"
|
||||
#include "lib/ffmpeg/SampleFormat.hxx"
|
||||
#include "lib/ffmpeg/LibFmt.hxx"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "FfmpegMetaData.hxx"
|
||||
#include "FfmpegIo.hxx"
|
||||
@ -384,7 +385,8 @@ static void
|
||||
FfmpegParseMetaData(const AVStream &stream,
|
||||
ReplayGainInfo &rg, MixRampInfo &mr)
|
||||
{
|
||||
FfmpegParseMetaData(*stream.metadata, rg, mr);
|
||||
if (stream.metadata != nullptr)
|
||||
FfmpegParseMetaData(*stream.metadata, rg, mr);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -393,7 +395,9 @@ FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream,
|
||||
{
|
||||
assert(audio_stream >= 0);
|
||||
|
||||
FfmpegParseMetaData(*format_context.metadata, rg, mr);
|
||||
if (format_context.metadata != nullptr)
|
||||
FfmpegParseMetaData(*format_context.metadata, rg, mr);
|
||||
|
||||
FfmpegParseMetaData(*format_context.streams[audio_stream],
|
||||
rg, mr);
|
||||
}
|
||||
@ -468,7 +472,7 @@ static bool
|
||||
IsSeekable(const AVFormatContext &format_context) noexcept
|
||||
{
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 6, 100)
|
||||
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) != 0;
|
||||
return (format_context.ctx_flags & AVFMTCTX_UNSEEKABLE) == 0;
|
||||
#else
|
||||
(void)format_context;
|
||||
return false;
|
||||
@ -520,9 +524,15 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
return;
|
||||
}
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
const unsigned channels = codec_context->ch_layout.nb_channels;
|
||||
#else
|
||||
const unsigned channels = codec_context->channels;
|
||||
#endif
|
||||
|
||||
const auto audio_format = CheckAudioFormat(codec_context->sample_rate,
|
||||
sample_format,
|
||||
codec_context->channels);
|
||||
channels);
|
||||
|
||||
const SignedSongTime total_time =
|
||||
av_stream.duration != (int64_t)AV_NOPTS_VALUE
|
||||
@ -530,9 +540,8 @@ FfmpegDecode(DecoderClient &client, InputStream *input,
|
||||
: FromFfmpegTimeChecked(format_context.duration, AV_TIME_BASE_Q);
|
||||
|
||||
client.Ready(audio_format,
|
||||
input
|
||||
? input->IsSeekable()
|
||||
: IsSeekable(format_context),
|
||||
(input ? input->IsSeekable() : false)
|
||||
|| IsSeekable(format_context),
|
||||
total_time);
|
||||
|
||||
FfmpegParseMetaData(client, format_context, audio_stream);
|
||||
@ -633,10 +642,17 @@ FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||
AV_TIME_BASE_Q));
|
||||
|
||||
const auto &codec_params = *stream.codecpar;
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
const unsigned channels = codec_params.ch_layout.nb_channels;
|
||||
#else
|
||||
const unsigned channels = codec_params.channels;
|
||||
#endif
|
||||
|
||||
try {
|
||||
handler.OnAudioFormat(CheckAudioFormat(codec_params.sample_rate,
|
||||
ffmpeg_sample_format(AVSampleFormat(codec_params.format)),
|
||||
codec_params.channels));
|
||||
channels));
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,13 @@
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
|
||||
#include "FfmpegIo.hxx"
|
||||
#include "libavutil/mem.h"
|
||||
#include "../DecoderAPI.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/mem.h>
|
||||
}
|
||||
|
||||
AvioStream::~AvioStream()
|
||||
{
|
||||
if (io != nullptr) {
|
||||
|
@ -56,20 +56,17 @@ struct GmeContainerPath {
|
||||
unsigned track;
|
||||
};
|
||||
|
||||
#if GME_VERSION >= 0x000600
|
||||
static int gme_accuracy;
|
||||
#endif
|
||||
static unsigned gme_default_fade;
|
||||
|
||||
static bool
|
||||
gme_plugin_init([[maybe_unused]] const ConfigBlock &block)
|
||||
{
|
||||
#if GME_VERSION >= 0x000600
|
||||
auto accuracy = block.GetBlockParam("accuracy");
|
||||
gme_accuracy = accuracy != nullptr
|
||||
? (int)accuracy->GetBoolValue()
|
||||
: -1;
|
||||
#endif
|
||||
|
||||
auto fade = block.GetBlockParam("default_fade");
|
||||
gme_default_fade = fade != nullptr
|
||||
? fade->GetUnsignedValue() * 1000
|
||||
@ -163,10 +160,8 @@ gme_file_decode(DecoderClient &client, Path path_fs)
|
||||
FmtDebug(gme_domain, "emulator type '{}'",
|
||||
gme_type_system(gme_type(emu)));
|
||||
|
||||
#if GME_VERSION >= 0x000600
|
||||
if (gme_accuracy >= 0)
|
||||
gme_enable_accuracy(emu, gme_accuracy);
|
||||
#endif
|
||||
|
||||
gme_info_t *ti;
|
||||
const char *gme_err = gme_track_info(emu, &ti, container.track);
|
||||
|
@ -798,6 +798,8 @@ MadDecoder::UpdateTimerNextFrame() noexcept
|
||||
DecoderCommand
|
||||
MadDecoder::SubmitPCM(size_t i, size_t pcm_length) noexcept
|
||||
{
|
||||
assert(i <= pcm_length);
|
||||
|
||||
size_t num_samples = pcm_length - i;
|
||||
|
||||
mad_fixed_to_24_buffer(output_buffer, synth.pcm,
|
||||
@ -843,7 +845,7 @@ MadDecoder::SynthAndSubmit() noexcept
|
||||
size_t pcm_length = synth.pcm.length;
|
||||
if (drop_end_samples &&
|
||||
current_frame == max_frames - drop_end_frames - 1) {
|
||||
if (drop_end_samples >= pcm_length)
|
||||
if (i + drop_end_samples >= pcm_length)
|
||||
return DecoderCommand::STOP;
|
||||
|
||||
pcm_length -= drop_end_samples;
|
||||
|
@ -81,7 +81,7 @@ if libfaad_dep.found()
|
||||
decoder_plugins_sources += 'FaadDecoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
libgme_dep = c_compiler.find_library('gme', required: get_option('gme'))
|
||||
libgme_dep = dependency('libgme', version: '>= 0.6', required: get_option('gme'))
|
||||
decoder_features.set('ENABLE_GME', libgme_dep.found())
|
||||
if libgme_dep.found()
|
||||
decoder_plugins_sources += 'GmeDecoderPlugin.cxx'
|
||||
|
@ -3,6 +3,23 @@ encoder_features = configuration_data()
|
||||
encoder_features.set('ENABLE_ENCODER', need_encoder)
|
||||
|
||||
if not need_encoder
|
||||
if need_wave_encoder
|
||||
# Special case for the Snapcast output plugin which only needs the
|
||||
# PCM wave encoder encoder plugin
|
||||
encoder_glue = static_library(
|
||||
'encoder_glue',
|
||||
'plugins/WaveEncoderPlugin.cxx',
|
||||
include_directories: inc,
|
||||
)
|
||||
|
||||
encoder_glue_dep = declare_dependency(
|
||||
link_with: encoder_glue,
|
||||
)
|
||||
|
||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
encoder_glue_dep = dependency('', required: false)
|
||||
configure_file(output: 'Features.h', configuration: encoder_features)
|
||||
subdir_done()
|
||||
|
@ -38,6 +38,7 @@ class FlacEncoder final : public Encoder {
|
||||
|
||||
FLAC__StreamEncoder *const fse;
|
||||
const unsigned compression;
|
||||
const bool oggflac;
|
||||
|
||||
PcmBuffer expand_buffer;
|
||||
|
||||
@ -122,7 +123,7 @@ flac_encoder_init(const ConfigBlock &block)
|
||||
}
|
||||
|
||||
static void
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression, bool oggflac,
|
||||
const AudioFormat &audio_format)
|
||||
{
|
||||
unsigned bits_per_sample;
|
||||
@ -157,7 +158,7 @@ flac_encoder_setup(FLAC__StreamEncoder *fse, unsigned compression,
|
||||
throw FormatRuntimeError("error setting flac sample rate to %d",
|
||||
audio_format.sample_rate);
|
||||
|
||||
if (!FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
if (oggflac && !FLAC__stream_encoder_set_ogg_serial_number(fse,
|
||||
GenerateSerial()))
|
||||
throw FormatRuntimeError("error setting ogg serial number");
|
||||
}
|
||||
@ -166,11 +167,12 @@ FlacEncoder::FlacEncoder(AudioFormat _audio_format, FLAC__StreamEncoder *_fse, u
|
||||
:Encoder(_oggchaining),
|
||||
audio_format(_audio_format), fse(_fse),
|
||||
compression(_compression),
|
||||
oggflac(_oggflac),
|
||||
output_buffer(8192)
|
||||
{
|
||||
/* this immediately outputs data through callback */
|
||||
|
||||
auto init_status = _oggflac ?
|
||||
auto init_status = oggflac ?
|
||||
FLAC__stream_encoder_init_ogg_stream(fse,
|
||||
nullptr, WriteCallback,
|
||||
nullptr, nullptr, nullptr,
|
||||
@ -209,7 +211,7 @@ PreparedFlacEncoder::Open(AudioFormat &audio_format)
|
||||
throw std::runtime_error("FLAC__stream_encoder_new() failed");
|
||||
|
||||
try {
|
||||
flac_encoder_setup(fse, compression, audio_format);
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
} catch (...) {
|
||||
FLAC__stream_encoder_delete(fse);
|
||||
throw;
|
||||
@ -222,7 +224,7 @@ void
|
||||
FlacEncoder::SendTag(const Tag &tag)
|
||||
{
|
||||
/* re-initialize encoder since flac_encoder_finish resets everything */
|
||||
flac_encoder_setup(fse, compression, audio_format);
|
||||
flac_encoder_setup(fse, compression, oggflac, audio_format);
|
||||
|
||||
FLAC__StreamMetadata *metadata = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
|
||||
FLAC__StreamMetadata_VorbisComment_Entry entry;
|
||||
|
@ -35,7 +35,7 @@ if libshine_dep.found()
|
||||
endif
|
||||
|
||||
encoder_features.set('ENABLE_WAVE_ENCODER', get_option('wave_encoder'))
|
||||
if get_option('wave_encoder')
|
||||
if get_option('wave_encoder') or need_wave_encoder
|
||||
encoder_plugins_sources += 'WaveEncoderPlugin.cxx'
|
||||
endif
|
||||
|
||||
|
@ -272,9 +272,8 @@ EventLoop::Run() noexcept
|
||||
#endif
|
||||
|
||||
assert(IsInside());
|
||||
assert(!quit);
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
assert(alive);
|
||||
assert(alive || quit);
|
||||
assert(busy);
|
||||
|
||||
wake_event.Schedule(SocketEvent::READ);
|
||||
@ -299,7 +298,7 @@ EventLoop::Run() noexcept
|
||||
|
||||
steady_clock_cache.flush();
|
||||
|
||||
do {
|
||||
while (!quit) {
|
||||
again = false;
|
||||
|
||||
/* invoke timers */
|
||||
@ -361,7 +360,7 @@ EventLoop::Run() noexcept
|
||||
|
||||
socket_event.Dispatch();
|
||||
}
|
||||
} while (!quit);
|
||||
}
|
||||
|
||||
#ifdef HAVE_THREADED_EVENT_LOOP
|
||||
#ifndef NDEBUG
|
||||
|
@ -40,10 +40,15 @@ FfmpegFilter::FfmpegFilter(const AudioFormat &in_audio_format,
|
||||
buffer_sink(_buffer_sink),
|
||||
in_format(Ffmpeg::ToFfmpegSampleFormat(in_audio_format.format)),
|
||||
in_sample_rate(in_audio_format.sample_rate),
|
||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 25, 100)
|
||||
in_channels(in_audio_format.channels),
|
||||
#endif
|
||||
in_audio_frame_size(in_audio_format.GetFrameSize()),
|
||||
out_audio_frame_size(_out_audio_format.GetFrameSize())
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
av_channel_layout_default(&in_ch_layout, in_audio_format.channels);
|
||||
#endif
|
||||
}
|
||||
|
||||
ConstBuffer<void>
|
||||
@ -54,7 +59,11 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
|
||||
frame.Unref();
|
||||
frame->format = in_format;
|
||||
frame->sample_rate = in_sample_rate;
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
frame->ch_layout = in_ch_layout;
|
||||
#else
|
||||
frame->channels = in_channels;
|
||||
#endif
|
||||
frame->nb_samples = src.size / in_audio_frame_size;
|
||||
|
||||
frame.GetBuffer();
|
||||
|
@ -35,7 +35,13 @@ class FfmpegFilter final : public Filter {
|
||||
|
||||
FfmpegBuffer interleave_buffer;
|
||||
|
||||
const int in_format, in_sample_rate, in_channels;
|
||||
const int in_format, in_sample_rate;
|
||||
|
||||
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 25, 100)
|
||||
AVChannelLayout in_ch_layout;
|
||||
#else
|
||||
const int in_channels;
|
||||
#endif
|
||||
|
||||
const size_t in_audio_frame_size;
|
||||
const size_t out_audio_frame_size;
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "ReplayGainInfo.hxx"
|
||||
#include "ReplayGainConfig.hxx"
|
||||
#include "mixer/MixerControl.hxx"
|
||||
#include "mixer/MixerInternal.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
#include "pcm/Volume.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
@ -171,9 +173,11 @@ ReplayGainFilter::Update()
|
||||
try {
|
||||
mixer_set_volume(mixer, _volume);
|
||||
|
||||
/* TODO: emit this idle event only for the
|
||||
current partition */
|
||||
idle_add(IDLE_MIXER);
|
||||
/* invoke the mixer's listener manually, just
|
||||
in case the mixer implementation didn't do
|
||||
that already (this depends on the
|
||||
implementation) */
|
||||
mixer->listener.OnMixerVolumeChanged(*mixer, _volume);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Failed to update hardware mixer");
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include "Charset.hxx"
|
||||
#include "Features.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "lib/icu/Converter.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "config.h"
|
||||
@ -45,11 +44,9 @@ SetFSCharset(const char *charset)
|
||||
assert(charset != nullptr);
|
||||
assert(fs_converter == nullptr);
|
||||
|
||||
fs_charset = charset;
|
||||
fs_converter = IcuConverter::Create(charset);
|
||||
assert(fs_converter != nullptr);
|
||||
|
||||
FmtDebug(path_domain,
|
||||
"SetFSCharset: fs charset is {}", fs_charset);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -67,6 +67,16 @@ StatFile(Path file, struct stat &buf, bool follow_symlinks = true)
|
||||
|
||||
#endif
|
||||
|
||||
static inline bool
|
||||
CreateDirectoryNoThrow(Path path) noexcept
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return CreateDirectory(path.c_str(), nullptr);
|
||||
#else
|
||||
return mkdir(path.c_str(), 0777);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate a file that exists already. Throws std::system_error on
|
||||
* error.
|
||||
|
@ -34,7 +34,6 @@
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#endif
|
||||
|
||||
@ -53,6 +52,12 @@
|
||||
#include "Main.hxx"
|
||||
#endif
|
||||
|
||||
#ifdef USE_XDG
|
||||
#include "Version.h" // for PACKAGE_NAME
|
||||
#define APP_FILENAME PATH_LITERAL(PACKAGE_NAME)
|
||||
static constexpr Path app_filename = Path::FromFS(APP_FILENAME);
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(ANDROID)
|
||||
class PasswdEntry
|
||||
{
|
||||
@ -74,15 +79,6 @@ public:
|
||||
return result != nullptr;
|
||||
}
|
||||
|
||||
bool ReadByUid(uid_t uid) {
|
||||
#ifdef HAVE_GETPWUID_R
|
||||
getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
|
||||
#else
|
||||
result = getpwuid(uid);
|
||||
#endif
|
||||
return result != nullptr;
|
||||
}
|
||||
|
||||
const passwd *operator->() {
|
||||
assert(result != nullptr);
|
||||
return result;
|
||||
@ -254,7 +250,8 @@ GetUserMusicDir() noexcept
|
||||
#elif defined(USE_XDG)
|
||||
return GetUserDir("XDG_MUSIC_DIR");
|
||||
#elif defined(ANDROID)
|
||||
return Environment::getExternalStoragePublicDirectory("Music");
|
||||
return Environment::getExternalStoragePublicDirectory(Java::GetEnv(),
|
||||
"Music");
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@ -283,6 +280,24 @@ GetUserCacheDir() noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
GetAppCacheDir() noexcept
|
||||
{
|
||||
#ifdef USE_XDG
|
||||
if (const auto user_dir = GetUserCacheDir(); !user_dir.IsNull()) {
|
||||
auto dir = user_dir / app_filename;
|
||||
CreateDirectoryNoThrow(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
#elif defined(ANDROID)
|
||||
return context->GetCacheDir(Java::GetEnv());
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
GetUserRuntimeDir() noexcept
|
||||
{
|
||||
@ -296,7 +311,7 @@ GetUserRuntimeDir() noexcept
|
||||
AllocatedPath
|
||||
GetAppRuntimeDir() noexcept
|
||||
{
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && !defined(ANDROID)
|
||||
/* systemd specific; see systemd.exec(5) */
|
||||
if (const char *runtime_directory = getenv("RUNTIME_DIRECTORY"))
|
||||
if (auto dir = StringView{runtime_directory}.Split(':').first;
|
||||
@ -306,8 +321,8 @@ GetAppRuntimeDir() noexcept
|
||||
|
||||
#ifdef USE_XDG
|
||||
if (const auto user_dir = GetUserRuntimeDir(); !user_dir.IsNull()) {
|
||||
auto dir = user_dir / Path::FromFS("mpd");
|
||||
mkdir(dir.c_str(), 0700);
|
||||
auto dir = user_dir / app_filename;
|
||||
CreateDirectoryNoThrow(dir);
|
||||
return dir;
|
||||
}
|
||||
#endif
|
||||
@ -350,10 +365,8 @@ GetHomeDir() noexcept
|
||||
if (const auto home = getenv("HOME");
|
||||
IsValidPathString(home) && IsValidDir(home))
|
||||
return AllocatedPath::FromFS(home);
|
||||
|
||||
if (PasswdEntry pw; pw.ReadByUid(getuid()))
|
||||
return SafePathFromFS(pw->pw_dir);
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,13 @@ GetUserMusicDir() noexcept;
|
||||
AllocatedPath
|
||||
GetUserCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains cache directory for this application.
|
||||
*/
|
||||
[[gnu::const]]
|
||||
AllocatedPath
|
||||
GetAppCacheDir() noexcept;
|
||||
|
||||
/**
|
||||
* Obtains the runtime directory for the current user.
|
||||
*/
|
||||
|
@ -101,9 +101,17 @@ AsyncInputStream::Seek(std::unique_lock<Mutex> &lock,
|
||||
assert(IsReady());
|
||||
assert(seek_state == SeekState::NONE);
|
||||
|
||||
if (new_offset == offset)
|
||||
/* no-op */
|
||||
if (new_offset == offset) {
|
||||
/* no-op, but if the stream is not open anymore (maybe
|
||||
because it has failed), nothing can be read, so we
|
||||
should check for errors here instead of pretending
|
||||
everything's fine */
|
||||
|
||||
if (!open)
|
||||
Check();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSeekable())
|
||||
throw std::runtime_error("Not seekable");
|
||||
|
@ -30,10 +30,12 @@
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/ByteOrder.hxx"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "config/Block.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
@ -43,31 +45,35 @@
|
||||
|
||||
#include <cdio/cd_types.h>
|
||||
|
||||
static constexpr Domain cdio_domain("cdio");
|
||||
|
||||
static bool default_reverse_endian;
|
||||
static unsigned speed = 0;
|
||||
|
||||
/* Default to full paranoia, but allow skipping sectors. */
|
||||
static int mode_flags = PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP;
|
||||
|
||||
class CdioParanoiaInputStream final : public InputStream {
|
||||
cdrom_drive_t *const drv;
|
||||
CdIo_t *const cdio;
|
||||
CdromParanoia para;
|
||||
|
||||
const lsn_t lsn_from, lsn_to;
|
||||
int lsn_relofs;
|
||||
const lsn_t lsn_from;
|
||||
|
||||
char buffer[CDIO_CD_FRAMESIZE_RAW];
|
||||
int buffer_lsn;
|
||||
lsn_t buffer_lsn;
|
||||
|
||||
public:
|
||||
CdioParanoiaInputStream(const char *_uri, Mutex &_mutex,
|
||||
cdrom_drive_t *_drv, CdIo_t *_cdio,
|
||||
bool reverse_endian,
|
||||
lsn_t _lsn_from, lsn_t _lsn_to)
|
||||
lsn_t _lsn_from, lsn_t lsn_to)
|
||||
:InputStream(_uri, _mutex),
|
||||
drv(_drv), cdio(_cdio), para(drv),
|
||||
lsn_from(_lsn_from), lsn_to(_lsn_to),
|
||||
lsn_relofs(0),
|
||||
lsn_from(_lsn_from),
|
||||
buffer_lsn(-1)
|
||||
{
|
||||
/* Set reading mode for full paranoia, but allow
|
||||
skipping sectors. */
|
||||
para.SetMode(PARANOIA_MODE_FULL^PARANOIA_MODE_NEVERSKIP);
|
||||
para.SetMode(mode_flags);
|
||||
|
||||
/* seek to beginning of the track */
|
||||
para.Seek(lsn_from);
|
||||
@ -98,11 +104,6 @@ class CdioParanoiaInputStream final : public InputStream {
|
||||
void Seek(std::unique_lock<Mutex> &lock, offset_type offset) override;
|
||||
};
|
||||
|
||||
static constexpr Domain cdio_domain("cdio");
|
||||
|
||||
static bool default_reverse_endian;
|
||||
static unsigned speed = 0;
|
||||
|
||||
static void
|
||||
input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
{
|
||||
@ -117,6 +118,26 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
||||
value);
|
||||
}
|
||||
speed = block.GetBlockValue("speed",0U);
|
||||
|
||||
if (const auto *param = block.GetBlockParam("mode")) {
|
||||
param->With([](const char *s){
|
||||
if (StringIsEqual(s, "disable"))
|
||||
mode_flags = PARANOIA_MODE_DISABLE;
|
||||
else if (StringIsEqual(s, "overlap"))
|
||||
mode_flags = PARANOIA_MODE_OVERLAP;
|
||||
else if (StringIsEqual(s, "full"))
|
||||
mode_flags = PARANOIA_MODE_FULL;
|
||||
else
|
||||
throw std::invalid_argument{"Invalid paranoia mode"};
|
||||
});
|
||||
}
|
||||
|
||||
if (const auto *param = block.GetBlockParam("skip")) {
|
||||
if (param->GetBoolValue())
|
||||
mode_flags &= ~PARANOIA_MODE_NEVERSKIP;
|
||||
else
|
||||
mode_flags |= PARANOIA_MODE_NEVERSKIP;
|
||||
}
|
||||
}
|
||||
|
||||
struct CdioUri {
|
||||
@ -173,9 +194,12 @@ cdio_detect_device()
|
||||
if (devices == nullptr)
|
||||
return nullptr;
|
||||
|
||||
AllocatedPath path = AllocatedPath::FromFS(devices[0]);
|
||||
cdio_free_device_list(devices);
|
||||
return path;
|
||||
AtScopeExit(devices) { cdio_free_device_list(devices); };
|
||||
|
||||
if (devices[0] == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return AllocatedPath::FromFS(devices[0]);
|
||||
}
|
||||
|
||||
static InputStreamPtr
|
||||
@ -271,81 +295,70 @@ CdioParanoiaInputStream::Seek(std::unique_lock<Mutex> &,
|
||||
return;
|
||||
|
||||
/* calculate current LSN */
|
||||
lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
offset = new_offset;
|
||||
const lsn_t lsn_relofs = new_offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
{
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
para.Seek(lsn_from + lsn_relofs);
|
||||
}
|
||||
|
||||
offset = new_offset;
|
||||
}
|
||||
|
||||
size_t
|
||||
CdioParanoiaInputStream::Read(std::unique_lock<Mutex> &,
|
||||
void *ptr, size_t length)
|
||||
{
|
||||
size_t nbytes = 0;
|
||||
char *wptr = (char *) ptr;
|
||||
/* end of track ? */
|
||||
if (IsEOF())
|
||||
return 0;
|
||||
|
||||
while (length > 0) {
|
||||
/* end of track ? */
|
||||
if (lsn_from + lsn_relofs > lsn_to)
|
||||
break;
|
||||
//current sector was changed ?
|
||||
const int16_t *rbuf;
|
||||
|
||||
//current sector was changed ?
|
||||
const int16_t *rbuf;
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
const lsn_t lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
const std::size_t diff = offset % CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
try {
|
||||
rbuf = para.Read().data;
|
||||
} catch (...) {
|
||||
char *s_err = cdio_cddap_errors(drv);
|
||||
if (s_err) {
|
||||
FmtError(cdio_domain,
|
||||
"paranoia_read: {}", s_err);
|
||||
cdio_cddap_free_messages(s_err);
|
||||
}
|
||||
if (lsn_relofs != buffer_lsn) {
|
||||
const ScopeUnlock unlock(mutex);
|
||||
|
||||
throw;
|
||||
try {
|
||||
rbuf = para.Read().data;
|
||||
} catch (...) {
|
||||
char *s_err = cdio_cddap_errors(drv);
|
||||
if (s_err) {
|
||||
FmtError(cdio_domain,
|
||||
"paranoia_read: {}", s_err);
|
||||
cdio_cddap_free_messages(s_err);
|
||||
}
|
||||
|
||||
//store current buffer
|
||||
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
|
||||
buffer_lsn = lsn_relofs;
|
||||
} else {
|
||||
//use cached sector
|
||||
rbuf = (const int16_t *)buffer;
|
||||
throw;
|
||||
}
|
||||
|
||||
//correct offset
|
||||
const int diff = offset - lsn_relofs * CDIO_CD_FRAMESIZE_RAW;
|
||||
|
||||
assert(diff >= 0 && diff < CDIO_CD_FRAMESIZE_RAW);
|
||||
|
||||
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
||||
const size_t len = (length < maxwrite? length : maxwrite);
|
||||
|
||||
//skip diff bytes from this lsn
|
||||
memcpy(wptr, ((const char *)rbuf) + diff, len);
|
||||
//update pointer
|
||||
wptr += len;
|
||||
nbytes += len;
|
||||
|
||||
//update offset
|
||||
offset += len;
|
||||
lsn_relofs = offset / CDIO_CD_FRAMESIZE_RAW;
|
||||
//update length
|
||||
length -= len;
|
||||
//store current buffer
|
||||
memcpy(buffer, rbuf, CDIO_CD_FRAMESIZE_RAW);
|
||||
buffer_lsn = lsn_relofs;
|
||||
} else {
|
||||
//use cached sector
|
||||
rbuf = (const int16_t *)buffer;
|
||||
}
|
||||
|
||||
const size_t maxwrite = CDIO_CD_FRAMESIZE_RAW - diff; //# of bytes pending in current buffer
|
||||
const std::size_t nbytes = std::min(length, maxwrite);
|
||||
|
||||
//skip diff bytes from this lsn
|
||||
memcpy(ptr, ((const char *)rbuf) + diff, nbytes);
|
||||
|
||||
//update offset
|
||||
offset += nbytes;
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
bool
|
||||
CdioParanoiaInputStream::IsEOF() const noexcept
|
||||
{
|
||||
return lsn_from + lsn_relofs > lsn_to;
|
||||
return offset >= size;
|
||||
}
|
||||
|
||||
static constexpr const char *cdio_paranoia_prefixes[] = {
|
||||
|
@ -82,7 +82,7 @@ class CurlInputStream final : public AsyncInputStream, CurlResponseHandler {
|
||||
public:
|
||||
template<typename I>
|
||||
CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
I &&_icy,
|
||||
Mutex &_mutex);
|
||||
|
||||
@ -92,7 +92,7 @@ public:
|
||||
CurlInputStream &operator=(const CurlInputStream &) = delete;
|
||||
|
||||
static InputStreamPtr Open(const char *url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
Mutex &mutex);
|
||||
|
||||
private:
|
||||
@ -131,8 +131,7 @@ private:
|
||||
void SeekInternal(offset_type new_offset);
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
void OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
void OnHeaders(unsigned status, Curl::Headers &&headers) override;
|
||||
void OnData(ConstBuffer<void> data) override;
|
||||
void OnEnd() override;
|
||||
void OnError(std::exception_ptr e) noexcept override;
|
||||
@ -227,7 +226,7 @@ WithConvertedTagValue(const char *uri, const char *value, F &&f) noexcept
|
||||
|
||||
void
|
||||
CurlInputStream::OnHeaders(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
Curl::Headers &&headers)
|
||||
{
|
||||
assert(GetEventLoop().IsInside());
|
||||
assert(!postponed_exception);
|
||||
@ -391,7 +390,7 @@ input_curl_finish() noexcept
|
||||
template<typename I>
|
||||
inline
|
||||
CurlInputStream::CurlInputStream(EventLoop &event_loop, const char *_url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
I &&_icy,
|
||||
Mutex &_mutex)
|
||||
:AsyncInputStream(event_loop, _url, _mutex,
|
||||
@ -418,7 +417,6 @@ CurlInputStream::InitEasy()
|
||||
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
|
||||
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
|
||||
request->SetOption(CURLOPT_MAXREDIRS, 5L);
|
||||
request->SetOption(CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
/* this option eliminates the probe request when
|
||||
username/password are specified */
|
||||
@ -440,6 +438,14 @@ CurlInputStream::InitEasy()
|
||||
request->SetVerifyPeer(verify_peer);
|
||||
request->SetVerifyHost(verify_host);
|
||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||
|
||||
try {
|
||||
request->SetProxyVerifyPeer(verify_peer);
|
||||
request->SetProxyVerifyHost(verify_host);
|
||||
} catch (...) {
|
||||
/* these methods fail if libCURL was compiled with
|
||||
CURL_DISABLE_PROXY; ignore silently */
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -491,7 +497,7 @@ CurlInputStream::DoSeek(offset_type new_offset)
|
||||
|
||||
inline InputStreamPtr
|
||||
CurlInputStream::Open(const char *url,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
const Curl::Headers &headers,
|
||||
Mutex &mutex)
|
||||
{
|
||||
auto icy = std::make_shared<IcyMetaDataParser>();
|
||||
@ -510,8 +516,7 @@ CurlInputStream::Open(const char *url,
|
||||
}
|
||||
|
||||
InputStreamPtr
|
||||
OpenCurlInputStream(const char *uri,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||
Mutex &mutex)
|
||||
{
|
||||
return CurlInputStream::Open(uri, headers, mutex);
|
||||
|
@ -20,12 +20,10 @@
|
||||
#ifndef MPD_INPUT_CURL_HXX
|
||||
#define MPD_INPUT_CURL_HXX
|
||||
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "input/Ptr.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
extern const struct InputPlugin input_plugin_curl;
|
||||
|
||||
/**
|
||||
@ -36,8 +34,7 @@ extern const struct InputPlugin input_plugin_curl;
|
||||
* Throws on error.
|
||||
*/
|
||||
InputStreamPtr
|
||||
OpenCurlInputStream(const char *uri,
|
||||
const std::multimap<std::string, std::string> &headers,
|
||||
OpenCurlInputStream(const char *uri, const Curl::Headers &headers,
|
||||
Mutex &mutex);
|
||||
|
||||
#endif
|
||||
|
@ -164,7 +164,7 @@ QobuzClient::InvokeHandlers() noexcept
|
||||
|
||||
std::string
|
||||
QobuzClient::MakeUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept
|
||||
const Curl::Headers &query) const noexcept
|
||||
{
|
||||
assert(!query.empty());
|
||||
|
||||
@ -183,7 +183,7 @@ QobuzClient::MakeUrl(const char *object, const char *method,
|
||||
|
||||
std::string
|
||||
QobuzClient::MakeSignedUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept
|
||||
const Curl::Headers &query) const noexcept
|
||||
{
|
||||
assert(!query.empty());
|
||||
|
||||
|
@ -23,12 +23,12 @@
|
||||
#include "QobuzSession.hxx"
|
||||
#include "QobuzLoginRequest.hxx"
|
||||
#include "lib/curl/Init.hxx"
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "util/IntrusiveList.hxx"
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class QobuzSessionHandler
|
||||
@ -94,10 +94,10 @@ public:
|
||||
QobuzSession GetSession() const;
|
||||
|
||||
std::string MakeUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
||||
const Curl::Headers &query) const noexcept;
|
||||
|
||||
std::string MakeSignedUrl(const char *object, const char *method,
|
||||
const std::multimap<std::string, std::string> &query) const noexcept;
|
||||
const Curl::Headers &query) const noexcept;
|
||||
|
||||
private:
|
||||
void StartLogin();
|
||||
|
@ -38,7 +38,7 @@ static constexpr yajl_callbacks qobuz_error_parser_callbacks = {
|
||||
};
|
||||
|
||||
QobuzErrorParser::QobuzErrorParser(unsigned _status,
|
||||
const std::multimap<std::string, std::string> &headers)
|
||||
const Curl::Headers &headers)
|
||||
:YajlResponseParser(&qobuz_error_parser_callbacks, nullptr, this),
|
||||
status(_status)
|
||||
{
|
||||
|
@ -20,11 +20,9 @@
|
||||
#ifndef QOBUZ_ERROR_PARSER_HXX
|
||||
#define QOBUZ_ERROR_PARSER_HXX
|
||||
|
||||
#include "lib/curl/Headers.hxx"
|
||||
#include "lib/yajl/ResponseParser.hxx"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
struct StringView;
|
||||
|
||||
@ -46,8 +44,7 @@ public:
|
||||
* May throw if there is a formal error in the response
|
||||
* headers.
|
||||
*/
|
||||
QobuzErrorParser(unsigned status,
|
||||
const std::multimap<std::string, std::string> &headers);
|
||||
QobuzErrorParser(unsigned status, const Curl::Headers &headers);
|
||||
|
||||
protected:
|
||||
/* virtual methods from CurlResponseParser */
|
||||
|
@ -77,7 +77,7 @@ QobuzLoginRequest::ResponseParser::GetSession()
|
||||
return std::move(session);
|
||||
}
|
||||
|
||||
static std::multimap<std::string, std::string>
|
||||
static Curl::Headers
|
||||
MakeLoginForm(const char *app_id,
|
||||
const char *username, const char *email,
|
||||
const char *password,
|
||||
@ -85,7 +85,7 @@ MakeLoginForm(const char *app_id,
|
||||
{
|
||||
assert(username != nullptr || email != nullptr);
|
||||
|
||||
std::multimap<std::string, std::string> form{
|
||||
Curl::Headers form{
|
||||
{"app_id", app_id},
|
||||
{"password", password},
|
||||
{"device_manufacturer_id", device_manufacturer_id},
|
||||
@ -134,8 +134,7 @@ QobuzLoginRequest::~QobuzLoginRequest() noexcept
|
||||
}
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzLoginRequest::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
QobuzLoginRequest::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
@ -99,8 +99,7 @@ QobuzTagScanner::~QobuzTagScanner() noexcept
|
||||
}
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzTagScanner::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
QobuzTagScanner::MakeParser(unsigned status, Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@ -49,7 +49,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
@ -93,7 +93,7 @@ QobuzTrackRequest::~QobuzTrackRequest() noexcept
|
||||
|
||||
std::unique_ptr<CurlResponseParser>
|
||||
QobuzTrackRequest::MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers)
|
||||
Curl::Headers &&headers)
|
||||
{
|
||||
if (status != 200)
|
||||
return std::make_unique<QobuzErrorParser>(status, headers);
|
||||
|
@ -56,7 +56,7 @@ public:
|
||||
private:
|
||||
/* virtual methods from DelegateCurlResponseHandler */
|
||||
std::unique_ptr<CurlResponseParser> MakeParser(unsigned status,
|
||||
std::multimap<std::string, std::string> &&headers) override;
|
||||
Curl::Headers &&headers) override;
|
||||
void FinishParser(std::unique_ptr<CurlResponseParser> p) override;
|
||||
|
||||
/* virtual methods from CurlResponseHandler */
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "io/UniqueFileDescriptor.hxx"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Path;
|
||||
class FileInfo;
|
||||
|
||||
|
@ -49,9 +49,6 @@ Java::File::Initialise(JNIEnv *env) noexcept
|
||||
AllocatedPath
|
||||
Java::File::ToAbsolutePath(JNIEnv *env, jobject _file) noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
assert(_file != nullptr);
|
||||
|
||||
LocalObject file(env, _file);
|
||||
|
||||
const jstring path = GetAbsolutePath(env, file);
|
||||
|
@ -89,6 +89,16 @@ public:
|
||||
String(JNIEnv *_env, const char *_value) noexcept
|
||||
:LocalRef<jstring>(_env, _env->NewStringUTF(_value)) {}
|
||||
|
||||
/**
|
||||
* This constructor allows passing a nullptr value, which maps
|
||||
* to a "null" in Java.
|
||||
*/
|
||||
static String Optional(JNIEnv *_env, const char *_value) noexcept {
|
||||
return _value != nullptr
|
||||
? String{_env, _value}
|
||||
: String{};
|
||||
}
|
||||
|
||||
static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept {
|
||||
return {env, s, env->GetStringUTFChars(s, nullptr)};
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ endif
|
||||
|
||||
conf.set('HAVE_MD5', crypto_md5_dep.found())
|
||||
|
||||
if libavutil_dep.found()
|
||||
if ffmpeg_util_dep.found()
|
||||
crypto_base64 = static_library(
|
||||
'crypto_base64',
|
||||
'Base64.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
libavutil_dep,
|
||||
ffmpeg_util_dep,
|
||||
],
|
||||
)
|
||||
|
||||
|
204
src/lib/curl/Adapter.cxx
Normal file
204
src/lib/curl/Adapter.cxx
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2008-2021 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.
|
||||
*/
|
||||
|
||||
#include "Adapter.hxx"
|
||||
#include "Easy.hxx"
|
||||
#include "Handler.hxx"
|
||||
#include "util/CharUtil.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::Install(CurlEasy &easy)
|
||||
{
|
||||
assert(state == State::UNINITIALISED);
|
||||
|
||||
error_buffer[0] = 0;
|
||||
easy.SetErrorBuffer(error_buffer);
|
||||
|
||||
easy.SetHeaderFunction(_HeaderFunction, this);
|
||||
easy.SetWriteFunction(WriteFunction, this);
|
||||
|
||||
curl = easy.Get();
|
||||
|
||||
state = State::HEADERS;
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::FinishHeaders()
|
||||
{
|
||||
assert(state >= State::HEADERS);
|
||||
|
||||
if (state != State::HEADERS)
|
||||
return;
|
||||
|
||||
state = State::BODY;
|
||||
|
||||
long status = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
handler.OnHeaders(status, std::move(headers));
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::FinishBody()
|
||||
{
|
||||
FinishHeaders();
|
||||
|
||||
if (state != State::BODY)
|
||||
return;
|
||||
|
||||
state = State::CLOSED;
|
||||
handler.OnEnd();
|
||||
}
|
||||
|
||||
void
|
||||
CurlResponseHandlerAdapter::Done(CURLcode result) noexcept
|
||||
{
|
||||
if (postponed_error) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::move(postponed_error));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (result != CURLE_OK) {
|
||||
StripRight(error_buffer);
|
||||
const char *msg = error_buffer;
|
||||
if (*msg == 0)
|
||||
msg = curl_easy_strerror(result);
|
||||
throw FormatRuntimeError("CURL failed: %s", msg);
|
||||
}
|
||||
|
||||
FinishBody();
|
||||
} catch (...) {
|
||||
state = State::CLOSED;
|
||||
handler.OnError(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::pure]]
|
||||
static bool
|
||||
IsResponseBoundaryHeader(StringView s) noexcept
|
||||
{
|
||||
return s.size > 5 && (s.StartsWith("HTTP/") ||
|
||||
/* the proprietary "ICY 200 OK" is
|
||||
emitted by Shoutcast */
|
||||
s.StartsWith("ICY 2"));
|
||||
}
|
||||
|
||||
inline void
|
||||
CurlResponseHandlerAdapter::HeaderFunction(StringView s) noexcept
|
||||
{
|
||||
if (state > State::HEADERS)
|
||||
return;
|
||||
|
||||
if (IsResponseBoundaryHeader(s)) {
|
||||
/* this is the boundary to a new response, for example
|
||||
after a redirect */
|
||||
headers.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const char *header = s.data;
|
||||
const char *end = StripRight(header, header + s.size);
|
||||
|
||||
const char *value = s.Find(':');
|
||||
if (value == nullptr)
|
||||
return;
|
||||
|
||||
std::string name(header, value);
|
||||
std::transform(name.begin(), name.end(), name.begin(),
|
||||
static_cast<char(*)(char)>(ToLowerASCII));
|
||||
|
||||
/* skip the colon */
|
||||
|
||||
++value;
|
||||
|
||||
/* strip the value */
|
||||
|
||||
value = StripLeft(value, end);
|
||||
end = StripRight(value, end);
|
||||
|
||||
headers.emplace(std::move(name), std::string(value, end));
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size,
|
||||
std::size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
|
||||
c.HeaderFunction({ptr, size});
|
||||
return size;
|
||||
}
|
||||
|
||||
inline std::size_t
|
||||
CurlResponseHandlerAdapter::DataReceived(const void *ptr,
|
||||
std::size_t received_size) noexcept
|
||||
{
|
||||
assert(received_size > 0);
|
||||
|
||||
try {
|
||||
FinishHeaders();
|
||||
handler.OnData({ptr, received_size});
|
||||
return received_size;
|
||||
} catch (CurlResponseHandler::Pause) {
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
} catch (...) {
|
||||
/* from inside this libCURL callback function, we
|
||||
can't do much, so we remember the exception to be
|
||||
handled later by Done(), and return 0, causing the
|
||||
response to be aborted with CURLE_WRITE_ERROR */
|
||||
postponed_error = std::current_exception();
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size,
|
||||
std::size_t nmemb,
|
||||
void *stream) noexcept
|
||||
{
|
||||
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
|
||||
|
||||
size *= nmemb;
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
return c.DataReceived(ptr, size);
|
||||
}
|
91
src/lib/curl/Adapter.hxx
Normal file
91
src/lib/curl/Adapter.hxx
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2008-2022 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Headers.hxx"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
|
||||
struct StringView;
|
||||
class CurlEasy;
|
||||
class CurlResponseHandler;
|
||||
|
||||
class CurlResponseHandlerAdapter {
|
||||
CURL *curl;
|
||||
|
||||
CurlResponseHandler &handler;
|
||||
|
||||
Curl::Headers headers;
|
||||
|
||||
/**
|
||||
* An exception caught from within the WriteFunction() which
|
||||
* will later be handled by Done().
|
||||
*/
|
||||
std::exception_ptr postponed_error;
|
||||
|
||||
/** error message provided by libcurl */
|
||||
char error_buffer[CURL_ERROR_SIZE];
|
||||
|
||||
enum class State {
|
||||
UNINITIALISED,
|
||||
HEADERS,
|
||||
BODY,
|
||||
CLOSED,
|
||||
} state = State::UNINITIALISED;
|
||||
|
||||
public:
|
||||
explicit CurlResponseHandlerAdapter(CurlResponseHandler &_handler) noexcept
|
||||
:handler(_handler) {}
|
||||
|
||||
void Install(CurlEasy &easy);
|
||||
|
||||
void Done(CURLcode result) noexcept;
|
||||
|
||||
private:
|
||||
void FinishHeaders();
|
||||
void FinishBody();
|
||||
|
||||
void HeaderFunction(StringView s) noexcept;
|
||||
|
||||
/** called by curl when a new header is available */
|
||||
static std::size_t _HeaderFunction(char *ptr,
|
||||
std::size_t size, std::size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
|
||||
std::size_t DataReceived(const void *ptr, std::size_t size) noexcept;
|
||||
|
||||
/** called by curl when new data is available */
|
||||
static std::size_t WriteFunction(char *ptr,
|
||||
std::size_t size, std::size_t nmemb,
|
||||
void *stream) noexcept;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user