Compare commits

...

87 Commits

Author SHA1 Message Date
Max Kellermann
5680a3a4b7 release v0.21.17 2019-12-16 23:32:44 +01:00
Max Kellermann
15ce8eb487 time/ISO8601: support omitting field separators
Closes https://github.com/MusicPlayerDaemon/MPD/issues/685
2019-12-16 23:31:29 +01:00
Max Kellermann
b7744be208 song/Filter: try ParseISO8601() first
Prepare for allowing ISO8601 time stamps without delimiters, such as
20191216, and prevent them from being interpreted as numeric UNIX time
stamps.
2019-12-16 23:31:29 +01:00
Max Kellermann
63c5d66016 time/ISO8601: support omitting minutes 2019-12-16 23:24:43 +01:00
Max Kellermann
d09bd9178f time/ISO8601: support omitting seconds 2019-12-16 23:24:43 +01:00
Max Kellermann
7d8b1860c3 time/ISO8601: support time zone offset 2019-12-16 23:24:43 +01:00
Max Kellermann
b06825829b time/ISO8601: allow omitting the "Z" suffix
And allow "Z" suffix after date.
2019-12-16 23:24:43 +01:00
Max Kellermann
ba4cd47fd8 time/ISO8601: allow omitting the time of day 2019-12-16 23:24:43 +01:00
Max Kellermann
bbe403f141 test/TestISO8601: unit test for time/ISO8601 2019-12-16 23:24:43 +01:00
Max Kellermann
5df2707d98 time/ISO8601: ParseISO8601() returns precision 2019-12-16 23:24:43 +01:00
Max Kellermann
4859ea468f time/ISO8601: implement with strptime(), without ParseTimePoint()
Prepare for adding more flexible parsing.
2019-12-16 23:24:43 +01:00
Max Kellermann
2a8830db70 time/Convert: fallback TimeGm() implementation
Move code from Parser.cxx.
2019-12-16 23:24:38 +01:00
Max Kellermann
fed9b6fd74 time/Parser: use TimeGm() 2019-12-16 23:03:31 +01:00
Max Kellermann
b02890eb8a time/Parser: explicitly initialize struct tm before strptime()
This is recommended by the strptime() manpage, because strptime() does
not initialize/set attributes which were not specified in the format
string.
2019-12-16 23:03:25 +01:00
Max Kellermann
da882a6eb6 time/Convert: include sys/time.h for struct timeval
Closes https://github.com/MusicPlayerDaemon/MPD/issues/562
2019-12-16 23:03:21 +01:00
Max Kellermann
aeb89aa9d6 time/ISO8601: forward-declare StringBuffer 2019-12-16 23:02:59 +01:00
Max Kellermann
f885807ecc time/Convert: update copyright 2019-12-16 23:02:58 +01:00
Max Kellermann
b826fd71f0 time/Convert: mention exceptions 2019-12-16 23:02:57 +01:00
Max Kellermann
ae35df1126 zeroconf/AvahiPoll: move TimevalToChrono() to time/Convert.cxx 2019-12-16 23:02:45 +01:00
Max Kellermann
80e55f6bfc time/Convert: add noexcept 2019-12-16 23:02:40 +01:00
Max Kellermann
e7411c0c4b time/Convert: add pure attributes 2019-12-16 23:02:38 +01:00
Max Kellermann
e9af692973 util/Time*: move to time/ 2019-12-16 23:02:14 +01:00
Max Kellermann
0cf90ee8b6 decoder/mad: work around bogus -Wuninitialized in GCC 10 2019-12-16 23:01:31 +01:00
Max Kellermann
dc3c0c8866 pcm/Convert, ...: add missing include for std::runtime_error 2019-12-16 22:52:50 +01:00
Max Kellermann
1c46bb1ba6 lib/gcrypt/MD5: add missing include for uint8_t 2019-12-16 22:52:22 +01:00
Max Kellermann
2e8f42c6ad util/StringBuffer: use std::size_t instead of size_t 2019-12-16 22:51:23 +01:00
Jacob Vosmaer
2b301ffd2c lib/xiph: add missing meson dependency 2019-12-16 17:11:14 +01:00
Jacob Vosmaer
ef0765ca10 input: add missing boost meson dependency 2019-12-16 17:11:14 +01:00
Naglis Jonaitis
9766ac6db3 Fix typo in documentation 2019-12-16 17:09:45 +01:00
Max Kellermann
32799ff682 archive/zzip: improve error reporting
Most importantly, this commit translates ZZIP_ENOENT to
std::system_error(ENOENT) so IsFileNotFound() returns true and
find_stream_art() can suppress the log line.
2019-12-04 12:33:42 +01:00
Max Kellermann
ce093be12c system/Error: add FormatFileNotFound() 2019-12-04 12:33:38 +01:00
Max Kellermann
2c276770f0 util/PrintException, ...: update copyright 2019-12-04 12:33:36 +01:00
Max Kellermann
75a592f629 system/Error: move code to IsErrno() 2019-12-04 12:33:33 +01:00
Max Kellermann
13ce07d181 output/shout: declare metadata as UTF-8
Apparently, Icecast defaults to ISO-8859-1 for MP3:

 http://icecast.org/docs/icecast-2.4.0/config-file.html#mountsettings

This change forces Icecast to UTF-8 without having to configure it in
Icecast's configuration file.
2019-11-06 16:05:22 +01:00
Max Kellermann
d659c7df19 python/build/libs: update CURL to 7.66.0 2019-11-04 14:27:00 +01:00
Max Kellermann
f8403a1d29 python/build/libs: update FFmpeg to 4.2.1 2019-11-04 14:26:05 +01:00
Max Kellermann
ebb952c4ad neighbor/meson.build: disable if -Ddatabase=false
Fixes yet another build failure (which however only affects the 0.22
branch).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/666
2019-11-04 14:18:48 +01:00
Max Kellermann
bea3b954a5 meson.build, SongUpdate: disable db-specific archive code if -Ddatabase=false
Fixes another build failure.
2019-11-04 14:15:48 +01:00
Max Kellermann
129d8e89b9 lib/sqlite: disable if -Ddatabase=false
Fixes build failure in StickerCommands.cxx.
2019-11-04 14:10:03 +01:00
Niklas Haas
65778a3774 output/Jack: mark ports as terminal
This is the correct thing to do for ports from which a signal ultimately
"originates", such as is the case with mpd.
2019-10-29 10:07:44 +01:00
kaliko
d9841668ff doc/user.rst: update build dependencies on Debian Buster 2019-10-28 15:20:05 +01:00
Max Kellermann
85d27cbcb9 increment version number to 0.21.17 2019-10-16 14:20:28 +02:00
Max Kellermann
9b95e65bd9 release v0.21.16 2019-10-16 11:58:36 +02:00
Max Kellermann
12a86c4975 queue/PlaylistEdit: fix relative destination offset when moving a range
Commit 13208bf5a7 added range support to
the `move` command, but applied the wrong offset to the `to` variable.
When the source range is before the current song, and the song thus
gets decremented by the range size, then the final destination offset
must also be decremented by the range size.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/663
2019-10-15 17:00:16 +02:00
Max Kellermann
0b9435858b storage/curl: unescape file names from PROPFIND
This is the last missing piece for https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:49:17 +02:00
Max Kellermann
f0386459ee storage/curl: follow redirects for collections without trailing slash 2019-10-15 16:42:39 +02:00
Max Kellermann
e98d4670b8 storage/curl: work around different case in hex digits 2019-10-15 16:26:53 +02:00
Max Kellermann
56cc42b752 storage/curl: use MapUTF8() to reuse existing escaping code
Commit 29f78b18b1 continued.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/662
2019-10-15 16:26:53 +02:00
Max Kellermann
ead208987d storage/curl: unescape URI in MapToRelativeUTF8() 2019-10-15 16:26:49 +02:00
Max Kellermann
364acc8949 lib/curl/Escape: add CurlUnescape() 2019-10-15 13:39:02 +02:00
Max Kellermann
a8f4d2b6fc storage/curl: move code to EscapeUriPath() 2019-10-15 13:24:06 +02:00
Max Kellermann
0eb113e7c6 lib/curl/String: OO wrapper for allocated strings returned from CURL 2019-10-15 13:13:39 +02:00
Andre Heider
96a9670c69 lib/icu: fix build with iconv() 2019-10-07 13:34:04 +02:00
Max Kellermann
dcc5ce6792 storage/curl: request the "resourcetype" property to fix update
Without requesting the property, "good" WebDAV servers would not send
it, and so MPD could never recognize a directory, failing the database
update.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:44:48 +02:00
Max Kellermann
23d08820a2 db/update/Walk: fix crash when music_directory is not a directory
Add a runtime sanity check to avoid the assertion failure.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/660
2019-10-07 12:24:25 +02:00
Max Kellermann
b9b906ab20 increment version number to 0.21.16 2019-10-07 12:24:25 +02:00
Max Kellermann
964804a4c2 release v0.21.15 2019-09-25 21:24:15 +02:00
Max Kellermann
92495d2b0b decoder/mpcdec: fix bogus ReplayGain values
Apparently, libmpcdec sets gain/peak variables to zero if they are not
present.  This clashes with our formula and results in bogus values
which cause noise during playback.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/640
2019-09-13 19:52:11 +02:00
Max Kellermann
9270829b5b ReplayGainInfo: move more code to a function 2019-09-13 19:50:49 +02:00
Max Kellermann
b6243a9945 decoder/mpcdec: merge duplicate code 2019-09-13 19:50:43 +02:00
Max Kellermann
496f88653d ReplayGainInfo: add static method Undefined() 2019-09-13 19:46:39 +02:00
Max Kellermann
5ef645df97 NEWS: add missing line for 818b7e0641 2019-09-08 12:54:16 +02:00
Max Kellermann
bf49c9e4e2 decoder/{dsf,dsdiff}: precalculate bit rate 2019-09-08 12:52:02 +02:00
Max Kellermann
0da9c91af2 decoder/{dsf,dsdiff}: fix displayed bit rate
The formula did not consider the channel count.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/639
2019-09-08 12:45:05 +02:00
Max Kellermann
193e637dd9 python/build/libs: update Boost to 1.71.0 2019-09-01 13:03:50 +02:00
Max Kellermann
928bee933d python/build/libs: update expat to 2.2.7 2019-09-01 13:02:56 +02:00
Max Kellermann
4d1720c886 python/build/libs: update CURL to 7.65.3 2019-09-01 13:02:04 +02:00
Max Kellermann
8f8ed87327 python/build/libs: update FFmpeg to 4.2 2019-09-01 13:00:26 +02:00
Max Kellermann
28a441c977 python/build/libs: update Opus to 1.3.1 2019-09-01 12:59:17 +02:00
Max Kellermann
8cf50b08f2 python/build/libs: update libogg to 1.3.4 2019-09-01 12:58:26 +02:00
Max Kellermann
818b7e0641 output/solaris: include sys/stropts.h only on Solaris
This header had been available for a long time on Linux, but was
removed in glibc 2.30.  This commit moves the `#include` line inside
the `#ifdef __sun` block and adds a fake declaration of `I_FLUSH` for
the Linux build.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/630
2019-08-22 11:41:12 +02:00
Max Kellermann
e70f40fac1 increment version number to 0.21.15 2019-08-22 11:40:17 +02:00
Max Kellermann
bc89ca92b4 release v0.21.14 2019-08-21 10:47:53 +02:00
Max Kellermann
b968e1b6de output/Thread: add missing return in exception handler 2019-08-21 10:20:17 +02:00
Max Kellermann
6c9f9c136b command/all: don't create new Response instance in exception handler
The new Response instance in the `catch` block didn't have the
`command` attribute set, so the error response didn't indicate which
command had failed, which however is required in the MPD protocol.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/628
2019-08-20 20:31:36 +02:00
Max Kellermann
9bff5f9e36 client/Process, command/all: add noexcept
Clarify that those can't throw, preparing for the next commit.
2019-08-20 20:28:15 +02:00
Max Kellermann
2bf26a2ff8 command/all: remove obsolete prototype 2019-08-20 20:28:10 +02:00
Max Kellermann
e33b50d9c5 command/all: simplify return from command_process() 2019-08-20 20:26:07 +02:00
Max Kellermann
21fa44c0d5 command/all: catch all exceptions 2019-08-20 20:23:54 +02:00
Max Kellermann
44444e1b89 decoder/Thread: on late SEEK, start decoder at seek position
Previously, a bogus value (whatever happened to be still in
`start_time`) was used.
2019-08-20 20:15:08 +02:00
Max Kellermann
ca450663d0 decoder/Control: work around crash after SEEK was too late
See code comment.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/629
2019-08-20 20:01:53 +02:00
Max Kellermann
f3d16f6d1b output/Thread: fix typo in comment 2019-08-13 13:08:40 +02:00
Max Kellermann
4464cdcc67 doc/protocol.rst: add missing newline to "albumart" example
This was missing in commit 0f488dcecf
2019-08-12 20:20:17 +02:00
Fredrik Noring
2d61e526de decoder/sidplay: Fix date field to have year but not company or author
Field 2 is called <released>, formerly used as <copyright>[1][2]. It is
formatted <year><space><company or author or group>, where <year> may be
<YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for example "1987", "199?", "19??"
or "1985-87". The <company or author or group> may be for example Rob
Hubbard. A full field may be for example "1987 Rob Hubbard".

This change splits the <released> field at the first <space>, to retain
the <year> part.

The 51823 SID files in High Voltage SID Collection (HVSC) version 71
have the following distribution of dates:

    333 19??         11 1990-92       6 1995-99       2 2006-08
    827 198?         88 1990-93    2140 1996        530 2007
     32 1982         69 1990-94       9 1996-97      15 2007-08
      1 1982-83      49 1990-95       2 1996-98       2 2007-09
    255 1983       3467 1991          5 1996-99       1 2007-10
    677 1984         75 1991-92    1840 1997        430 2008
    775 1985         65 1991-93       4 1997-98      23 2008-09
      3 1985-86      10 1991-94    1276 1998          1 2008-12
     10 1985-87      35 1991-97       4 1998-99     631 2009
    943 1986       3320 1992        865 1999          1 2009-10
     12 1986-87      26 1992-93      24 200?        645 2010
      5 1986-89      59 1992-94     590 2000          1 2010-12
   2083 1987          1 1992-96       4 2000-01     538 2011
     31 1987-88    2996 1993        727 2001          1 2011-12
     44 1987-89      42 1993-94     875 2002        651 2012
   2510 1988         12 1993-95       2 2002-04     811 2013
    129 1988-89       2 1993-97     844 2003        790 2014
     91 1988-90    2737 1994          3 2003-05     740 2015
     58 1988-91      16 1994-95     842 2004        792 2016
   3466 1989         20 1994-96       2 2004-05     775 2017
     95 1989-90      17 1994-97     707 2005        638 2018
    150 1989-91    2271 1995          1 2005-06     284 2019
   1077 199?          2 1995-96       2 2005-07
   2834 1990          4 1995-97     785 2006
    119 1990-91       2 1995-98       6 2006-07

References:

[1] https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt
[2] https://hvsc.c64.org/info
2019-08-10 10:50:51 +02:00
Fredrik Noring
7723c481db decoder/sidplay: Fix windows-1252 to utf-8 string conversion
High Voltage SID Collection (HVSC) metadata fields are encoded in
windows-1252, as described in DOCUMENTS/SID_file_format.txt:

https://www.hvsc.c64.org/download/C64Music/DOCUMENTS/SID_file_format.txt

If utf-8 transcoding fails, or the ICU library is unavailable, fall
back to plain ASCII and replace other characters with '?'.
2019-08-10 10:45:02 +02:00
Fredrik Noring
0ed10542cc decoder/sidplay: Fix song length initialisation during container scan
The song length was previously undetermined.
2019-08-09 15:39:36 +02:00
Max Kellermann
ab830f9afd increment version number to 0.21.14 2019-08-09 15:38:01 +02:00
92 changed files with 957 additions and 287 deletions

40
NEWS
View File

@@ -1,3 +1,43 @@
ver 0.21.17 (2019/12/16)
* protocol
- relax the ISO 8601 parser: allow omitting field separators, the
time of day and the "Z" suffix
* archive
- zzip: improve error reporting
* outputs
- jack: mark ports as terminal
- shout: declare metadata as UTF-8
* fix build failure with -Ddatabase=false
ver 0.21.16 (2019/10/16)
* queue
- fix relative destination offset when moving a range
* storage
- curl: request the "resourcetype" property to fix database update
- curl: URL-encode more paths
- curl: follow redirects for collections without trailing slash
* update
- fix crash when music_directory is not a directory
* fix build with iconv() instead of ICU
ver 0.21.15 (2019/09/25)
* decoder
- dsdiff, dsf: fix displayed bit rate
- mpcdec: fix bogus ReplayGain values
* output
- solaris: fix build with glibc 2.30
ver 0.21.14 (2019/08/21)
* decoder
- sidplay: show track durations in database
- sidplay: convert tag values from Windows-1252 charset
- sidplay: strip text from "Date" tag
* player
- fix crash after song change
- fix seek position after restarting the decoder
* protocol
- include command name in error responses
ver 0.21.13 (2019/08/06) ver 0.21.13 (2019/08/06)
* input * input
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1 - cdio_paranoia: require libcdio-paranoia 10.2+0.93+1

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="36" android:versionCode="40"
android:versionName="0.21.13"> android:versionName="0.21.17">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>

View File

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.21.13' version = '0.21.17'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = version release = version

View File

@@ -42,7 +42,7 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
* - **password** * - **password**
- The password used to log in to the "master" :program:`MPD` instance. - The password used to log in to the "master" :program:`MPD` instance.
* - **keepalive yes|no** * - **keepalive yes|no**
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default. - Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
upnp upnp
---- ----
@@ -1069,7 +1069,7 @@ Filter plugins
normalize normalize
--------- ---------
Normalize the volume during playback (at the expensve of quality). Normalize the volume during playback (at the expense of quality).
null null

View File

@@ -824,7 +824,8 @@ The music database
albumart albumart
size: 1024768 size: 1024768
binary: 8192 binary: 8192
<8192 bytes>OK <8192 bytes>
OK
:command:`count {FILTER} [group {GROUPTYPE}]` :command:`count {FILTER} [group {GROUPTYPE}]`
Count the number of songs and their total playtime in Count the number of songs and their total playtime in

View File

@@ -62,16 +62,16 @@ In any case, you need:
Each plugin usually needs a codec library, which you also need to Each plugin usually needs a codec library, which you also need to
install. Check the :doc:`plugins` for details about required libraries install. Check the :doc:`plugins` for details about required libraries
For example, the following installs a fairly complete list of build dependencies on Debian Jessie: For example, the following installs a fairly complete list of build dependencies on Debian Buster:
.. code-block:: none .. code-block:: none
apt install g++ \ apt install meson g++ \
libpcre3-dev \ libpcre3-dev \
libmad0-dev libmpg123-dev libid3tag0-dev \ libmad0-dev libmpg123-dev libid3tag0-dev \
libflac-dev libvorbis-dev libopus-dev \ libflac-dev libvorbis-dev libopus-dev libogg-dev \
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \ libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \ libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
libmpcdec-dev libwavpack-dev libwildmidi-dev \ libmpcdec-dev libwavpack-dev libwildmidi-dev \
libsidplay2-dev libsidutils-dev libresid-builder-dev \ libsidplay2-dev libsidutils-dev libresid-builder-dev \
libavcodec-dev libavformat-dev \ libavcodec-dev libavformat-dev \
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
libsystemd-dev \ libsystemd-dev \
libgtest-dev \ libgtest-dev \
libboost-dev \ libboost-dev \
libicu-dev libicu-dev \
libchromaprint-dev \
libgcrypt20-dev
Now configure the source tree: Now configure the source tree:

View File

@@ -1,7 +1,7 @@
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.21.13', version: '0.21.17',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options: [ default_options: [
'c_std=c99', 'c_std=c99',
@@ -304,6 +304,7 @@ if enable_database
endif endif
subdir('src/util') subdir('src/util')
subdir('src/time')
subdir('src/system') subdir('src/system')
subdir('src/thread') subdir('src/thread')
subdir('src/event') subdir('src/event')
@@ -385,8 +386,11 @@ endif
if archive_glue_dep.found() if archive_glue_dep.found()
sources += [ sources += [
'src/TagArchive.cxx', 'src/TagArchive.cxx',
'src/db/update/Archive.cxx',
] ]
if enable_database
sources += ['src/db/update/Archive.cxx']
endif
endif endif
if is_windows if is_windows

View File

@@ -15,8 +15,8 @@ libmpdclient = MesonProject(
) )
libogg = AutotoolsProject( libogg = AutotoolsProject(
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz', 'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08', 'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
'lib/libogg.a', 'lib/libogg.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -38,8 +38,8 @@ libvorbis = AutotoolsProject(
) )
opus = AutotoolsProject( opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz', 'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550', '65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
'lib/libopus.a', 'lib/libopus.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-4.2.1.tar.xz',
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d', 'cec7c87e9b60d174509e263ac4011b522385fd0775292e1670ecc1180c9bb6d4',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
) )
curl = AutotoolsProject( curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.64.1.tar.xz', 'http://curl.haxx.se/download/curl-7.66.0.tar.xz',
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3', 'dbb48088193016d079b97c5c3efde8efa56ada2ebf336e8a97d04eb8e2ed98c1',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -365,8 +365,8 @@ curl = AutotoolsProject(
) )
libexpat = AutotoolsProject( libexpat = AutotoolsProject(
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2', 'https://github.com/libexpat/libexpat/releases/download/R_2_2_7/expat-2.2.7.tar.bz2',
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2', 'cbc9102f4a31a8dafd42d642e9a3aa31e79a0aedaa1f6efd2795ebc83174ec18',
'lib/libexpat.a', 'lib/libexpat.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -392,7 +392,7 @@ libnfs = AutotoolsProject(
) )
boost = BoostProject( boost = BoostProject(
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2', 'https://dl.bintray.com/boostorg/release/1.71.0/source/boost_1_71_0.tar.bz2',
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778', 'd73a8da01e8bf8c7eda40b4c84915071a8c8a0df4a6734537ddde4a8580524ee',
'include/boost/version.hpp', 'include/boost/version.hpp',
) )

View File

@@ -29,6 +29,8 @@
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#endif #endif
#include <stdexcept>
static LocatedUri static LocatedUri
LocateFileUri(const char *uri, const Client *client LocateFileUri(const char *uri, const Client *client
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE

View File

@@ -21,8 +21,8 @@
#include "db/PlaylistVector.hxx" #include "db/PlaylistVector.hxx"
#include "fs/io/TextFile.hxx" #include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx"
#include "time/ChronoUtil.hxx"
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"
#include "util/ChronoUtil.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include <string.h> #include <string.h>

View File

@@ -38,6 +38,10 @@ struct ReplayGainTuple {
return gain > -100; return gain > -100;
} }
static constexpr ReplayGainTuple Undefined() noexcept {
return {-200.0f, 0.0f};
}
gcc_pure gcc_pure
float CalculateScale(const ReplayGainConfig &config) const noexcept; float CalculateScale(const ReplayGainConfig &config) const noexcept;
}; };
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
return track.IsDefined() || album.IsDefined(); return track.IsDefined() || album.IsDefined();
} }
static constexpr ReplayGainInfo Undefined() noexcept {
return {
ReplayGainTuple::Undefined(),
ReplayGainTuple::Undefined(),
};
}
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept { const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
return mode == ReplayGainMode::ALBUM return mode == ReplayGainMode::ALBUM
? (album.IsDefined() ? album : track) ? (album.IsDefined() ? album : track)

View File

@@ -27,7 +27,7 @@
#include "TagPrint.hxx" #include "TagPrint.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#define SONG_FILE "file: " #define SONG_FILE "file: "

View File

@@ -27,7 +27,7 @@
#include "tag/ParseName.hxx" #include "tag/ParseName.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "tag/Builder.hxx" #include "tag/Builder.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"

View File

@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
return true; return true;
} }
#endif
#ifdef ENABLE_ARCHIVE #ifdef ENABLE_ARCHIVE
Song * Song *
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
#endif #endif
#endif /* ENABLE_DATABASE */
bool bool
DetachedSong::LoadFile(Path path) noexcept DetachedSong::LoadFile(Path path) noexcept
{ {

View File

@@ -28,7 +28,7 @@
#include "db/Stats.hxx" #include "db/Stats.hxx"
#include "system/Clock.hxx" #include "system/Clock.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include <chrono> #include <chrono>
#include <cmath> #include <cmath>

View File

@@ -19,7 +19,7 @@
#include "TimePrint.hxx" #include "TimePrint.hxx"
#include "client/Response.hxx" #include "client/Response.hxx"
#include "util/TimeISO8601.hxx" #include "time/ISO8601.hxx"
void void
time_print(Response &r, const char *name, time_print(Response &r, const char *name,

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2018 The Music Player Daemon Project * Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org * http://www.musicpd.org
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -27,6 +27,7 @@
#include "../ArchiveVisitor.hxx" #include "../ArchiveVisitor.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "system/Error.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include <zzip/zzip.h> #include <zzip/zzip.h>
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
Mutex &mutex) Mutex &mutex)
{ {
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0); ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
if (_file == nullptr) if (_file == nullptr) {
throw FormatRuntimeError("not found in the ZIP file: %s", const auto error = (zzip_error_t)zzip_error(dir->dir);
pathname); switch (error) {
case ZZIP_ENOENT:
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
pathname);
default:
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
pathname,
zzip_strerror(error));
}
}
return std::make_unique<ZzipInputStream>(dir, pathname, return std::make_unique<ZzipInputStream>(dir, pathname,
mutex, mutex,

View File

@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
extern size_t client_max_output_buffer_size; extern size_t client_max_output_buffer_size;
CommandResult CommandResult
client_process_line(Client &client, char *line); client_process_line(Client &client, char *line) noexcept;
#endif #endif

View File

@@ -30,7 +30,7 @@
static CommandResult static CommandResult
client_process_command_list(Client &client, bool list_ok, client_process_command_list(Client &client, bool list_ok,
std::list<std::string> &&list) std::list<std::string> &&list) noexcept
{ {
CommandResult ret = CommandResult::OK; CommandResult ret = CommandResult::OK;
unsigned num = 0; unsigned num = 0;
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
} }
CommandResult CommandResult
client_process_line(Client &client, char *line) client_process_line(Client &client, char *line) noexcept
{ {
CommandResult ret; CommandResult ret;

View File

@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
static constexpr unsigned num_commands = ARRAY_SIZE(commands); static constexpr unsigned num_commands = ARRAY_SIZE(commands);
gcc_pure
static bool static bool
command_available(gcc_unused const Partition &partition, command_available(gcc_unused const Partition &partition,
gcc_unused const struct command *cmd) gcc_unused const struct command *cmd) noexcept
{ {
#ifdef ENABLE_SQLITE #ifdef ENABLE_SQLITE
if (StringIsEqual(cmd->cmd, "sticker")) if (StringIsEqual(cmd->cmd, "sticker"))
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
static CommandResult static CommandResult
PrintAvailableCommands(Response &r, const Partition &partition, PrintAvailableCommands(Response &r, const Partition &partition,
unsigned permission) unsigned permission) noexcept
{ {
for (unsigned i = 0; i < num_commands; ++i) { for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i]; const struct command *cmd = &commands[i];
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
} }
static CommandResult static CommandResult
PrintUnavailableCommands(Response &r, unsigned permission) PrintUnavailableCommands(Response &r, unsigned permission) noexcept
{ {
for (unsigned i = 0; i < num_commands; ++i) { for (unsigned i = 0; i < num_commands; ++i) {
const struct command *cmd = &commands[i]; const struct command *cmd = &commands[i];
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
} }
void void
command_init() command_init() noexcept
{ {
#ifndef NDEBUG #ifndef NDEBUG
/* ensure that the command list is sorted */ /* ensure that the command list is sorted */
@@ -285,8 +286,9 @@ command_init()
#endif #endif
} }
gcc_pure
static const struct command * static const struct command *
command_lookup(const char *name) command_lookup(const char *name) noexcept
{ {
unsigned a = 0, b = num_commands, i; unsigned a = 0, b = num_commands, i;
@@ -308,7 +310,7 @@ command_lookup(const char *name)
static bool static bool
command_check_request(const struct command *cmd, Response &r, command_check_request(const struct command *cmd, Response &r,
unsigned permission, Request args) unsigned permission, Request args) noexcept
{ {
if (cmd->permission != (permission & cmd->permission)) { if (cmd->permission != (permission & cmd->permission)) {
r.FormatError(ACK_ERROR_PERMISSION, r.FormatError(ACK_ERROR_PERMISSION,
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
static const struct command * static const struct command *
command_checked_lookup(Response &r, unsigned permission, command_checked_lookup(Response &r, unsigned permission,
const char *cmd_name, Request args) const char *cmd_name, Request args) noexcept
{ {
const struct command *cmd = command_lookup(cmd_name); const struct command *cmd = command_lookup(cmd_name);
if (cmd == nullptr) { if (cmd == nullptr) {
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
} }
CommandResult CommandResult
command_process(Client &client, unsigned num, char *line) command_process(Client &client, unsigned num, char *line) noexcept
try { {
Response r(client, num); Response r(client, num);
/* get the command name (first word on the line) */ /* get the command name (first word on the line) */
@@ -389,34 +391,33 @@ try {
char *argv[COMMAND_ARGV_MAX]; char *argv[COMMAND_ARGV_MAX];
Request args(argv, 0); Request args(argv, 0);
/* now parse the arguments (quoted or unquoted) */ try {
/* now parse the arguments (quoted or unquoted) */
while (true) { while (true) {
if (args.size == COMMAND_ARGV_MAX) { if (args.size == COMMAND_ARGV_MAX) {
r.Error(ACK_ERROR_ARG, "Too many arguments"); r.Error(ACK_ERROR_ARG, "Too many arguments");
return CommandResult::ERROR; return CommandResult::ERROR;
}
char *a = tokenizer.NextParam();
if (a == nullptr)
break;
argv[args.size++] = a;
} }
char *a = tokenizer.NextParam(); /* look up and invoke the command handler */
if (a == nullptr)
break;
argv[args.size++] = a; const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
if (cmd == nullptr)
return CommandResult::ERROR;
return cmd->handler(client, args, r);
} catch (...) {
PrintError(r, std::current_exception());
return CommandResult::ERROR;
} }
/* look up and invoke the command handler */
const struct command *cmd =
command_checked_lookup(r, client.GetPermission(),
cmd_name, args);
CommandResult ret = cmd
? cmd->handler(client, args, r)
: CommandResult::ERROR;
return ret;
} catch (const std::exception &e) {
Response r(client, num);
PrintError(r, std::current_exception());
return CommandResult::ERROR;
} }

View File

@@ -25,12 +25,9 @@
class Client; class Client;
void void
command_init(); command_init() noexcept;
void
command_finish();
CommandResult CommandResult
command_process(Client &client, unsigned num, char *line); command_process(Client &client, unsigned num, char *line) noexcept;
#endif #endif

View File

@@ -35,7 +35,7 @@
#include "decoder/DecoderPrint.hxx" #include "decoder/DecoderPrint.hxx"
#include "ls.hxx" #include "ls.hxx"
#include "mixer/Volume.hxx" #include "mixer/Volume.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"

View File

@@ -37,9 +37,9 @@
#include "client/Response.hxx" #include "client/Response.hxx"
#include "Mapper.hxx" #include "Mapper.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/ChronoUtil.hxx"
#include "LocateUri.hxx" #include "LocateUri.hxx"
bool bool

View File

@@ -23,8 +23,8 @@
#include "StorageCommands.hxx" #include "StorageCommands.hxx"
#include "Request.hxx" #include "Request.hxx"
#include "CommandError.hxx" #include "CommandError.hxx"
#include "time/ChronoUtil.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "client/Client.hxx" #include "client/Client.hxx"

View File

@@ -34,7 +34,7 @@
#include "PlaylistInfo.hxx" #include "PlaylistInfo.hxx"
#include "Interface.hxx" #include "Interface.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/RecursiveMap.hxx" #include "util/RecursiveMap.hxx"
#include <functional> #include <functional>

View File

@@ -25,7 +25,7 @@
#include "PlaylistDatabase.hxx" #include "PlaylistDatabase.hxx"
#include "fs/io/TextFile.hxx" #include "fs/io/TextFile.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/NumberParser.hxx" #include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"

View File

@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
if (!GetInfo(storage, "", info)) if (!GetInfo(storage, "", info))
return false; return false;
if (!info.IsDirectory()) {
FormatError(update_domain, "Not a directory: %s",
storage.MapUTF8("").c_str());
return false;
}
ExcludeList exclude_list; ExcludeList exclude_list;
UpdateDirectory(root, exclude_list, info); UpdateDirectory(root, exclude_list, info);

View File

@@ -147,6 +147,18 @@ DecoderControl::Seek(SongTime t)
seek_error = false; seek_error = false;
SynchronousCommandLocked(DecoderCommand::SEEK); SynchronousCommandLocked(DecoderCommand::SEEK);
while (state == DecoderState::START)
/* If the decoder falls back to DecoderState::START,
this means that our SEEK command arrived too late,
and the decoder had meanwhile finished decoding and
went idle. Our SEEK command is finished, but that
means only that the decoder thread has launched the
decoder. To work around illegal states, we wait
until the decoder plugin has become ready. This is
a kludge, built on top of the "late seek" kludge.
Not exactly elegant, sorry. */
WaitForDecoder();
if (seek_error) if (seek_error)
throw std::runtime_error("Decoder failed to seek"); throw std::runtime_error("Decoder failed to seek");
} }

View File

@@ -455,6 +455,11 @@ static void
decoder_run_song(DecoderControl &dc, decoder_run_song(DecoderControl &dc,
const DetachedSong &song, const char *uri, Path path_fs) const DetachedSong &song, const char *uri, Path path_fs)
{ {
if (dc.command == DecoderCommand::SEEK)
/* if the SEEK command arrived too late, start the
decoder at the seek position */
dc.start_time = dc.seek_time;
DecoderBridge bridge(dc, dc.start_time.IsPositive(), DecoderBridge bridge(dc, dc.start_time.IsPositive(),
/* pass the song tag only if it's /* pass the song tag only if it's
authoritative, i.e. if it's a local authoritative, i.e. if it's a local

View File

@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
unsigned channels, unsigned sample_rate, unsigned channels, unsigned sample_rate,
const offset_type total_bytes) const offset_type total_bytes)
{ {
const unsigned kbit_rate = channels * sample_rate / 1000;
const offset_type start_offset = is.GetOffset(); const offset_type start_offset = is.GetOffset();
uint8_t buffer[8192]; uint8_t buffer[8192];
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
bit_reverse_buffer(buffer, buffer + nbytes); bit_reverse_buffer(buffer, buffer + nbytes);
cmd = client.SubmitData(is, buffer, nbytes, cmd = client.SubmitData(is, buffer, nbytes,
sample_rate / 1000); kbit_rate);
} }
return true; return true;

View File

@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
offset_type n_blocks, offset_type n_blocks,
bool bitreverse) bool bitreverse)
{ {
const unsigned kbit_rate = channels * sample_rate / 1000;
const size_t block_size = channels * DSF_BLOCK_SIZE; const size_t block_size = channels * DSF_BLOCK_SIZE;
const offset_type start_offset = is.GetOffset(); const offset_type start_offset = is.GetOffset();
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
cmd = client.SubmitData(is, cmd = client.SubmitData(is,
interleaved_buffer, block_size, interleaved_buffer, block_size,
sample_rate / 1000); kbit_rate);
++i; ++i;
} }

View File

@@ -719,6 +719,11 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
{ {
struct xing xing; struct xing xing;
#if GCC_CHECK_VERSION(10,0)
/* work around bogus -Wuninitialized in GCC 10 */
xing.frames = 0;
#endif
while (true) { while (true) {
MadDecoderAction ret; MadDecoderAction ret;
do { do {

View File

@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
*dest++ = mpc_to_mpd_sample(*src++); *dest++ = mpc_to_mpd_sample(*src++);
} }
static constexpr ReplayGainTuple
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
{
auto t = ReplayGainTuple::Undefined();
if (gain != 0 && peak != 0) {
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
t.peak = pow(10, peak / 256. / 20) / 32767;
}
return t;
}
static constexpr ReplayGainInfo
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
{
auto rgi = ReplayGainInfo::Undefined();
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
return rgi;
}
static void static void
mpcdec_decode(DecoderClient &client, InputStream &is) mpcdec_decode(DecoderClient &client, InputStream &is)
{ {
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
mpcdec_sample_format, mpcdec_sample_format,
info.channels); info.channels);
ReplayGainInfo rgi; {
rgi.Clear(); const auto rgi = ImportMpcdecReplayGain(info);
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); if (rgi.IsDefined())
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767; client.SubmitReplayGain(&rgi);
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); }
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
client.SubmitReplayGain(&rgi);
client.Ready(audio_format, is.IsSeekable(), client.Ready(audio_format, is.IsSeekable(),
SongTime::FromS(mpc_streaminfo_get_length(&info))); SongTime::FromS(mpc_streaminfo_get_length(&info)));

View File

@@ -25,6 +25,7 @@
#include "song/DetachedSong.hxx" #include "song/DetachedSong.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "lib/icu/Converter.hxx"
#ifdef HAVE_SIDPLAYFP #ifdef HAVE_SIDPLAYFP
#include "fs/io/FileReader.hxx" #include "fs/io/FileReader.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
@@ -32,6 +33,8 @@
#include "util/Macros.hxx" #include "util/Macros.hxx"
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/AllocatedString.hxx"
#include "util/CharUtil.hxx"
#include "system/ByteOrder.hxx" #include "system/ByteOrder.hxx"
#include "Log.hxx" #include "Log.hxx"
@@ -432,19 +435,70 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
} while (cmd != DecoderCommand::STOP); } while (cmd != DecoderCommand::STOP);
} }
static AllocatedString<char>
Windows1252ToUTF8(const char *s) noexcept
{
#ifdef HAVE_ICU_CONVERTER
try {
std::unique_ptr<IcuConverter>
converter(IcuConverter::Create("windows-1252"));
return converter->ToUTF8(s);
} catch (...) { }
#endif
/*
* Fallback to not transcoding windows-1252 to utf-8, that may result
* in invalid utf-8 unless nonprintable characters are replaced.
*/
auto t = AllocatedString<char>::Duplicate(s);
for (size_t i = 0; t[i] != AllocatedString<char>::SENTINEL; i++)
if (!IsPrintableASCII(t[i]))
t[i] = '?';
return t;
}
gcc_pure gcc_pure
static const char * static AllocatedString<char>
GetInfoString(const SidTuneInfo &info, unsigned i) noexcept GetInfoString(const SidTuneInfo &info, unsigned i) noexcept
{ {
#ifdef HAVE_SIDPLAYFP #ifdef HAVE_SIDPLAYFP
return info.numberOfInfoStrings() > i const char *s = info.numberOfInfoStrings() > i
? info.infoString(i) ? info.infoString(i)
: nullptr; : "";
#else #else
return info.numberOfInfoStrings > i const char *s = info.numberOfInfoStrings > i
? info.infoString[i] ? info.infoString[i]
: nullptr; : "";
#endif #endif
return Windows1252ToUTF8(s);
}
gcc_pure
static AllocatedString<char>
GetDateString(const SidTuneInfo &info) noexcept
{
/*
* Field 2 is called <released>, previously used as <copyright>.
* It is formatted <year><space><company or author or group>,
* where <year> may be <YYYY>, <YYY?>, <YY??> or <YYYY-YY>, for
* example "1987", "199?", "19??" or "1985-87". The <company or
* author or group> may be for example Rob Hubbard. A full field
* may be for example "1987 Rob Hubbard".
*/
AllocatedString<char> release = GetInfoString(info, 2);
/* Keep the <year> part only for the date. */
for (size_t i = 0; release[i] != AllocatedString<char>::SENTINEL; i++)
if (std::isspace(release[i])) {
release[i] = AllocatedString<char>::SENTINEL;
break;
}
return release;
} }
static void static void
@@ -452,27 +506,25 @@ ScanSidTuneInfo(const SidTuneInfo &info, unsigned track, unsigned n_tracks,
TagHandler &handler) noexcept TagHandler &handler) noexcept
{ {
/* title */ /* title */
const char *title = GetInfoString(info, 0); const auto title = GetInfoString(info, 0);
if (title == nullptr)
title = "";
if (n_tracks > 1) { if (n_tracks > 1) {
const auto tag_title = const auto tag_title =
StringFormat<1024>("%s (%u/%u)", StringFormat<1024>("%s (%u/%u)",
title, track, n_tracks); title.c_str(), track, n_tracks);
handler.OnTag(TAG_TITLE, tag_title); handler.OnTag(TAG_TITLE, tag_title.c_str());
} else } else
handler.OnTag(TAG_TITLE, title); handler.OnTag(TAG_TITLE, title.c_str());
/* artist */ /* artist */
const char *artist = GetInfoString(info, 1); const auto artist = GetInfoString(info, 1);
if (artist != nullptr) if (!artist.empty())
handler.OnTag(TAG_ARTIST, artist); handler.OnTag(TAG_ARTIST, artist.c_str());
/* date */ /* date */
const char *date = GetInfoString(info, 2); const auto date = GetDateString(info);
if (date != nullptr) if (!date.empty())
handler.OnTag(TAG_DATE, date); handler.OnTag(TAG_DATE, date.c_str());
/* track */ /* track */
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track)); handler.OnTag(TAG_TRACK, StringFormat<16>("%u", track));
@@ -547,6 +599,10 @@ sidplay_container_scan(Path path_fs)
AddTagHandler h(tag_builder); AddTagHandler h(tag_builder);
ScanSidTuneInfo(info, i, n_tracks, h); ScanSidTuneInfo(info, i, n_tracks, h);
const SignedSongTime duration = get_song_length(tune);
if (!duration.IsNegative())
h.OnDuration(SongTime(duration));
char track_name[32]; char track_name[32];
/* Construct container/tune path names, eg. /* Construct container/tune path names, eg.
Delta.sid/tune_001.sid */ Delta.sid/tune_001.sid */

View File

@@ -35,6 +35,9 @@ input_glue = static_library(
'BufferedInputStream.cxx', 'BufferedInputStream.cxx',
'MaybeBufferedInputStream.cxx', 'MaybeBufferedInputStream.cxx',
include_directories: inc, include_directories: inc,
dependencies: [
boost_dep,
],
) )
input_glue_dep = declare_dependency( input_glue_dep = declare_dependency(

View File

@@ -30,6 +30,8 @@
#ifndef CURL_EASY_HXX #ifndef CURL_EASY_HXX
#define CURL_EASY_HXX #define CURL_EASY_HXX
#include "String.hxx"
#include <curl/curl.h> #include <curl/curl.h>
#include <utility> #include <utility>
@@ -88,8 +90,8 @@ public:
throw std::runtime_error(curl_easy_strerror(code)); throw std::runtime_error(curl_easy_strerror(code));
} }
char *Escape(const char *string, int length=0) const noexcept { CurlString Escape(const char *string, int length=0) const noexcept {
return curl_easy_escape(handle, string, length); return CurlString(curl_easy_escape(handle, string, length));
} }
}; };

71
src/lib/curl/Escape.cxx Normal file
View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 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 "Escape.hxx"
#include "Easy.hxx"
#include "String.hxx"
#include "util/IterableSplitString.hxx"
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept
{
std::string dest;
for (const auto i : IterableSplitString(src, '/')) {
CurlString escaped(curl_easy_escape(curl, i.data, i.size));
if (!dest.empty())
dest.push_back('/');
dest += escaped.c_str();
}
return dest;
}
std::string
CurlEscapeUriPath(StringView src) noexcept
{
CurlEasy easy;
return CurlEscapeUriPath(easy.Get(), src);
}
std::string
CurlUnescape(CURL *curl, StringView src) noexcept
{
int outlength;
CurlString tmp(curl_easy_unescape(curl, src.data, src.size,
&outlength));
return std::string(tmp.c_str(), outlength);
}
std::string
CurlUnescape(StringView src) noexcept
{
CurlEasy easy;
return CurlUnescape(easy.Get(), src);
}

View File

@@ -1,8 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright (C) 2018 Max Kellermann <max.kellermann@gmail.com>
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@@ -30,32 +27,25 @@
* OF THE POSSIBILITY OF SUCH DAMAGE. * OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "TimeISO8601.hxx" #ifndef CURL_ESCAPE_HXX
#include "TimeConvert.hxx" #define CURL_ESCAPE_HXX
#include "TimeParser.hxx"
#include <curl/curl.h>
#include <string>
struct StringView;
std::string
CurlEscapeUriPath(CURL *curl, StringView src) noexcept;
std::string
CurlEscapeUriPath(StringView src) noexcept;
std::string
CurlUnescape(CURL *curl, StringView src) noexcept;
std::string
CurlUnescape(StringView src) noexcept;
StringBuffer<64>
FormatISO8601(const struct tm &tm) noexcept
{
StringBuffer<64> buffer;
strftime(buffer.data(), buffer.capacity(),
#ifdef _WIN32
"%Y-%m-%dT%H:%M:%SZ",
#else
"%FT%TZ",
#endif #endif
&tm);
return buffer;
}
StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp)
{
return FormatISO8601(GmTime(tp));
}
std::chrono::system_clock::time_point
ParseISO8601(const char *s)
{
return ParseTimePoint(s, "%FT%TZ");
}

View File

@@ -28,6 +28,7 @@
*/ */
#include "Form.hxx" #include "Form.hxx"
#include "String.hxx"
std::string std::string
EncodeForm(CURL *curl, EncodeForm(CURL *curl,
@@ -43,12 +44,10 @@ EncodeForm(CURL *curl,
result.push_back('='); result.push_back('=');
if (!i.second.empty()) { if (!i.second.empty()) {
char *value = curl_easy_escape(curl, i.second.data(), CurlString value(curl_easy_escape(curl, i.second.data(),
i.second.length()); i.second.length()));
if (value != nullptr) { if (value)
result.append(value); result.append(value);
curl_free(value);
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2016 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2016 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2016 Max Kellermann <max.kellermann@gmail.com> * Copyright 2016-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@@ -33,6 +33,7 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <algorithm> #include <algorithm>
#include <stdexcept>
/** /**
* OO wrapper for "struct curl_slist *". * OO wrapper for "struct curl_slist *".

77
src/lib/curl/String.hxx Normal file
View File

@@ -0,0 +1,77 @@
/*
* Copyright 2019 Max Kellermann <max.kellermann@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef CURL_STRING_HXX
#define CURL_STRING_HXX
#include <curl/curl.h>
#include <utility>
/**
* An OO wrapper for an allocated string to be freed with curl_free().
*/
class CurlString {
char *p = nullptr;
public:
CurlString() noexcept = default;
CurlString(std::nullptr_t) noexcept {}
explicit CurlString(char *_p) noexcept
:p(_p) {}
CurlString(CurlString &&src) noexcept
:p(std::exchange(src.p, nullptr)) {}
~CurlString() noexcept {
if (p != nullptr)
curl_free(p);
}
CurlString &operator=(CurlString &&src) noexcept {
using std::swap;
swap(p, src.p);
return *this;
}
operator bool() const noexcept {
return p != nullptr;
}
operator const char *() const noexcept {
return p;
}
const char *c_str() const noexcept {
return p;
}
};
#endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2017-2018 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -11,6 +11,7 @@ curl = static_library(
'Init.cxx', 'Init.cxx',
'Global.cxx', 'Global.cxx',
'Request.cxx', 'Request.cxx',
'Escape.cxx',
'Form.cxx', 'Form.cxx',
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [

View File

@@ -35,6 +35,8 @@
#include <array> #include <array>
#include <stdint.h>
template<typename T> struct ConstBuffer; template<typename T> struct ConstBuffer;
gcc_pure gcc_pure

View File

@@ -20,7 +20,7 @@ if icu_dep.found()
elif not get_option('iconv').disabled() elif not get_option('iconv').disabled()
have_iconv = compiler.has_function('iconv') have_iconv = compiler.has_function('iconv')
conf.set('HAVE_ICONV', have_iconv) conf.set('HAVE_ICONV', have_iconv)
if get_option('iconv').enabled() if not have_iconv and get_option('iconv').enabled()
error('iconv() not available') error('iconv() not available')
endif endif
endif endif

View File

@@ -25,6 +25,7 @@
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include <utility> #include <utility>
#include <stdexcept>
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>

View File

@@ -1,4 +1,9 @@
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite')) if enable_database
sqlite_dep = dependency('sqlite3', version: '>= 3.7.3', required: get_option('sqlite'))
else
sqlite_dep = dependency('', required: false)
endif
conf.set('ENABLE_SQLITE', sqlite_dep.found()) conf.set('ENABLE_SQLITE', sqlite_dep.found())
if not sqlite_dep.found() if not sqlite_dep.found()
subdir_done() subdir_done()

View File

@@ -49,6 +49,9 @@ xiph = static_library(
'VorbisComments.cxx', 'VorbisComments.cxx',
'XiphTags.cxx', 'XiphTags.cxx',
include_directories: inc, include_directories: inc,
dependencies: [
libvorbis_dep,
],
) )
xiph_dep = declare_dependency( xiph_dep = declare_dependency(

View File

@@ -1,4 +1,4 @@
if not get_option('neighbor') if not get_option('neighbor') or not enable_database
conf.set('ENABLE_NEIGHBOR_PLUGINS', false) conf.set('ENABLE_NEIGHBOR_PLUGINS', false)
neighbor_glue_dep = dependency('', required: false) neighbor_glue_dep = dependency('', required: false)
subdir_done() subdir_done()

View File

@@ -31,6 +31,7 @@
#define STATIC_SOCKET_ADDRESS_HXX #define STATIC_SOCKET_ADDRESS_HXX
#include "SocketAddress.hxx" #include "SocketAddress.hxx"
#include "Features.hxx"
#include "util/Compiler.h" #include "util/Compiler.h"
#include <assert.h> #include <assert.h>

View File

@@ -159,6 +159,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format,
} catch (...) { } catch (...) {
LogError(std::current_exception()); LogError(std::current_exception());
Failure(std::current_exception()); Failure(std::current_exception());
return;
} }
if (f != in_audio_format || f != output->out_audio_format) if (f != in_audio_format || f != output->out_audio_format)
@@ -458,7 +459,7 @@ AudioOutputControl::Task() noexcept
case Command::RELEASE: case Command::RELEASE:
if (!open) { if (!open) {
/* the output has failed after /* the output has failed after
the PAUSE command was submitted; bail the RELEASE command was submitted; bail
out */ out */
CommandFinished(); CommandFinished();
break; break;

View File

@@ -403,10 +403,11 @@ JackOutput::Connect()
jack_on_shutdown(client, mpd_jack_shutdown, this); jack_on_shutdown(client, mpd_jack_shutdown, this);
for (unsigned i = 0; i < num_source_ports; ++i) { for (unsigned i = 0; i < num_source_ports; ++i) {
unsigned long portflags = JackPortIsOutput | JackPortIsTerminal;
ports[i] = jack_port_register(client, ports[i] = jack_port_register(client,
source_ports[i].c_str(), source_ports[i].c_str(),
JACK_DEFAULT_AUDIO_TYPE, JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0); portflags, 0);
if (ports[i] == nullptr) { if (ports[i] == nullptr) {
Disconnect(); Disconnect();
throw FormatRuntimeError("Cannot register output port \"%s\"", throw FormatRuntimeError("Cannot register output port \"%s\"",

View File

@@ -383,6 +383,7 @@ ShoutOutput::SendTag(const Tag &tag)
shout_tag_to_metadata(tag, song, sizeof(song)); shout_tag_to_metadata(tag, song, sizeof(song));
shout_metadata_add(meta, "song", song); shout_metadata_add(meta, "song", song);
shout_metadata_add(meta, "charset", "UTF-8");
if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) { if (SHOUTERR_SUCCESS != shout_set_metadata(shout_conn, meta)) {
LogWarning(shout_output_domain, LogWarning(shout_output_domain,
"error setting shout metadata"); "error setting shout metadata");

View File

@@ -22,7 +22,6 @@
#include "system/FileDescriptor.hxx" #include "system/FileDescriptor.hxx"
#include "system/Error.hxx" #include "system/Error.hxx"
#include <sys/stropts.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@@ -31,11 +30,18 @@
#ifdef __sun #ifdef __sun
#include <sys/audio.h> #include <sys/audio.h>
#include <sys/stropts.h>
#else #else
/* some fake declarations that allow build this plugin on systems /* some fake declarations that allow build this plugin on systems
other than Solaris, just to see if it compiles */ other than Solaris, just to see if it compiles */
#include <sys/ioctl.h>
#ifndef I_FLUSH
#define I_FLUSH 0
#endif
#define AUDIO_GETINFO 0 #define AUDIO_GETINFO 0
#define AUDIO_SETINFO 0 #define AUDIO_SETINFO 0
#define AUDIO_ENCODING_LINEAR 0 #define AUDIO_ENCODING_LINEAR 0

View File

@@ -21,6 +21,8 @@
#include "ConfiguredResampler.hxx" #include "ConfiguredResampler.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include <stdexcept>
#include <assert.h> #include <assert.h>
void void

View File

@@ -353,7 +353,7 @@ playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to)
return; return;
to = (currentSong + abs(to)) % GetLength(); to = (currentSong + abs(to)) % GetLength();
if (start < (unsigned)to) if (start < (unsigned)to)
to--; to -= end - start;
} }
queue.MoveRange(start, end, to); queue.MoveRange(start, end, to);

View File

@@ -29,8 +29,9 @@
#include "AudioParser.hxx" #include "AudioParser.hxx"
#include "tag/ParseName.hxx" #include "tag/ParseName.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
#include "time/ChronoUtil.hxx"
#include "time/ISO8601.hxx"
#include "util/CharUtil.hxx" #include "util/CharUtil.hxx"
#include "util/ChronoUtil.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
@@ -38,7 +39,6 @@
#include "util/StringStrip.hxx" #include "util/StringStrip.hxx"
#include "util/StringView.hxx" #include "util/StringView.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/TimeISO8601.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include "lib/icu/CaseFold.hxx" #include "lib/icu/CaseFold.hxx"
@@ -113,14 +113,19 @@ ParseTimeStamp(const char *s)
{ {
assert(s != nullptr); assert(s != nullptr);
char *endptr; try {
unsigned long long value = strtoull(s, &endptr, 10); /* try ISO 8601 */
if (*endptr == 0 && endptr > s) return ParseISO8601(s).first;
/* it's an integral UNIX time stamp */ } catch (...) {
return std::chrono::system_clock::from_time_t((time_t)value); char *endptr;
unsigned long long value = strtoull(s, &endptr, 10);
if (*endptr == 0 && endptr > s)
/* it's an integral UNIX time stamp */
return std::chrono::system_clock::from_time_t((time_t)value);
/* try ISO 8601 */ /* rethrow the ParseISO8601() error */
return ParseISO8601(s); throw;
}
} }
static constexpr bool static constexpr bool

View File

@@ -19,7 +19,8 @@
#include "ModifiedSinceSongFilter.hxx" #include "ModifiedSinceSongFilter.hxx"
#include "LightSong.hxx" #include "LightSong.hxx"
#include "util/TimeISO8601.hxx" #include "time/ISO8601.hxx"
#include "util/StringBuffer.hxx"
std::string std::string
ModifiedSinceSongFilter::ToExpression() const noexcept ModifiedSinceSongFilter::ToExpression() const noexcept

View File

@@ -24,6 +24,7 @@ song_dep = declare_dependency(
icu_dep, icu_dep,
pcre_dep, pcre_dep,
tag_dep, tag_dep,
time_dep,
util_dep, util_dep,
], ],
) )

View File

@@ -25,21 +25,23 @@
#include "lib/curl/Init.hxx" #include "lib/curl/Init.hxx"
#include "lib/curl/Global.hxx" #include "lib/curl/Global.hxx"
#include "lib/curl/Slist.hxx" #include "lib/curl/Slist.hxx"
#include "lib/curl/String.hxx"
#include "lib/curl/Request.hxx" #include "lib/curl/Request.hxx"
#include "lib/curl/Handler.hxx" #include "lib/curl/Handler.hxx"
#include "lib/curl/Escape.hxx"
#include "lib/expat/ExpatParser.hxx" #include "lib/expat/ExpatParser.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "event/Call.hxx" #include "event/Call.hxx"
#include "event/DeferEvent.hxx" #include "event/DeferEvent.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "time/ChronoUtil.hxx"
#include "time/Parser.hxx"
#include "util/ASCII.hxx" #include "util/ASCII.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/IterableSplitString.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/StringCompare.hxx" #include "util/StringCompare.hxx"
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
#include "util/TimeParser.hxx"
#include "util/UriUtil.hxx" #include "util/UriUtil.hxx"
#include <algorithm> #include <algorithm>
@@ -77,26 +79,15 @@ CurlStorage::MapUTF8(const char *uri_utf8) const noexcept
if (StringIsEmpty(uri_utf8)) if (StringIsEmpty(uri_utf8))
return base; return base;
CurlEasy easy; std::string path_esc = CurlEscapeUriPath(uri_utf8);
std::string path_esc;
for (auto elt: IterableSplitString(uri_utf8, '/')) {
char *elt_esc = easy.Escape(elt.data, elt.size);
if (!path_esc.empty())
path_esc.push_back('/');
path_esc += elt_esc;
curl_free(elt_esc);
}
return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str()); return PathTraitsUTF8::Build(base.c_str(), path_esc.c_str());
} }
const char * const char *
CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept CurlStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept
{ {
// TODO: escape/unescape? return PathTraitsUTF8::Relative(base.c_str(),
CurlUnescape(uri_utf8).c_str());
return PathTraitsUTF8::Relative(base.c_str(), uri_utf8);
} }
class BlockingHttpRequest : protected CurlResponseHandler { class BlockingHttpRequest : protected CurlResponseHandler {
@@ -132,6 +123,10 @@ public:
std::rethrow_exception(postponed_error); std::rethrow_exception(postponed_error);
} }
CURL *GetEasy() noexcept {
return request.Get();
}
protected: protected:
void SetDone() { void SetDone() {
assert(!done); assert(!done);
@@ -269,6 +264,8 @@ public:
CommonExpatParser(ExpatNamespaceSeparator{'|'}) CommonExpatParser(ExpatNamespaceSeparator{'|'})
{ {
request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND"); request.SetOption(CURLOPT_CUSTOMREQUEST, "PROPFIND");
request.SetOption(CURLOPT_FOLLOWLOCATION, 1l);
request.SetOption(CURLOPT_MAXREDIRS, 1l);
request_headers.Append(StringFormat<40>("depth: %u", depth)); request_headers.Append(StringFormat<40>("depth: %u", depth));
@@ -277,6 +274,7 @@ public:
request.SetOption(CURLOPT_POSTFIELDS, request.SetOption(CURLOPT_POSTFIELDS,
"<?xml version=\"1.0\"?>\n" "<?xml version=\"1.0\"?>\n"
"<a:propfind xmlns:a=\"DAV:\">" "<a:propfind xmlns:a=\"DAV:\">"
"<a:prop><a:resourcetype/></a:prop>"
"<a:prop><a:getcontenttype/></a:prop>" "<a:prop><a:getcontenttype/></a:prop>"
"<a:prop><a:getcontentlength/></a:prop>" "<a:prop><a:getcontentlength/></a:prop>"
"</a:propfind>"); "</a:propfind>");
@@ -284,6 +282,7 @@ public:
// TODO: send request body // TODO: send request body
} }
using BlockingHttpRequest::GetEasy;
using BlockingHttpRequest::Wait; using BlockingHttpRequest::Wait;
protected: protected:
@@ -454,9 +453,7 @@ CurlStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow)
{ {
// TODO: escape the given URI // TODO: escape the given URI
std::string uri = base; const auto uri = MapUTF8(uri_utf8);
uri += uri_utf8;
return HttpGetInfoOperation(*curl, uri.c_str()).Perform(); return HttpGetInfoOperation(*curl, uri.c_str()).Perform();
} }
@@ -503,7 +500,11 @@ private:
if (path == nullptr) if (path == nullptr)
return nullptr; return nullptr;
path = StringAfterPrefix(path, base_path.c_str()); /* kludge: ignoring case in this comparison to avoid
false negatives if the web server uses a different
case in hex digits in escaped characters; TODO:
implement properly */
path = StringAfterPrefixIgnoreCase(path, base_path.c_str());
if (path == nullptr || *path == 0) if (path == nullptr || *path == 0)
return nullptr; return nullptr;
@@ -529,10 +530,7 @@ protected:
if (escaped_name.IsNull()) if (escaped_name.IsNull())
return; return;
// TODO: unescape entries.emplace_front(CurlUnescape(GetEasy(), escaped_name));
const auto name = escaped_name;
entries.emplace_front(std::string(name.data, name.size));
auto &info = entries.front().info; auto &info = entries.front().info;
info = StorageFileInfo(r.collection info = StorageFileInfo(r.collection
@@ -546,10 +544,7 @@ protected:
std::unique_ptr<StorageDirectoryReader> std::unique_ptr<StorageDirectoryReader>
CurlStorage::OpenDirectory(const char *uri_utf8) CurlStorage::OpenDirectory(const char *uri_utf8)
{ {
// TODO: escape the given URI std::string uri = MapUTF8(uri_utf8);
std::string uri = base;
uri += uri_utf8;
/* collection URIs must end with a slash */ /* collection URIs must end with a slash */
if (uri.back() != '/') if (uri.back() != '/')

View File

@@ -57,5 +57,6 @@ storage_plugins_dep = declare_dependency(
dependencies: [ dependencies: [
storage_api_dep, storage_api_dep,
fs_dep, fs_dep,
time_dep,
], ],
) )

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2018 Max Kellermann <max.kellermann@gmail.com> * Copyright 2013-2019 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2013-2015 Max Kellermann <max.kellermann@gmail.com> * Copyright 2013-2015 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@@ -147,6 +147,26 @@ FormatErrno(const char *fmt, Args&&... args) noexcept
return FormatErrno(errno, fmt, std::forward<Args>(args)...); return FormatErrno(errno, fmt, std::forward<Args>(args)...);
} }
template<typename... Args>
static inline std::system_error
FormatFileNotFound(const char *fmt, Args&&... args) noexcept
{
#ifdef _WIN32
return FormatLastError(ERROR_FILE_NOT_FOUND, fmt,
std::forward<Args>(args)...);
#else
return FormatErrno(ENOENT, fmt, std::forward<Args>(args)...);
#endif
}
gcc_pure
inline bool
IsErrno(const std::system_error &e, int code) noexcept
{
return e.code().category() == ErrnoCategory() &&
e.code().value() == code;
}
gcc_pure gcc_pure
static inline bool static inline bool
IsFileNotFound(const std::system_error &e) noexcept IsFileNotFound(const std::system_error &e) noexcept
@@ -155,8 +175,7 @@ IsFileNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() && return e.code().category() == std::system_category() &&
e.code().value() == ERROR_FILE_NOT_FOUND; e.code().value() == ERROR_FILE_NOT_FOUND;
#else #else
return e.code().category() == ErrnoCategory() && return IsErrno(e, ENOENT);
e.code().value() == ENOENT;
#endif #endif
} }
@@ -168,8 +187,7 @@ IsPathNotFound(const std::system_error &e) noexcept
return e.code().category() == std::system_category() && return e.code().category() == std::system_category() &&
e.code().value() == ERROR_PATH_NOT_FOUND; e.code().value() == ERROR_PATH_NOT_FOUND;
#else #else
return e.code().category() == ErrnoCategory() && return IsErrno(e, ENOTDIR);
e.code().value() == ENOTDIR;
#endif #endif
} }
@@ -181,8 +199,7 @@ IsAccessDenied(const std::system_error &e) noexcept
return e.code().category() == std::system_category() && return e.code().category() == std::system_category() &&
e.code().value() == ERROR_ACCESS_DENIED; e.code().value() == ERROR_ACCESS_DENIED;
#else #else
return e.code().category() == ErrnoCategory() && return IsErrno(e, EACCES);
e.code().value() == EACCES;
#endif #endif
} }

View File

@@ -20,9 +20,9 @@
#include "Format.hxx" #include "Format.hxx"
#include "Tag.hxx" #include "Tag.hxx"
#include "ParseName.hxx" #include "ParseName.hxx"
#include "time/Convert.hxx"
#include "util/format.h" #include "util/format.h"
#include "util/TruncateString.hxx" #include "util/TruncateString.hxx"
#include "util/TimeConvert.hxx"
#include <algorithm> #include <algorithm>

View File

@@ -48,6 +48,7 @@ tag = static_library(
tag_dep = declare_dependency( tag_dep = declare_dependency(
link_with: tag, link_with: tag,
dependencies: [ dependencies: [
time_dep,
util_dep, util_dep,
], ],
) )

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright 2007-2019 Content Management AG
* All rights reserved. * All rights reserved.
* *
* author: Max Kellermann <mk@cm4all.com> * author: Max Kellermann <mk@cm4all.com>
@@ -30,11 +30,12 @@
* OF THE POSSIBILITY OF SUCH DAMAGE. * OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "TimeConvert.hxx" #include "Convert.hxx"
#include <stdexcept> #include <stdexcept>
#include <time.h> #include <time.h>
#include <sys/time.h> /* for struct timeval */
struct tm struct tm
GmTime(std::chrono::system_clock::time_point tp) GmTime(std::chrono::system_clock::time_point tp)
@@ -66,18 +67,52 @@ LocalTime(std::chrono::system_clock::time_point tp)
return *tm; return *tm;
} }
#ifdef __GLIBC__ #ifndef __GLIBC__
std::chrono::system_clock::time_point /**
TimeGm(struct tm &tm) * Determine the time zone offset in a portable way.
*/
gcc_const
static time_t
GetTimeZoneOffset() noexcept
{ {
return std::chrono::system_clock::from_time_t(timegm(&tm)); time_t t = 1234567890;
struct tm tm;
tm.tm_isdst = 0;
#ifdef _WIN32
struct tm *p = gmtime(&t);
#else
struct tm *p = &tm;
gmtime_r(&t, p);
#endif
return t - mktime(&tm);
} }
#endif #endif /* !__GLIBC__ */
std::chrono::system_clock::time_point std::chrono::system_clock::time_point
MakeTime(struct tm &tm) TimeGm(struct tm &tm) noexcept
{
#ifdef __GLIBC__
/* timegm() is a GNU extension */
const auto t = timegm(&tm);
#else
tm.tm_isdst = 0;
const auto t = mktime(&tm) + GetTimeZoneOffset();
#endif /* !__GLIBC__ */
return std::chrono::system_clock::from_time_t(t);
}
std::chrono::system_clock::time_point
MakeTime(struct tm &tm) noexcept
{ {
return std::chrono::system_clock::from_time_t(mktime(&tm)); return std::chrono::system_clock::from_time_t(mktime(&tm));
} }
std::chrono::steady_clock::duration
ToSteadyClockDuration(const struct timeval &tv) noexcept
{
return std::chrono::steady_clock::duration(std::chrono::seconds(tv.tv_sec)) +
std::chrono::steady_clock::duration(std::chrono::microseconds(tv.tv_usec));
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright 2007-2019 Content Management AG
* All rights reserved. * All rights reserved.
* *
* author: Max Kellermann <mk@cm4all.com> * author: Max Kellermann <mk@cm4all.com>
@@ -33,34 +33,42 @@
#ifndef TIME_CONVERT_HXX #ifndef TIME_CONVERT_HXX
#define TIME_CONVERT_HXX #define TIME_CONVERT_HXX
#include "util/Compiler.h"
#include <chrono> #include <chrono>
/** /**
* Convert a UTC-based time point to a UTC-based "struct tm". * Convert a UTC-based time point to a UTC-based "struct tm".
*
* Throws on error.
*/ */
struct tm struct tm
GmTime(std::chrono::system_clock::time_point tp); GmTime(std::chrono::system_clock::time_point tp);
/** /**
* Convert a UTC-based time point to a local "struct tm". * Convert a UTC-based time point to a local "struct tm".
*
* Throws on error.
*/ */
struct tm struct tm
LocalTime(std::chrono::system_clock::time_point tp); LocalTime(std::chrono::system_clock::time_point tp);
#ifdef __GLIBC__
/** /**
* Convert a UTC-based "struct tm" to a UTC-based time point. * Convert a UTC-based "struct tm" to a UTC-based time point.
*/ */
gcc_pure
std::chrono::system_clock::time_point std::chrono::system_clock::time_point
TimeGm(struct tm &tm); TimeGm(struct tm &tm) noexcept;
#endif
/** /**
* Convert a local "struct tm" to a UTC-based time point. * Convert a local "struct tm" to a UTC-based time point.
*/ */
gcc_pure
std::chrono::system_clock::time_point std::chrono::system_clock::time_point
MakeTime(struct tm &tm); MakeTime(struct tm &tm) noexcept;
gcc_pure
std::chrono::steady_clock::duration
ToSteadyClockDuration(const struct timeval &tv) noexcept;
#endif #endif

172
src/time/ISO8601.cxx Normal file
View File

@@ -0,0 +1,172 @@
/*
* Copyright 2007-2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ISO8601.hxx"
#include "Convert.hxx"
#include "util/StringBuffer.hxx"
#include <stdexcept>
#include <assert.h>
StringBuffer<64>
FormatISO8601(const struct tm &tm) noexcept
{
StringBuffer<64> buffer;
strftime(buffer.data(), buffer.capacity(),
#ifdef _WIN32
"%Y-%m-%dT%H:%M:%SZ",
#else
"%FT%TZ",
#endif
&tm);
return buffer;
}
StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp)
{
return FormatISO8601(GmTime(tp));
}
static std::pair<unsigned, unsigned>
ParseTimeZoneOffsetRaw(const char *&s)
{
char *endptr;
unsigned long value = strtoul(s, &endptr, 10);
if (endptr == s + 4) {
s = endptr;
return std::make_pair(value / 100, value % 100);
} else if (endptr == s + 2) {
s = endptr;
unsigned hours = value, minutes = 0;
if (*s == ':') {
++s;
minutes = strtoul(s, &endptr, 10);
if (endptr != s + 2)
throw std::runtime_error("Failed to parse time zone offset");
s = endptr;
}
return std::make_pair(hours, minutes);
} else
throw std::runtime_error("Failed to parse time zone offset");
}
static std::chrono::system_clock::duration
ParseTimeZoneOffset(const char *&s)
{
assert(*s == '+' || *s == '-');
bool negative = *s == '-';
++s;
auto raw = ParseTimeZoneOffsetRaw(s);
if (raw.first > 13)
throw std::runtime_error("Time offset hours out of range");
if (raw.second >= 60)
throw std::runtime_error("Time offset minutes out of range");
std::chrono::system_clock::duration d = std::chrono::hours(raw.first);
d += std::chrono::minutes(raw.second);
if (negative)
d = -d;
return d;
}
std::pair<std::chrono::system_clock::time_point,
std::chrono::system_clock::duration>
ParseISO8601(const char *s)
{
assert(s != nullptr);
#ifdef _WIN32
/* TODO: emulate strptime()? */
(void)s;
throw std::runtime_error("Time parsing not implemented on Windows");
#else
struct tm tm{};
/* parse the date */
const char *end = strptime(s, "%F", &tm);
if (end == nullptr) {
/* try without field separators */
end = strptime(s, "%Y%m%d", &tm);
if (end == nullptr)
throw std::runtime_error("Failed to parse date");
}
s = end;
std::chrono::system_clock::duration precision = std::chrono::hours(24);
/* parse the time of day */
if (*s == 'T') {
++s;
if ((end = strptime(s, "%T", &tm)) != nullptr)
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M%S", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::seconds(1);
else if ((end = strptime(s, "%H%M", &tm)) != nullptr)
/* no field separators */
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H:%M", &tm)) != nullptr)
precision = std::chrono::minutes(1);
else if ((end = strptime(s, "%H", &tm)) != nullptr)
precision = std::chrono::hours(1);
else
throw std::runtime_error("Failed to parse time of day");
s = end;
}
auto tp = TimeGm(tm);
/* time zone */
if (*s == 'Z')
++s;
else if (*s == '+' || *s == '-')
tp -= ParseTimeZoneOffset(s);
if (*s != 0)
throw std::runtime_error("Garbage at end of time stamp");
return std::make_pair(tp, precision);
#endif /* !_WIN32 */
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright 2007-2019 Content Management AG
* All rights reserved. * All rights reserved.
* *
* author: Max Kellermann <mk@cm4all.com> * author: Max Kellermann <mk@cm4all.com>
@@ -33,13 +33,15 @@
#ifndef TIME_ISO8601_HXX #ifndef TIME_ISO8601_HXX
#define TIME_ISO8601_HXX #define TIME_ISO8601_HXX
#include "StringBuffer.hxx" #include "util/Compiler.h"
#include "Compiler.h"
#include <string>
#include <chrono> #include <chrono>
#include <utility>
#include <stddef.h>
struct tm; struct tm;
template<size_t CAPACITY> class StringBuffer;
gcc_pure gcc_pure
StringBuffer<64> StringBuffer<64>
@@ -49,7 +51,16 @@ gcc_pure
StringBuffer<64> StringBuffer<64>
FormatISO8601(std::chrono::system_clock::time_point tp); FormatISO8601(std::chrono::system_clock::time_point tp);
std::chrono::system_clock::time_point /**
* Parse a time stamp in ISO8601 format.
*
* Throws on error.
*
* @return a pair consisting of the time point and the specified
* precision; e.g. for a date, the second value is "one day"
*/
std::pair<std::chrono::system_clock::time_point,
std::chrono::system_clock::duration>
ParseISO8601(const char *s); ParseISO8601(const char *s);
#endif #endif

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@@ -27,32 +27,14 @@
* OF THE POSSIBILITY OF SUCH DAMAGE. * OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "TimeParser.hxx" #include "Parser.hxx"
#include "Compiler.h" #include "Convert.hxx"
#include <stdexcept> #include <stdexcept>
#include <assert.h> #include <assert.h>
#include <time.h> #include <time.h>
#if !defined(__GLIBC__) && !defined(_WIN32)
/**
* Determine the time zone offset in a portable way.
*/
gcc_const
static time_t
GetTimeZoneOffset() noexcept
{
time_t t = 1234567890;
struct tm tm;
tm.tm_isdst = 0;
gmtime_r(&t, &tm);
return t - mktime(&tm);
}
#endif
std::chrono::system_clock::time_point std::chrono::system_clock::time_point
ParseTimePoint(const char *s, const char *format) ParseTimePoint(const char *s, const char *format)
{ {
@@ -65,20 +47,11 @@ ParseTimePoint(const char *s, const char *format)
(void)format; (void)format;
throw std::runtime_error("Time parsing not implemented on Windows"); throw std::runtime_error("Time parsing not implemented on Windows");
#else #else
struct tm tm; struct tm tm{};
const char *end = strptime(s, format, &tm); const char *end = strptime(s, format, &tm);
if (end == nullptr || *end != 0) if (end == nullptr || *end != 0)
throw std::runtime_error("Failed to parse time stamp"); throw std::runtime_error("Failed to parse time stamp");
#ifdef __GLIBC__ return TimeGm(tm);
/* timegm() is a GNU extension */
const auto t = timegm(&tm);
#else
tm.tm_isdst = 0;
const auto t = mktime(&tm) + GetTimeZoneOffset();
#endif /* !__GLIBC__ */
return std::chrono::system_clock::from_time_t(t);
#endif /* !_WIN32 */ #endif /* !_WIN32 */
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2014-2017 Max Kellermann <max.kellermann@gmail.com> * Copyright 2014-2019 Max Kellermann <max.kellermann@gmail.com>
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions

11
src/time/meson.build Normal file
View File

@@ -0,0 +1,11 @@
time = static_library(
'time',
'Parser.cxx',
'Convert.cxx',
'ISO8601.cxx',
include_directories: inc,
)
time_dep = declare_dependency(
link_with: time,
)

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright 2007-2019 Content Management AG
* All rights reserved. * All rights reserved.
* *
* author: Max Kellermann <mk@cm4all.com> * author: Max Kellermann <mk@cm4all.com>

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2007-2017 Content Management AG * Copyright 2007-2019 Content Management AG
* All rights reserved. * All rights reserved.
* *
* author: Max Kellermann <mk@cm4all.com> * author: Max Kellermann <mk@cm4all.com>

View File

@@ -35,14 +35,14 @@
/** /**
* A statically allocated string buffer. * A statically allocated string buffer.
*/ */
template<typename T, size_t CAPACITY> template<typename T, std::size_t CAPACITY>
class BasicStringBuffer { class BasicStringBuffer {
public: public:
typedef T value_type; typedef T value_type;
typedef T &reference; typedef T &reference;
typedef T *pointer; typedef T *pointer;
typedef const T *const_pointer; typedef const T *const_pointer;
typedef size_t size_type; typedef std::size_t size_type;
static constexpr value_type SENTINEL = '\0'; static constexpr value_type SENTINEL = '\0';
@@ -104,7 +104,7 @@ public:
} }
}; };
template<size_t CAPACITY> template<std::size_t CAPACITY>
class StringBuffer : public BasicStringBuffer<char, CAPACITY> {}; class StringBuffer : public BasicStringBuffer<char, CAPACITY> {};
#endif #endif

View File

@@ -36,13 +36,13 @@
template<typename... Args> template<typename... Args>
static inline void static inline void
StringFormat(char *buffer, size_t size, StringFormat(char *buffer, std::size_t size,
const char *fmt, Args&&... args) noexcept const char *fmt, Args&&... args) noexcept
{ {
snprintf(buffer, size, fmt, args...); snprintf(buffer, size, fmt, args...);
} }
template<size_t CAPACITY, typename... Args> template<std::size_t CAPACITY, typename... Args>
static inline void static inline void
StringFormat(StringBuffer<CAPACITY> &buffer, StringFormat(StringBuffer<CAPACITY> &buffer,
const char *fmt, Args&&... args) noexcept const char *fmt, Args&&... args) noexcept
@@ -50,7 +50,7 @@ StringFormat(StringBuffer<CAPACITY> &buffer,
StringFormat(buffer.data(), buffer.capacity(), fmt, args...); StringFormat(buffer.data(), buffer.capacity(), fmt, args...);
} }
template<size_t CAPACITY, typename... Args> template<std::size_t CAPACITY, typename... Args>
static inline StringBuffer<CAPACITY> static inline StringBuffer<CAPACITY>
StringFormat(const char *fmt, Args&&... args) noexcept StringFormat(const char *fmt, Args&&... args) noexcept
{ {

View File

@@ -16,9 +16,6 @@ util = static_library(
'SplitString.cxx', 'SplitString.cxx',
'FormatString.cxx', 'FormatString.cxx',
'Tokenizer.cxx', 'Tokenizer.cxx',
'TimeParser.cxx',
'TimeConvert.cxx',
'TimeISO8601.cxx',
'UriUtil.cxx', 'UriUtil.cxx',
'LazyRandomEngine.cxx', 'LazyRandomEngine.cxx',
'HugeAllocator.cxx', 'HugeAllocator.cxx',

View File

@@ -20,6 +20,7 @@
#include "AvahiPoll.hxx" #include "AvahiPoll.hxx"
#include "event/SocketMonitor.hxx" #include "event/SocketMonitor.hxx"
#include "event/TimerEvent.hxx" #include "event/TimerEvent.hxx"
#include "time/Convert.hxx"
static unsigned static unsigned
FromAvahiWatchEvent(AvahiWatchEvent e) FromAvahiWatchEvent(AvahiWatchEvent e)
@@ -78,12 +79,6 @@ private:
} }
}; };
static constexpr std::chrono::steady_clock::duration
TimevalToChrono(const timeval &tv)
{
return std::chrono::seconds(tv.tv_sec) + std::chrono::microseconds(tv.tv_usec);
}
struct AvahiTimeout final { struct AvahiTimeout final {
TimerEvent timer; TimerEvent timer;
@@ -97,12 +92,12 @@ public:
:timer(_loop, BIND_THIS_METHOD(OnTimeout)), :timer(_loop, BIND_THIS_METHOD(OnTimeout)),
callback(_callback), userdata(_userdata) { callback(_callback), userdata(_userdata) {
if (tv != nullptr) if (tv != nullptr)
timer.Schedule(TimevalToChrono(*tv)); timer.Schedule(ToSteadyClockDuration(*tv));
} }
static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) { static void TimeoutUpdate(AvahiTimeout *t, const struct timeval *tv) {
if (tv != nullptr) if (tv != nullptr)
t->timer.Schedule(TimevalToChrono(*tv)); t->timer.Schedule(ToSteadyClockDuration(*tv));
else else
t->timer.Cancel(); t->timer.Cancel();
} }

View File

@@ -67,6 +67,7 @@ else
dependencies: [ dependencies: [
libavahi_client_dep, libavahi_client_dep,
dbus_dep, dbus_dep,
time_dep,
], ],
) )

92
test/TestISO8601.cxx Normal file
View File

@@ -0,0 +1,92 @@
/*
* Copyright 2019 Content Management AG
* All rights reserved.
*
* author: Max Kellermann <mk@cm4all.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "time/ISO8601.hxx"
#include <gtest/gtest.h>
static constexpr struct {
const char *s;
time_t t;
std::chrono::system_clock::duration d;
} parse_tests[] = {
/* full ISO8601 */
{ "1970-01-01T00:00:00Z", 0, std::chrono::seconds(1) },
{ "1970-01-01T00:00:01Z", 1, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41Z", 1549298801, std::chrono::seconds(1) },
{ "2018-12-31T23:59:59Z", 1546300799, std::chrono::seconds(1) },
{ "2019-01-01T00:00:00Z", 1546300800, std::chrono::seconds(1) },
/* only date */
{ "1970-01-01", 0, std::chrono::hours(24) },
{ "2019-02-04", 1549238400, std::chrono::hours(24) },
{ "2018-12-31", 1546214400, std::chrono::hours(24) },
{ "2019-01-01", 1546300800, std::chrono::hours(24) },
/* date with time zone */
{ "2019-02-04Z", 1549238400, std::chrono::hours(24) },
/* without time zone */
{ "2019-02-04T16:46:41", 1549298801, std::chrono::seconds(1) },
/* without seconds */
{ "2019-02-04T16:46", 1549298760, std::chrono::minutes(1) },
{ "2019-02-04T16:46Z", 1549298760, std::chrono::minutes(1) },
/* without minutes */
{ "2019-02-04T16", 1549296000, std::chrono::hours(1) },
{ "2019-02-04T16Z", 1549296000, std::chrono::hours(1) },
/* with time zone */
{ "2019-02-04T16:46:41+02", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41+0200", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41+02:00", 1549291601, std::chrono::seconds(1) },
{ "2019-02-04T16:46:41-0200", 1549306001, std::chrono::seconds(1) },
/* without field separators */
{ "19700101T000000Z", 0, std::chrono::seconds(1) },
{ "19700101T000001Z", 1, std::chrono::seconds(1) },
{ "20190204T164641Z", 1549298801, std::chrono::seconds(1) },
{ "19700101", 0, std::chrono::hours(24) },
{ "20190204", 1549238400, std::chrono::hours(24) },
{ "20190204T1646", 1549298760, std::chrono::minutes(1) },
{ "20190204T16", 1549296000, std::chrono::hours(1) },
};
TEST(ISO8601, Parse)
{
for (const auto &i : parse_tests) {
const auto result = ParseISO8601(i.s);
EXPECT_EQ(std::chrono::system_clock::to_time_t(result.first), i.t);
EXPECT_EQ(result.second, i.d);
}
}

View File

@@ -46,6 +46,19 @@ test('TestUtil', executable(
], ],
)) ))
test(
'TestTime',
executable(
'TestTime',
'TestISO8601.cxx',
include_directories: inc,
dependencies: [
time_dep,
gtest_dep,
],
),
)
test('TestRewindInputStream', executable( test('TestRewindInputStream', executable(
'TestRewindInputStream', 'TestRewindInputStream',
'TestRewindInputStream.cxx', 'TestRewindInputStream.cxx',

View File

@@ -22,7 +22,7 @@
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "storage/FileInfo.hxx" #include "storage/FileInfo.hxx"
#include "net/Init.hxx" #include "net/Init.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include <memory> #include <memory>

View File

@@ -17,7 +17,7 @@
#include "storage/StorageInterface.hxx" #include "storage/StorageInterface.hxx"
#include "storage/plugins/LocalStorage.hxx" #include "storage/plugins/LocalStorage.hxx"
#include "Mapper.hxx" #include "Mapper.hxx"
#include "util/ChronoUtil.hxx" #include "time/ChronoUtil.hxx"
#include <gtest/gtest.h> #include <gtest/gtest.h>