diff --git a/Makefile.am b/Makefile.am index c02ca7ce7..aa52c68ef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,6 +16,7 @@ noinst_LIBRARIES = \ libthread.a \ libsystem.a \ libevent.a \ + libicu.a \ libpcm.a \ libconf.a \ libtag.a \ @@ -56,6 +57,7 @@ src_mpd_LDADD = \ libevent.a \ libthread.a \ libsystem.a \ + $(ICU_LDADD) \ libutil.a \ libfs.a \ $(SYSTEMD_DAEMON_LIBS) \ @@ -410,6 +412,14 @@ libevent_a_SOURCES = \ src/event/Call.hxx src/event/Call.cxx \ src/event/Loop.cxx src/event/Loop.hxx +# UTF-8 library + +libicu_a_SOURCES = \ + src/lib/icu/Collate.cxx src/lib/icu/Collate.hxx \ + src/lib/icu/Error.cxx src/lib/icu/Error.hxx + +ICU_LDADD = libicu.a $(ICU_LIBS) + # PCM library libpcm_a_SOURCES = \ @@ -1426,6 +1436,7 @@ test_DumpDatabase_LDADD = \ libevent.a \ libsystem.a \ libfs.a \ + $(ICU_LDADD) \ $(GLIB_LIBS) test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \ src/protocol/Ack.cxx \ diff --git a/android/build.py b/android/build.py index 090d47769..5898d0f91 100755 --- a/android/build.py +++ b/android/build.py @@ -319,6 +319,7 @@ configure = [ '--enable-silent-rules', '--disable-glib', + '--disable-icu', # disabled for now because these features require GLib: '--disable-database', diff --git a/configure.ac b/configure.ac index f05c17b2a..f449c2d2e 100644 --- a/configure.ac +++ b/configure.ac @@ -627,6 +627,19 @@ dnl --------------------------------------------------------------------------- dnl Mandatory Libraries dnl --------------------------------------------------------------------------- +AC_ARG_ENABLE(icu, + AS_HELP_STRING([--enable-icu], + [enable libicu for Unicode (default: enabled)]),, + enable_icu=yes) + +if test x$enable_icu = xyes; then + PKG_CHECK_MODULES([ICU], [icu-i18n],, + [AC_MSG_ERROR([libicu not found])]) + + AC_DEFINE(HAVE_ICU, 1, [Define if libicu is used]) +fi +AM_CONDITIONAL(HAVE_ICU, test x$enable_icu = xyes) + AC_ARG_ENABLE(glib, AS_HELP_STRING([--enable-glib], [enable GLib usage (default: enabled)]),, diff --git a/src/Main.cxx b/src/Main.cxx index fe91ed3a2..4c7c6d97c 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -57,6 +57,7 @@ #include "util/Domain.hxx" #include "thread/Id.hxx" #include "thread/Slack.hxx" +#include "lib/icu/Collate.hxx" #include "config/ConfigGlobal.hxx" #include "config/ConfigData.hxx" #include "config/ConfigDefaults.hxx" @@ -405,6 +406,11 @@ int mpd_main(int argc, char *argv[]) #endif #endif + if (!IcuCollateInit(error)) { + LogError(error); + return EXIT_FAILURE; + } + winsock_init(); io_thread_init(); config_global_init(); @@ -651,6 +657,8 @@ int mpd_main(int argc, char *argv[]) WSACleanup(); #endif + IcuCollateFinish(); + log_deinit(); return EXIT_SUCCESS; } diff --git a/src/SongFilter.cxx b/src/SongFilter.cxx index 77fea606e..03ff3991e 100644 --- a/src/SongFilter.cxx +++ b/src/SongFilter.cxx @@ -25,10 +25,7 @@ #include "tag/Tag.hxx" #include "util/ASCII.hxx" #include "util/UriUtil.hxx" - -#ifdef HAVE_GLIB -#include -#endif +#include "lib/icu/Collate.hxx" #include #include @@ -54,27 +51,12 @@ locate_parse_type(const char *str) return tag_name_parse_i(str); } -gcc_pure -static std::string -CaseFold(const char *p) -{ -#ifdef HAVE_GLIB - char *q = g_utf8_casefold(p, -1); - std::string result(q); - g_free(q); - return result; -#else - // TODO: implement without GLib - return p; -#endif -} - gcc_pure static std::string ImportString(const char *p, bool fold_case) { return fold_case - ? CaseFold(p) + ? IcuCaseFold(p) : std::string(p); } @@ -90,17 +72,8 @@ SongFilter::Item::StringMatch(const char *s) const assert(s != nullptr); if (fold_case) { -#ifdef HAVE_GLIB - char *p = g_utf8_casefold(s, -1); -#else - // TODO: implement without GLib - const char *p = s; -#endif - const bool result = strstr(p, value.c_str()) != NULL; -#ifdef HAVE_GLIB - g_free(p); -#endif - return result; + const std::string folded = IcuCaseFold(s); + return folded.find(value) != folded.npos; } else { return s == value; } diff --git a/src/db/Directory.cxx b/src/db/Directory.cxx index 01e147eff..1da19be98 100644 --- a/src/db/Directory.cxx +++ b/src/db/Directory.cxx @@ -27,6 +27,7 @@ #include "SongSort.hxx" #include "Song.hxx" #include "LightSong.hxx" +#include "lib/icu/Collate.hxx" #include "fs/Traits.hxx" #include "util/Alloc.hxx" #include "util/Error.hxx" @@ -35,8 +36,6 @@ extern "C" { #include "util/list_sort.h" } -#include - #include #include #include @@ -229,7 +228,8 @@ directory_cmp(gcc_unused void *priv, { const Directory *a = (const Directory *)_a; const Directory *b = (const Directory *)_b; - return g_utf8_collate(a->path.c_str(), b->path.c_str()); + + return IcuCollate(a->path.c_str(), b->path.c_str()); } void diff --git a/src/db/SongSort.cxx b/src/db/SongSort.cxx index dcea033b6..c5752f568 100644 --- a/src/db/SongSort.cxx +++ b/src/db/SongSort.cxx @@ -21,13 +21,12 @@ #include "SongSort.hxx" #include "Song.hxx" #include "tag/Tag.hxx" +#include "lib/icu/Collate.hxx" extern "C" { #include "util/list_sort.h" } -#include - #include static int @@ -39,7 +38,7 @@ compare_utf8_string(const char *a, const char *b) if (b == nullptr) return 1; - return g_utf8_collate(a, b); + return IcuCollate(a, b); } /** @@ -104,7 +103,7 @@ song_cmp(gcc_unused void *priv, struct list_head *_a, struct list_head *_b) return ret; /* still no difference? compare file name */ - return g_utf8_collate(a->uri, b->uri); + return IcuCollate(a->uri, b->uri); } void diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx new file mode 100644 index 000000000..8dd757fb0 --- /dev/null +++ b/src/lib/icu/Collate.cxx @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Collate.hxx" + +#ifdef HAVE_ICU +#include "Error.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include +#include +#elif defined(HAVE_GLIB) +#include +#else +#include +#include +#endif + +#include +#include + +#ifdef HAVE_ICU +static UCollator *collator; +#endif + +bool +IcuCollateInit(Error &error) +{ +#ifdef HAVE_ICU + assert(collator == nullptr); + assert(!error.IsDefined()); + + UErrorCode code; + collator = ucol_open("", &code); + if (collator == nullptr) { + error.Format(icu_domain, int(code), + "ucol_open() failed: %s", u_errorName(code)); + return false; + } +#else + (void)error; +#endif + + return true; +} + +void +IcuCollateFinish() +{ +#ifdef HAVE_ICU + assert(collator != nullptr); + + ucol_close(collator); +#endif +} + +#ifdef HAVE_ICU + +static UChar * +UCharFromUTF8(const char *src, int32_t *dest_length) +{ + assert(src != nullptr); + + const size_t src_length = strlen(src); + size_t dest_capacity = src_length + 1; + UChar *dest = new UChar[dest_capacity]; + + UErrorCode error_code; + u_strFromUTF8(dest, dest_capacity, + dest_length, + src, src_length, + &error_code); + if (U_FAILURE(error_code)) { + delete[] dest; + return nullptr; + } + + return dest; +} + +#endif + +gcc_pure +int +IcuCollate(const char *a, const char *b) +{ + assert(a != nullptr); + assert(b != nullptr); + +#ifdef HAVE_ICU + assert(collator != nullptr); + +#if U_ICU_VERSION_MAJOR_NUM >= 50 + return (int)ucol_strcollUTF8(collator, a, -1, b, -1, nullptr); +#else + /* fall back to ucol_strcoll() */ + + UChar *au = UCharFromUTF8(a, nullptr); + UChar *bu = UCharFromUTF8(b, nullptr); + + int result = au != nullptr && bu != nullptr + ? (int)ucol_strcoll(collator, au, -1, bu, -1) + : strcasecmp(a, b); + + delete[] au; + delete[] bu; + + return result; +#endif + +#elif defined(HAVE_GLIB) + return g_utf8_collate(a, b); +#else + return strcasecmp(a, b); +#endif +} + +std::string +IcuCaseFold(const char *src) +{ +#ifdef HAVE_ICU + assert(collator != nullptr); + assert(src != nullptr); + + int32_t u_length; + UChar *u = UCharFromUTF8(src, &u_length); + if (u == nullptr) + return std::string(src); + + size_t dest_length = ucol_getSortKey(collator, u, u_length, + nullptr, 0); + if (dest_length == 0) { + delete[] u; + return std::string(src); + } + + uint8_t *dest = new uint8_t[dest_length]; + ucol_getSortKey(collator, u, u_length, + dest, dest_length); + std::string result((const char *)dest); + delete[] dest; +#elif defined(HAVE_GLIB) + char *tmp = g_utf8_casefold(src, -1); + std::string result(tmp); + g_free(tmp); +#else + std::string result(src); + std::transform(result.begin(), result.end(), result.begin(), tolower); +#endif + return result; +} + diff --git a/src/lib/icu/Collate.hxx b/src/lib/icu/Collate.hxx new file mode 100644 index 000000000..8ae8de46a --- /dev/null +++ b/src/lib/icu/Collate.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_COLLATE_HXX +#define MPD_ICU_COLLATE_HXX + +#include "check.h" +#include "Compiler.h" + +#include + +class Error; + +bool +IcuCollateInit(Error &error); + +void +IcuCollateFinish(); + +gcc_pure gcc_nonnull_all +int +IcuCollate(const char *a, const char *b); + +gcc_pure gcc_nonnull_all +std::string +IcuCaseFold(const char *src); + +#endif diff --git a/src/lib/icu/Error.cxx b/src/lib/icu/Error.cxx new file mode 100644 index 000000000..1fef078ac --- /dev/null +++ b/src/lib/icu/Error.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Error.hxx" +#include "util/Domain.hxx" + +const Domain icu_domain("icu"); diff --git a/src/lib/icu/Error.hxx b/src/lib/icu/Error.hxx new file mode 100644 index 000000000..e96667f57 --- /dev/null +++ b/src/lib/icu/Error.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_ERROR_HXX +#define MPD_ICU_ERROR_HXX + +#include "check.h" + +class Domain; + +extern const Domain icu_domain; + +#endif