release v0.21.22

-----BEGIN PGP SIGNATURE-----
 
 iQJEBAABCgAuFiEEA5IzWngIOJSkMBxDI26KWMbbRRIFAl6GCWgQHG1heEBtdXNp
 Y3BkLm9yZwAKCRAjbopYxttFEvkQD/9qg6cnCgCKSOmTjteJu1ayeXBMDiL3cCCg
 AwarHQoxsB0102NpV+MLka+4HIwHn+WNL55TzYgfbSh0nfmnki2fZ0YpsZoQR79w
 MP11iMPnFH1oKqj58minBkFNmAis2aLYHJGKaQNUh7wcf0WhbVTqtWBUrKb07RQ0
 Zj5lXtg65O/+yaCVdQGS6fMk2t7CqBM+S3RmbXCib/JRMC6aozoC7nWPvj8b2R8d
 PgxwKMRzyslyFoxDQZrusDjJ1piyigzUMMr32yzYDED4Xr8jsEELaJfULbr6qWT2
 ZNYF91e+D5V7riASAtlFTaVMaISx4QbHjKWR5Xcx0q/SJPAXTxF8RrAqGvqpWYmc
 kqmC8iNxQsW5o3sNhI9qg6sOkq2dIu43VasRCvuo19GABR36wwTK5ORoazIi8fbU
 /Ki/oZHtZczHRop9Cd6698Qr9jyTPdIs55FbgejzrVADvAmslqtcA6XxCBuG1nSF
 Qo48dp9Px7J74qNNuDt5/xLnQGJKaW60/BXrMK9G9QG4x9r4zuCayDO6Qc7FMWWR
 DG7k9nYoXJU5YG7xp9Rk+Yj3Ade8kqhTnKfqw0f2JfkLt0ChPG+rZcxICWSdzZm0
 AfHxuEQlwKaaFHFEsZhVehlsXkeU9OVywo+QDzmY2uqQ2ddlBZ47Qm2MWgTsv8i/
 euVsrzLtag==
 =EedX
 -----END PGP SIGNATURE-----

Merge tag 'v0.21.22'

release v0.21.22
This commit is contained in:
Max Kellermann 2020-04-02 18:02:10 +02:00
commit 12b97bbe38
48 changed files with 605 additions and 97 deletions

View File

@ -100,6 +100,22 @@ jobs:
packages: packages:
- ccache - ccache
- meson - meson
- icu4c
- ffmpeg
- libnfs
- yajl
- libupnp
- libid3tag
- chromaprint
- libsamplerate
- libsoxr
- libzzip
- flac
- opus
- libvorbis
- faad2
- wavpack
- libmpdclient
update: true update: true
env: env:
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1" - MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"

15
NEWS
View File

@ -35,6 +35,21 @@ ver 0.22 (not yet released)
* switch to C++17 * switch to C++17
- GCC 7 or clang 4 (or newer) recommended - 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) ver 0.21.21 (2020/03/19)
* configuration * configuration
- fix bug in "metadata_to_use" setting - fix bug in "metadata_to_use" setting

View File

@ -2,18 +2,25 @@
<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="44" android:versionCode="45"
android:versionName="0.21.21"> android:versionName="0.21.22">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/> <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.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application android:allowBackup="true" <application android:allowBackup="true"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:banner="@drawable/icon"
android:label="@string/app_name"> android:label="@string/app_name">
<activity android:name=".Settings" <activity android:name=".Settings"
android:label="@string/app_name"> android:label="@string/app_name">
@ -22,6 +29,14 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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"> <receiver android:name=".Receiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />

View File

@ -21,6 +21,7 @@ package org.musicpd;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.ComponentName; import android.content.ComponentName;
@ -35,6 +36,9 @@ import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import android.widget.RemoteViews; import android.widget.RemoteViews;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Main extends Service implements Runnable { public class Main extends Service implements Runnable {
private static final String TAG = "Main"; private static final String TAG = "Main";
private static final String REMOTE_ERROR = "MPD process was killed"; 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); 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() { private void start() {
if (mThread != null) if (mThread != null)
return; return;
mThread = new Thread(this);
mThread.start();
final Intent mainIntent = new Intent(this, Settings.class); final Intent mainIntent = new Intent(this, Settings.class);
mainIntent.setAction("android.intent.action.MAIN"); mainIntent.setAction("android.intent.action.MAIN");
@ -168,13 +197,25 @@ public class Main extends Service implements Runnable {
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT); mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this) Notification.Builder nBuilder;
.setContentTitle(getText(R.string.notification_title_mpd_running)) 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)) .setContentText(getText(R.string.notification_text_mpd_running))
.setSmallIcon(R.drawable.notification_icon) .setSmallIcon(R.drawable.notification_icon)
.setContentIntent(contentIntent) .setContentIntent(contentIntent)
.build(); .build();
mThread = new Thread(this);
mThread.start();
startForeground(R.string.notification_title_mpd_running, notification); startForeground(R.string.notification_title_mpd_running, notification);
startService(new Intent(this, Main.class)); startService(new Intent(this, Main.class));
} }

View File

@ -105,12 +105,13 @@ public class Settings extends Activity {
else else
mRunButton.setChecked(false); mRunButton.setChecked(false);
mFirstRun = true; mFirstRun = true;
mTextStatus.setText("");
break; break;
case MSG_STARTED: case MSG_STARTED:
Log.d(TAG, "onStarted"); Log.d(TAG, "onStarted");
mRunButton.setChecked(true); mRunButton.setChecked(true);
mFirstRun = true; mFirstRun = true;
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX mTextStatus.setText("MPD service started");
break; break;
case MSG_LOG: case MSG_LOG:
if (mLogListArray.size() > MAX_LOGS) if (mLogListArray.size() > MAX_LOGS)

View File

@ -311,6 +311,7 @@ if not is_android
else else
sources += [ sources += [
'src/android/Context.cxx', 'src/android/Context.cxx',
'src/android/AudioManager.cxx',
'src/android/Environment.cxx', 'src/android/Environment.cxx',
'src/android/LogListener.cxx', 'src/android/LogListener.cxx',
] ]
@ -332,6 +333,7 @@ subdir('src/util')
subdir('src/time') subdir('src/time')
subdir('src/system') subdir('src/system')
subdir('src/thread') subdir('src/thread')
subdir('src/net')
subdir('src/event') subdir('src/event')
subdir('src/lib/dbus') subdir('src/lib/dbus')
@ -359,7 +361,6 @@ subdir('src/lib/crypto')
subdir('src/fs') subdir('src/fs')
subdir('src/config') subdir('src/config')
subdir('src/net')
subdir('src/tag') subdir('src/tag')
subdir('src/pcm') subdir('src/pcm')
subdir('src/neighbor') subdir('src/neighbor')

View File

@ -9,8 +9,8 @@ from build.ffmpeg import FfmpegProject
from build.boost import BoostProject from build.boost import BoostProject
libmpdclient = MesonProject( libmpdclient = MesonProject(
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.17.tar.xz', 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
'ee9b8f1c7e95b65c8f18a354daf7b16bfcd455fc52a0f3b5abe402316bce3559', '4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
'lib/libmpdclient.a', 'lib/libmpdclient.a',
) )
@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
) )
curl = AutotoolsProject( curl = AutotoolsProject(
'http://curl.haxx.se/download/curl-7.68.0.tar.xz', 'http://curl.haxx.se/download/curl-7.69.1.tar.xz',
'b724240722276a27f6e770b952121a3afd097129d8c9fe18e6272dc34192035a', '03c7d5e6697f7b7e40ada1b2256e565a555657398e6c1fcfa4cb251ccd819d4f',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',

View File

@ -33,6 +33,7 @@
#include "playlist/PlaylistRegistry.hxx" #include "playlist/PlaylistRegistry.hxx"
#include "playlist/PlaylistPlugin.hxx" #include "playlist/PlaylistPlugin.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "fs/FileSystem.hxx" #include "fs/FileSystem.hxx"
#include "fs/StandardDirectory.hxx" #include "fs/StandardDirectory.hxx"
@ -378,17 +379,7 @@ ParseCommandLine(int argc, char **argv, struct options &options,
if (config_file != nullptr) { if (config_file != nullptr) {
/* use specified configuration file */ /* use specified configuration file */
#ifdef _UNICODE ReadConfigFile(config, FromNarrowPath(config_file));
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
return; return;
} }

View File

@ -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);
}

View File

@ -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

View File

@ -21,8 +21,11 @@
#include "java/Class.hxx" #include "java/Class.hxx"
#include "java/Exception.hxx" #include "java/Exception.hxx"
#include "java/File.hxx" #include "java/File.hxx"
#include "java/String.hxx"
#include "fs/AllocatedPath.hxx" #include "fs/AllocatedPath.hxx"
#include "AudioManager.hxx"
AllocatedPath AllocatedPath
Context::GetCacheDir(JNIEnv *env) const noexcept Context::GetCacheDir(JNIEnv *env) const noexcept
{ {
@ -39,3 +42,21 @@ Context::GetCacheDir(JNIEnv *env) const noexcept
return Java::File::ToAbsolutePath(env, file); 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);
}

View File

@ -23,6 +23,7 @@
#include "java/Object.hxx" #include "java/Object.hxx"
class AllocatedPath; class AllocatedPath;
class AudioManager;
class Context : public Java::GlobalObject { class Context : public Java::GlobalObject {
public: public:
@ -31,6 +32,9 @@ public:
gcc_pure gcc_pure
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
gcc_pure
AudioManager *GetAudioManager(JNIEnv *env) noexcept;
}; };
#endif #endif

View File

@ -32,6 +32,7 @@
#include "fs/Traits.hxx" #include "fs/Traits.hxx"
#include "util/Alloc.hxx" #include "util/Alloc.hxx"
#include "util/DeleteDisposer.hxx" #include "util/DeleteDisposer.hxx"
#include "util/StringCompare.hxx"
#include <cassert> #include <cassert>
@ -70,7 +71,15 @@ Directory::GetName() const noexcept
{ {
assert(!IsRoot()); 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 * Directory *

View File

@ -287,7 +287,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
*/ */
static DecoderCommand static DecoderCommand
ffmpeg_send_packet(DecoderClient &client, InputStream &is, ffmpeg_send_packet(DecoderClient &client, InputStream &is,
AVPacket &&packet, const AVPacket &packet,
AVCodecContext &codec_context, AVCodecContext &codec_context,
const AVStream &stream, const AVStream &stream,
AVFrame &frame, AVFrame &frame,
@ -340,24 +340,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
return cmd; 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 gcc_const
static SampleFormat static SampleFormat
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept

View File

@ -26,6 +26,7 @@ event_dep = declare_dependency(
link_with: event, link_with: event,
dependencies: [ dependencies: [
thread_dep, thread_dep,
net_dep,
system_dep, system_dep,
boost_dep, boost_dep,
], ],

54
src/fs/NarrowPath.cxx Normal file
View File

@ -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 */

View File

@ -23,9 +23,8 @@
#include "Path.hxx" #include "Path.hxx"
#ifdef _UNICODE #ifdef _UNICODE
#include "lib/icu/Win32.hxx" #include "AllocatedPath.hxx"
#include "util/AllocatedString.hxx" #include "util/AllocatedString.hxx"
#include <windows.h>
#else #else
#include "util/StringPointer.hxx" #include "util/StringPointer.hxx"
#endif #endif
@ -47,12 +46,7 @@ class NarrowPath {
public: public:
#ifdef _UNICODE #ifdef _UNICODE
explicit NarrowPath(Path _path) explicit NarrowPath(Path _path) noexcept;
:value(WideCharToMultiByte(CP_ACP, _path.c_str())) {
if (value.IsNull())
/* fall back to empty string */
value = Value::Empty();
}
#else #else
explicit NarrowPath(Path _path):value(_path.c_str()) {} explicit NarrowPath(Path _path):value(_path.c_str()) {}
#endif #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 #endif

View File

@ -50,7 +50,7 @@ class BufferedReader {
public: public:
explicit BufferedReader(Reader &_reader) noexcept explicit BufferedReader(Reader &_reader) noexcept
:reader(_reader), buffer(4096) {} :reader(_reader), buffer(16384) {}
/** /**
* Reset the internal state. Should be called after rewinding * Reset the internal state. Should be called after rewinding

View File

@ -45,7 +45,7 @@ class GunzipReader final : public Reader {
z_stream z; z_stream z;
StaticFifoBuffer<Bytef, 4096> buffer; StaticFifoBuffer<Bytef, 65536> buffer;
public: public:
/** /**

View File

@ -62,7 +62,7 @@ GzipOutputStream::Flush()
z.avail_in = 0; z.avail_in = 0;
while (true) { while (true) {
Bytef output[4096]; Bytef output[16384];
z.next_out = output; z.next_out = output;
z.avail_out = sizeof(output); z.avail_out = sizeof(output);
@ -87,7 +87,7 @@ GzipOutputStream::Write(const void *_data, size_t size)
z.avail_in = size; z.avail_in = size;
while (z.avail_in > 0) { while (z.avail_in > 0) {
Bytef output[4096]; Bytef output[16384];
z.next_out = output; z.next_out = output;
z.avail_out = sizeof(output); z.avail_out = sizeof(output);

View File

@ -6,6 +6,7 @@ fs_sources = [
'Path.cxx', 'Path.cxx',
'Path2.cxx', 'Path2.cxx',
'AllocatedPath.cxx', 'AllocatedPath.cxx',
'NarrowPath.cxx',
'FileSystem.cxx', 'FileSystem.cxx',
'List.cxx', 'List.cxx',
'StandardDirectory.cxx', 'StandardDirectory.cxx',

View File

@ -56,7 +56,9 @@ CurlRequest::CurlRequest(CurlGlobal &_global,
easy.SetUserAgent("Music Player Daemon " VERSION); easy.SetUserAgent("Music Player Daemon " VERSION);
easy.SetHeaderFunction(_HeaderFunction, this); easy.SetHeaderFunction(_HeaderFunction, this);
easy.SetWriteFunction(WriteFunction, this); easy.SetWriteFunction(WriteFunction, this);
#ifndef ANDROID
easy.SetOption(CURLOPT_NETRC, 1L); easy.SetOption(CURLOPT_NETRC, 1L);
#endif
easy.SetErrorBuffer(error_buffer); easy.SetErrorBuffer(error_buffer);
easy.SetNoProgress(); easy.SetNoProgress();
easy.SetNoSignal(); easy.SetNoSignal();

View File

@ -29,6 +29,7 @@ struct MixerPlugin;
extern const MixerPlugin null_mixer_plugin; extern const MixerPlugin null_mixer_plugin;
extern const MixerPlugin software_mixer_plugin; extern const MixerPlugin software_mixer_plugin;
extern const MixerPlugin android_mixer_plugin;
extern const MixerPlugin alsa_mixer_plugin; extern const MixerPlugin alsa_mixer_plugin;
extern const MixerPlugin haiku_mixer_plugin; extern const MixerPlugin haiku_mixer_plugin;
extern const MixerPlugin oss_mixer_plugin; extern const MixerPlugin oss_mixer_plugin;

View File

@ -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,
};

View File

@ -34,6 +34,10 @@ if is_windows
mixer_plugins_sources += 'WinmmMixerPlugin.cxx' mixer_plugins_sources += 'WinmmMixerPlugin.cxx'
endif endif
if is_android
mixer_plugins_sources += 'AndroidMixerPlugin.cxx'
endif
mixer_plugins = static_library( mixer_plugins = static_library(
'mixer_plugins', 'mixer_plugins',
mixer_plugins_sources, mixer_plugins_sources,

View File

@ -27,6 +27,7 @@
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "util/ByteOrder.hxx" #include "util/ByteOrder.hxx"
#include "mixer/MixerList.hxx"
#include "Log.hxx" #include "Log.hxx"
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES.h>
@ -412,5 +413,5 @@ const struct AudioOutputPlugin sles_output_plugin = {
"sles", "sles",
sles_test_default_device, sles_test_default_device,
SlesOutput::Create, SlesOutput::Create,
nullptr, &android_mixer_plugin,
}; };

View File

@ -160,6 +160,7 @@ static const char *const rss_suffixes[] = {
static const char *const rss_mime_types[] = { static const char *const rss_mime_types[] = {
"application/rss+xml", "application/rss+xml",
"application/xml",
"text/xml", "text/xml",
nullptr nullptr
}; };

View File

@ -32,7 +32,7 @@
Mutex tag_pool_lock; Mutex tag_pool_lock;
static constexpr size_t NUM_SLOTS = 4093; static constexpr size_t NUM_SLOTS = 16127;
struct TagPoolSlot { struct TagPoolSlot {
TagPoolSlot *next; TagPoolSlot *next;

View File

@ -77,15 +77,15 @@ static time_t
GetTimeZoneOffset() noexcept GetTimeZoneOffset() noexcept
{ {
time_t t = 1234567890; time_t t = 1234567890;
struct tm tm;
tm.tm_isdst = 0;
#ifdef _WIN32 #ifdef _WIN32
struct tm *p = gmtime(&t); struct tm *p = gmtime(&t);
#else #else
struct tm tm;
tm.tm_isdst = 0;
struct tm *p = &tm; struct tm *p = &tm;
gmtime_r(&t, p); gmtime_r(&t, p);
#endif #endif
return t - mktime(&tm); return t - mktime(p);
} }
#endif /* !__GLIBC__ */ #endif /* !__GLIBC__ */

View File

@ -23,6 +23,7 @@
#include "decoder/DecoderList.hxx" #include "decoder/DecoderList.hxx"
#include "decoder/DecoderPlugin.hxx" #include "decoder/DecoderPlugin.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/io/StdioOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
@ -63,7 +64,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path path = Path::FromFS(argv[1]); const FromNarrowPath path = argv[1];
const ScopeDecoderPluginsInit decoder_plugins_init({}); const ScopeDecoderPluginsInit decoder_plugins_init({});

View File

@ -29,6 +29,7 @@
#include "ConfigGlue.hxx" #include "ConfigGlue.hxx"
#include "tag/Config.hxx" #include "tag/Config.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "event/Thread.hxx" #include "event/Thread.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
@ -106,7 +107,7 @@ try {
return 1; return 1;
} }
const Path config_path = Path::FromFS(argv[1]); const FromNarrowPath config_path = argv[1];
const char *const plugin_name = argv[2]; const char *const plugin_name = argv[2];
const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name); const DatabasePlugin *plugin = GetDatabasePluginByName(plugin_name);

View File

@ -21,6 +21,7 @@
#include "tag/ApeLoader.hxx" #include "tag/ApeLoader.hxx"
#include "thread/Mutex.hxx" #include "thread/Mutex.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "input/LocalOpen.hxx" #include "input/LocalOpen.hxx"
#include "util/StringView.hxx" #include "util/StringView.hxx"
@ -58,7 +59,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path path = Path::FromFS(argv[1]); const FromNarrowPath path = argv[1];
Mutex mutex; Mutex mutex;

View File

@ -29,8 +29,8 @@ public:
}; };
#ifdef _WIN32 #ifdef _WIN32
ShutdownHandler::ShutdownHandler(EventLoop &loop) {} inline ShutdownHandler::ShutdownHandler(EventLoop &) {}
ShutdownHandler::~ShutdownHandler() {} inline ShutdownHandler::~ShutdownHandler() {}
#endif #endif
#endif #endif

View File

@ -18,6 +18,7 @@
*/ */
#include "fs/io/FileOutputStream.hxx" #include "fs/io/FileOutputStream.hxx"
#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include <cerrno> #include <cerrno>
@ -55,7 +56,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path path = Path::FromFS(argv[1]); const FromNarrowPath path = argv[1];
FileOutputStream fos(path); FileOutputStream fos(path);

View File

@ -28,6 +28,7 @@
#include "playlist/PlaylistRegistry.hxx" #include "playlist/PlaylistRegistry.hxx"
#include "playlist/PlaylistPlugin.hxx" #include "playlist/PlaylistPlugin.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx"
#include "fs/io/StdioOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx"
#include "thread/Cond.hxx" #include "thread/Cond.hxx"
@ -54,7 +55,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path config_path = Path::FromFS(argv[1]); const FromNarrowPath config_path = argv[1];
uri = argv[2]; uri = argv[2];
/* initialize MPD */ /* initialize MPD */

View File

@ -24,6 +24,7 @@ gtest_dep = declare_dependency(
) )
subdir('net') subdir('net')
subdir('time')
executable( executable(
'read_conf', '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( test('TestRewindInputStream', executable(
'TestRewindInputStream', 'TestRewindInputStream',
'TestRewindInputStream.cxx', 'TestRewindInputStream.cxx',
@ -326,6 +314,11 @@ if curl_dep.found()
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
curl_dep, 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,
], ],
) )

View File

@ -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 * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@ -34,13 +34,22 @@
#include <stdexcept> #include <stdexcept>
#ifndef _WIN32
#include <arpa/inet.h> #include <arpa/inet.h>
#endif
static std::string static std::string
ToString(const struct in_addr &a) 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]; 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) if (result == nullptr)
throw std::runtime_error("inet_ntop() failed"); throw std::runtime_error("inet_ntop() failed");
return result; return result;

View File

@ -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 * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@ -34,13 +34,22 @@
#include <stdexcept> #include <stdexcept>
#ifndef _WIN32
#include <arpa/inet.h> #include <arpa/inet.h>
#endif
static std::string static std::string
ToString(const struct in6_addr &a) 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]; 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) if (result == nullptr)
throw std::runtime_error("inet_ntop() failed"); throw std::runtime_error("inet_ntop() failed");
return result; return result;

View File

@ -21,6 +21,7 @@
#include "config/Param.hxx" #include "config/Param.hxx"
#include "config/File.hxx" #include "config/File.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
@ -34,7 +35,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path config_path = Path::FromFS(argv[1]); const FromNarrowPath config_path = argv[1];
const char *name = argv[2]; const char *name = argv[2];
const auto option = ParseConfigOptionName(name); const auto option = ParseConfigOptionName(name);

View File

@ -27,6 +27,7 @@
#include "tag/Handler.hxx" #include "tag/Handler.hxx"
#include "tag/Generic.hxx" #include "tag/Generic.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
@ -97,7 +98,7 @@ try {
} }
decoder_name = argv[1]; decoder_name = argv[1];
const Path path = Path::FromFS(argv[2]); const char *path = argv[2];
EventThread io_thread; EventThread io_thread;
io_thread.Start(); io_thread.Start();
@ -116,7 +117,7 @@ try {
DumpTagHandler h; DumpTagHandler h;
bool success; bool success;
try { try {
success = plugin->ScanFile(path, h); success = plugin->ScanFile(FromNarrowPath(path), h);
} catch (...) { } catch (...) {
PrintException(std::current_exception()); PrintException(std::current_exception());
success = false; success = false;
@ -126,7 +127,7 @@ try {
InputStreamPtr is; InputStreamPtr is;
if (!success && plugin->scan_stream != nullptr) { if (!success && plugin->scan_stream != nullptr) {
is = InputStream::OpenReady(path.c_str(), mutex); is = InputStream::OpenReady(path, mutex);
success = plugin->ScanStream(*is, h); success = plugin->ScanStream(*is, h);
} }
@ -139,7 +140,7 @@ try {
if (is) if (is)
ScanGenericTags(*is, h); ScanGenericTags(*is, h);
else else
ScanGenericTags(path, h); ScanGenericTags(FromNarrowPath(path), h);
} }
return 0; return 0;

View File

@ -26,6 +26,7 @@
#include "input/Init.hxx" #include "input/Init.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/OptionDef.hxx" #include "util/OptionDef.hxx"
#include "util/OptionParser.hxx" #include "util/OptionParser.hxx"
@ -44,7 +45,7 @@ struct CommandLine {
const char *decoder = nullptr; const char *decoder = nullptr;
const char *uri = nullptr; const char *uri = nullptr;
Path config_path = nullptr; FromNarrowPath config_path;
bool verbose = false; bool verbose = false;
@ -72,7 +73,7 @@ ParseCommandLine(int argc, char **argv)
while (auto o = option_parser.Next()) { while (auto o = option_parser.Next()) {
switch (Option(o.index)) { switch (Option(o.index)) {
case OPTION_CONFIG: case OPTION_CONFIG:
c.config_path = Path::FromFS(o.value); c.config_path = o.value;
break; break;
case OPTION_VERBOSE: case OPTION_VERBOSE:
@ -205,7 +206,7 @@ try {
MyDecoderClient client(c.seek_where); MyDecoderClient client(c.seek_where);
if (plugin->file_decode != nullptr) { if (plugin->file_decode != nullptr) {
try { try {
plugin->FileDecode(client, Path::FromFS(c.uri)); plugin->FileDecode(client, FromNarrowPath(c.uri));
} catch (StopDecoder) { } catch (StopDecoder) {
} }
} else if (plugin->stream_decode != nullptr) { } else if (plugin->stream_decode != nullptr) {

View File

@ -19,6 +19,7 @@
#include "ConfigGlue.hxx" #include "ConfigGlue.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "filter/LoadOne.hxx" #include "filter/LoadOne.hxx"
#include "filter/Filter.hxx" #include "filter/Filter.hxx"
#include "filter/Prepared.hxx" #include "filter/Prepared.hxx"
@ -123,7 +124,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path config_path = Path::FromFS(argv[1]); const FromNarrowPath config_path = argv[1];
AudioFormat audio_format(44100, SampleFormat::S16, 2); AudioFormat audio_format(44100, SampleFormat::S16, 2);

View File

@ -20,6 +20,7 @@
#include "fs/io/GunzipReader.hxx" #include "fs/io/GunzipReader.hxx"
#include "fs/io/FileReader.hxx" #include "fs/io/FileReader.hxx"
#include "fs/io/StdioOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx"
#include "fs/NarrowPath.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include <stdio.h> #include <stdio.h>
@ -62,7 +63,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
Path path = Path::FromFS(argv[1]); FromNarrowPath path = argv[1];
CopyGunzip(stdout, path); CopyGunzip(stdout, path);
return EXIT_SUCCESS; return EXIT_SUCCESS;

View File

@ -32,6 +32,7 @@
#include "Log.hxx" #include "Log.hxx"
#include "LogBackend.hxx" #include "LogBackend.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "fs/io/BufferedOutputStream.hxx" #include "fs/io/BufferedOutputStream.hxx"
#include "fs/io/StdioOutputStream.hxx" #include "fs/io/StdioOutputStream.hxx"
#include "util/ConstBuffer.hxx" #include "util/ConstBuffer.hxx"
@ -51,7 +52,7 @@
struct CommandLine { struct CommandLine {
const char *uri = nullptr; const char *uri = nullptr;
Path config_path = nullptr; FromNarrowPath config_path;
bool verbose = false; bool verbose = false;
@ -79,7 +80,7 @@ ParseCommandLine(int argc, char **argv)
while (auto o = option_parser.Next()) { while (auto o = option_parser.Next()) {
switch (Option(o.index)) { switch (Option(o.index)) {
case OPTION_CONFIG: case OPTION_CONFIG:
c.config_path = Path::FromFS(o.value); c.config_path = o.value;
break; break;
case OPTION_VERBOSE: case OPTION_VERBOSE:

View File

@ -23,6 +23,7 @@
#include "ConfigGlue.hxx" #include "ConfigGlue.hxx"
#include "event/Thread.hxx" #include "event/Thread.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx"
#include "pcm/AudioParser.hxx" #include "pcm/AudioParser.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
@ -111,7 +112,7 @@ try {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
const Path config_path = Path::FromFS(argv[1]); const FromNarrowPath config_path = argv[1];
AudioFormat audio_format(44100, SampleFormat::S16, 2); AudioFormat audio_format(44100, SampleFormat::S16, 2);

65
test/time/TestConvert.cxx Normal file
View File

@ -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);
}
}

17
test/time/meson.build Normal file
View File

@ -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,
],
),
)