summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2020-04-02 18:02:10 +0200
committerMax Kellermann <max@musicpd.org>2020-04-02 18:02:10 +0200
commit12b97bbe3896df558b3b8f52e8d9d495058aa996 (patch)
tree4430cf6fcd89a7c9d389f1f903c927b6d5297d1a
parent7d7bd51bc0a4748593e774decc91a1dbc939fd90 (diff)
parent5ccfcffcc124e406233359fe8fe65b704b98b8c8 (diff)
Merge tag 'v0.21.22'
release v0.21.22
-rw-r--r--.travis.yml16
-rw-r--r--NEWS15
-rw-r--r--android/AndroidManifest.xml19
-rw-r--r--android/src/Main.java49
-rw-r--r--android/src/Settings.java3
-rw-r--r--meson.build3
-rw-r--r--python/build/libs.py8
-rw-r--r--src/CommandLine.cxx13
-rw-r--r--src/android/AudioManager.cxx56
-rw-r--r--src/android/AudioManager.hxx42
-rw-r--r--src/android/Context.cxx21
-rw-r--r--src/android/Context.hxx4
-rw-r--r--src/db/plugins/simple/Directory.cxx11
-rw-r--r--src/decoder/plugins/FfmpegDecoderPlugin.cxx20
-rw-r--r--src/event/meson.build1
-rw-r--r--src/fs/NarrowPath.cxx54
-rw-r--r--src/fs/NarrowPath.hxx44
-rw-r--r--src/fs/io/BufferedReader.hxx2
-rw-r--r--src/fs/io/GunzipReader.hxx2
-rw-r--r--src/fs/io/GzipOutputStream.cxx4
-rw-r--r--src/fs/meson.build1
-rw-r--r--src/lib/curl/Request.cxx2
-rw-r--r--src/mixer/MixerList.hxx1
-rw-r--r--src/mixer/plugins/AndroidMixerPlugin.cxx116
-rw-r--r--src/mixer/plugins/meson.build4
-rw-r--r--src/output/plugins/sles/SlesOutputPlugin.cxx3
-rw-r--r--src/playlist/plugins/RssPlaylistPlugin.cxx1
-rw-r--r--src/tag/Pool.cxx2
-rw-r--r--src/time/Convert.cxx6
-rw-r--r--test/ContainerScan.cxx3
-rw-r--r--test/DumpDatabase.cxx3
-rw-r--r--test/ReadApeTags.cxx3
-rw-r--r--test/ShutdownHandler.hxx4
-rw-r--r--test/WriteFile.cxx3
-rw-r--r--test/dump_playlist.cxx3
-rw-r--r--test/meson.build19
-rw-r--r--test/net/TestIPv4Address.cxx13
-rw-r--r--test/net/TestIPv6Address.cxx13
-rw-r--r--test/read_conf.cxx3
-rw-r--r--test/read_tags.cxx9
-rw-r--r--test/run_decoder.cxx7
-rw-r--r--test/run_filter.cxx3
-rw-r--r--test/run_gunzip.cxx3
-rw-r--r--test/run_input.cxx5
-rw-r--r--test/run_output.cxx3
-rw-r--r--test/time/TestConvert.cxx65
-rw-r--r--test/time/TestISO8601.cxx (renamed from test/TestISO8601.cxx)0
-rw-r--r--test/time/meson.build17
48 files changed, 605 insertions, 97 deletions
diff --git a/.travis.yml b/.travis.yml
index d57057db6..38bf7a331 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -100,6 +100,22 @@ jobs:
packages:
- ccache
- meson
+ - icu4c
+ - ffmpeg
+ - libnfs
+ - yajl
+ - libupnp
+ - libid3tag
+ - chromaprint
+ - libsamplerate
+ - libsoxr
+ - libzzip
+ - flac
+ - opus
+ - libvorbis
+ - faad2
+ - wavpack
+ - libmpdclient
update: true
env:
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
diff --git a/NEWS b/NEWS
index f4cb15cbd..5dbb3c53d 100644
--- a/NEWS
+++ b/NEWS
@@ -35,6 +35,21 @@ ver 0.22 (not yet released)
* switch to C++17
- GCC 7 or clang 4 (or newer) recommended
+ver 0.21.22 (2020/04/02)
+* database
+ - simple: optimize startup
+* input
+ - curl: fix streaming errors on Android
+* playlist
+ - rss: support MIME type application/xml
+* mixer
+ - android: new mixer plugin for "sles" output
+* Android
+ - TV support
+* Windows
+ - fix time zone offset check
+* fix build failures with uClibc-ng
+
ver 0.21.21 (2020/03/19)
* configuration
- fix bug in "metadata_to_use" setting
diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
index 83286b0b7..4dcdba4fd 100644
--- a/android/AndroidManifest.xml
+++ b/android/AndroidManifest.xml
@@ -2,18 +2,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
- android:versionCode="44"
- android:versionName="0.21.21">
+ android:versionCode="45"
+ android:versionName="0.21.22">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
+ <uses-feature android:name="android.software.leanback"
+ android:required="false" />
+ <uses-feature android:name="android.hardware.touchscreen"
+ android:required="false" />
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true"
android:icon="@drawable/icon"
+ android:banner="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".Settings"
android:label="@string/app_name">
@@ -22,6 +29,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity android:name=".Settings"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+ </intent-filter>
+ </activity>
+
<receiver android:name=".Receiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
diff --git a/android/src/Main.java b/android/src/Main.java
index 57abee56c..b0c63136a 100644
--- a/android/src/Main.java
+++ b/android/src/Main.java
@@ -21,6 +21,7 @@ package org.musicpd;
import android.annotation.TargetApi;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
@@ -35,6 +36,9 @@ import android.os.RemoteException;
import android.util.Log;
import android.widget.RemoteViews;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
public class Main extends Service implements Runnable {
private static final String TAG = "Main";
private static final String REMOTE_ERROR = "MPD process was killed";
@@ -156,11 +160,36 @@ public class Main extends Service implements Runnable {
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
}
+ private Notification.Builder createNotificationBuilderWithChannel() {
+ final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null)
+ return null;
+
+ final String id = "org.musicpd";
+ final String name = "MPD service";
+ final int importance = 3; /* NotificationManager.IMPORTANCE_DEFAULT */
+
+ try {
+ Class<?> ncClass = Class.forName("android.app.NotificationChannel");
+ Constructor<?> ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class);
+ Object nc = ncCtor.newInstance(id, name, importance);
+
+ Method nmCreateNotificationChannelMethod =
+ NotificationManager.class.getMethod("createNotificationChannel", ncClass);
+ nmCreateNotificationChannelMethod.invoke(notificationManager, nc);
+
+ Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class);
+ return (Notification.Builder) nbCtor.newInstance(this, id);
+ } catch (Exception e)
+ {
+ Log.e(TAG, "error creating the NotificationChannel", e);
+ return null;
+ }
+ }
+
private void start() {
if (mThread != null)
return;
- mThread = new Thread(this);
- mThread.start();
final Intent mainIntent = new Intent(this, Settings.class);
mainIntent.setAction("android.intent.action.MAIN");
@@ -168,13 +197,25 @@ public class Main extends Service implements Runnable {
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- Notification notification = new Notification.Builder(this)
- .setContentTitle(getText(R.string.notification_title_mpd_running))
+ Notification.Builder nBuilder;
+ if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */)
+ {
+ nBuilder = createNotificationBuilderWithChannel();
+ if (nBuilder == null)
+ return;
+ }
+ else
+ nBuilder = new Notification.Builder(this);
+
+ Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running))
.setContentText(getText(R.string.notification_text_mpd_running))
.setSmallIcon(R.drawable.notification_icon)
.setContentIntent(contentIntent)
.build();
+ mThread = new Thread(this);
+ mThread.start();
+
startForeground(R.string.notification_title_mpd_running, notification);
startService(new Intent(this, Main.class));
}
diff --git a/android/src/Settings.java b/android/src/Settings.java
index af3008be5..4650bbce6 100644
--- a/android/src/Settings.java
+++ b/android/src/Settings.java
@@ -105,12 +105,13 @@ public class Settings extends Activity {
else
mRunButton.setChecked(false);
mFirstRun = true;
+ mTextStatus.setText("");
break;
case MSG_STARTED:
Log.d(TAG, "onStarted");
mRunButton.setChecked(true);
mFirstRun = true;
- mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
+ mTextStatus.setText("MPD service started");
break;
case MSG_LOG:
if (mLogListArray.size() > MAX_LOGS)
diff --git a/meson.build b/meson.build
index a9116f90a..1aef557f9 100644
--- a/meson.build
+++ b/meson.build
@@ -311,6 +311,7 @@ if not is_android
else
sources += [
'src/android/Context.cxx',
+ 'src/android/AudioManager.cxx',
'src/android/Environment.cxx',
'src/android/LogListener.cxx',
]
@@ -332,6 +333,7 @@ subdir('src/util')
subdir('src/time')
subdir('src/system')
subdir('src/thread')
+subdir('src/net')
subdir('src/event')
subdir('src/lib/dbus')
@@ -359,7 +361,6 @@ subdir('src/lib/crypto')
subdir('src/fs')
subdir('src/config')
-subdir('src/net')
subdir('src/tag')
subdir('src/pcm')
subdir('src/neighbor')
diff --git a/python/build/libs.py b/python/build/libs.py
index 7c317d46a..af018ed8f 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -9,8 +9,8 @@ from build.ffmpeg import FfmpegProject
from build.boost import BoostProject
libmpdclient = MesonProject(
- 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz',
- 'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559',
+ 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
+ '4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
'lib/libmpdclient.a',
)
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
)
curl = AutotoolsProject(
- 'http://curl.haxx.se/download/curl-7.68.0.tar.xz',
- 'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a',
+ 'http://curl.haxx.se/download/curl-7.69.1.tar.xz',
+ '03c7d5e6697f7b7e40ada1b2256e565a555657398e6c1fcfa4cb251ccd819d4f',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index e6682c20f..213867168 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -33,6 +33,7 @@
#include "playlist/PlaylistRegistry.hxx"
#include "playlist/PlaylistPlugin.hxx"
#include "fs/AllocatedPath.hxx"
+#include "fs/NarrowPath.hxx"
#include "fs/Traits.hxx"
#include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx"
@@ -378,17 +379,7 @@ ParseCommandLine(int argc, char **argv, struct options &options,
if (config_file != nullptr) {
/* use specified configuration file */
-#ifdef _UNICODE
- wchar_t buffer[MAX_PATH];
- auto result = MultiByteToWideChar(CP_ACP, 0, config_file, -1,
- buffer, std::size(buffer));
- if (result <= 0)
- throw MakeLastError("MultiByteToWideChar() failed");
-
- ReadConfigFile(config, Path::FromFS(buffer));
-#else
- ReadConfigFile(config, Path::FromFS(config_file));
-#endif
+ ReadConfigFile(config, FromNarrowPath(config_file));
return;
}
diff --git a/src/android/AudioManager.cxx b/src/android/AudioManager.cxx
new file mode 100644
index 000000000..5e3062250
--- /dev/null
+++ b/src/android/AudioManager.cxx
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "AudioManager.hxx"
+#include "java/Class.hxx"
+#include "java/Exception.hxx"
+#include "java/File.hxx"
+
+#define STREAM_MUSIC 3
+
+AudioManager::AudioManager(JNIEnv *env, jobject obj) noexcept
+ : Java::GlobalObject(env, obj)
+{
+ Java::Class cls(env, env->GetObjectClass(Get()));
+ jmethodID method = env->GetMethodID(cls, "getStreamMaxVolume", "(I)I");
+ assert(method);
+ maxVolume = env->CallIntMethod(Get(), method, STREAM_MUSIC);
+
+ getStreamVolumeMethod = env->GetMethodID(cls, "getStreamVolume", "(I)I");
+ assert(getStreamVolumeMethod);
+
+ setStreamVolumeMethod = env->GetMethodID(cls, "setStreamVolume", "(III)V");
+ assert(setStreamVolumeMethod);
+}
+
+int
+AudioManager::GetVolume(JNIEnv *env)
+{
+ if (maxVolume == 0)
+ return 0;
+ return env->CallIntMethod(Get(), getStreamVolumeMethod, STREAM_MUSIC);
+}
+
+void
+AudioManager::SetVolume(JNIEnv *env, int volume)
+{
+ if (maxVolume == 0)
+ return;
+ env->CallVoidMethod(Get(), setStreamVolumeMethod, STREAM_MUSIC, volume, 0);
+}
diff --git a/src/android/AudioManager.hxx b/src/android/AudioManager.hxx
new file mode 100644
index 000000000..d4de3deb3
--- /dev/null
+++ b/src/android/AudioManager.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_ANDROID_AUDIO_MANAGER_HXX
+#define MPD_ANDROID_AUDIO_MANAGER_HXX
+
+#include "java/Object.hxx"
+
+class AudioManager : public Java::GlobalObject {
+ int maxVolume;
+ jmethodID getStreamVolumeMethod;
+ jmethodID setStreamVolumeMethod;
+
+public:
+ AudioManager(JNIEnv *env, jobject obj) noexcept;
+
+ AudioManager(std::nullptr_t) noexcept { maxVolume = 0; }
+
+ ~AudioManager() noexcept {}
+
+ int GetMaxVolume() { return maxVolume; }
+ int GetVolume(JNIEnv *env);
+ void SetVolume(JNIEnv *env, int);
+};
+
+#endif
diff --git a/src/android/Context.cxx b/src/android/Context.cxx
index b4248fa2b..c461c4a1b 100644
--- a/src/android/Context.cxx
+++ b/src/android/Context.cxx
@@ -21,8 +21,11 @@
#include "java/Class.hxx"
#include "java/Exception.hxx"
#include "java/File.hxx"
+#include "java/String.hxx"
#include "fs/AllocatedPath.hxx"
+#include "AudioManager.hxx"
+
AllocatedPath
Context::GetCacheDir(JNIEnv *env) const noexcept
{
@@ -39,3 +42,21 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
return Java::File::ToAbsolutePath(env, file);
}
+
+AudioManager *
+Context::GetAudioManager(JNIEnv *env) noexcept
+{
+ assert(env != nullptr);
+
+ Java::Class cls(env, env->GetObjectClass(Get()));
+ jmethodID method = env->GetMethodID(cls, "getSystemService",
+ "(Ljava/lang/String;)Ljava/lang/Object;");
+ assert(method);
+
+ Java::String name(env, "audio");
+ jobject am = env->CallObjectMethod(Get(), method, name.Get());
+ if (Java::DiscardException(env) || am == nullptr)
+ return nullptr;
+
+ return new AudioManager(env, am);
+}
diff --git a/src/android/Context.hxx b/src/android/Context.hxx
index 64687cada..a8ae5070f 100644
--- a/src/android/Context.hxx
+++ b/src/android/Context.hxx
@@ -23,6 +23,7 @@
#include "java/Object.hxx"
class AllocatedPath;
+class AudioManager;
class Context : public Java::GlobalObject {
public:
@@ -31,6 +32,9 @@ public:
gcc_pure
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
+
+ gcc_pure
+ AudioManager *GetAudioManager(JNIEnv *env) noexcept;
};
#endif
diff --git a/src/db/plugins/simple/Directory.cxx b/src/db/plugins/simple/Directory.cxx
index 72d169086..0c0811540 100644
--- a/src/db/plugins/simple/Directory.cxx
+++ b/src/db/plugins/simple/Directory.cxx
@@ -32,6 +32,7 @@
#include "fs/Traits.hxx"
#include "util/Alloc.hxx"
#include "util/DeleteDisposer.hxx"
+#include "util/StringCompare.hxx"
#include <cassert>
@@ -70,7 +71,15 @@ Directory::GetName() const noexcept
{
assert(!IsRoot());
- return PathTraitsUTF8::GetBase(path.c_str());
+ if (parent->IsRoot())
+ return path.c_str();
+
+ assert(StringAfterPrefix(path.c_str(), parent->path.c_str()) != nullptr);
+ assert(*StringAfterPrefix(path.c_str(), parent->path.c_str()) == PathTraitsUTF8::SEPARATOR);
+
+ /* strip the parent directory path and the slash separator
+ from this directory's path, and the base name remains */
+ return path.c_str() + parent->path.length() + 1;
}
Directory *
diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
index 9bedd3f7e..971241826 100644
--- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx
@@ -287,7 +287,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
*/
static DecoderCommand
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
- AVPacket &&packet,
+ const AVPacket &packet,
AVCodecContext &codec_context,
const AVStream &stream,
AVFrame &frame,
@@ -340,24 +340,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
return cmd;
}
-static DecoderCommand
-ffmpeg_send_packet(DecoderClient &client, InputStream &is,
- const AVPacket &packet,
- AVCodecContext &codec_context,
- const AVStream &stream,
- AVFrame &frame,
- uint64_t min_frame, size_t pcm_frame_size,
- FfmpegBuffer &buffer)
-{
- return ffmpeg_send_packet(client, is,
- /* copy the AVPacket, because FFmpeg
- < 3.0 requires this */
- AVPacket(packet),
- codec_context, stream,
- frame, min_frame, pcm_frame_size,
- buffer);
-}
-
gcc_const
static SampleFormat
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept
diff --git a/src/event/meson.build b/src/event/meson.build
index 268734f5a..6877bc37a 100644
--- a/src/event/meson.build
+++ b/src/event/meson.build
@@ -26,6 +26,7 @@ event_dep = declare_dependency(
link_with: event,
dependencies: [
thread_dep,
+ net_dep,
system_dep,
boost_dep,
],
diff --git a/src/fs/NarrowPath.cxx b/src/fs/NarrowPath.cxx
new file mode 100644
index 000000000..e2597f83f
--- /dev/null
+++ b/src/fs/NarrowPath.cxx
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2003-2018 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "NarrowPath.hxx"
+
+#ifdef _UNICODE
+
+#include "lib/icu/Win32.hxx"
+#include "system/Error.hxx"
+
+#include <windows.h>
+
+NarrowPath::NarrowPath(Path _path) noexcept
+ :value(WideCharToMultiByte(CP_ACP, _path.c_str()))
+{
+ if (value.IsNull())
+ /* fall back to empty string */
+ value = Value::Empty();
+}
+
+static AllocatedPath
+AcpToAllocatedPath(const char *s)
+{
+ wchar_t buffer[MAX_PATH];
+ auto result = MultiByteToWideChar(CP_ACP, 0, s, -1,
+ buffer, std::size(buffer));
+ if (result <= 0)
+ throw MakeLastError("MultiByteToWideChar() failed");
+
+ return AllocatedPath::FromFS(buffer);
+}
+
+FromNarrowPath::FromNarrowPath(const char *s)
+ :value(AcpToAllocatedPath(s))
+{
+}
+
+#endif /* _UNICODE */
diff --git a/src/fs/NarrowPath.hxx b/src/fs/NarrowPath.hxx
index 9ac5d926b..c0e15ea23 100644
--- a/src/fs/NarrowPath.hxx
+++ b/src/fs/NarrowPath.hxx
@@ -23,9 +23,8 @@
#include "Path.hxx"
#ifdef _UNICODE
-#include "lib/icu/Win32.hxx"
+#include "AllocatedPath.hxx"
#include "util/AllocatedString.hxx"
-#include <windows.h>
#else
#include "util/StringPointer.hxx"
#endif
@@ -47,12 +46,7 @@ class NarrowPath {
public:
#ifdef _UNICODE
- explicit NarrowPath(Path _path)
- :value(WideCharToMultiByte(CP_ACP, _path.c_str())) {
- if (value.IsNull())
- /* fall back to empty string */
- value = Value::Empty();
- }
+ explicit NarrowPath(Path _path) noexcept;
#else
explicit NarrowPath(Path _path):value(_path.c_str()) {}
#endif
@@ -66,4 +60,38 @@ public:
}
};
+/**
+ * A path name converted from a "narrow" string. This is used to
+ * import an existing narrow string to a #Path.
+ */
+class FromNarrowPath {
+#ifdef _UNICODE
+ using Value = AllocatedPath;
+#else
+ using Value = Path;
+#endif
+
+ Value value{nullptr};
+
+public:
+ FromNarrowPath() = default;
+
+#ifdef _UNICODE
+ /**
+ * Throws on error.
+ */
+ FromNarrowPath(const char *s);
+#else
+ constexpr FromNarrowPath(const char *s) noexcept
+ :value(Value::FromFS(s)) {}
+#endif
+
+#ifndef _UNICODE
+ constexpr
+#endif
+ operator Path() const noexcept {
+ return value;
+ }
+};
+
#endif
diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx
index 819b74e6d..536d8f482 100644
--- a/src/fs/io/BufferedReader.hxx
+++ b/src/fs/io/BufferedReader.hxx
@@ -50,7 +50,7 @@ class BufferedReader {
public:
explicit BufferedReader(Reader &_reader) noexcept
- :reader(_reader), buffer(4096) {}
+ :reader(_reader), buffer(16384) {}
/**
* Reset the internal state. Should be called after rewinding
diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx
index 46a00540d..ce2aee3f5 100644
--- a/src/fs/io/GunzipReader.hxx
+++ b/src/fs/io/GunzipReader.hxx
@@ -45,7 +45,7 @@ class GunzipReader final : public Reader {
z_stream z;
- StaticFifoBuffer<Bytef, 4096> buffer;
+ StaticFifoBuffer<Bytef, 65536> buffer;
public:
/**
diff --git a/src/fs/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx
index 67f97a278..051582ae3 100644
--- a/src/fs/io/GzipOutputStream.cxx
+++ b/src/fs/io/GzipOutputStream.cxx
@@ -62,7 +62,7 @@ GzipOutputStream::Flush()
z.avail_in = 0;
while (true) {
- Bytef output[4096];
+ Bytef output[16384];
z.next_out = output;
z.avail_out = sizeof(output);
@@ -87,7 +87,7 @@ GzipOutputStream::Write(const void *_data, size_t size)
z.avail_in = size;
while (z.avail_in > 0) {
- Bytef output[4096];
+ Bytef output[16384];
z.next_out = output;
z.avail_out = sizeof(output);
diff --git a/src/fs/meson.build b/src/fs/meson.build
index daf14a201..980ca6138 100644
--- a/src/fs/meson.build
+++ b/src/fs/meson.build
@@ -6,6 +6,7 @@ fs_sources = [
'Path.cxx',
'Path2.cxx',
'AllocatedPath.cxx',
+ 'NarrowPath.cxx',
'FileSystem.cxx',
'List.cxx',
'StandardDirectory.cxx',
diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx
index eb3afbff1..5051d4d16 100644
--- a/src/lib/curl/Request.cxx
+++ b/src/lib/curl/Request.cxx
@@ -56,7 +56,9 @@ CurlRequest::CurlRequest(CurlGlobal &_global,
easy.SetUserAgent("Music Player Daemon " VERSION);
easy.SetHeaderFunction(_HeaderFunction, this);
easy.SetWriteFunction(WriteFunction, this);
+#ifndef ANDROID
easy.SetOption(CURLOPT_NETRC, 1L);
+#endif
easy.SetErrorBuffer(error_buffer);
easy.SetNoProgress();
easy.SetNoSignal();
diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx
index d64d5d34b..7f68d84f5 100644
--- a/src/mixer/MixerList.hxx
+++ b/src/mixer/MixerList.hxx
@@ -29,6 +29,7 @@ struct MixerPlugin;
extern const MixerPlugin null_mixer_plugin;
extern const MixerPlugin software_mixer_plugin;
+extern const MixerPlugin android_mixer_plugin;
extern const MixerPlugin alsa_mixer_plugin;
extern const MixerPlugin haiku_mixer_plugin;
extern const MixerPlugin oss_mixer_plugin;
diff --git a/src/mixer/plugins/AndroidMixerPlugin.cxx b/src/mixer/plugins/AndroidMixerPlugin.cxx
new file mode 100644
index 000000000..b24b64fab
--- /dev/null
+++ b/src/mixer/plugins/AndroidMixerPlugin.cxx
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "mixer/MixerInternal.hxx"
+#include "filter/plugins/VolumeFilterPlugin.hxx"
+#include "pcm/Volume.hxx"
+#include "android/Context.hxx"
+#include "android/AudioManager.hxx"
+
+#include "Main.hxx"
+
+#include <cassert>
+#include <cmath>
+
+class AndroidMixer final : public Mixer {
+ AudioManager *audioManager;
+ int currentVolume;
+ int maxAndroidVolume;
+ int lastAndroidVolume;
+public:
+ explicit AndroidMixer(MixerListener &_listener);
+
+ ~AndroidMixer() override;
+
+ /* virtual methods from class Mixer */
+ void Open() override {
+ }
+
+ void Close() noexcept override {
+ }
+
+ int GetVolume() override;
+
+ void SetVolume(unsigned volume) override;
+};
+
+static Mixer *
+android_mixer_init([[maybe_unused]] EventLoop &event_loop,
+ [[maybe_unused]] AudioOutput &ao,
+ MixerListener &listener,
+ [[maybe_unused]] const ConfigBlock &block)
+{
+ return new AndroidMixer(listener);
+}
+
+AndroidMixer::AndroidMixer(MixerListener &_listener)
+ :Mixer(android_mixer_plugin, _listener)
+{
+ JNIEnv *env = Java::GetEnv();
+ audioManager = context->GetAudioManager(env);
+
+ maxAndroidVolume = audioManager->GetMaxVolume();
+ if (maxAndroidVolume != 0)
+ {
+ lastAndroidVolume = audioManager->GetVolume(env);
+ currentVolume = 100 * lastAndroidVolume / maxAndroidVolume;
+ }
+}
+
+AndroidMixer::~AndroidMixer()
+{
+ delete audioManager;
+}
+
+int
+AndroidMixer::GetVolume()
+{
+ JNIEnv *env = Java::GetEnv();
+ if (maxAndroidVolume == 0)
+ return -1;
+
+ // The android volume index (or scale) is very likely inferior to the
+ // MPD one (100). The last volume set by MPD is saved into
+ // currentVolume, this volume is returned instead of the Android one
+ // when the Android mixer was not touched by an other application. This
+ // allows to fake a 0..100 scale from MPD.
+
+ int volume = audioManager->GetVolume(env);
+ if (volume == lastAndroidVolume)
+ return currentVolume;
+
+ return 100 * volume / maxAndroidVolume;
+}
+
+void
+AndroidMixer::SetVolume(unsigned newVolume)
+{
+ JNIEnv *env = Java::GetEnv();
+ if (maxAndroidVolume == 0)
+ return;
+ currentVolume = newVolume;
+ lastAndroidVolume = currentVolume * maxAndroidVolume / 100;
+ audioManager->SetVolume(env, lastAndroidVolume);
+
+}
+
+const MixerPlugin android_mixer_plugin = {
+ android_mixer_init,
+ true,
+};
diff --git a/src/mixer/plugins/meson.build b/src/mixer/plugins/meson.build
index eda7c8e2a..ed9df39b3 100644
--- a/src/mixer/plugins/meson.build
+++ b/src/mixer/plugins/meson.build
@@ -34,6 +34,10 @@ if is_windows
mixer_plugins_sources += 'WinmmMixerPlugin.cxx'
endif
+if is_android
+ mixer_plugins_sources += 'AndroidMixerPlugin.cxx'
+endif
+
mixer_plugins = static_library(
'mixer_plugins',
mixer_plugins_sources,
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
index 89ad1b7cd..3b345e2e0 100644
--- a/src/output/plugins/sles/SlesOutputPlugin.cxx
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -27,6 +27,7 @@
#include "thread/Cond.hxx"
#include "util/Domain.hxx"
#include "util/ByteOrder.hxx"
+#include "mixer/MixerList.hxx"
#include "Log.hxx"
#include <SLES/OpenSLES.h>
@@ -412,5 +413,5 @@ const struct AudioOutputPlugin sles_output_plugin = {
"sles",
sles_test_default_device,
SlesOutput::Create,
- nullptr,
+ &android_mixer_plugin,
};
diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx
index 8295b05b0..15f29cf7c 100644
--- a/src/playlist/plugins/RssPlaylistPlugin.cxx
+++ b/src/playlist/plugins/RssPlaylistPlugin.cxx
@@ -160,6 +160,7 @@ static const char *const rss_suffixes[] = {
static const char *const rss_mime_types[] = {
"application/rss+xml",
+ "application/xml",
"text/xml",
nullptr
};
diff --git a/src/tag/Pool.cxx b/src/tag/Pool.cxx
index f0f5c5e8d..5e2af0c79 100644
--- a/src/tag/Pool.cxx
+++ b/src/tag/Pool.cxx
@@ -32,7 +32,7 @@
Mutex tag_pool_lock;
-static constexpr size_t NUM_SLOTS = 4093;
+static constexpr size_t NUM_SLOTS = 16127;
struct TagPoolSlot {
TagPoolSlot *next;
diff --git a/src/time/Convert.cxx b/src/time/Convert.cxx
index 2351f4b52..352c60604 100644
--- a/src/time/Convert.cxx
+++ b/src/time/Convert.cxx
@@ -77,15 +77,15 @@ static time_t
GetTimeZoneOffset() noexcept
{
time_t t = 1234567890;
- struct tm tm;
- tm.tm_isdst = 0;
#ifdef _WIN32
struct tm *p = gmtime(&t);
#else
+ struct tm tm;
+ tm.tm_isdst = 0;
struct tm *p = &tm;
gmtime_r(&t, p);
#endif
- return t - mktime(&tm);
+ return t - mktime(p);
}
#endif /* !__GLIBC__ */
diff --git a/test/ContainerScan.cxx b/test/ContainerScan.cxx
index 5a7cc41df..886adb0ba 100644
--- a/test/ContainerScan.cxx
+++ b/test/ContainerScan.cxx
@@ -23,6 +23,7 @@
#include "decoder/DecoderList.hxx"
#include "decoder/DecoderPlugin.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "fs/io/StdioOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "util/PrintException.hxx"
@@ -63,7 +64,7 @@ try {
return EXIT_FAILURE;
}
- const Path path = Path::FromFS(argv[1]);
+ const FromNarrowPath path = argv[1];
const ScopeDecoderPluginsInit decoder_plugins_init({});
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 402aa31fe..05b98bd1d 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -29,6 +29,7 @@
#include "ConfigGlue.hxx"
#include "tag/Config.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "event/Thread.hxx"
#include "util/ScopeExit.hxx"
#include "util/PrintException.hxx"
@@ -106,7 +107,7 @@ try {
return 1;
}
- const Path config_path = Path::FromFS(argv[1]);
+ const FromNarrowPath config_path = argv[1];
const char *const plugin_name = argv[2];
const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name);
diff --git a/test/ReadApeTags.cxx b/test/ReadApeTags.cxx
index 90f50a46b..1ac4957d5 100644
--- a/test/ReadApeTags.cxx
+++ b/test/ReadApeTags.cxx
@@ -21,6 +21,7 @@
#include "tag/ApeLoader.hxx"
#include "thread/Mutex.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "input/InputStream.hxx"
#include "input/LocalOpen.hxx"
#include "util/StringView.hxx"
@@ -58,7 +59,7 @@ try {
return EXIT_FAILURE;
}
- const Path path = Path::FromFS(argv[1]);
+ const FromNarrowPath path = argv[1];
Mutex mutex;
diff --git a/test/ShutdownHandler.hxx b/test/ShutdownHandler.hxx
index 4215275fd..41f44d3c7 100644
--- a/test/ShutdownHandler.hxx
+++ b/test/ShutdownHandler.hxx
@@ -29,8 +29,8 @@ public:
};
#ifdef _WIN32
-ShutdownHandler::ShutdownHandler(EventLoop &loop) {}
-ShutdownHandler::~ShutdownHandler() {}
+inline ShutdownHandler::ShutdownHandler(EventLoop &) {}
+inline ShutdownHandler::~ShutdownHandler() {}
#endif
#endif
diff --git a/test/WriteFile.cxx b/test/WriteFile.cxx
index 587eecdb2..4832ee17e 100644
--- a/test/WriteFile.cxx
+++ b/test/WriteFile.cxx
@@ -18,6 +18,7 @@
*/
#include "fs/io/FileOutputStream.hxx"
+#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx"
#include <cerrno>
@@ -55,7 +56,7 @@ try {
return EXIT_FAILURE;
}
- const Path path = Path::FromFS(argv[1]);
+ const FromNarrowPath path = argv[1];
FileOutputStream fos(path);
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 34892a557..6f8dadabe 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -28,6 +28,7 @@
#include "playlist/PlaylistRegistry.hxx"
#include "playlist/PlaylistPlugin.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "fs/io/StdioOutputStream.hxx"
#include "thread/Cond.hxx"
@@ -54,7 +55,7 @@ try {
return EXIT_FAILURE;
}
- const Path config_path = Path::FromFS(argv[1]);
+ const FromNarrowPath config_path = argv[1];
uri = argv[2];
/* initialize MPD */
diff --git a/test/meson.build b/test/meson.build
index 0f01aca3e..e3f2a6fa6 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -24,6 +24,7 @@ gtest_dep = declare_dependency(
)
subdir('net')
+subdir('time')
executable(
'read_conf',
@@ -52,19 +53,6 @@ test('TestUtil', executable(
],
))
-test(
- 'TestTime',
- executable(
- 'TestTime',
- 'TestISO8601.cxx',
- include_directories: inc,
- dependencies: [
- time_dep,
- gtest_dep,
- ],
- ),
-)
-
test('TestRewindInputStream', executable(
'TestRewindInputStream',
'TestRewindInputStream.cxx',
@@ -326,6 +314,11 @@ if curl_dep.found()
include_directories: inc,
dependencies: [
curl_dep,
+
+ # Explicitly linking with zlib here works around a linker
+ # failure on Windows, because our Windows CURL build is
+ # statically linked and thus declares no dependency on zlib
+ zlib_dep,
],
)
diff --git a/test/net/TestIPv4Address.cxx b/test/net/TestIPv4Address.cxx
index aa7f727be..a6c05e681 100644
--- a/test/net/TestIPv4Address.cxx
+++ b/test/net/TestIPv4Address.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
+ * Copyright 2012-2020 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
@@ -34,13 +34,22 @@
#include <stdexcept>
+#ifndef _WIN32
#include <arpa/inet.h>
+#endif
static std::string
ToString(const struct in_addr &a)
{
+#ifdef _WIN32
+ /* on mingw32, the parameter is non-const (PVOID) */
+ const auto p = const_cast<struct in_addr *>(&a);
+#else
+ const auto p = &a;
+#endif
+
char buffer[256];
- const char *result = inet_ntop(AF_INET, &a, buffer, sizeof(buffer));
+ const char *result = inet_ntop(AF_INET, p, buffer, sizeof(buffer));
if (result == nullptr)
throw std::runtime_error("inet_ntop() failed");
return result;
diff --git a/test/net/TestIPv6Address.cxx b/test/net/TestIPv6Address.cxx
index 325d25bbf..cdadf6a7f 100644
--- a/test/net/TestIPv6Address.cxx
+++ b/test/net/TestIPv6Address.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
+ * Copyright 2012-2020 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
@@ -34,13 +34,22 @@
#include <stdexcept>
+#ifndef _WIN32
#include <arpa/inet.h>
+#endif
static std::string
ToString(const struct in6_addr &a)
{
+#ifdef _WIN32
+ /* on mingw32, the parameter is non-const (PVOID) */
+ const auto p = const_cast<struct in6_addr *>(&a);
+#else
+ const auto p = &a;
+#endif
+
char buffer[256];
- const char *result = inet_ntop(AF_INET6, &a, buffer, sizeof(buffer));
+ const char *result = inet_ntop(AF_INET6, p, buffer, sizeof(buffer));
if (result == nullptr)
throw std::runtime_error("inet_ntop() failed");
return result;
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
index 5350102f0..eaec60a6d 100644
--- a/test/read_conf.cxx
+++ b/test/read_conf.cxx
@@ -21,6 +21,7 @@
#include "config/Param.hxx"
#include "config/File.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx"
#include "util/RuntimeError.hxx"
@@ -34,7 +35,7 @@ try {
return EXIT_FAILURE;
}
- const Path config_path = Path::FromFS(argv[1]);
+ const FromNarrowPath config_path = argv[1];
const char *name = argv[2];
const auto option = ParseConfigOptionName(name);
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index c47c37665..7a1c32e9b 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -27,6 +27,7 @@
#include "tag/Handler.hxx"
#include "tag/Generic.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "pcm/AudioFormat.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringBuffer.hxx"
@@ -97,7 +98,7 @@ try {
}
decoder_name = argv[1];
- const Path path = Path::FromFS(argv[2]);
+ const char *path = argv[2];
EventThread io_thread;
io_thread.Start();
@@ -116,7 +117,7 @@ try {
DumpTagHandler h;
bool success;
try {
- success = plugin->ScanFile(path, h);
+ success = plugin->ScanFile(FromNarrowPath(path), h);
} catch (...) {
PrintException(std::current_exception());
success = false;
@@ -126,7 +127,7 @@ try {
InputStreamPtr is;
if (!success && plugin->scan_stream != nullptr) {
- is = InputStream::OpenReady(path.c_str(), mutex);
+ is = InputStream::OpenReady(path, mutex);
success = plugin->ScanStream(*is, h);
}
@@ -139,7 +140,7 @@ try {
if (is)
ScanGenericTags(*is, h);
else
- ScanGenericTags(path, h);
+ ScanGenericTags(FromNarrowPath(path), h);
}
return 0;
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 8b8e6ef61..1147e4faf 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -26,6 +26,7 @@
#include "input/Init.hxx"
#include "input/InputStream.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "pcm/AudioFormat.hxx"
#include "util/OptionDef.hxx"
#include "util/OptionParser.hxx"
@@ -44,7 +45,7 @@ struct CommandLine {
const char *decoder = nullptr;
const char *uri = nullptr;
- Path config_path = nullptr;
+ FromNarrowPath config_path;
bool verbose = false;
@@ -72,7 +73,7 @@ ParseCommandLine(int argc, char **argv)
while (auto o = option_parser.Next()) {
switch (Option(o.index)) {
case OPTION_CONFIG:
- c.config_path = Path::FromFS(o.value);
+ c.config_path = o.value;
break;
case OPTION_VERBOSE:
@@ -205,7 +206,7 @@ try {
MyDecoderClient client(c.seek_where);
if (plugin->file_decode != nullptr) {
try {
- plugin->FileDecode(client, Path::FromFS(c.uri));
+ plugin->FileDecode(client, FromNarrowPath(c.uri));
} catch (StopDecoder) {
}
} else if (plugin->stream_decode != nullptr) {
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 77c76106a..44b54b88c 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -19,6 +19,7 @@
#include "ConfigGlue.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "filter/LoadOne.hxx"
#include "filter/Filter.hxx"
#include "filter/Prepared.hxx"
@@ -123,7 +124,7 @@ try {
return EXIT_FAILURE;
}
- const Path config_path = Path::FromFS(argv[1]);
+ const FromNarrowPath config_path = argv[1];
AudioFormat audio_format(44100, SampleFormat::S16, 2);
diff --git a/test/run_gunzip.cxx b/test/run_gunzip.cxx
index fe719bfa4..a5227df4b 100644
--- a/test/run_gunzip.cxx
+++ b/test/run_gunzip.cxx
@@ -20,6 +20,7 @@
#include "fs/io/GunzipReader.hxx"
#include "fs/io/FileReader.hxx"
#include "fs/io/StdioOutputStream.hxx"
+#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx"
#include <stdio.h>
@@ -62,7 +63,7 @@ try {
return EXIT_FAILURE;
}
- Path path = Path::FromFS(argv[1]);
+ FromNarrowPath path = argv[1];
CopyGunzip(stdout, path);
return EXIT_SUCCESS;
diff --git a/test/run_input.cxx b/test/run_input.cxx
index d10a17518..20fe8c2d7 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -32,6 +32,7 @@
#include "Log.hxx"
#include "LogBackend.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "fs/io/BufferedOutputStream.hxx"
#include "fs/io/StdioOutputStream.hxx"
#include "util/ConstBuffer.hxx"
@@ -51,7 +52,7 @@
struct CommandLine {
const char *uri = nullptr;
- Path config_path = nullptr;
+ FromNarrowPath config_path;
bool verbose = false;
@@ -79,7 +80,7 @@ ParseCommandLine(int argc, char **argv)
while (auto o = option_parser.Next()) {
switch (Option(o.index)) {
case OPTION_CONFIG:
- c.config_path = Path::FromFS(o.value);
+ c.config_path = o.value;
break;
case OPTION_VERBOSE:
diff --git a/test/run_output.cxx b/test/run_output.cxx
index 232c94d38..7c0730152 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -23,6 +23,7 @@
#include "ConfigGlue.hxx"
#include "event/Thread.hxx"
#include "fs/Path.hxx"
+#include "fs/NarrowPath.hxx"
#include "pcm/AudioParser.hxx"
#include "pcm/AudioFormat.hxx"
#include "util/StringBuffer.hxx"
@@ -111,7 +112,7 @@ try {
return EXIT_FAILURE;
}
- const Path config_path = Path::FromFS(argv[1]);
+ const FromNarrowPath config_path = argv[1];
AudioFormat audio_format(44100, SampleFormat::S16, 2);
diff --git a/test/time/TestConvert.cxx b/test/time/TestConvert.cxx
new file mode 100644
index 000000000..ff31d7097
--- /dev/null
+++ b/test/time/TestConvert.cxx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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
+ * 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/Convert.hxx"
+
+#include <gtest/gtest.h>
+
+static constexpr time_t times[] = {
+ 1234567890,
+ 1580566807,
+ 1585750807,
+ 1590934807,
+};
+
+TEST(Time, LocalTime)
+{
+ /* convert back and forth using local time zone */
+
+ for (const auto t : times) {
+ auto tp = std::chrono::system_clock::from_time_t(t);
+ auto tm = LocalTime(tp);
+ EXPECT_EQ(MakeTime(tm), tp);
+ }
+}
+
+TEST(Time, GmTime)
+{
+ /* convert back and forth using UTC */
+
+ for (const auto t : times) {
+ auto tp = std::chrono::system_clock::from_time_t(t);
+ auto tm = GmTime(tp);
+ EXPECT_EQ(std::chrono::system_clock::to_time_t(TimeGm(tm)),
+ t);
+ }
+}
diff --git a/test/TestISO8601.cxx b/test/time/TestISO8601.cxx
index cd0897c1a..cd0897c1a 100644
--- a/test/TestISO8601.cxx
+++ b/test/time/TestISO8601.cxx
diff --git a/test/time/meson.build b/test/time/meson.build
new file mode 100644
index 000000000..53b583cf1
--- /dev/null
+++ b/test/time/meson.build
@@ -0,0 +1,17 @@
+test_time_sources = [
+ 'TestConvert.cxx',
+ 'TestISO8601.cxx',
+]
+
+test(
+ 'TestTime',
+ executable(
+ 'TestTime',
+ test_time_sources,
+ include_directories: inc,
+ dependencies: [
+ time_dep,
+ gtest_dep,
+ ],
+ ),
+)