mediascanner2-0.111+16.04.20160317/0000755000015600001650000000000012672422030016571 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/cmake/0000755000015600001650000000000012672422030017651 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/0000755000015600001650000000000012672422030017360 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/ms-dbus/0000755000015600001650000000000012672422030020732 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/ms-dbus/com.canonical.MediaScanner2.service.in0000644000015600001650000000017112672421600030040 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.MediaScanner2 Exec=@CMAKE_INSTALL_FULL_LIBDIR@/mediascanner-2.0/mediascanner-dbus-2.0 mediascanner2-0.111+16.04.20160317/src/ms-dbus/service-stub.cc0000644000015600001650000001202512672421600023656 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "service-stub.hh" #include #include #include #include #include "dbus-interface.hh" #include "dbus-codec.hh" using std::string; namespace mediascanner { namespace dbus { struct ServiceStub::Private { core::dbus::Object::Ptr object; }; ServiceStub::ServiceStub(core::dbus::Bus::Ptr bus) : core::dbus::Stub(bus), p(new Private{access_service()->object_for_path( core::dbus::types::ObjectPath(core::dbus::traits::Service::object_path()))}) { } ServiceStub::~ServiceStub() { } MediaFile ServiceStub::lookup(const string &filename) const { auto result = p->object->invoke_method_synchronously(filename); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::query(const string &q, MediaType type, const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(q, (int32_t)type, filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::queryAlbums(const string &core_term, const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(core_term, filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::queryArtists(const string &q, const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(q, filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::getAlbumSongs(const Album& album) const { auto result = p->object->invoke_method_synchronously>(album); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } string ServiceStub::getETag(const string &filename) const { auto result = p->object->invoke_method_synchronously(filename); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::listSongs(const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::listAlbums(const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::listArtists(const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::listAlbumArtists(const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } std::vector ServiceStub::listGenres(const Filter &filter) const { auto result = p->object->invoke_method_synchronously>(filter); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } bool ServiceStub::hasMedia(MediaType type) const { auto result = p->object->invoke_method_synchronously(static_cast(type)); if (result.is_error()) throw std::runtime_error(result.error().print()); return result.value(); } } } mediascanner2-0.111+16.04.20160317/src/ms-dbus/service.hh0000644000015600001650000000313512672421600022717 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_DBUS_SERVICE_HH #define MEDIASCANNER_DBUS_SERVICE_HH #include namespace mediascanner { namespace dbus { class MediaStoreService { public: MediaStoreService() {} MediaStoreService(const MediaStoreService&) = delete; virtual ~MediaStoreService() = default; MediaStoreService& operator=(const MediaStoreService&) = delete; bool operator==(const MediaStoreService&) const = delete; }; } } namespace core { namespace dbus { namespace traits { template<> struct Service { inline static const std::string& interface_name() { static const std::string iface("com.canonical.MediaScanner2"); return iface; } inline static const std::string& object_path() { static const std::string path("/com/canonical/MediaScanner2"); return path; } }; } } } #endif mediascanner2-0.111+16.04.20160317/src/ms-dbus/dbus-codec.hh0000644000015600001650000000562712672421600023277 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_DBUS_CODEC_HH #define MEDIASCANNER_DBUS_CODEC_HH #include #include #include namespace mediascanner { class MediaFile; class Album; class Filter; } namespace core { namespace dbus { template <> struct Codec { static void encode_argument(Message::Writer &out, const mediascanner::MediaFile &file); static void decode_argument(Message::Reader &in, mediascanner::MediaFile &file); }; template <> struct Codec { static void encode_argument(Message::Writer &out, const mediascanner::Album &album); static void decode_argument(Message::Reader &in, mediascanner::Album &album); }; template <> struct Codec { static void encode_argument(Message::Writer &out, const mediascanner::Filter &filter); static void decode_argument(Message::Reader &in, mediascanner::Filter &filter); }; namespace helper { template<> struct TypeMapper { constexpr static ArgumentType type_value() { return ArgumentType::structure; } constexpr static bool is_basic_type() { return false; } constexpr static bool requires_signature() { return true; } static const std::string &signature() { static const std::string s = "(sssssssssiiiiiddbti)"; return s; } }; template<> struct TypeMapper { constexpr static ArgumentType type_value() { return ArgumentType::structure; } constexpr static bool is_basic_type() { return false; } constexpr static bool requires_signature() { return true; } static const std::string &signature() { static const std::string s = "(sssssb)"; return s; } }; template<> struct TypeMapper { constexpr static ArgumentType type_value() { return ArgumentType::array; } constexpr static bool is_basic_type() { return false; } constexpr static bool requires_signature() { return true; } static const std::string &signature() { static const std::string s = "a{sv}"; return s; } }; } } } #endif mediascanner2-0.111+16.04.20160317/src/ms-dbus/dbus-interface.hh0000644000015600001650000001327312672421600024156 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_DBUS_INTERFACE_HH #define MEDIASCANNER_DBUS_INTERFACE_HH #include #include #include #include namespace mediascanner { namespace dbus { struct MediaStoreInterface { inline static const std::string& name() { static std::string s = "com.canonical.MediaScanner2"; return s; } inline static const std::chrono::milliseconds default_timeout() { return std::chrono::seconds{10}; } struct Errors { struct Error { inline static const std::string& name() { static std::string s = "com.canonical.MediaScanner2.Error"; return s; } }; struct Unauthorized { inline static const std::string& name() { static std::string s = "com.canonical.MediaScanner2.Error.Unauthorized"; return s; } }; }; struct Lookup { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "Lookup"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct Query { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "Query"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct QueryAlbums { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "QueryAlbums"; return s; } inline static const std::chrono::milliseconds default_timeout() { return std::chrono::seconds{1}; } }; struct QueryArtists { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "QueryArtists"; return s; } inline static const std::chrono::milliseconds default_timeout() { return std::chrono::seconds{1}; } }; struct GetAlbumSongs { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "GetAlbumSongs"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct GetETag { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "GetETag"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct ListSongs { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "ListSongs"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct ListAlbums { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "ListAlbums"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct ListArtists { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "ListArtists"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct ListAlbumArtists { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "ListAlbumArtists"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct ListGenres { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "ListGenres"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; struct HasMedia { typedef MediaStoreInterface Interface; inline static const std::string& name() { static std::string s = "HasMedia"; return s; } inline static const std::chrono::milliseconds default_timeout() { return Interface::default_timeout(); } }; }; } } #endif mediascanner2-0.111+16.04.20160317/src/ms-dbus/main.cc0000644000015600001650000000231312672421600022166 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include #include #include #include #include "service-skeleton.hh" using namespace mediascanner; int main(int , char **) { auto bus = std::make_shared(core::dbus::WellKnownBus::session); bus->install_executor(core::dbus::asio::make_executor(bus)); auto store = std::make_shared(MS_READ_ONLY); dbus::ServiceSkeleton service(bus, store); service.run(); return 0; } mediascanner2-0.111+16.04.20160317/src/ms-dbus/dbus-codec.cc0000644000015600001650000001533712672421600023264 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "dbus-codec.hh" #include #include #include #include #include #include #include using core::dbus::Message; using core::dbus::Codec; using core::dbus::types::Variant; using mediascanner::MediaFile; using mediascanner::MediaFileBuilder; using mediascanner::MediaOrder; using mediascanner::MediaType; using mediascanner::Album; using mediascanner::Filter; using std::string; void Codec::encode_argument(Message::Writer &out, const MediaFile &file) { auto w = out.open_structure(); core::dbus::encode_argument(w, file.getFileName()); core::dbus::encode_argument(w, file.getContentType()); core::dbus::encode_argument(w, file.getETag()); core::dbus::encode_argument(w, file.getTitle()); core::dbus::encode_argument(w, file.getAuthor()); core::dbus::encode_argument(w, file.getAlbum()); core::dbus::encode_argument(w, file.getAlbumArtist()); core::dbus::encode_argument(w, file.getDate()); core::dbus::encode_argument(w, file.getGenre()); core::dbus::encode_argument(w, (int32_t)file.getDiscNumber()); core::dbus::encode_argument(w, (int32_t)file.getTrackNumber()); core::dbus::encode_argument(w, (int32_t)file.getDuration()); core::dbus::encode_argument(w, (int32_t)file.getWidth()); core::dbus::encode_argument(w, (int32_t)file.getHeight()); core::dbus::encode_argument(w, file.getLatitude()); core::dbus::encode_argument(w, file.getLongitude()); core::dbus::encode_argument(w, file.getHasThumbnail()); core::dbus::encode_argument(w, file.getModificationTime()); core::dbus::encode_argument(w, (int32_t)file.getType()); out.close_structure(std::move(w)); } void Codec::decode_argument(Message::Reader &in, MediaFile &file) { auto r = in.pop_structure(); string filename, content_type, etag, title, author; string album, album_artist, date, genre; int32_t disc_number, track_number, duration, width, height, type; double latitude, longitude; bool has_thumbnail; uint64_t mtime; r >> filename >> content_type >> etag >> title >> author >> album >> album_artist >> date >> genre >> disc_number >> track_number >> duration >> width >> height >> latitude >> longitude >> has_thumbnail >> mtime >> type; file = MediaFileBuilder(filename) .setContentType(content_type) .setETag(etag) .setTitle(title) .setAuthor(author) .setAlbum(album) .setAlbumArtist(album_artist) .setDate(date) .setGenre(genre) .setDiscNumber(disc_number) .setTrackNumber(track_number) .setDuration(duration) .setWidth(width) .setHeight(height) .setLatitude(latitude) .setLongitude(longitude) .setHasThumbnail(has_thumbnail) .setModificationTime(mtime) .setType((MediaType)type); } void Codec::encode_argument(Message::Writer &out, const Album &album) { auto w = out.open_structure(); core::dbus::encode_argument(w, album.getTitle()); core::dbus::encode_argument(w, album.getArtist()); core::dbus::encode_argument(w, album.getDate()); core::dbus::encode_argument(w, album.getGenre()); core::dbus::encode_argument(w, album.getArtFile()); core::dbus::encode_argument(w, album.getHasThumbnail()); out.close_structure(std::move(w)); } void Codec::decode_argument(Message::Reader &in, Album &album) { auto r = in.pop_structure(); string title, artist, date, genre, art_file; bool has_thumbnail; r >> title >> artist >> date >> genre >> art_file >> has_thumbnail; album = Album(title, artist, date, genre, art_file, has_thumbnail); } void Codec::encode_argument(Message::Writer &out, const Filter &filter) { auto w = out.open_array(core::dbus::types::Signature("{sv}")); if (filter.hasArtist()) { w.close_dict_entry( w.open_dict_entry() << string("artist") << Variant::encode(filter.getArtist())); } if (filter.hasAlbum()) { w.close_dict_entry( w.open_dict_entry() << string("album") << Variant::encode(filter.getAlbum())); } if (filter.hasAlbumArtist()) { w.close_dict_entry( w.open_dict_entry() << string("album_artist") << Variant::encode(filter.getAlbumArtist())); } if (filter.hasGenre()) { w.close_dict_entry( w.open_dict_entry() << string("genre") << Variant::encode(filter.getGenre())); } w.close_dict_entry( w.open_dict_entry() << string("offset") << Variant::encode((int32_t)filter.getOffset())); w.close_dict_entry( w.open_dict_entry() << string("limit") << Variant::encode((int32_t)filter.getLimit())); w.close_dict_entry( w.open_dict_entry() << string("order") << Variant::encode(static_cast(filter.getOrder()))); w.close_dict_entry( w.open_dict_entry() << string("reverse") << Variant::encode(filter.getReverse())); out.close_array(std::move(w)); } void Codec::decode_argument(Message::Reader &in, Filter &filter) { auto r = in.pop_array(); filter.clear(); while (r.type() != ArgumentType::invalid) { string key; Variant value; r.pop_dict_entry() >> key >> value; if (key == "artist") { filter.setArtist(value.as()); } else if (key == "album") { filter.setAlbum(value.as()); } else if (key == "album_artist") { filter.setAlbumArtist(value.as()); } else if (key == "genre") { filter.setGenre(value.as()); } else if (key == "offset") { filter.setOffset(value.as()); } else if (key == "limit") { filter.setLimit(value.as()); } else if (key == "order") { filter.setOrder(static_cast(value.as())); } else if (key == "reverse") { filter.setReverse(value.as()); } } } mediascanner2-0.111+16.04.20160317/src/ms-dbus/service-skeleton.hh0000644000015600001650000000237612672421600024547 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_DBUS_SERVICE_SKELETON_HH #define MEDIASCANNER_DBUS_SERVICE_SKELETON_HH #include #include #include #include "service.hh" namespace mediascanner { class MediaStore; namespace dbus { class ServiceSkeleton : public core::dbus::Skeleton { public: ServiceSkeleton(core::dbus::Bus::Ptr bus, std::shared_ptr store); ~ServiceSkeleton(); void run(); void stop(); private: struct Private; std::unique_ptr p; }; } } #endif mediascanner2-0.111+16.04.20160317/src/ms-dbus/service-stub.hh0000644000015600001650000000453212672421600023674 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_DBUS_SERVICE_STUB_HH #define MEDIASCANNER_DBUS_SERVICE_STUB_HH #include #include #include #include #include #include #include "service.hh" namespace mediascanner { class Album; class Filter; class MediaFile; namespace dbus { class ServiceStub : public core::dbus::Stub, public virtual MediaStoreBase { public: explicit ServiceStub(core::dbus::Bus::Ptr bus); virtual ~ServiceStub(); virtual MediaFile lookup(const std::string &filename) const override; virtual std::vector query(const std::string &q, MediaType type, const Filter &filter) const override; virtual std::vector queryAlbums(const std::string &core_term, const Filter &filter) const override; virtual std::vector queryArtists(const std::string &q, const Filter &filter) const override; virtual std::vector getAlbumSongs(const Album& album) const override; virtual std::string getETag(const std::string &filename) const override; virtual std::vector listSongs(const Filter &filter) const override; virtual std::vector listAlbums(const Filter &filter) const override; virtual std::vector listArtists(const Filter &filter) const override; virtual std::vector listAlbumArtists(const Filter &filter) const override; virtual std::vector listGenres(const Filter &filter) const override; virtual bool hasMedia(MediaType type) const override; private: struct Private; std::unique_ptr p; }; } } #endif mediascanner2-0.111+16.04.20160317/src/ms-dbus/CMakeLists.txt0000644000015600001650000000164712672421600023504 0ustar pbuserpbgroup00000000000000include_directories(..) add_definitions(${MEDIASCANNER_CFLAGS} ${DBUSCPP_CFLAGS} ${APPARMOR_CFLAGS}) add_library(ms-dbus STATIC dbus-codec.cc service-skeleton.cc service-stub.cc ) target_link_libraries(ms-dbus mediascanner ${DBUSCPP_LDFLAGS} ${APPARMOR_LDFLAGS}) # Compile with -fPIC, since this code is linked into the QML plugin. set_target_properties(ms-dbus PROPERTIES COMPILE_FLAGS "-fPIC ") add_executable(mediascanner-dbus main.cc ) target_link_libraries(mediascanner-dbus ms-dbus) set_target_properties(mediascanner-dbus PROPERTIES OUTPUT_NAME "mediascanner-dbus-2.0") install( TARGETS mediascanner-dbus RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/mediascanner-2.0 ) configure_file( com.canonical.MediaScanner2.service.in com.canonical.MediaScanner2.service) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.MediaScanner2.service DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services ) mediascanner2-0.111+16.04.20160317/src/ms-dbus/service-skeleton.cc0000644000015600001650000003344712672421600024540 0ustar pbuserpbgroup00000000000000#include "service-skeleton.hh" #include #include #include #include #include #include #include #include #include #include "dbus-interface.hh" #include "dbus-codec.hh" using core::dbus::Message; namespace mediascanner { namespace dbus { struct Apparmor { static const std::string &name() { static std::string s = "org.freedesktop.DBus"; return s; } struct GetConnectionAppArmorSecurityContext { typedef Apparmor Interface; static const std::string &name() { static std::string s = "GetConnectionAppArmorSecurityContext"; return s; } static const std::chrono::milliseconds default_timeout() { return std::chrono::seconds{1}; } }; }; struct ServiceSkeleton::Private { ServiceSkeleton *impl; std::shared_ptr store; core::dbus::Object::Ptr object; Private(ServiceSkeleton *impl, std::shared_ptr store) : impl(impl), store(store), object(impl->access_service()->add_object_for_path( core::dbus::traits::Service::object_path())) { object->install_method_handler( std::bind( &Private::handle_lookup, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_query, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_query_albums, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_query_artists, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_get_album_songs, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_get_etag, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_list_songs, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_list_albums, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_list_artists, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_list_album_artists, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_list_genres, this, std::placeholders::_1)); object->install_method_handler( std::bind( &Private::handle_has_media, this, std::placeholders::_1)); } std::string get_client_apparmor_context(const Message::Ptr &message) { if (!aa_is_enabled()) { return "unconfined"; } auto service = core::dbus::Service::use_service( impl->access_bus(), "org.freedesktop.DBus"); auto obj = service->object_for_path( core::dbus::types::ObjectPath("/org/freedesktop/DBus")); core::dbus::Result result; try { result = obj->invoke_method_synchronously(message->sender()); } catch (const std::runtime_error &e) { fprintf(stderr, "Error getting apparmor context: %s\n", e.what()); return std::string(); } if (result.is_error()) { fprintf(stderr, "Error getting apparmor context: %s\n", result.error().print().c_str()); return std::string(); } return result.value(); } bool does_client_have_access(const std::string &context, MediaType type) { if (context.empty()) { // Deny access if we don't have a context return false; } if (context == "unconfined") { // Unconfined return true; } auto pos = context.find_first_of('_'); if (pos == std::string::npos) { fprintf(stderr, "Badly formed AppArmor context: %s\n", context.c_str()); return false; } const std::string pkgname = context.substr(0, pos); // TODO: when the trust store lands, check it to see if this // app can access the index. if (type == AudioMedia && pkgname == "com.ubuntu.music") { return true; } return false; } bool check_access(const Message::Ptr &message, MediaType type) { const std::string context = get_client_apparmor_context(message); bool have_access = does_client_have_access(context, type); if (!have_access) { auto reply = Message::make_error( message, MediaStoreInterface::Errors::Unauthorized::name(), "Unauthorized"); impl->access_bus()->send(reply); } return have_access; } void handle_lookup(const Message::Ptr &message) { if (!check_access(message, AllMedia)) return; std::string filename; message->reader() >> filename; Message::Ptr reply; try { MediaFile file = store->lookup(filename); reply = Message::make_method_return(message); reply->writer() << file; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_query(const Message::Ptr &message) { std::string query; int32_t type; Filter filter; message->reader() >> query >> type >> filter; if (!check_access(message, (MediaType)type)) return; Message::Ptr reply; try { auto results = store->query(query, (MediaType)type, filter); reply = Message::make_method_return(message); reply->writer() << results; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_query_albums(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; std::string query; Filter filter; message->reader() >> query >> filter; Message::Ptr reply; try { auto albums = store->queryAlbums(query, filter); reply = Message::make_method_return(message); reply->writer() << albums; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_query_artists(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; std::string query; Filter filter; message->reader() >> query >> filter; Message::Ptr reply; try { auto artists = store->queryArtists(query, filter); reply = Message::make_method_return(message); reply->writer() << artists; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_get_album_songs(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Album album("", ""); message->reader() >> album; Message::Ptr reply; try { auto results = store->getAlbumSongs(album); reply = Message::make_method_return(message); reply->writer() << results; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_get_etag(const Message::Ptr &message) { if (!check_access(message, AllMedia)) return; std::string filename; message->reader() >> filename; Message::Ptr reply; try { std::string etag = store->getETag(filename); reply = Message::make_method_return(message); reply->writer() << etag; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_list_songs(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Filter filter; message->reader() >> filter; Message::Ptr reply; try { auto results = store->listSongs(filter); reply = Message::make_method_return(message); reply->writer() << results; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_list_albums(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Filter filter; message->reader() >> filter; Message::Ptr reply; try { auto albums = store->listAlbums(filter); reply = Message::make_method_return(message); reply->writer() << albums; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_list_artists(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Filter filter; message->reader() >> filter; Message::Ptr reply; try { auto artists = store->listArtists(filter); reply = Message::make_method_return(message); reply->writer() << artists; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_list_album_artists(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Filter filter; message->reader() >> filter; Message::Ptr reply; try { auto artists = store->listAlbumArtists(filter); reply = Message::make_method_return(message); reply->writer() << artists; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_list_genres(const Message::Ptr &message) { if (!check_access(message, AudioMedia)) return; Filter filter; message->reader() >> filter; Message::Ptr reply; try { auto genres = store->listGenres(filter); reply = Message::make_method_return(message); reply->writer() << genres; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } void handle_has_media(const Message::Ptr &message) { int32_t type; message->reader() >> type; if (!check_access(message, static_cast(type))) return; Message::Ptr reply; try { bool result = store->hasMedia(static_cast(type)); reply = Message::make_method_return(message); reply->writer() << result; } catch (const std::exception &e) { reply = Message::make_error( message, MediaStoreInterface::Errors::Error::name(), e.what()); } impl->access_bus()->send(reply); } }; ServiceSkeleton::ServiceSkeleton(core::dbus::Bus::Ptr bus, std::shared_ptr store) : core::dbus::Skeleton(bus), p(new Private(this, store)) { } ServiceSkeleton::~ServiceSkeleton() { } void ServiceSkeleton::run() { access_bus()->run(); } void ServiceSkeleton::stop() { access_bus()->stop(); } } } mediascanner2-0.111+16.04.20160317/src/qml/0000755000015600001650000000000012672422030020151 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/0000755000015600001650000000000012672422030021433 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/0000755000015600001650000000000012672422030024261 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaStoreWrapper.cc0000644000015600001650000000657612672421600030205 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaStoreWrapper.hh" #include #include #include #include #include #include #include #include #include #include using namespace mediascanner::qml; static core::dbus::Bus::Ptr the_session_bus() { static core::dbus::Bus::Ptr bus = std::make_shared( core::dbus::WellKnownBus::session); static core::dbus::Executor::Ptr executor = core::dbus::asio::make_executor(bus); static std::once_flag once; std::call_once(once, []() {bus->install_executor(executor);}); return bus; } MediaStoreWrapper::MediaStoreWrapper(QObject *parent) : QObject(parent) { const char *use_dbus = getenv("MEDIASCANNER_USE_DBUS"); try { if (use_dbus != nullptr && !strcmp(use_dbus, "1")) { store.reset(new mediascanner::dbus::ServiceStub(the_session_bus())); } else { store.reset(new mediascanner::MediaStore(MS_READ_ONLY)); } } catch (const std::exception &e) { qWarning() << "Could not initialise media store:" << e.what(); } QDBusConnection::sessionBus().connect( "com.canonical.MediaScanner2.Daemon", "/com/canonical/unity/scopes", "com.canonical.unity.scopes", "InvalidateResults", QStringList{"mediascanner-music"}, "s", this, SLOT(resultsInvalidated())); } QList MediaStoreWrapper::query(const QString &q, MediaType type) { if (!store) { qWarning() << "query() called on invalid MediaStore"; return QList(); } QList result; try { for (const auto &media : store->query(q.toStdString(), static_cast(type), mediascanner::Filter())) { auto wrapper = new MediaFileWrapper(media); QQmlEngine::setObjectOwnership(wrapper, QQmlEngine::JavaScriptOwnership); result.append(wrapper); } } catch (const std::exception &e) { qWarning() << "Failed to retrieve query results:" << e.what(); } return result; } MediaFileWrapper *MediaStoreWrapper::lookup(const QString &filename) { if (!store) { qWarning() << "lookup() called on invalid MediaStore"; return nullptr; } MediaFileWrapper *wrapper; try { wrapper = new MediaFileWrapper(store->lookup(filename.toStdString())); } catch (std::exception &e) { return nullptr; } QQmlEngine::setObjectOwnership(wrapper, QQmlEngine::JavaScriptOwnership); return wrapper; } void MediaStoreWrapper::resultsInvalidated() { Q_EMIT updated(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/SongsModel.cc0000644000015600001650000000725512672421600026655 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "SongsModel.hh" #include using namespace mediascanner::qml; SongsModel::SongsModel(QObject *parent) : MediaFileModelBase(parent) { } QVariant SongsModel::getArtist() { if (!filter.hasArtist()) return QVariant(); return QString::fromStdString(filter.getArtist()); } void SongsModel::setArtist(const QVariant artist) { if (artist.isNull()) { if (filter.hasArtist()) { filter.unsetArtist(); invalidate(); } } else { const std::string std_artist = artist.value().toStdString(); if (!filter.hasArtist() || filter.getArtist() != std_artist) { filter.setArtist(std_artist); invalidate(); } } } QVariant SongsModel::getAlbum() { if (!filter.hasAlbum()) return QVariant(); return QString::fromStdString(filter.getAlbum()); } void SongsModel::setAlbum(const QVariant album) { if (album.isNull()) { if (filter.hasAlbum()) { filter.unsetAlbum(); invalidate(); } } else { const std::string std_album = album.value().toStdString(); if (!filter.hasAlbum() || filter.getAlbum() != std_album) { filter.setAlbum(std_album); invalidate(); } } } QVariant SongsModel::getAlbumArtist() { if (!filter.hasAlbumArtist()) return QVariant(); return QString::fromStdString(filter.getAlbumArtist()); } void SongsModel::setAlbumArtist(const QVariant album_artist) { if (album_artist.isNull()) { if (filter.hasAlbumArtist()) { filter.unsetAlbumArtist(); invalidate(); } } else { const std::string std_album_artist = album_artist.value().toStdString(); if (!filter.hasAlbumArtist() || filter.getAlbumArtist() != std_album_artist) { filter.setAlbumArtist(std_album_artist); invalidate(); } } } QVariant SongsModel::getGenre() { if (!filter.hasGenre()) return QVariant(); return QString::fromStdString(filter.getGenre()); } void SongsModel::setGenre(const QVariant genre) { if (genre.isNull()) { if (filter.hasGenre()) { filter.unsetGenre(); invalidate(); } } else { const std::string std_genre = genre.value().toStdString(); if (!filter.hasGenre() || filter.getGenre() != std_genre) { filter.setGenre(std_genre); invalidate(); } } } int SongsModel::getLimit() { return -1; } void SongsModel::setLimit(int) { qWarning() << "Setting limit on SongsModel is deprecated"; } std::unique_ptr SongsModel::retrieveRows(std::shared_ptr store, int limit, int offset) const { auto limit_filter = filter; limit_filter.setLimit(limit); limit_filter.setOffset(offset); return std::unique_ptr( new MediaFileRowData(store->listSongs(limit_filter))); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/AlbumsModel.hh0000644000015600001650000000337312672421600027016 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_ALBUMSMODEL_H #define MEDIASCANNER_QML_ALBUMSMODEL_H #include #include #include "MediaStoreWrapper.hh" #include "AlbumModelBase.hh" namespace mediascanner { namespace qml { class AlbumsModel : public AlbumModelBase { Q_OBJECT Q_PROPERTY(QVariant artist READ getArtist WRITE setArtist) Q_PROPERTY(QVariant albumArtist READ getAlbumArtist WRITE setAlbumArtist) Q_PROPERTY(QVariant genre READ getGenre WRITE setGenre) Q_PROPERTY(int limit READ getLimit WRITE setLimit) public: explicit AlbumsModel(QObject *parent=0); std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const override; protected: QVariant getArtist(); void setArtist(const QVariant artist); QVariant getAlbumArtist(); void setAlbumArtist(const QVariant album_artist); QVariant getGenre(); void setGenre(const QVariant genre); int getLimit(); void setLimit(int limit); private: Filter filter; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/ArtistsModel.cc0000644000015600001650000000670312672421600027212 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "ArtistsModel.hh" #include using namespace mediascanner::qml; ArtistsModel::ArtistsModel(QObject *parent) : StreamingModel(parent), album_artists(false) { roles[Roles::RoleArtist] = "artist"; } int ArtistsModel::rowCount(const QModelIndex &) const { return results.size(); } QVariant ArtistsModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= (ptrdiff_t)results.size()) { return QVariant(); } switch (role) { case RoleArtist: return QString::fromStdString(results[index.row()]); default: return QVariant(); } } QHash ArtistsModel::roleNames() const { return roles; } bool ArtistsModel::getAlbumArtists() { return album_artists; } void ArtistsModel::setAlbumArtists(bool album_artists) { if (this->album_artists != album_artists) { this->album_artists = album_artists; invalidate(); } } QVariant ArtistsModel::getGenre() { if (!filter.hasGenre()) return QVariant(); return QString::fromStdString(filter.getGenre()); } void ArtistsModel::setGenre(const QVariant genre) { if (genre.isNull()) { if (filter.hasGenre()) { filter.unsetGenre(); invalidate(); } } else { const std::string std_genre = genre.value().toStdString(); if (!filter.hasGenre() || filter.getGenre() != std_genre) { filter.setGenre(std_genre); invalidate(); } } } int ArtistsModel::getLimit() { return -1; } void ArtistsModel::setLimit(int) { qWarning() << "Setting limit on ArtistsModel is deprecated"; } namespace { class ArtistRowData : public StreamingModel::RowData { public: ArtistRowData(std::vector &&rows) : rows(std::move(rows)) {} ~ArtistRowData() {} size_t size() const override { return rows.size(); } std::vector rows; }; } std::unique_ptr ArtistsModel::retrieveRows(std::shared_ptr store, int limit, int offset) const { auto limit_filter = filter; limit_filter.setLimit(limit); limit_filter.setOffset(offset); std::vector artists; if (album_artists) { artists = store->listAlbumArtists(limit_filter); } else { artists = store->listArtists(limit_filter); } return std::unique_ptr( new ArtistRowData(std::move(artists))); } void ArtistsModel::appendRows(std::unique_ptr &&row_data) { ArtistRowData *data = static_cast(row_data.get()); std::move(data->rows.begin(), data->rows.end(), std::back_inserter(results)); } void ArtistsModel::clearBacking() { results.clear(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/StreamingModel.hh0000644000015600001650000000536412672421600027526 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef MEDIASCANNER_QML_STREAMINGMODEL_H #define MEDIASCANNER_QML_STREAMINGMODEL_H #include #include #include #include #include #include "MediaStoreWrapper.hh" namespace mediascanner { namespace qml { class StreamingModel : public QAbstractListModel { Q_OBJECT Q_ENUMS(ModelStatus) Q_PROPERTY(mediascanner::qml::MediaStoreWrapper* store READ getStore WRITE setStore) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(int rowCount READ rowCount NOTIFY countChanged) Q_PROPERTY(ModelStatus status READ getStatus NOTIFY statusChanged) public: enum ModelStatus { Ready, Loading, Error }; explicit StreamingModel(QObject *parent=nullptr); virtual ~StreamingModel(); Q_INVOKABLE QVariant get(int row, int role) const; bool event(QEvent *e) override; bool shouldWorkerStop() const noexcept { return stopflag.load(std::memory_order_consume); } // Subclasses should implement the following, along with rowCount and data class RowData { public: virtual ~RowData() {} virtual size_t size() const = 0; }; virtual std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const = 0; virtual void appendRows(std::unique_ptr &&row_data) = 0; virtual void clearBacking() = 0; protected: MediaStoreWrapper *getStore() const; void setStore(MediaStoreWrapper *store); ModelStatus getStatus() const; void setStatus(ModelStatus status); private: void updateModel(); void setWorkerStop(bool new_stop_status) noexcept { stopflag.store(new_stop_status, std::memory_order_release); } QPointer store; QFuture query_future; int generation; std::atomic stopflag; ModelStatus status; Q_SIGNALS: void countChanged(); void statusChanged(); // This next signal is here for backwards compatibility void filled(); public Q_SLOTS: void invalidate(); }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/AlbumsModel.cc0000644000015600001650000000617512672421600027007 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "AlbumsModel.hh" #include using namespace mediascanner::qml; AlbumsModel::AlbumsModel(QObject *parent) : AlbumModelBase(parent) { } QVariant AlbumsModel::getArtist() { if (!filter.hasArtist()) return QVariant(); return QString::fromStdString(filter.getArtist()); } void AlbumsModel::setArtist(const QVariant artist) { if (artist.isNull()) { if (filter.hasArtist()) { filter.unsetArtist(); invalidate(); } } else { const std::string std_artist = artist.value().toStdString(); if (!filter.hasArtist() || filter.getArtist() != std_artist) { filter.setArtist(std_artist); invalidate(); } } } QVariant AlbumsModel::getAlbumArtist() { if (!filter.hasAlbumArtist()) return QVariant(); return QString::fromStdString(filter.getAlbumArtist()); } void AlbumsModel::setAlbumArtist(const QVariant album_artist) { if (album_artist.isNull()) { if (filter.hasAlbumArtist()) { filter.unsetAlbumArtist(); invalidate(); } } else { const std::string std_album_artist = album_artist.value().toStdString(); if (!filter.hasAlbumArtist() || filter.getAlbumArtist() != std_album_artist) { filter.setAlbumArtist(std_album_artist); invalidate(); } } } QVariant AlbumsModel::getGenre() { if (!filter.hasGenre()) return QVariant(); return QString::fromStdString(filter.getGenre()); } void AlbumsModel::setGenre(const QVariant genre) { if (genre.isNull()) { if (filter.hasGenre()) { filter.unsetGenre(); invalidate(); } } else { const std::string std_genre = genre.value().toStdString(); if (!filter.hasGenre() || filter.getGenre() != std_genre) { filter.setGenre(std_genre); invalidate(); } } } int AlbumsModel::getLimit() { return -1; } void AlbumsModel::setLimit(int) { qWarning() << "Setting limit on AlbumsModel is deprecated"; } std::unique_ptr AlbumsModel::retrieveRows(std::shared_ptr store, int limit, int offset) const { auto limit_filter = filter; limit_filter.setLimit(limit); limit_filter.setOffset(offset); return std::unique_ptr( new AlbumRowData(store->listAlbums(limit_filter))); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/GenresModel.hh0000644000015600001650000000347612672421600027022 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_GENRESMODEL_H #define MEDIASCANNER_QML_GENRESMODEL_H #include #include #include #include #include #include "StreamingModel.hh" namespace mediascanner { namespace qml { class GenresModel : public StreamingModel { Q_OBJECT Q_PROPERTY(int limit READ getLimit WRITE setLimit) Q_ENUMS(Roles) public: enum Roles { RoleGenre, }; explicit GenresModel(QObject *parent = 0); int rowCount(const QModelIndex &parent=QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const override; void appendRows(std::unique_ptr &&row_data) override; void clearBacking() override; protected: QHash roleNames() const override; int getLimit(); void setLimit(int limit); private: QHash roles; std::vector results; mediascanner::Filter filter; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/plugin.cc0000644000015600001650000000300212672421600026063 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "plugin.hh" #include "MediaFileWrapper.hh" #include "MediaStoreWrapper.hh" #include "AlbumsModel.hh" #include "ArtistsModel.hh" #include "GenresModel.hh" #include "SongsModel.hh" #include "SongsSearchModel.hh" using namespace mediascanner::qml; void MediaScannerPlugin::registerTypes(const char *uri) { qmlRegisterType(uri, 0, 1, "MediaStore"); qmlRegisterUncreatableType(uri, 0, 1, "MediaFile", "Use a MediaStore to retrieve MediaFiles"); qmlRegisterType(uri, 0, 1, "AlbumsModel"); qmlRegisterType(uri, 0, 1, "ArtistsModel"); qmlRegisterType(uri, 0, 1, "GenresModel"); qmlRegisterType(uri, 0, 1, "SongsModel"); qmlRegisterType(uri, 0, 1, "SongsSearchModel"); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaFileModelBase.hh0000644000015600001650000000445412672421600030206 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_MEDIAFILEMODELBASE_H #define MEDIASCANNER_QML_MEDIAFILEMODELBASE_H #include #include #include #include "StreamingModel.hh" namespace mediascanner { namespace qml { class MediaFileModelBase : public StreamingModel { Q_OBJECT Q_ENUMS(Roles) public: enum Roles { RoleModelData, RoleFilename, RoleUri, RoleContentType, RoleETag, RoleTitle, RoleAuthor, RoleAlbum, RoleAlbumArtist, RoleDate, RoleGenre, RoleDiscNumber, RoleTrackNumber, RoleDuration, RoleWidth, RoleHeight, RoleLatitude, RoleLongitude, RoleArt, }; explicit MediaFileModelBase(QObject *parent = 0); int rowCount(const QModelIndex &parent=QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; void appendRows(std::unique_ptr &&row_data) override; void clearBacking() override; class MediaFileRowData : public RowData { public: MediaFileRowData(std::vector &&rows) : rows(std::move(rows)) {} ~MediaFileRowData() {} size_t size() const override { return rows.size(); } std::vector rows; }; protected: QHash roleNames() const override; void updateResults(const std::vector &results); private: QHash roles; std::vector results; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaFileWrapper.cc0000644000015600001650000000526712672421600027764 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaFileWrapper.hh" using namespace mediascanner::qml; MediaFileWrapper::MediaFileWrapper(const mediascanner::MediaFile &media, QObject *parent) : QObject(parent), media(media) { } QString MediaFileWrapper::filename() const { return QString::fromStdString(media.getFileName()); } QString MediaFileWrapper::uri() const { return QString::fromStdString(media.getUri()); } QString MediaFileWrapper::contentType() const { return QString::fromStdString(media.getContentType()); } QString MediaFileWrapper::eTag() const { return QString::fromStdString(media.getETag()); } QString MediaFileWrapper::title() const { return QString::fromStdString(media.getTitle()); } QString MediaFileWrapper::author() const { return QString::fromStdString(media.getAuthor()); } QString MediaFileWrapper::album() const { return QString::fromStdString(media.getAlbum()); } QString MediaFileWrapper::albumArtist() const { return QString::fromStdString(media.getAlbumArtist()); } QString MediaFileWrapper::date() const { return QString::fromStdString(media.getDate()); } QString MediaFileWrapper::genre() const { return QString::fromStdString(media.getGenre()); } int MediaFileWrapper::discNumber() const { return media.getDiscNumber(); } int MediaFileWrapper::trackNumber() const { return media.getTrackNumber(); } int MediaFileWrapper::duration() const { return media.getDuration(); } int MediaFileWrapper::width() const { return media.getWidth(); } int MediaFileWrapper::height() const { return media.getHeight(); } double MediaFileWrapper::latitude() const { return media.getLatitude(); } double MediaFileWrapper::longitude() const { return media.getLongitude(); } bool MediaFileWrapper::hasThumbnail() const { return media.getHasThumbnail(); } uint64_t MediaFileWrapper::modificationTime() const { return media.getModificationTime(); } QString MediaFileWrapper::art() const { return QString::fromStdString(media.getArtUri()); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/StreamingModel.cc0000644000015600001650000001177212672421600027514 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "StreamingModel.hh" #include #include #include #include #include #include using namespace mediascanner::qml; namespace { const int BATCH_SIZE = 200; class AdditionEvent : public QEvent { private: std::unique_ptr rows; bool error = false; int generation; public: AdditionEvent(int generation) : QEvent(AdditionEvent::additionEventType()), generation(generation) { } void setRows(std::unique_ptr &&r) { rows = std::move(r); } void setError(bool e) { error = e; } std::unique_ptr& getRows() { return rows; } bool getError() const { return error; } int getGeneration() const { return generation; } static QEvent::Type additionEventType() { static QEvent::Type type = static_cast(QEvent::registerEventType()); return type; } }; void runQuery(int generation, StreamingModel *model, std::shared_ptr store) { if (!store) { return; } int offset = 0; int cursize; do { if(model->shouldWorkerStop()) { return; } QScopedPointer e(new AdditionEvent(generation)); try { e->setRows(model->retrieveRows(store, BATCH_SIZE, offset)); } catch (const std::exception &exc) { qWarning() << "Failed to retrieve rows:" << exc.what(); e->setError(true); return; } cursize = e->getRows()->size(); if (model->shouldWorkerStop()) { return; } QCoreApplication::instance()->postEvent(model, e.take()); offset += cursize; } while(cursize >= BATCH_SIZE); } } StreamingModel::StreamingModel(QObject *parent) : QAbstractListModel(parent), generation(0), status(Ready) { } StreamingModel::~StreamingModel() { setWorkerStop(true); try { query_future.waitForFinished(); } catch(...) { qWarning() << "Unknown error when shutting down worker thread.\n"; } } void StreamingModel::updateModel() { if (store.isNull() || !store->store) { query_future = QFuture(); setStatus(Ready); return; } setStatus(Loading); setWorkerStop(false); query_future = QtConcurrent::run(runQuery, ++generation, this, store->store); } QVariant StreamingModel::get(int row, int role) const { return data(index(row, 0), role); } bool StreamingModel::event(QEvent *e) { if (e->type() != AdditionEvent::additionEventType()) { return QObject::event(e); } AdditionEvent *ae = dynamic_cast(e); assert(ae); // Old results may be in Qt's event queue and get delivered // after we have moved to a new query. Ignore them. if(ae->getGeneration() != generation) { return true; } if (ae->getError()) { setStatus(Error); return true; } auto &newrows = ae->getRows(); bool lastBatch = newrows->size() < BATCH_SIZE; beginInsertRows(QModelIndex(), rowCount(), rowCount()+newrows->size()-1); appendRows(std::move(newrows)); endInsertRows(); Q_EMIT countChanged(); if (lastBatch) { setStatus(Ready); } return true; } MediaStoreWrapper *StreamingModel::getStore() const { return store.data(); } void StreamingModel::setStore(MediaStoreWrapper *store) { if (this->store != store) { if (this->store) { disconnect(this->store, &MediaStoreWrapper::updated, this, &StreamingModel::invalidate); } this->store = store; if (store) { connect(this->store, &MediaStoreWrapper::updated, this, &StreamingModel::invalidate); } invalidate(); } } StreamingModel::ModelStatus StreamingModel::getStatus() const { return status; } void StreamingModel::setStatus(StreamingModel::ModelStatus status) { this->status = status; Q_EMIT statusChanged(); if (status == Ready) { Q_EMIT filled(); } } void StreamingModel::invalidate() { setWorkerStop(true); query_future.waitForFinished(); beginResetModel(); clearBacking(); endResetModel(); Q_EMIT countChanged(); updateModel(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaFileModelBase.cc0000644000015600001650000000746712672421600030203 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaFileModelBase.hh" #include "MediaFileWrapper.hh" using namespace mediascanner::qml; MediaFileModelBase::MediaFileModelBase(QObject *parent) : StreamingModel(parent) { roles[Roles::RoleModelData] = "modelData"; roles[Roles::RoleFilename] = "filename"; roles[Roles::RoleUri] = "uri"; roles[Roles::RoleContentType] = "contentType"; roles[Roles::RoleETag] = "eTag"; roles[Roles::RoleTitle] = "title"; roles[Roles::RoleAuthor] = "author"; roles[Roles::RoleAlbum] = "album"; roles[Roles::RoleAlbumArtist] = "albumArtist"; roles[Roles::RoleDate] = "date"; roles[Roles::RoleGenre] = "genre"; roles[Roles::RoleDiscNumber] = "discNumber"; roles[Roles::RoleTrackNumber] = "trackNumber"; roles[Roles::RoleDuration] = "duration"; roles[Roles::RoleWidth] = "width"; roles[Roles::RoleHeight] = "height"; roles[Roles::RoleLatitude] = "latitude"; roles[Roles::RoleLongitude] = "longitude"; roles[Roles::RoleArt] = "art"; } int MediaFileModelBase::rowCount(const QModelIndex &) const { return results.size(); } QVariant MediaFileModelBase::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= (ptrdiff_t)results.size()) { return QVariant(); } const mediascanner::MediaFile &media = results[index.row()]; switch (role) { case RoleModelData: return QVariant::fromValue(new MediaFileWrapper(media)); case RoleFilename: return QString::fromStdString(media.getFileName()); case RoleUri: return QString::fromStdString(media.getUri()); case RoleContentType: return QString::fromStdString(media.getContentType()); case RoleETag: return QString::fromStdString(media.getETag()); case RoleTitle: return QString::fromStdString(media.getTitle()); case RoleAuthor: return QString::fromStdString(media.getAuthor()); case RoleAlbum: return QString::fromStdString(media.getAlbum()); case RoleAlbumArtist: return QString::fromStdString(media.getAlbumArtist()); case RoleDate: return QString::fromStdString(media.getDate()); case RoleGenre: return QString::fromStdString(media.getGenre()); case RoleDiscNumber: return media.getDiscNumber(); case RoleTrackNumber: return media.getTrackNumber(); case RoleDuration: return media.getDuration(); case RoleWidth: return media.getWidth(); case RoleHeight: return media.getHeight(); case RoleLatitude: return media.getLatitude(); case RoleLongitude: return media.getLongitude(); case RoleArt: return QString::fromStdString(media.getArtUri()); default: return QVariant(); } } QHash MediaFileModelBase::roleNames() const { return roles; } void MediaFileModelBase::appendRows(std::unique_ptr &&row_data) { MediaFileRowData *data = static_cast(row_data.get()); std::move(data->rows.begin(), data->rows.end(), std::back_inserter(results)); } void MediaFileModelBase::clearBacking() { results.clear(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/GenresModel.cc0000644000015600001650000000465512672421600027010 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "GenresModel.hh" #include using namespace mediascanner::qml; GenresModel::GenresModel(QObject *parent) : StreamingModel(parent) { roles[Roles::RoleGenre] = "genre"; } int GenresModel::rowCount(const QModelIndex &) const { return results.size(); } QVariant GenresModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= (ptrdiff_t)results.size()) { return QVariant(); } switch (role) { case RoleGenre: return QString::fromStdString(results[index.row()]); default: return QVariant(); } } QHash GenresModel::roleNames() const { return roles; } int GenresModel::getLimit() { return -1; } void GenresModel::setLimit(int) { qWarning() << "Setting limit on GenresModel is deprecated"; } namespace { class GenreRowData : public StreamingModel::RowData { public: GenreRowData(std::vector &&rows) : rows(std::move(rows)) {} ~GenreRowData() {} size_t size() const override { return rows.size(); } std::vector rows; }; } std::unique_ptr GenresModel::retrieveRows(std::shared_ptr store, int limit, int offset) const { auto limit_filter = filter; limit_filter.setLimit(limit); limit_filter.setOffset(offset); return std::unique_ptr( new GenreRowData(store->listGenres(limit_filter))); } void GenresModel::appendRows(std::unique_ptr &&row_data) { GenreRowData *data = static_cast(row_data.get()); std::move(data->rows.begin(), data->rows.end(), std::back_inserter(results)); } void GenresModel::clearBacking() { results.clear(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaStoreWrapper.hh0000644000015600001650000000317512672421600030207 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_MEDIASTOREWRAPPER_H #define MEDIASCANNER_QML_MEDIASTOREWRAPPER_H #include #include #include #include #include #include "MediaFileWrapper.hh" namespace mediascanner { namespace qml { class MediaStoreWrapper : public QObject { Q_OBJECT Q_ENUMS(MediaType) public: enum MediaType { AudioMedia = mediascanner::AudioMedia, VideoMedia = mediascanner::VideoMedia, ImageMedia = mediascanner::ImageMedia, AllMedia = mediascanner::AllMedia, }; MediaStoreWrapper(QObject *parent=0); Q_INVOKABLE QList query(const QString &q, MediaType type); Q_INVOKABLE mediascanner::qml::MediaFileWrapper *lookup(const QString &filename); std::shared_ptr store; Q_SIGNALS: void updated(); private Q_SLOTS: void resultsInvalidated(); }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/AlbumModelBase.hh0000644000015600001650000000360112672421600027420 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_ALBUMMODELBASE_H #define MEDIASCANNER_QML_ALBUMMODELBASE_H #include #include #include #include "StreamingModel.hh" namespace mediascanner { namespace qml { class AlbumModelBase : public StreamingModel { Q_OBJECT Q_ENUMS(Roles) public: enum Roles { RoleTitle, RoleArtist, RoleDate, RoleGenre, RoleArt, }; explicit AlbumModelBase(QObject *parent = 0); int rowCount(const QModelIndex &parent=QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; void appendRows(std::unique_ptr &&row_data) override; void clearBacking() override; class AlbumRowData : public RowData { public: AlbumRowData(std::vector &&rows) : rows(std::move(rows)) {} ~AlbumRowData() {} size_t size() const override { return rows.size(); } std::vector rows; }; protected: QHash roleNames() const override; private: QHash roles; std::vector results; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/plugin.hh0000644000015600001650000000202312672421600026077 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef PLUGIN_HH_ #define PLUGIN_HH_ #include namespace mediascanner { namespace qml { class MediaScannerPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: virtual void registerTypes(const char *uri) override; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/MediaFileWrapper.hh0000644000015600001650000000533612672421600027773 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_MEDIAFILEWRAPPER_H #define MEDIASCANNER_QML_MEDIAFILEWRAPPER_H #include #include #include namespace mediascanner { namespace qml { class MediaFileWrapper : public QObject { Q_OBJECT Q_PROPERTY(QString filename READ filename CONSTANT) Q_PROPERTY(QString uri READ uri CONSTANT) Q_PROPERTY(QString contentType READ contentType CONSTANT) Q_PROPERTY(QString eTag READ eTag CONSTANT) Q_PROPERTY(QString title READ title CONSTANT) Q_PROPERTY(QString author READ author CONSTANT) Q_PROPERTY(QString album READ album CONSTANT) Q_PROPERTY(QString albumArtist READ albumArtist CONSTANT) Q_PROPERTY(QString date READ date CONSTANT) Q_PROPERTY(QString genre READ genre CONSTANT) Q_PROPERTY(int discNumber READ discNumber CONSTANT) Q_PROPERTY(int trackNumber READ trackNumber CONSTANT) Q_PROPERTY(int duration READ duration CONSTANT) Q_PROPERTY(int width READ width CONSTANT) Q_PROPERTY(int height READ height CONSTANT) Q_PROPERTY(double latitude READ latitude CONSTANT) Q_PROPERTY(double longitude READ longitude CONSTANT) Q_PROPERTY(bool hasThumbnail READ hasThumbnail CONSTANT) Q_PROPERTY(uint64_t modificationTime READ modificationTime CONSTANT) Q_PROPERTY(QString art READ art CONSTANT) public: MediaFileWrapper(const mediascanner::MediaFile &media, QObject *parent=0); QString filename() const; QString uri() const; QString contentType() const; QString eTag() const; QString title() const; QString author() const; QString album() const; QString albumArtist() const; QString date() const; QString genre() const; int discNumber() const; int trackNumber() const; int duration() const; int width() const; int height() const; double latitude() const; double longitude() const; bool hasThumbnail() const; uint64_t modificationTime() const; QString art() const; private: const mediascanner::MediaFile media; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/qmldir0000644000015600001650000000011412672421600025472 0ustar pbuserpbgroup00000000000000module Ubuntu.MediaScanner plugin mediascanner-qml typeinfo plugin.qmltypes mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/AlbumModelBase.cc0000644000015600001650000000423712672421600027414 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "AlbumModelBase.hh" using namespace mediascanner::qml; AlbumModelBase::AlbumModelBase(QObject *parent) : StreamingModel(parent) { roles[Roles::RoleTitle] = "title"; roles[Roles::RoleArtist] = "artist"; roles[Roles::RoleDate] = "date"; roles[Roles::RoleGenre] = "genre"; roles[Roles::RoleArt] = "art"; } int AlbumModelBase::rowCount(const QModelIndex &) const { return results.size(); } QVariant AlbumModelBase::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= (ptrdiff_t)results.size()) { return QVariant(); } const mediascanner::Album &album = results[index.row()]; switch (role) { case RoleTitle: return QString::fromStdString(album.getTitle()); case RoleArtist: return QString::fromStdString(album.getArtist()); case RoleDate: return QString::fromStdString(album.getDate()); case RoleGenre: return QString::fromStdString(album.getGenre()); case RoleArt: return QString::fromStdString(album.getArtUri()); default: return QVariant(); } } QHash AlbumModelBase::roleNames() const { return roles; } void AlbumModelBase::appendRows(std::unique_ptr &&row_data) { AlbumRowData *data = static_cast(row_data.get()); std::move(data->rows.begin(), data->rows.end(), std::back_inserter(results)); } void AlbumModelBase::clearBacking() { results.clear(); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/CMakeLists.txt0000644000015600001650000000305012672421600027021 0ustar pbuserpbgroup00000000000000include_directories(../../..) add_definitions(${DBUSCPP_CFLAGS} -DQT_NO_KEYWORDS) set(QML_PLUGIN_DIR "${CMAKE_INSTALL_LIBDIR}/qt5/qml/Ubuntu/MediaScanner.0.1") add_library(mediascanner-qml MODULE plugin.cc MediaFileWrapper.cc MediaStoreWrapper.cc StreamingModel.cc MediaFileModelBase.cc AlbumModelBase.cc AlbumsModel.cc ArtistsModel.cc GenresModel.cc SongsModel.cc SongsSearchModel.cc ) set_target_properties(mediascanner-qml PROPERTIES AUTOMOC TRUE NO_SONAME TRUE) qt5_use_modules(mediascanner-qml Qml Concurrent DBus) target_link_libraries(mediascanner-qml mediascanner ms-dbus) install( TARGETS mediascanner-qml LIBRARY DESTINATION ${QML_PLUGIN_DIR} ) file(GLOB QMLFILES qmldir ) add_custom_target(mediascanner-qmlfiles ALL COMMAND cp ${QMLFILES} ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${QMLFILES} ) install( FILES ${QMLFILES} DESTINATION ${QML_PLUGIN_DIR} ) if(NOT CMAKE_CROSSCOMPILING) find_program(qmlplugindump_exe qmlplugindump) if(NOT qmlplugindump_exe) msg(FATAL_ERROR "Could not locate qmlplugindump.") endif() # qmlplugindump doesn't run reliably in the CI environment (it seems # to be instantiating the types, which fails when there is no media # database). So add a add_custom_target(update-qmltypes COMMAND QML2_IMPORT_PATH=${CMAKE_BINARY_DIR}/src/qml ${qmlplugindump_exe} -notrelocatable Ubuntu.MediaScanner 0.1 > ${CMAKE_CURRENT_SOURCE_DIR}/plugin.qmltypes DEPENDS mediascanner-qml mediascanner-qmlfiles ) endif() install( FILES plugin.qmltypes DESTINATION ${QML_PLUGIN_DIR} ) mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/ArtistsModel.hh0000644000015600001650000000411112672421600027213 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_ARTISTSMODEL_H #define MEDIASCANNER_QML_ARTISTSMODEL_H #include #include #include #include #include "StreamingModel.hh" namespace mediascanner { namespace qml { class ArtistsModel : public StreamingModel { Q_OBJECT Q_ENUMS(Roles) Q_PROPERTY(bool albumArtists READ getAlbumArtists WRITE setAlbumArtists) Q_PROPERTY(QVariant genre READ getGenre WRITE setGenre) Q_PROPERTY(int limit READ getLimit WRITE setLimit) public: enum Roles { RoleArtist, }; explicit ArtistsModel(QObject *parent = 0); int rowCount(const QModelIndex &parent=QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const override; void appendRows(std::unique_ptr &&row_data) override; void clearBacking() override; protected: QHash roleNames() const override; bool getAlbumArtists(); void setAlbumArtists(bool album_artists); QVariant getGenre(); void setGenre(QVariant genre); int getLimit(); void setLimit(int limit); private: QHash roles; std::vector results; Filter filter; bool album_artists; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/SongsModel.hh0000644000015600001650000000357512672421600026670 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_SONGSMODEL_H #define MEDIASCANNER_QML_SONGSMODEL_H #include #include #include "MediaStoreWrapper.hh" #include "MediaFileModelBase.hh" namespace mediascanner { namespace qml { class SongsModel : public MediaFileModelBase { Q_OBJECT Q_PROPERTY(QVariant artist READ getArtist WRITE setArtist) Q_PROPERTY(QVariant album READ getAlbum WRITE setAlbum) Q_PROPERTY(QVariant albumArtist READ getAlbumArtist WRITE setAlbumArtist) Q_PROPERTY(QVariant genre READ getGenre WRITE setGenre) Q_PROPERTY(int limit READ getLimit WRITE setLimit) public: explicit SongsModel(QObject *parent=0); std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const override; protected: QVariant getArtist(); void setArtist(const QVariant artist); QVariant getAlbum(); void setAlbum(const QVariant album); QVariant getAlbumArtist(); void setAlbumArtist(const QVariant album_artist); QVariant getGenre(); void setGenre(const QVariant genre); int getLimit(); void setLimit(int limit); private: Filter filter; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/SongsSearchModel.cc0000644000015600001650000000311212672421600027767 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "SongsSearchModel.hh" #include #include using namespace mediascanner::qml; SongsSearchModel::SongsSearchModel(QObject *parent) : MediaFileModelBase(parent), query("") { } QString SongsSearchModel::getQuery() { return query; } void SongsSearchModel::setQuery(const QString query) { if (this->query != query) { this->query = query; invalidate(); } } std::unique_ptr SongsSearchModel::retrieveRows(std::shared_ptr store, int limit, int offset) const { std::vector songs; mediascanner::Filter limit_filter; limit_filter.setLimit(limit); limit_filter.setOffset(offset); return std::unique_ptr( new MediaFileRowData(store->query(query.toStdString(), mediascanner::AudioMedia, limit_filter))); } mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/SongsSearchModel.hh0000644000015600001650000000254212672421600030007 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASCANNER_QML_SONGSSEARCHMODEL_H #define MEDIASCANNER_QML_SONGSSEARCHMODEL_H #include #include "MediaStoreWrapper.hh" #include "MediaFileModelBase.hh" namespace mediascanner { namespace qml { class SongsSearchModel : public MediaFileModelBase { Q_OBJECT Q_PROPERTY(QString query READ getQuery WRITE setQuery) public: explicit SongsSearchModel(QObject *parent=0); std::unique_ptr retrieveRows(std::shared_ptr store, int limit, int offset) const override; protected: QString getQuery(); void setQuery(const QString query); private: QString query; }; } } #endif mediascanner2-0.111+16.04.20160317/src/qml/Ubuntu/MediaScanner.0.1/plugin.qmltypes0000644000015600001650000001525212672421600027366 0ustar pbuserpbgroup00000000000000import QtQuick.tooling 1.1 // This file describes the plugin-supplied types contained in the library. // It is used for QML tooling purposes only. // // This file was auto-generated by: // 'qmlplugindump -notrelocatable Ubuntu.MediaScanner 0.1' Module { Component { name: "mediascanner::qml::AlbumModelBase" prototype: "mediascanner::qml::StreamingModel" Enum { name: "Roles" values: { "RoleTitle": 0, "RoleArtist": 1, "RoleDate": 2, "RoleGenre": 3, "RoleArt": 4 } } } Component { name: "mediascanner::qml::AlbumsModel" prototype: "mediascanner::qml::AlbumModelBase" exports: ["Ubuntu.MediaScanner/AlbumsModel 0.1"] exportMetaObjectRevisions: [0] Property { name: "artist"; type: "QVariant" } Property { name: "albumArtist"; type: "QVariant" } Property { name: "genre"; type: "QVariant" } Property { name: "limit"; type: "int" } } Component { name: "mediascanner::qml::ArtistsModel" prototype: "mediascanner::qml::StreamingModel" exports: ["Ubuntu.MediaScanner/ArtistsModel 0.1"] exportMetaObjectRevisions: [0] Enum { name: "Roles" values: { "RoleArtist": 0 } } Property { name: "albumArtists"; type: "bool" } Property { name: "genre"; type: "QVariant" } Property { name: "limit"; type: "int" } } Component { name: "mediascanner::qml::GenresModel" prototype: "mediascanner::qml::StreamingModel" exports: ["Ubuntu.MediaScanner/GenresModel 0.1"] exportMetaObjectRevisions: [0] Enum { name: "Roles" values: { "RoleGenre": 0 } } Property { name: "limit"; type: "int" } } Component { name: "mediascanner::qml::MediaFileModelBase" prototype: "mediascanner::qml::StreamingModel" Enum { name: "Roles" values: { "RoleModelData": 0, "RoleFilename": 1, "RoleUri": 2, "RoleContentType": 3, "RoleETag": 4, "RoleTitle": 5, "RoleAuthor": 6, "RoleAlbum": 7, "RoleAlbumArtist": 8, "RoleDate": 9, "RoleGenre": 10, "RoleDiscNumber": 11, "RoleTrackNumber": 12, "RoleDuration": 13, "RoleWidth": 14, "RoleHeight": 15, "RoleLatitude": 16, "RoleLongitude": 17, "RoleArt": 18 } } } Component { name: "mediascanner::qml::MediaFileWrapper" prototype: "QObject" exports: ["Ubuntu.MediaScanner/MediaFile 0.1"] isCreatable: false exportMetaObjectRevisions: [0] Property { name: "filename"; type: "string"; isReadonly: true } Property { name: "uri"; type: "string"; isReadonly: true } Property { name: "contentType"; type: "string"; isReadonly: true } Property { name: "eTag"; type: "string"; isReadonly: true } Property { name: "title"; type: "string"; isReadonly: true } Property { name: "author"; type: "string"; isReadonly: true } Property { name: "album"; type: "string"; isReadonly: true } Property { name: "albumArtist"; type: "string"; isReadonly: true } Property { name: "date"; type: "string"; isReadonly: true } Property { name: "genre"; type: "string"; isReadonly: true } Property { name: "discNumber"; type: "int"; isReadonly: true } Property { name: "trackNumber"; type: "int"; isReadonly: true } Property { name: "duration"; type: "int"; isReadonly: true } Property { name: "width"; type: "int"; isReadonly: true } Property { name: "height"; type: "int"; isReadonly: true } Property { name: "latitude"; type: "double"; isReadonly: true } Property { name: "longitude"; type: "double"; isReadonly: true } Property { name: "hasThumbnail"; type: "bool"; isReadonly: true } Property { name: "art"; type: "string"; isReadonly: true } } Component { name: "mediascanner::qml::MediaStoreWrapper" prototype: "QObject" exports: ["Ubuntu.MediaScanner/MediaStore 0.1"] exportMetaObjectRevisions: [0] Enum { name: "MediaType" values: { "AudioMedia": 1, "VideoMedia": 2, "ImageMedia": 3, "AllMedia": 255 } } Signal { name: "updated" } Method { name: "query" type: "QList" Parameter { name: "q"; type: "string" } Parameter { name: "type"; type: "MediaType" } } Method { name: "lookup" type: "mediascanner::qml::MediaFileWrapper*" Parameter { name: "filename"; type: "string" } } } Component { name: "mediascanner::qml::SongsModel" prototype: "mediascanner::qml::MediaFileModelBase" exports: ["Ubuntu.MediaScanner/SongsModel 0.1"] exportMetaObjectRevisions: [0] Property { name: "artist"; type: "QVariant" } Property { name: "album"; type: "QVariant" } Property { name: "albumArtist"; type: "QVariant" } Property { name: "genre"; type: "QVariant" } Property { name: "limit"; type: "int" } } Component { name: "mediascanner::qml::SongsSearchModel" prototype: "mediascanner::qml::MediaFileModelBase" exports: ["Ubuntu.MediaScanner/SongsSearchModel 0.1"] exportMetaObjectRevisions: [0] Property { name: "query"; type: "string" } } Component { name: "mediascanner::qml::StreamingModel" prototype: "QAbstractListModel" Enum { name: "ModelStatus" values: { "Ready": 0, "Loading": 1, "Error": 2 } } Property { name: "store"; type: "mediascanner::qml::MediaStoreWrapper"; isPointer: true } Property { name: "count"; type: "int"; isReadonly: true } Property { name: "rowCount"; type: "int"; isReadonly: true } Property { name: "status"; type: "ModelStatus"; isReadonly: true } Signal { name: "filled" } Method { name: "invalidate" } Method { name: "get" type: "QVariant" Parameter { name: "row"; type: "int" } Parameter { name: "role"; type: "int" } } } } mediascanner2-0.111+16.04.20160317/src/mediascanner/0000755000015600001650000000000012672422030022011 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaFileBuilder.cc0000644000015600001650000000602512672421600025453 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include"MediaFileBuilder.hh" #include"MediaFile.hh" #include"internal/MediaFilePrivate.hh" namespace mediascanner { MediaFileBuilder::MediaFileBuilder(const std::string &fname) : p(new MediaFilePrivate(fname)) { } MediaFileBuilder::MediaFileBuilder(const MediaFile &mf) : p(new MediaFilePrivate(*mf.p)) { } MediaFileBuilder::~MediaFileBuilder() { delete p; } MediaFile MediaFileBuilder::build() const { return MediaFile(*this); } MediaFileBuilder &MediaFileBuilder::setType(MediaType t) { p->type = t; return *this; } MediaFileBuilder &MediaFileBuilder::setETag(const std::string &e) { p->etag = e; return *this; } MediaFileBuilder &MediaFileBuilder::setContentType(const std::string &c) { p->content_type = c; return *this; } MediaFileBuilder &MediaFileBuilder::setTitle(const std::string &t) { p->title = t; return *this; } MediaFileBuilder &MediaFileBuilder::setDate(const std::string &d) { p->date = d; return *this; } MediaFileBuilder &MediaFileBuilder::setAuthor(const std::string &a) { p->author = a; return *this; } MediaFileBuilder &MediaFileBuilder::setAlbum(const std::string &a) { p->album = a; return *this; } MediaFileBuilder &MediaFileBuilder::setAlbumArtist(const std::string &a) { p->album_artist = a; return *this; } MediaFileBuilder &MediaFileBuilder::setGenre(const std::string &g) { p->genre = g; return *this; } MediaFileBuilder &MediaFileBuilder::setDiscNumber(int n) { p->disc_number = n; return *this; } MediaFileBuilder &MediaFileBuilder::setTrackNumber(int n) { p->track_number = n; return *this; } MediaFileBuilder &MediaFileBuilder::setDuration(int n) { p->duration = n; return *this; } MediaFileBuilder &MediaFileBuilder::setWidth(int w) { p->width = w; return *this; } MediaFileBuilder &MediaFileBuilder::setHeight(int h) { p->height = h; return *this; } MediaFileBuilder &MediaFileBuilder::setLatitude(double l) { p->latitude = l; return *this; } MediaFileBuilder &MediaFileBuilder::setLongitude(double l) { p->longitude = l; return *this; } MediaFileBuilder & MediaFileBuilder::setHasThumbnail(bool t) { p->has_thumbnail = t; return *this; } MediaFileBuilder & MediaFileBuilder::setModificationTime(uint64_t t) { p->modification_time = t; return *this; } } mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaFilePrivate.cc0000644000015600001650000000430212672421600025473 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "scannercore.hh" #include "internal/MediaFilePrivate.hh" #include "internal/utils.hh" namespace mediascanner { MediaFilePrivate::MediaFilePrivate() = default; MediaFilePrivate::MediaFilePrivate(const std::string &filename) : filename(filename) { } MediaFilePrivate::MediaFilePrivate(const MediaFilePrivate &other) { *this = other; } bool MediaFilePrivate::operator==(const MediaFilePrivate &other) const { return filename == other.filename && content_type == other.content_type && etag == other.etag && title == other.title && author == other.author && album == other.album && album_artist == other.album_artist && date == other.date && genre == other.genre && disc_number == other.disc_number && track_number == other.track_number && duration == other.duration && width == other.width && height == other.height && latitude == other.latitude && longitude == other.longitude && has_thumbnail == other.has_thumbnail && modification_time == other.modification_time && type == other.type; } bool MediaFilePrivate::operator!=(const MediaFilePrivate &other) const { return !(*this == other); } void MediaFilePrivate::setFallbackMetadata() { if (title.empty()) { title = filenameToTitle(filename); } if (album_artist.empty()) { album_artist = author; } } } mediascanner2-0.111+16.04.20160317/src/mediascanner/Filter.cc0000644000015600001650000001037012672421600023550 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "Filter.hh" using std::string; namespace mediascanner { struct Filter::Private { string artist; string album; string album_artist; string genre; int offset = 0; int limit = -1; MediaOrder order = MediaOrder::Default; bool reverse = false; bool have_artist = false; bool have_album = false; bool have_album_artist = false; bool have_genre = false; Private() {} }; Filter::Filter() : p(new Private) { } Filter::Filter(const Filter &other) : Filter() { *p = *other.p; } Filter::Filter(Filter &&other) : p(nullptr) { *this = std::move(other); } Filter::~Filter() { delete p; } bool Filter::operator==(const Filter &other) const { return p->have_artist == other.p->have_artist && p->have_album == other.p->have_album && p->have_album_artist == other.p->have_album_artist && p->have_genre == other.p->have_genre && p->artist == other.p->artist && p->album == other.p->album && p->album_artist == other.p->album_artist && p->genre == other.p->genre && p->offset == other.p->offset && p->limit == other.p->limit && p->order == other.p->order && p->reverse == other.p->reverse; } bool Filter::operator!=(const Filter &other) const { return !(*this == other); } Filter &Filter::operator=(const Filter &other) { *p = *other.p; return *this; } Filter &Filter::operator=(Filter &&other) { if (this != &other) { delete p; p = other.p; other.p = nullptr; } return *this; } void Filter::clear() { unsetArtist(); unsetAlbum(); unsetAlbumArtist(); unsetGenre(); p->offset = 0; p->limit = -1; p->order = MediaOrder::Default; p->reverse = false; } void Filter::setArtist(const std::string &artist) { p->artist = artist; p->have_artist = true; } void Filter::unsetArtist() { p->artist = ""; p->have_artist = false; } bool Filter::hasArtist() const { return p->have_artist; } const std::string &Filter::getArtist() const { return p->artist; } void Filter::setAlbum(const std::string &album) { p->album = album; p->have_album = true; } void Filter::unsetAlbum() { p->album = ""; p->have_album = false; } bool Filter::hasAlbum() const { return p->have_album; } const std::string &Filter::getAlbum() const { return p->album; } void Filter::setAlbumArtist(const std::string &album_artist) { p->album_artist = album_artist; p->have_album_artist = true; } void Filter::unsetAlbumArtist() { p->album_artist = ""; p->have_album_artist = false; } bool Filter::hasAlbumArtist() const { return p->have_album_artist; } const std::string &Filter::getAlbumArtist() const { return p->album_artist; } void Filter::setGenre(const std::string &genre) { p->genre = genre; p->have_genre = true; } void Filter::unsetGenre() { p->genre = ""; p->have_genre = false; } bool Filter::hasGenre() const { return p->have_genre; } const std::string &Filter::getGenre() const { return p->genre; } void Filter::setOffset(int offset) { p->offset = offset; } int Filter::getOffset() const { return p->offset; } void Filter::setLimit(int limit) { p->limit = limit; } int Filter::getLimit() const { return p->limit; } void Filter::setOrder(MediaOrder order) { p->order = order; } MediaOrder Filter::getOrder() const { return p->order; } void Filter::setReverse(bool reverse) { p->reverse = reverse; } bool Filter::getReverse() const { return p->reverse; } } mediascanner2-0.111+16.04.20160317/src/mediascanner/mozilla/0000755000015600001650000000000012672422030023460 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/mediascanner/mozilla/Normalize.c0000644000015600001650000025675712672421600025614 0ustar pbuserpbgroup00000000000000/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* THIS FILE IS GENERATED BY generate_table.py. DON'T EDIT THIS */ static const unsigned short gNormalizeTable0040[] = { /* U+0040 */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, }; static const unsigned short gNormalizeTable0080[] = { /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x0020, 0x00ae, 0x0020, 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7, 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf, }; static const unsigned short gNormalizeTable00c0[] = { /* U+00c0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00d7, 0x00f8, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0073, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7, 0x00f8, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, }; static const unsigned short gNormalizeTable0100[] = { /* U+0100 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0063, 0x0064, 0x0064, 0x0111, 0x0111, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0067, 0x0068, 0x0068, 0x0127, 0x0127, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0069, 0x0131, 0x0069, 0x0069, 0x006a, 0x006a, 0x006b, 0x006b, 0x0138, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, }; static const unsigned short gNormalizeTable0140[] = { /* U+0140 */ 0x006c, 0x0142, 0x0142, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x02bc, 0x014b, 0x014b, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x0153, 0x0153, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0074, 0x0074, 0x0074, 0x0074, 0x0167, 0x0167, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0077, 0x0077, 0x0079, 0x0079, 0x0079, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x0073, }; static const unsigned short gNormalizeTable0180[] = { /* U+0180 */ 0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188, 0x0188, 0x0256, 0x0257, 0x018c, 0x018c, 0x018d, 0x01dd, 0x0259, 0x025b, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268, 0x0199, 0x0199, 0x019a, 0x019b, 0x026f, 0x0272, 0x019e, 0x0275, 0x006f, 0x006f, 0x01a3, 0x01a3, 0x01a5, 0x01a5, 0x0280, 0x01a8, 0x01a8, 0x0283, 0x01aa, 0x01ab, 0x01ad, 0x01ad, 0x0288, 0x0075, 0x0075, 0x028a, 0x028b, 0x01b4, 0x01b4, 0x01b6, 0x01b6, 0x0292, 0x01b9, 0x01b9, 0x01ba, 0x01bb, 0x01bd, 0x01bd, 0x01be, 0x01bf, }; static const unsigned short gNormalizeTable01c0[] = { /* U+01c0 */ 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0064, 0x0064, 0x0064, 0x006c, 0x006c, 0x006c, 0x006e, 0x006e, 0x006e, 0x0061, 0x0061, 0x0069, 0x0069, 0x006f, 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x01dd, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x00e6, 0x01e5, 0x01e5, 0x0067, 0x0067, 0x006b, 0x006b, 0x006f, 0x006f, 0x006f, 0x006f, 0x0292, 0x0292, 0x006a, 0x0064, 0x0064, 0x0064, 0x0067, 0x0067, 0x0195, 0x01bf, 0x006e, 0x006e, 0x0061, 0x0061, 0x00e6, 0x00e6, 0x00f8, 0x00f8, }; static const unsigned short gNormalizeTable0200[] = { /* U+0200 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, 0x006f, 0x006f, 0x006f, 0x006f, 0x0072, 0x0072, 0x0072, 0x0072, 0x0075, 0x0075, 0x0075, 0x0075, 0x0073, 0x0073, 0x0074, 0x0074, 0x021d, 0x021d, 0x0068, 0x0068, 0x019e, 0x0221, 0x0223, 0x0223, 0x0225, 0x0225, 0x0061, 0x0061, 0x0065, 0x0065, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x0079, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, 0x0238, 0x0239, 0x2c65, 0x023c, 0x023c, 0x019a, 0x2c66, 0x023f, }; static const unsigned short gNormalizeTable0240[] = { /* U+0240 */ 0x0240, 0x0242, 0x0242, 0x0180, 0x0289, 0x028c, 0x0247, 0x0247, 0x0249, 0x0249, 0x024b, 0x024b, 0x024d, 0x024d, 0x024f, 0x024f, 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f, 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f, 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, }; static const unsigned short gNormalizeTable0280[] = { /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077, 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, }; static const unsigned short gNormalizeTable02c0[] = { /* U+02c0 */ 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df, 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7, 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, }; static const unsigned short gNormalizeTable0340[] = { /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x03b9, 0x0346, 0x0347, 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x0020, 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, 0x0371, 0x0371, 0x0373, 0x0373, 0x02b9, 0x0375, 0x0377, 0x0377, 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f, }; static const unsigned short gNormalizeTable0380[] = { /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x0020, 0x03b1, 0x00b7, 0x03b5, 0x03b7, 0x03b9, 0x038b, 0x03bf, 0x038d, 0x03c5, 0x03c9, 0x03b9, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, 0x03c0, 0x03c1, 0x03a2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03b1, 0x03b5, 0x03b7, 0x03b9, 0x03c5, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, }; static const unsigned short gNormalizeTable03c0[] = { /* U+03c0 */ 0x03c0, 0x03c1, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03d7, 0x03b2, 0x03b8, 0x03c5, 0x03c5, 0x03c5, 0x03c6, 0x03c0, 0x03d7, 0x03d9, 0x03d9, 0x03db, 0x03db, 0x03dd, 0x03dd, 0x03df, 0x03df, 0x03e1, 0x03e1, 0x03e3, 0x03e3, 0x03e5, 0x03e5, 0x03e7, 0x03e7, 0x03e9, 0x03e9, 0x03eb, 0x03eb, 0x03ed, 0x03ed, 0x03ef, 0x03ef, 0x03ba, 0x03c1, 0x03c3, 0x03f3, 0x03b8, 0x03b5, 0x03f6, 0x03f8, 0x03f8, 0x03c3, 0x03fb, 0x03fb, 0x03fc, 0x037b, 0x037c, 0x037d, }; static const unsigned short gNormalizeTable0400[] = { /* U+0400 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, }; static const unsigned short gNormalizeTable0440[] = { /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467, 0x0469, 0x0469, 0x046b, 0x046b, 0x046d, 0x046d, 0x046f, 0x046f, 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0475, 0x0475, 0x0479, 0x0479, 0x047b, 0x047b, 0x047d, 0x047d, 0x047f, 0x047f, }; static const unsigned short gNormalizeTable0480[] = { /* U+0480 */ 0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x0489, 0x048b, 0x048b, 0x048d, 0x048d, 0x048f, 0x048f, 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497, 0x0499, 0x0499, 0x049b, 0x049b, 0x049d, 0x049d, 0x049f, 0x049f, 0x04a1, 0x04a1, 0x04a3, 0x04a3, 0x04a5, 0x04a5, 0x04a7, 0x04a7, 0x04a9, 0x04a9, 0x04ab, 0x04ab, 0x04ad, 0x04ad, 0x04af, 0x04af, 0x04b1, 0x04b1, 0x04b3, 0x04b3, 0x04b5, 0x04b5, 0x04b7, 0x04b7, 0x04b9, 0x04b9, 0x04bb, 0x04bb, 0x04bd, 0x04bd, 0x04bf, 0x04bf, }; static const unsigned short gNormalizeTable04c0[] = { /* U+04c0 */ 0x04cf, 0x0436, 0x0436, 0x04c4, 0x04c4, 0x04c6, 0x04c6, 0x04c8, 0x04c8, 0x04ca, 0x04ca, 0x04cc, 0x04cc, 0x04ce, 0x04ce, 0x04cf, 0x0430, 0x0430, 0x0430, 0x0430, 0x04d5, 0x04d5, 0x0435, 0x0435, 0x04d9, 0x04d9, 0x04d9, 0x04d9, 0x0436, 0x0436, 0x0437, 0x0437, 0x04e1, 0x04e1, 0x0438, 0x0438, 0x0438, 0x0438, 0x043e, 0x043e, 0x04e9, 0x04e9, 0x04e9, 0x04e9, 0x044d, 0x044d, 0x0443, 0x0443, 0x0443, 0x0443, 0x0443, 0x0443, 0x0447, 0x0447, 0x04f7, 0x04f7, 0x044b, 0x044b, 0x04fb, 0x04fb, 0x04fd, 0x04fd, 0x04ff, 0x04ff, }; static const unsigned short gNormalizeTable0500[] = { /* U+0500 */ 0x0501, 0x0501, 0x0503, 0x0503, 0x0505, 0x0505, 0x0507, 0x0507, 0x0509, 0x0509, 0x050b, 0x050b, 0x050d, 0x050d, 0x050f, 0x050f, 0x0511, 0x0511, 0x0513, 0x0513, 0x0515, 0x0515, 0x0517, 0x0517, 0x0519, 0x0519, 0x051b, 0x051b, 0x051d, 0x051d, 0x051f, 0x051f, 0x0521, 0x0521, 0x0523, 0x0523, 0x0525, 0x0525, 0x0526, 0x0527, 0x0528, 0x0529, 0x052a, 0x052b, 0x052c, 0x052d, 0x052e, 0x052f, 0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, 0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f, }; static const unsigned short gNormalizeTable0540[] = { /* U+0540 */ 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, 0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f, 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557, 0x0558, 0x0559, 0x055a, 0x055b, 0x055c, 0x055d, 0x055e, 0x055f, 0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567, 0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f, 0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577, 0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f, }; static const unsigned short gNormalizeTable0580[] = { /* U+0580 */ 0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0565, 0x0588, 0x0589, 0x058a, 0x058b, 0x058c, 0x058d, 0x058e, 0x058f, 0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597, 0x0598, 0x0599, 0x059a, 0x059b, 0x059c, 0x059d, 0x059e, 0x059f, 0x05a0, 0x05a1, 0x05a2, 0x05a3, 0x05a4, 0x05a5, 0x05a6, 0x05a7, 0x05a8, 0x05a9, 0x05aa, 0x05ab, 0x05ac, 0x05ad, 0x05ae, 0x05af, 0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05ba, 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf, }; static const unsigned short gNormalizeTable0600[] = { /* U+0600 */ 0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607, 0x0608, 0x0609, 0x060a, 0x060b, 0x060c, 0x060d, 0x060e, 0x060f, 0x0610, 0x0611, 0x0612, 0x0613, 0x0614, 0x0615, 0x0616, 0x0617, 0x0618, 0x0619, 0x061a, 0x061b, 0x061c, 0x061d, 0x061e, 0x061f, 0x0620, 0x0621, 0x0627, 0x0627, 0x0648, 0x0627, 0x064a, 0x0627, 0x0628, 0x0629, 0x062a, 0x062b, 0x062c, 0x062d, 0x062e, 0x062f, 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063a, 0x063b, 0x063c, 0x063d, 0x063e, 0x063f, }; static const unsigned short gNormalizeTable0640[] = { /* U+0640 */ 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064a, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651, 0x0652, 0x0653, 0x0654, 0x0655, 0x0656, 0x0657, 0x0658, 0x0659, 0x065a, 0x065b, 0x065c, 0x065d, 0x065e, 0x065f, 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669, 0x066a, 0x066b, 0x066c, 0x066d, 0x066e, 0x066f, 0x0670, 0x0671, 0x0672, 0x0673, 0x0674, 0x0627, 0x0648, 0x06c7, 0x064a, 0x0679, 0x067a, 0x067b, 0x067c, 0x067d, 0x067e, 0x067f, }; static const unsigned short gNormalizeTable06c0[] = { /* U+06c0 */ 0x06d5, 0x06c1, 0x06c1, 0x06c3, 0x06c4, 0x06c5, 0x06c6, 0x06c7, 0x06c8, 0x06c9, 0x06ca, 0x06cb, 0x06cc, 0x06cd, 0x06ce, 0x06cf, 0x06d0, 0x06d1, 0x06d2, 0x06d2, 0x06d4, 0x06d5, 0x06d6, 0x06d7, 0x06d8, 0x06d9, 0x06da, 0x06db, 0x06dc, 0x06dd, 0x06de, 0x06df, 0x06e0, 0x06e1, 0x06e2, 0x06e3, 0x06e4, 0x06e5, 0x06e6, 0x06e7, 0x06e8, 0x06e9, 0x06ea, 0x06eb, 0x06ec, 0x06ed, 0x06ee, 0x06ef, 0x06f0, 0x06f1, 0x06f2, 0x06f3, 0x06f4, 0x06f5, 0x06f6, 0x06f7, 0x06f8, 0x06f9, 0x06fa, 0x06fb, 0x06fc, 0x06fd, 0x06fe, 0x06ff, }; static const unsigned short gNormalizeTable0900[] = { /* U+0900 */ 0x0900, 0x0901, 0x0902, 0x0903, 0x0904, 0x0905, 0x0906, 0x0907, 0x0908, 0x0909, 0x090a, 0x090b, 0x090c, 0x090d, 0x090e, 0x090f, 0x0910, 0x0911, 0x0912, 0x0913, 0x0914, 0x0915, 0x0916, 0x0917, 0x0918, 0x0919, 0x091a, 0x091b, 0x091c, 0x091d, 0x091e, 0x091f, 0x0920, 0x0921, 0x0922, 0x0923, 0x0924, 0x0925, 0x0926, 0x0927, 0x0928, 0x0928, 0x092a, 0x092b, 0x092c, 0x092d, 0x092e, 0x092f, 0x0930, 0x0930, 0x0932, 0x0933, 0x0933, 0x0935, 0x0936, 0x0937, 0x0938, 0x0939, 0x093a, 0x093b, 0x093c, 0x093d, 0x093e, 0x093f, }; static const unsigned short gNormalizeTable0940[] = { /* U+0940 */ 0x0940, 0x0941, 0x0942, 0x0943, 0x0944, 0x0945, 0x0946, 0x0947, 0x0948, 0x0949, 0x094a, 0x094b, 0x094c, 0x094d, 0x094e, 0x094f, 0x0950, 0x0951, 0x0952, 0x0953, 0x0954, 0x0955, 0x0956, 0x0957, 0x0915, 0x0916, 0x0917, 0x091c, 0x0921, 0x0922, 0x092b, 0x092f, 0x0960, 0x0961, 0x0962, 0x0963, 0x0964, 0x0965, 0x0966, 0x0967, 0x0968, 0x0969, 0x096a, 0x096b, 0x096c, 0x096d, 0x096e, 0x096f, 0x0970, 0x0971, 0x0972, 0x0973, 0x0974, 0x0975, 0x0976, 0x0977, 0x0978, 0x0979, 0x097a, 0x097b, 0x097c, 0x097d, 0x097e, 0x097f, }; static const unsigned short gNormalizeTable09c0[] = { /* U+09c0 */ 0x09c0, 0x09c1, 0x09c2, 0x09c3, 0x09c4, 0x09c5, 0x09c6, 0x09c7, 0x09c8, 0x09c9, 0x09ca, 0x09c7, 0x09c7, 0x09cd, 0x09ce, 0x09cf, 0x09d0, 0x09d1, 0x09d2, 0x09d3, 0x09d4, 0x09d5, 0x09d6, 0x09d7, 0x09d8, 0x09d9, 0x09da, 0x09db, 0x09a1, 0x09a2, 0x09de, 0x09af, 0x09e0, 0x09e1, 0x09e2, 0x09e3, 0x09e4, 0x09e5, 0x09e6, 0x09e7, 0x09e8, 0x09e9, 0x09ea, 0x09eb, 0x09ec, 0x09ed, 0x09ee, 0x09ef, 0x09f0, 0x09f1, 0x09f2, 0x09f3, 0x09f4, 0x09f5, 0x09f6, 0x09f7, 0x09f8, 0x09f9, 0x09fa, 0x09fb, 0x09fc, 0x09fd, 0x09fe, 0x09ff, }; static const unsigned short gNormalizeTable0a00[] = { /* U+0a00 */ 0x0a00, 0x0a01, 0x0a02, 0x0a03, 0x0a04, 0x0a05, 0x0a06, 0x0a07, 0x0a08, 0x0a09, 0x0a0a, 0x0a0b, 0x0a0c, 0x0a0d, 0x0a0e, 0x0a0f, 0x0a10, 0x0a11, 0x0a12, 0x0a13, 0x0a14, 0x0a15, 0x0a16, 0x0a17, 0x0a18, 0x0a19, 0x0a1a, 0x0a1b, 0x0a1c, 0x0a1d, 0x0a1e, 0x0a1f, 0x0a20, 0x0a21, 0x0a22, 0x0a23, 0x0a24, 0x0a25, 0x0a26, 0x0a27, 0x0a28, 0x0a29, 0x0a2a, 0x0a2b, 0x0a2c, 0x0a2d, 0x0a2e, 0x0a2f, 0x0a30, 0x0a31, 0x0a32, 0x0a32, 0x0a34, 0x0a35, 0x0a38, 0x0a37, 0x0a38, 0x0a39, 0x0a3a, 0x0a3b, 0x0a3c, 0x0a3d, 0x0a3e, 0x0a3f, }; static const unsigned short gNormalizeTable0a40[] = { /* U+0a40 */ 0x0a40, 0x0a41, 0x0a42, 0x0a43, 0x0a44, 0x0a45, 0x0a46, 0x0a47, 0x0a48, 0x0a49, 0x0a4a, 0x0a4b, 0x0a4c, 0x0a4d, 0x0a4e, 0x0a4f, 0x0a50, 0x0a51, 0x0a52, 0x0a53, 0x0a54, 0x0a55, 0x0a56, 0x0a57, 0x0a58, 0x0a16, 0x0a17, 0x0a1c, 0x0a5c, 0x0a5d, 0x0a2b, 0x0a5f, 0x0a60, 0x0a61, 0x0a62, 0x0a63, 0x0a64, 0x0a65, 0x0a66, 0x0a67, 0x0a68, 0x0a69, 0x0a6a, 0x0a6b, 0x0a6c, 0x0a6d, 0x0a6e, 0x0a6f, 0x0a70, 0x0a71, 0x0a72, 0x0a73, 0x0a74, 0x0a75, 0x0a76, 0x0a77, 0x0a78, 0x0a79, 0x0a7a, 0x0a7b, 0x0a7c, 0x0a7d, 0x0a7e, 0x0a7f, }; static const unsigned short gNormalizeTable0b40[] = { /* U+0b40 */ 0x0b40, 0x0b41, 0x0b42, 0x0b43, 0x0b44, 0x0b45, 0x0b46, 0x0b47, 0x0b47, 0x0b49, 0x0b4a, 0x0b47, 0x0b47, 0x0b4d, 0x0b4e, 0x0b4f, 0x0b50, 0x0b51, 0x0b52, 0x0b53, 0x0b54, 0x0b55, 0x0b56, 0x0b57, 0x0b58, 0x0b59, 0x0b5a, 0x0b5b, 0x0b21, 0x0b22, 0x0b5e, 0x0b5f, 0x0b60, 0x0b61, 0x0b62, 0x0b63, 0x0b64, 0x0b65, 0x0b66, 0x0b67, 0x0b68, 0x0b69, 0x0b6a, 0x0b6b, 0x0b6c, 0x0b6d, 0x0b6e, 0x0b6f, 0x0b70, 0x0b71, 0x0b72, 0x0b73, 0x0b74, 0x0b75, 0x0b76, 0x0b77, 0x0b78, 0x0b79, 0x0b7a, 0x0b7b, 0x0b7c, 0x0b7d, 0x0b7e, 0x0b7f, }; static const unsigned short gNormalizeTable0b80[] = { /* U+0b80 */ 0x0b80, 0x0b81, 0x0b82, 0x0b83, 0x0b84, 0x0b85, 0x0b86, 0x0b87, 0x0b88, 0x0b89, 0x0b8a, 0x0b8b, 0x0b8c, 0x0b8d, 0x0b8e, 0x0b8f, 0x0b90, 0x0b91, 0x0b92, 0x0b93, 0x0b92, 0x0b95, 0x0b96, 0x0b97, 0x0b98, 0x0b99, 0x0b9a, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0b9f, 0x0ba0, 0x0ba1, 0x0ba2, 0x0ba3, 0x0ba4, 0x0ba5, 0x0ba6, 0x0ba7, 0x0ba8, 0x0ba9, 0x0baa, 0x0bab, 0x0bac, 0x0bad, 0x0bae, 0x0baf, 0x0bb0, 0x0bb1, 0x0bb2, 0x0bb3, 0x0bb4, 0x0bb5, 0x0bb6, 0x0bb7, 0x0bb8, 0x0bb9, 0x0bba, 0x0bbb, 0x0bbc, 0x0bbd, 0x0bbe, 0x0bbf, }; static const unsigned short gNormalizeTable0bc0[] = { /* U+0bc0 */ 0x0bc0, 0x0bc1, 0x0bc2, 0x0bc3, 0x0bc4, 0x0bc5, 0x0bc6, 0x0bc7, 0x0bc8, 0x0bc9, 0x0bc6, 0x0bc7, 0x0bc6, 0x0bcd, 0x0bce, 0x0bcf, 0x0bd0, 0x0bd1, 0x0bd2, 0x0bd3, 0x0bd4, 0x0bd5, 0x0bd6, 0x0bd7, 0x0bd8, 0x0bd9, 0x0bda, 0x0bdb, 0x0bdc, 0x0bdd, 0x0bde, 0x0bdf, 0x0be0, 0x0be1, 0x0be2, 0x0be3, 0x0be4, 0x0be5, 0x0be6, 0x0be7, 0x0be8, 0x0be9, 0x0bea, 0x0beb, 0x0bec, 0x0bed, 0x0bee, 0x0bef, 0x0bf0, 0x0bf1, 0x0bf2, 0x0bf3, 0x0bf4, 0x0bf5, 0x0bf6, 0x0bf7, 0x0bf8, 0x0bf9, 0x0bfa, 0x0bfb, 0x0bfc, 0x0bfd, 0x0bfe, 0x0bff, }; static const unsigned short gNormalizeTable0c40[] = { /* U+0c40 */ 0x0c40, 0x0c41, 0x0c42, 0x0c43, 0x0c44, 0x0c45, 0x0c46, 0x0c47, 0x0c46, 0x0c49, 0x0c4a, 0x0c4b, 0x0c4c, 0x0c4d, 0x0c4e, 0x0c4f, 0x0c50, 0x0c51, 0x0c52, 0x0c53, 0x0c54, 0x0c55, 0x0c56, 0x0c57, 0x0c58, 0x0c59, 0x0c5a, 0x0c5b, 0x0c5c, 0x0c5d, 0x0c5e, 0x0c5f, 0x0c60, 0x0c61, 0x0c62, 0x0c63, 0x0c64, 0x0c65, 0x0c66, 0x0c67, 0x0c68, 0x0c69, 0x0c6a, 0x0c6b, 0x0c6c, 0x0c6d, 0x0c6e, 0x0c6f, 0x0c70, 0x0c71, 0x0c72, 0x0c73, 0x0c74, 0x0c75, 0x0c76, 0x0c77, 0x0c78, 0x0c79, 0x0c7a, 0x0c7b, 0x0c7c, 0x0c7d, 0x0c7e, 0x0c7f, }; static const unsigned short gNormalizeTable0cc0[] = { /* U+0cc0 */ 0x0cbf, 0x0cc1, 0x0cc2, 0x0cc3, 0x0cc4, 0x0cc5, 0x0cc6, 0x0cc6, 0x0cc6, 0x0cc9, 0x0cc6, 0x0cc6, 0x0ccc, 0x0ccd, 0x0cce, 0x0ccf, 0x0cd0, 0x0cd1, 0x0cd2, 0x0cd3, 0x0cd4, 0x0cd5, 0x0cd6, 0x0cd7, 0x0cd8, 0x0cd9, 0x0cda, 0x0cdb, 0x0cdc, 0x0cdd, 0x0cde, 0x0cdf, 0x0ce0, 0x0ce1, 0x0ce2, 0x0ce3, 0x0ce4, 0x0ce5, 0x0ce6, 0x0ce7, 0x0ce8, 0x0ce9, 0x0cea, 0x0ceb, 0x0cec, 0x0ced, 0x0cee, 0x0cef, 0x0cf0, 0x0cf1, 0x0cf2, 0x0cf3, 0x0cf4, 0x0cf5, 0x0cf6, 0x0cf7, 0x0cf8, 0x0cf9, 0x0cfa, 0x0cfb, 0x0cfc, 0x0cfd, 0x0cfe, 0x0cff, }; static const unsigned short gNormalizeTable0d40[] = { /* U+0d40 */ 0x0d40, 0x0d41, 0x0d42, 0x0d43, 0x0d44, 0x0d45, 0x0d46, 0x0d47, 0x0d48, 0x0d49, 0x0d46, 0x0d47, 0x0d46, 0x0d4d, 0x0d4e, 0x0d4f, 0x0d50, 0x0d51, 0x0d52, 0x0d53, 0x0d54, 0x0d55, 0x0d56, 0x0d57, 0x0d58, 0x0d59, 0x0d5a, 0x0d5b, 0x0d5c, 0x0d5d, 0x0d5e, 0x0d5f, 0x0d60, 0x0d61, 0x0d62, 0x0d63, 0x0d64, 0x0d65, 0x0d66, 0x0d67, 0x0d68, 0x0d69, 0x0d6a, 0x0d6b, 0x0d6c, 0x0d6d, 0x0d6e, 0x0d6f, 0x0d70, 0x0d71, 0x0d72, 0x0d73, 0x0d74, 0x0d75, 0x0d76, 0x0d77, 0x0d78, 0x0d79, 0x0d7a, 0x0d7b, 0x0d7c, 0x0d7d, 0x0d7e, 0x0d7f, }; static const unsigned short gNormalizeTable0dc0[] = { /* U+0dc0 */ 0x0dc0, 0x0dc1, 0x0dc2, 0x0dc3, 0x0dc4, 0x0dc5, 0x0dc6, 0x0dc7, 0x0dc8, 0x0dc9, 0x0dca, 0x0dcb, 0x0dcc, 0x0dcd, 0x0dce, 0x0dcf, 0x0dd0, 0x0dd1, 0x0dd2, 0x0dd3, 0x0dd4, 0x0dd5, 0x0dd6, 0x0dd7, 0x0dd8, 0x0dd9, 0x0dd9, 0x0ddb, 0x0dd9, 0x0dd9, 0x0dd9, 0x0ddf, 0x0de0, 0x0de1, 0x0de2, 0x0de3, 0x0de4, 0x0de5, 0x0de6, 0x0de7, 0x0de8, 0x0de9, 0x0dea, 0x0deb, 0x0dec, 0x0ded, 0x0dee, 0x0def, 0x0df0, 0x0df1, 0x0df2, 0x0df3, 0x0df4, 0x0df5, 0x0df6, 0x0df7, 0x0df8, 0x0df9, 0x0dfa, 0x0dfb, 0x0dfc, 0x0dfd, 0x0dfe, 0x0dff, }; static const unsigned short gNormalizeTable0e00[] = { /* U+0e00 */ 0x0e00, 0x0e01, 0x0e02, 0x0e03, 0x0e04, 0x0e05, 0x0e06, 0x0e07, 0x0e08, 0x0e09, 0x0e0a, 0x0e0b, 0x0e0c, 0x0e0d, 0x0e0e, 0x0e0f, 0x0e10, 0x0e11, 0x0e12, 0x0e13, 0x0e14, 0x0e15, 0x0e16, 0x0e17, 0x0e18, 0x0e19, 0x0e1a, 0x0e1b, 0x0e1c, 0x0e1d, 0x0e1e, 0x0e1f, 0x0e20, 0x0e21, 0x0e22, 0x0e23, 0x0e24, 0x0e25, 0x0e26, 0x0e27, 0x0e28, 0x0e29, 0x0e2a, 0x0e2b, 0x0e2c, 0x0e2d, 0x0e2e, 0x0e2f, 0x0e30, 0x0e31, 0x0e32, 0x0e4d, 0x0e34, 0x0e35, 0x0e36, 0x0e37, 0x0e38, 0x0e39, 0x0e3a, 0x0e3b, 0x0e3c, 0x0e3d, 0x0e3e, 0x0e3f, }; static const unsigned short gNormalizeTable0e80[] = { /* U+0e80 */ 0x0e80, 0x0e81, 0x0e82, 0x0e83, 0x0e84, 0x0e85, 0x0e86, 0x0e87, 0x0e88, 0x0e89, 0x0e8a, 0x0e8b, 0x0e8c, 0x0e8d, 0x0e8e, 0x0e8f, 0x0e90, 0x0e91, 0x0e92, 0x0e93, 0x0e94, 0x0e95, 0x0e96, 0x0e97, 0x0e98, 0x0e99, 0x0e9a, 0x0e9b, 0x0e9c, 0x0e9d, 0x0e9e, 0x0e9f, 0x0ea0, 0x0ea1, 0x0ea2, 0x0ea3, 0x0ea4, 0x0ea5, 0x0ea6, 0x0ea7, 0x0ea8, 0x0ea9, 0x0eaa, 0x0eab, 0x0eac, 0x0ead, 0x0eae, 0x0eaf, 0x0eb0, 0x0eb1, 0x0eb2, 0x0ecd, 0x0eb4, 0x0eb5, 0x0eb6, 0x0eb7, 0x0eb8, 0x0eb9, 0x0eba, 0x0ebb, 0x0ebc, 0x0ebd, 0x0ebe, 0x0ebf, }; static const unsigned short gNormalizeTable0ec0[] = { /* U+0ec0 */ 0x0ec0, 0x0ec1, 0x0ec2, 0x0ec3, 0x0ec4, 0x0ec5, 0x0ec6, 0x0ec7, 0x0ec8, 0x0ec9, 0x0eca, 0x0ecb, 0x0ecc, 0x0ecd, 0x0ece, 0x0ecf, 0x0ed0, 0x0ed1, 0x0ed2, 0x0ed3, 0x0ed4, 0x0ed5, 0x0ed6, 0x0ed7, 0x0ed8, 0x0ed9, 0x0eda, 0x0edb, 0x0eab, 0x0eab, 0x0ede, 0x0edf, 0x0ee0, 0x0ee1, 0x0ee2, 0x0ee3, 0x0ee4, 0x0ee5, 0x0ee6, 0x0ee7, 0x0ee8, 0x0ee9, 0x0eea, 0x0eeb, 0x0eec, 0x0eed, 0x0eee, 0x0eef, 0x0ef0, 0x0ef1, 0x0ef2, 0x0ef3, 0x0ef4, 0x0ef5, 0x0ef6, 0x0ef7, 0x0ef8, 0x0ef9, 0x0efa, 0x0efb, 0x0efc, 0x0efd, 0x0efe, 0x0eff, }; static const unsigned short gNormalizeTable0f00[] = { /* U+0f00 */ 0x0f00, 0x0f01, 0x0f02, 0x0f03, 0x0f04, 0x0f05, 0x0f06, 0x0f07, 0x0f08, 0x0f09, 0x0f0a, 0x0f0b, 0x0f0b, 0x0f0d, 0x0f0e, 0x0f0f, 0x0f10, 0x0f11, 0x0f12, 0x0f13, 0x0f14, 0x0f15, 0x0f16, 0x0f17, 0x0f18, 0x0f19, 0x0f1a, 0x0f1b, 0x0f1c, 0x0f1d, 0x0f1e, 0x0f1f, 0x0f20, 0x0f21, 0x0f22, 0x0f23, 0x0f24, 0x0f25, 0x0f26, 0x0f27, 0x0f28, 0x0f29, 0x0f2a, 0x0f2b, 0x0f2c, 0x0f2d, 0x0f2e, 0x0f2f, 0x0f30, 0x0f31, 0x0f32, 0x0f33, 0x0f34, 0x0f35, 0x0f36, 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3b, 0x0f3c, 0x0f3d, 0x0f3e, 0x0f3f, }; static const unsigned short gNormalizeTable0f40[] = { /* U+0f40 */ 0x0f40, 0x0f41, 0x0f42, 0x0f42, 0x0f44, 0x0f45, 0x0f46, 0x0f47, 0x0f48, 0x0f49, 0x0f4a, 0x0f4b, 0x0f4c, 0x0f4c, 0x0f4e, 0x0f4f, 0x0f50, 0x0f51, 0x0f51, 0x0f53, 0x0f54, 0x0f55, 0x0f56, 0x0f56, 0x0f58, 0x0f59, 0x0f5a, 0x0f5b, 0x0f5b, 0x0f5d, 0x0f5e, 0x0f5f, 0x0f60, 0x0f61, 0x0f62, 0x0f63, 0x0f64, 0x0f65, 0x0f66, 0x0f67, 0x0f68, 0x0f40, 0x0f6a, 0x0f6b, 0x0f6c, 0x0f6d, 0x0f6e, 0x0f6f, 0x0f70, 0x0f71, 0x0f72, 0x0f71, 0x0f74, 0x0f71, 0x0fb2, 0x0fb2, 0x0fb3, 0x0fb3, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f7e, 0x0f7f, }; static const unsigned short gNormalizeTable0f80[] = { /* U+0f80 */ 0x0f80, 0x0f71, 0x0f82, 0x0f83, 0x0f84, 0x0f85, 0x0f86, 0x0f87, 0x0f88, 0x0f89, 0x0f8a, 0x0f8b, 0x0f8c, 0x0f8d, 0x0f8e, 0x0f8f, 0x0f90, 0x0f91, 0x0f92, 0x0f92, 0x0f94, 0x0f95, 0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0f9a, 0x0f9b, 0x0f9c, 0x0f9c, 0x0f9e, 0x0f9f, 0x0fa0, 0x0fa1, 0x0fa1, 0x0fa3, 0x0fa4, 0x0fa5, 0x0fa6, 0x0fa6, 0x0fa8, 0x0fa9, 0x0faa, 0x0fab, 0x0fab, 0x0fad, 0x0fae, 0x0faf, 0x0fb0, 0x0fb1, 0x0fb2, 0x0fb3, 0x0fb4, 0x0fb5, 0x0fb6, 0x0fb7, 0x0fb8, 0x0f90, 0x0fba, 0x0fbb, 0x0fbc, 0x0fbd, 0x0fbe, 0x0fbf, }; static const unsigned short gNormalizeTable1000[] = { /* U+1000 */ 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1008, 0x1009, 0x100a, 0x100b, 0x100c, 0x100d, 0x100e, 0x100f, 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016, 0x1017, 0x1018, 0x1019, 0x101a, 0x101b, 0x101c, 0x101d, 0x101e, 0x101f, 0x1020, 0x1021, 0x1022, 0x1023, 0x1024, 0x1025, 0x1025, 0x1027, 0x1028, 0x1029, 0x102a, 0x102b, 0x102c, 0x102d, 0x102e, 0x102f, 0x1030, 0x1031, 0x1032, 0x1033, 0x1034, 0x1035, 0x1036, 0x1037, 0x1038, 0x1039, 0x103a, 0x103b, 0x103c, 0x103d, 0x103e, 0x103f, }; static const unsigned short gNormalizeTable1080[] = { /* U+1080 */ 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, 0x1087, 0x1088, 0x1089, 0x108a, 0x108b, 0x108c, 0x108d, 0x108e, 0x108f, 0x1090, 0x1091, 0x1092, 0x1093, 0x1094, 0x1095, 0x1096, 0x1097, 0x1098, 0x1099, 0x109a, 0x109b, 0x109c, 0x109d, 0x109e, 0x109f, 0x2d00, 0x2d01, 0x2d02, 0x2d03, 0x2d04, 0x2d05, 0x2d06, 0x2d07, 0x2d08, 0x2d09, 0x2d0a, 0x2d0b, 0x2d0c, 0x2d0d, 0x2d0e, 0x2d0f, 0x2d10, 0x2d11, 0x2d12, 0x2d13, 0x2d14, 0x2d15, 0x2d16, 0x2d17, 0x2d18, 0x2d19, 0x2d1a, 0x2d1b, 0x2d1c, 0x2d1d, 0x2d1e, 0x2d1f, }; static const unsigned short gNormalizeTable10c0[] = { /* U+10c0 */ 0x2d20, 0x2d21, 0x2d22, 0x2d23, 0x2d24, 0x2d25, 0x10c6, 0x10c7, 0x10c8, 0x10c9, 0x10ca, 0x10cb, 0x10cc, 0x10cd, 0x10ce, 0x10cf, 0x10d0, 0x10d1, 0x10d2, 0x10d3, 0x10d4, 0x10d5, 0x10d6, 0x10d7, 0x10d8, 0x10d9, 0x10da, 0x10db, 0x10dc, 0x10dd, 0x10de, 0x10df, 0x10e0, 0x10e1, 0x10e2, 0x10e3, 0x10e4, 0x10e5, 0x10e6, 0x10e7, 0x10e8, 0x10e9, 0x10ea, 0x10eb, 0x10ec, 0x10ed, 0x10ee, 0x10ef, 0x10f0, 0x10f1, 0x10f2, 0x10f3, 0x10f4, 0x10f5, 0x10f6, 0x10f7, 0x10f8, 0x10f9, 0x10fa, 0x10fb, 0x10dc, 0x10fd, 0x10fe, 0x10ff, }; static const unsigned short gNormalizeTable1140[] = { /* U+1140 */ 0x1140, 0x1141, 0x1142, 0x1143, 0x1144, 0x1145, 0x1146, 0x1147, 0x1148, 0x1149, 0x114a, 0x114b, 0x114c, 0x114d, 0x114e, 0x114f, 0x1150, 0x1151, 0x1152, 0x1153, 0x1154, 0x1155, 0x1156, 0x1157, 0x1158, 0x1159, 0x115a, 0x115b, 0x115c, 0x115d, 0x115e, 0x0020, 0x0020, 0x1161, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, 0x1169, 0x116a, 0x116b, 0x116c, 0x116d, 0x116e, 0x116f, 0x1170, 0x1171, 0x1172, 0x1173, 0x1174, 0x1175, 0x1176, 0x1177, 0x1178, 0x1179, 0x117a, 0x117b, 0x117c, 0x117d, 0x117e, 0x117f, }; static const unsigned short gNormalizeTable1780[] = { /* U+1780 */ 0x1780, 0x1781, 0x1782, 0x1783, 0x1784, 0x1785, 0x1786, 0x1787, 0x1788, 0x1789, 0x178a, 0x178b, 0x178c, 0x178d, 0x178e, 0x178f, 0x1790, 0x1791, 0x1792, 0x1793, 0x1794, 0x1795, 0x1796, 0x1797, 0x1798, 0x1799, 0x179a, 0x179b, 0x179c, 0x179d, 0x179e, 0x179f, 0x17a0, 0x17a1, 0x17a2, 0x17a3, 0x17a4, 0x17a5, 0x17a6, 0x17a7, 0x17a8, 0x17a9, 0x17aa, 0x17ab, 0x17ac, 0x17ad, 0x17ae, 0x17af, 0x17b0, 0x17b1, 0x17b2, 0x17b3, 0x0020, 0x0020, 0x17b6, 0x17b7, 0x17b8, 0x17b9, 0x17ba, 0x17bb, 0x17bc, 0x17bd, 0x17be, 0x17bf, }; static const unsigned short gNormalizeTable1800[] = { /* U+1800 */ 0x1800, 0x1801, 0x1802, 0x1803, 0x1804, 0x1805, 0x1806, 0x1807, 0x1808, 0x1809, 0x180a, 0x0020, 0x0020, 0x0020, 0x180e, 0x180f, 0x1810, 0x1811, 0x1812, 0x1813, 0x1814, 0x1815, 0x1816, 0x1817, 0x1818, 0x1819, 0x181a, 0x181b, 0x181c, 0x181d, 0x181e, 0x181f, 0x1820, 0x1821, 0x1822, 0x1823, 0x1824, 0x1825, 0x1826, 0x1827, 0x1828, 0x1829, 0x182a, 0x182b, 0x182c, 0x182d, 0x182e, 0x182f, 0x1830, 0x1831, 0x1832, 0x1833, 0x1834, 0x1835, 0x1836, 0x1837, 0x1838, 0x1839, 0x183a, 0x183b, 0x183c, 0x183d, 0x183e, 0x183f, }; static const unsigned short gNormalizeTable1b00[] = { /* U+1b00 */ 0x1b00, 0x1b01, 0x1b02, 0x1b03, 0x1b04, 0x1b05, 0x1b05, 0x1b07, 0x1b07, 0x1b09, 0x1b09, 0x1b0b, 0x1b0b, 0x1b0d, 0x1b0d, 0x1b0f, 0x1b10, 0x1b11, 0x1b11, 0x1b13, 0x1b14, 0x1b15, 0x1b16, 0x1b17, 0x1b18, 0x1b19, 0x1b1a, 0x1b1b, 0x1b1c, 0x1b1d, 0x1b1e, 0x1b1f, 0x1b20, 0x1b21, 0x1b22, 0x1b23, 0x1b24, 0x1b25, 0x1b26, 0x1b27, 0x1b28, 0x1b29, 0x1b2a, 0x1b2b, 0x1b2c, 0x1b2d, 0x1b2e, 0x1b2f, 0x1b30, 0x1b31, 0x1b32, 0x1b33, 0x1b34, 0x1b35, 0x1b36, 0x1b37, 0x1b38, 0x1b39, 0x1b3a, 0x1b3a, 0x1b3c, 0x1b3c, 0x1b3e, 0x1b3f, }; static const unsigned short gNormalizeTable1b40[] = { /* U+1b40 */ 0x1b3e, 0x1b3f, 0x1b42, 0x1b42, 0x1b44, 0x1b45, 0x1b46, 0x1b47, 0x1b48, 0x1b49, 0x1b4a, 0x1b4b, 0x1b4c, 0x1b4d, 0x1b4e, 0x1b4f, 0x1b50, 0x1b51, 0x1b52, 0x1b53, 0x1b54, 0x1b55, 0x1b56, 0x1b57, 0x1b58, 0x1b59, 0x1b5a, 0x1b5b, 0x1b5c, 0x1b5d, 0x1b5e, 0x1b5f, 0x1b60, 0x1b61, 0x1b62, 0x1b63, 0x1b64, 0x1b65, 0x1b66, 0x1b67, 0x1b68, 0x1b69, 0x1b6a, 0x1b6b, 0x1b6c, 0x1b6d, 0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72, 0x1b73, 0x1b74, 0x1b75, 0x1b76, 0x1b77, 0x1b78, 0x1b79, 0x1b7a, 0x1b7b, 0x1b7c, 0x1b7d, 0x1b7e, 0x1b7f, }; static const unsigned short gNormalizeTable1d00[] = { /* U+1d00 */ 0x1d00, 0x1d01, 0x1d02, 0x1d03, 0x1d04, 0x1d05, 0x1d06, 0x1d07, 0x1d08, 0x1d09, 0x1d0a, 0x1d0b, 0x1d0c, 0x1d0d, 0x1d0e, 0x1d0f, 0x1d10, 0x1d11, 0x1d12, 0x1d13, 0x1d14, 0x1d15, 0x1d16, 0x1d17, 0x1d18, 0x1d19, 0x1d1a, 0x1d1b, 0x1d1c, 0x1d1d, 0x1d1e, 0x1d1f, 0x1d20, 0x1d21, 0x1d22, 0x1d23, 0x1d24, 0x1d25, 0x1d26, 0x1d27, 0x1d28, 0x1d29, 0x1d2a, 0x1d2b, 0x0061, 0x00e6, 0x0062, 0x1d2f, 0x0064, 0x0065, 0x01dd, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x1d3b, 0x006f, 0x0223, 0x0070, 0x0072, }; static const unsigned short gNormalizeTable1d40[] = { /* U+1d40 */ 0x0074, 0x0075, 0x0077, 0x0061, 0x0250, 0x0251, 0x1d02, 0x0062, 0x0064, 0x0065, 0x0259, 0x025b, 0x025c, 0x0067, 0x1d4e, 0x006b, 0x006d, 0x014b, 0x006f, 0x0254, 0x1d16, 0x1d17, 0x0070, 0x0074, 0x0075, 0x1d1d, 0x026f, 0x0076, 0x1d25, 0x03b2, 0x03b3, 0x03b4, 0x03c6, 0x03c7, 0x0069, 0x0072, 0x0075, 0x0076, 0x03b2, 0x03b3, 0x03c1, 0x03c6, 0x03c7, 0x1d6b, 0x1d6c, 0x1d6d, 0x1d6e, 0x1d6f, 0x1d70, 0x1d71, 0x1d72, 0x1d73, 0x1d74, 0x1d75, 0x1d76, 0x1d77, 0x043d, 0x1d79, 0x1d7a, 0x1d7b, 0x1d7c, 0x1d7d, 0x1d7e, 0x1d7f, }; static const unsigned short gNormalizeTable1d80[] = { /* U+1d80 */ 0x1d80, 0x1d81, 0x1d82, 0x1d83, 0x1d84, 0x1d85, 0x1d86, 0x1d87, 0x1d88, 0x1d89, 0x1d8a, 0x1d8b, 0x1d8c, 0x1d8d, 0x1d8e, 0x1d8f, 0x1d90, 0x1d91, 0x1d92, 0x1d93, 0x1d94, 0x1d95, 0x1d96, 0x1d97, 0x1d98, 0x1d99, 0x1d9a, 0x0252, 0x0063, 0x0255, 0x00f0, 0x025c, 0x0066, 0x025f, 0x0261, 0x0265, 0x0268, 0x0269, 0x026a, 0x1d7b, 0x029d, 0x026d, 0x1d85, 0x029f, 0x0271, 0x0270, 0x0272, 0x0273, 0x0274, 0x0275, 0x0278, 0x0282, 0x0283, 0x01ab, 0x0289, 0x028a, 0x1d1c, 0x028b, 0x028c, 0x007a, 0x0290, 0x0291, 0x0292, 0x03b8, }; static const unsigned short gNormalizeTable1e00[] = { /* U+1e00 */ 0x0061, 0x0061, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0062, 0x0063, 0x0063, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0064, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0066, 0x0066, 0x0067, 0x0067, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0068, 0x0069, 0x0069, 0x0069, 0x0069, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006b, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006c, 0x006d, 0x006d, }; static const unsigned short gNormalizeTable1e40[] = { /* U+1e40 */ 0x006d, 0x006d, 0x006d, 0x006d, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x0070, 0x0070, 0x0070, 0x0070, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0072, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0073, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0074, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0076, 0x0076, 0x0076, 0x0076, }; static const unsigned short gNormalizeTable1e80[] = { /* U+1e80 */ 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0077, 0x0078, 0x0078, 0x0078, 0x0078, 0x0079, 0x0079, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x007a, 0x0068, 0x0074, 0x0077, 0x0079, 0x0061, 0x0073, 0x1e9c, 0x1e9d, 0x0073, 0x1e9f, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, }; static const unsigned short gNormalizeTable1ec0[] = { /* U+1ec0 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x1efb, 0x1efb, 0x1efd, 0x1efd, 0x1eff, 0x1eff, }; static const unsigned short gNormalizeTable1f00[] = { /* U+1f00 */ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x1f16, 0x1f17, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x03b5, 0x1f1e, 0x1f1f, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, }; static const unsigned short gNormalizeTable1f40[] = { /* U+1f40 */ 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x1f46, 0x1f47, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x03bf, 0x1f4e, 0x1f4f, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x1f58, 0x03c5, 0x1f5a, 0x03c5, 0x1f5c, 0x03c5, 0x1f5e, 0x03c5, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03b1, 0x03b1, 0x03b5, 0x03b5, 0x03b7, 0x03b7, 0x03b9, 0x03b9, 0x03bf, 0x03bf, 0x03c5, 0x03c5, 0x03c9, 0x03c9, 0x1f7e, 0x1f7f, }; static const unsigned short gNormalizeTable1f80[] = { /* U+1f80 */ 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03b7, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03c9, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x1fb5, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x03b1, 0x0020, 0x03b9, 0x0020, }; static const unsigned short gNormalizeTable1fc0[] = { /* U+1fc0 */ 0x0020, 0x0020, 0x03b7, 0x03b7, 0x03b7, 0x1fc5, 0x03b7, 0x03b7, 0x03b5, 0x03b5, 0x03b7, 0x03b7, 0x03b7, 0x0020, 0x0020, 0x0020, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x1fd4, 0x1fd5, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x03b9, 0x1fdc, 0x0020, 0x0020, 0x0020, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c1, 0x03c1, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c5, 0x03c1, 0x0020, 0x0020, 0x0060, 0x1ff0, 0x1ff1, 0x03c9, 0x03c9, 0x03c9, 0x1ff5, 0x03c9, 0x03c9, 0x03bf, 0x03bf, 0x03c9, 0x03c9, 0x03c9, 0x0020, 0x0020, 0x1fff, }; static const unsigned short gNormalizeTable2000[] = { /* U+2000 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x2010, 0x2010, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x0020, 0x2018, 0x2019, 0x201a, 0x201b, 0x201c, 0x201d, 0x201e, 0x201f, 0x2020, 0x2021, 0x2022, 0x2023, 0x002e, 0x002e, 0x002e, 0x2027, 0x2028, 0x2029, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x2030, 0x2031, 0x2032, 0x2032, 0x2032, 0x2035, 0x2035, 0x2035, 0x2038, 0x2039, 0x203a, 0x203b, 0x0021, 0x203d, 0x0020, 0x203f, }; static const unsigned short gNormalizeTable2040[] = { /* U+2040 */ 0x2040, 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x003f, 0x003f, 0x0021, 0x204a, 0x204b, 0x204c, 0x204d, 0x204e, 0x204f, 0x2050, 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2032, 0x2058, 0x2059, 0x205a, 0x205b, 0x205c, 0x205d, 0x205e, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0030, 0x0069, 0x2072, 0x2073, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x002b, 0x2212, 0x003d, 0x0028, 0x0029, 0x006e, }; static const unsigned short gNormalizeTable2080[] = { /* U+2080 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x002b, 0x2212, 0x003d, 0x0028, 0x0029, 0x208f, 0x0061, 0x0065, 0x006f, 0x0078, 0x0259, 0x2095, 0x2096, 0x2097, 0x2098, 0x2099, 0x209a, 0x209b, 0x209c, 0x209d, 0x209e, 0x209f, 0x20a0, 0x20a1, 0x20a2, 0x20a3, 0x20a4, 0x20a5, 0x20a6, 0x20a7, 0x0072, 0x20a9, 0x20aa, 0x20ab, 0x20ac, 0x20ad, 0x20ae, 0x20af, 0x20b0, 0x20b1, 0x20b2, 0x20b3, 0x20b4, 0x20b5, 0x20b6, 0x20b7, 0x20b8, 0x20b9, 0x20ba, 0x20bb, 0x20bc, 0x20bd, 0x20be, 0x20bf, }; static const unsigned short gNormalizeTable2100[] = { /* U+2100 */ 0x0061, 0x0061, 0x0063, 0x00b0, 0x2104, 0x0063, 0x0063, 0x025b, 0x2108, 0x00b0, 0x0067, 0x0068, 0x0068, 0x0068, 0x0068, 0x0127, 0x0069, 0x0069, 0x006c, 0x006c, 0x2114, 0x006e, 0x006e, 0x2117, 0x2118, 0x0070, 0x0071, 0x0072, 0x0072, 0x0072, 0x211e, 0x211f, 0x0073, 0x0074, 0x0074, 0x2123, 0x007a, 0x2125, 0x03c9, 0x2127, 0x007a, 0x2129, 0x006b, 0x0061, 0x0062, 0x0063, 0x212e, 0x0065, 0x0065, 0x0066, 0x214e, 0x006d, 0x006f, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x0069, 0x213a, 0x0066, 0x03c0, 0x03b3, 0x03b3, 0x03c0, }; static const unsigned short gNormalizeTable2140[] = { /* U+2140 */ 0x2211, 0x2141, 0x2142, 0x2143, 0x2144, 0x0064, 0x0064, 0x0065, 0x0069, 0x006a, 0x214a, 0x214b, 0x214c, 0x214d, 0x214e, 0x214f, 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0031, 0x0032, 0x0033, 0x0034, 0x0031, 0x0035, 0x0031, 0x0033, 0x0035, 0x0037, 0x0031, 0x0069, 0x0069, 0x0069, 0x0069, 0x0076, 0x0076, 0x0076, 0x0076, 0x0069, 0x0078, 0x0078, 0x0078, 0x006c, 0x0063, 0x0064, 0x006d, 0x0069, 0x0069, 0x0069, 0x0069, 0x0076, 0x0076, 0x0076, 0x0076, 0x0069, 0x0078, 0x0078, 0x0078, 0x006c, 0x0063, 0x0064, 0x006d, }; static const unsigned short gNormalizeTable2180[] = { /* U+2180 */ 0x2180, 0x2181, 0x2182, 0x2184, 0x2184, 0x2185, 0x2186, 0x2187, 0x2188, 0x0030, 0x218a, 0x218b, 0x218c, 0x218d, 0x218e, 0x218f, 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x2190, 0x2192, 0x219c, 0x219d, 0x219e, 0x219f, 0x21a0, 0x21a1, 0x21a2, 0x21a3, 0x21a4, 0x21a5, 0x21a6, 0x21a7, 0x21a8, 0x21a9, 0x21aa, 0x21ab, 0x21ac, 0x21ad, 0x2194, 0x21af, 0x21b0, 0x21b1, 0x21b2, 0x21b3, 0x21b4, 0x21b5, 0x21b6, 0x21b7, 0x21b8, 0x21b9, 0x21ba, 0x21bb, 0x21bc, 0x21bd, 0x21be, 0x21bf, }; static const unsigned short gNormalizeTable21c0[] = { /* U+21c0 */ 0x21c0, 0x21c1, 0x21c2, 0x21c3, 0x21c4, 0x21c5, 0x21c6, 0x21c7, 0x21c8, 0x21c9, 0x21ca, 0x21cb, 0x21cc, 0x21d0, 0x21d4, 0x21d2, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x21d5, 0x21d6, 0x21d7, 0x21d8, 0x21d9, 0x21da, 0x21db, 0x21dc, 0x21dd, 0x21de, 0x21df, 0x21e0, 0x21e1, 0x21e2, 0x21e3, 0x21e4, 0x21e5, 0x21e6, 0x21e7, 0x21e8, 0x21e9, 0x21ea, 0x21eb, 0x21ec, 0x21ed, 0x21ee, 0x21ef, 0x21f0, 0x21f1, 0x21f2, 0x21f3, 0x21f4, 0x21f5, 0x21f6, 0x21f7, 0x21f8, 0x21f9, 0x21fa, 0x21fb, 0x21fc, 0x21fd, 0x21fe, 0x21ff, }; static const unsigned short gNormalizeTable2200[] = { /* U+2200 */ 0x2200, 0x2201, 0x2202, 0x2203, 0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2208, 0x220a, 0x220b, 0x220b, 0x220d, 0x220e, 0x220f, 0x2210, 0x2211, 0x2212, 0x2213, 0x2214, 0x2215, 0x2216, 0x2217, 0x2218, 0x2219, 0x221a, 0x221b, 0x221c, 0x221d, 0x221e, 0x221f, 0x2220, 0x2221, 0x2222, 0x2223, 0x2223, 0x2225, 0x2225, 0x2227, 0x2228, 0x2229, 0x222a, 0x222b, 0x222b, 0x222b, 0x222e, 0x222e, 0x222e, 0x2231, 0x2232, 0x2233, 0x2234, 0x2235, 0x2236, 0x2237, 0x2238, 0x2239, 0x223a, 0x223b, 0x223c, 0x223d, 0x223e, 0x223f, }; static const unsigned short gNormalizeTable2240[] = { /* U+2240 */ 0x2240, 0x223c, 0x2242, 0x2243, 0x2243, 0x2245, 0x2246, 0x2245, 0x2248, 0x2248, 0x224a, 0x224b, 0x224c, 0x224d, 0x224e, 0x224f, 0x2250, 0x2251, 0x2252, 0x2253, 0x2254, 0x2255, 0x2256, 0x2257, 0x2258, 0x2259, 0x225a, 0x225b, 0x225c, 0x225d, 0x225e, 0x225f, 0x003d, 0x2261, 0x2261, 0x2263, 0x2264, 0x2265, 0x2266, 0x2267, 0x2268, 0x2269, 0x226a, 0x226b, 0x226c, 0x224d, 0x003c, 0x003e, 0x2264, 0x2265, 0x2272, 0x2273, 0x2272, 0x2273, 0x2276, 0x2277, 0x2276, 0x2277, 0x227a, 0x227b, 0x227c, 0x227d, 0x227e, 0x227f, }; static const unsigned short gNormalizeTable2280[] = { /* U+2280 */ 0x227a, 0x227b, 0x2282, 0x2283, 0x2282, 0x2283, 0x2286, 0x2287, 0x2286, 0x2287, 0x228a, 0x228b, 0x228c, 0x228d, 0x228e, 0x228f, 0x2290, 0x2291, 0x2292, 0x2293, 0x2294, 0x2295, 0x2296, 0x2297, 0x2298, 0x2299, 0x229a, 0x229b, 0x229c, 0x229d, 0x229e, 0x229f, 0x22a0, 0x22a1, 0x22a2, 0x22a3, 0x22a4, 0x22a5, 0x22a6, 0x22a7, 0x22a8, 0x22a9, 0x22aa, 0x22ab, 0x22a2, 0x22a8, 0x22a9, 0x22ab, 0x22b0, 0x22b1, 0x22b2, 0x22b3, 0x22b4, 0x22b5, 0x22b6, 0x22b7, 0x22b8, 0x22b9, 0x22ba, 0x22bb, 0x22bc, 0x22bd, 0x22be, 0x22bf, }; static const unsigned short gNormalizeTable22c0[] = { /* U+22c0 */ 0x22c0, 0x22c1, 0x22c2, 0x22c3, 0x22c4, 0x22c5, 0x22c6, 0x22c7, 0x22c8, 0x22c9, 0x22ca, 0x22cb, 0x22cc, 0x22cd, 0x22ce, 0x22cf, 0x22d0, 0x22d1, 0x22d2, 0x22d3, 0x22d4, 0x22d5, 0x22d6, 0x22d7, 0x22d8, 0x22d9, 0x22da, 0x22db, 0x22dc, 0x22dd, 0x22de, 0x22df, 0x227c, 0x227d, 0x2291, 0x2292, 0x22e4, 0x22e5, 0x22e6, 0x22e7, 0x22e8, 0x22e9, 0x22b2, 0x22b3, 0x22b4, 0x22b5, 0x22ee, 0x22ef, 0x22f0, 0x22f1, 0x22f2, 0x22f3, 0x22f4, 0x22f5, 0x22f6, 0x22f7, 0x22f8, 0x22f9, 0x22fa, 0x22fb, 0x22fc, 0x22fd, 0x22fe, 0x22ff, }; static const unsigned short gNormalizeTable2300[] = { /* U+2300 */ 0x2300, 0x2301, 0x2302, 0x2303, 0x2304, 0x2305, 0x2306, 0x2307, 0x2308, 0x2309, 0x230a, 0x230b, 0x230c, 0x230d, 0x230e, 0x230f, 0x2310, 0x2311, 0x2312, 0x2313, 0x2314, 0x2315, 0x2316, 0x2317, 0x2318, 0x2319, 0x231a, 0x231b, 0x231c, 0x231d, 0x231e, 0x231f, 0x2320, 0x2321, 0x2322, 0x2323, 0x2324, 0x2325, 0x2326, 0x2327, 0x2328, 0x3008, 0x3009, 0x232b, 0x232c, 0x232d, 0x232e, 0x232f, 0x2330, 0x2331, 0x2332, 0x2333, 0x2334, 0x2335, 0x2336, 0x2337, 0x2338, 0x2339, 0x233a, 0x233b, 0x233c, 0x233d, 0x233e, 0x233f, }; static const unsigned short gNormalizeTable2440[] = { /* U+2440 */ 0x2440, 0x2441, 0x2442, 0x2443, 0x2444, 0x2445, 0x2446, 0x2447, 0x2448, 0x2449, 0x244a, 0x244b, 0x244c, 0x244d, 0x244e, 0x244f, 0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456, 0x2457, 0x2458, 0x2459, 0x245a, 0x245b, 0x245c, 0x245d, 0x245e, 0x245f, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, }; static const unsigned short gNormalizeTable2480[] = { /* U+2480 */ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, }; static const unsigned short gNormalizeTable24c0[] = { /* U+24c0 */ 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0030, 0x24eb, 0x24ec, 0x24ed, 0x24ee, 0x24ef, 0x24f0, 0x24f1, 0x24f2, 0x24f3, 0x24f4, 0x24f5, 0x24f6, 0x24f7, 0x24f8, 0x24f9, 0x24fa, 0x24fb, 0x24fc, 0x24fd, 0x24fe, 0x24ff, }; static const unsigned short gNormalizeTable2a00[] = { /* U+2a00 */ 0x2a00, 0x2a01, 0x2a02, 0x2a03, 0x2a04, 0x2a05, 0x2a06, 0x2a07, 0x2a08, 0x2a09, 0x2a0a, 0x2a0b, 0x222b, 0x2a0d, 0x2a0e, 0x2a0f, 0x2a10, 0x2a11, 0x2a12, 0x2a13, 0x2a14, 0x2a15, 0x2a16, 0x2a17, 0x2a18, 0x2a19, 0x2a1a, 0x2a1b, 0x2a1c, 0x2a1d, 0x2a1e, 0x2a1f, 0x2a20, 0x2a21, 0x2a22, 0x2a23, 0x2a24, 0x2a25, 0x2a26, 0x2a27, 0x2a28, 0x2a29, 0x2a2a, 0x2a2b, 0x2a2c, 0x2a2d, 0x2a2e, 0x2a2f, 0x2a30, 0x2a31, 0x2a32, 0x2a33, 0x2a34, 0x2a35, 0x2a36, 0x2a37, 0x2a38, 0x2a39, 0x2a3a, 0x2a3b, 0x2a3c, 0x2a3d, 0x2a3e, 0x2a3f, }; static const unsigned short gNormalizeTable2a40[] = { /* U+2a40 */ 0x2a40, 0x2a41, 0x2a42, 0x2a43, 0x2a44, 0x2a45, 0x2a46, 0x2a47, 0x2a48, 0x2a49, 0x2a4a, 0x2a4b, 0x2a4c, 0x2a4d, 0x2a4e, 0x2a4f, 0x2a50, 0x2a51, 0x2a52, 0x2a53, 0x2a54, 0x2a55, 0x2a56, 0x2a57, 0x2a58, 0x2a59, 0x2a5a, 0x2a5b, 0x2a5c, 0x2a5d, 0x2a5e, 0x2a5f, 0x2a60, 0x2a61, 0x2a62, 0x2a63, 0x2a64, 0x2a65, 0x2a66, 0x2a67, 0x2a68, 0x2a69, 0x2a6a, 0x2a6b, 0x2a6c, 0x2a6d, 0x2a6e, 0x2a6f, 0x2a70, 0x2a71, 0x2a72, 0x2a73, 0x003a, 0x003d, 0x003d, 0x2a77, 0x2a78, 0x2a79, 0x2a7a, 0x2a7b, 0x2a7c, 0x2a7d, 0x2a7e, 0x2a7f, }; static const unsigned short gNormalizeTable2ac0[] = { /* U+2ac0 */ 0x2ac0, 0x2ac1, 0x2ac2, 0x2ac3, 0x2ac4, 0x2ac5, 0x2ac6, 0x2ac7, 0x2ac8, 0x2ac9, 0x2aca, 0x2acb, 0x2acc, 0x2acd, 0x2ace, 0x2acf, 0x2ad0, 0x2ad1, 0x2ad2, 0x2ad3, 0x2ad4, 0x2ad5, 0x2ad6, 0x2ad7, 0x2ad8, 0x2ad9, 0x2ada, 0x2adb, 0x2add, 0x2add, 0x2ade, 0x2adf, 0x2ae0, 0x2ae1, 0x2ae2, 0x2ae3, 0x2ae4, 0x2ae5, 0x2ae6, 0x2ae7, 0x2ae8, 0x2ae9, 0x2aea, 0x2aeb, 0x2aec, 0x2aed, 0x2aee, 0x2aef, 0x2af0, 0x2af1, 0x2af2, 0x2af3, 0x2af4, 0x2af5, 0x2af6, 0x2af7, 0x2af8, 0x2af9, 0x2afa, 0x2afb, 0x2afc, 0x2afd, 0x2afe, 0x2aff, }; static const unsigned short gNormalizeTable2c00[] = { /* U+2c00 */ 0x2c30, 0x2c31, 0x2c32, 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37, 0x2c38, 0x2c39, 0x2c3a, 0x2c3b, 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f, 0x2c40, 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2c48, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c50, 0x2c51, 0x2c52, 0x2c53, 0x2c54, 0x2c55, 0x2c56, 0x2c57, 0x2c58, 0x2c59, 0x2c5a, 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c2f, 0x2c30, 0x2c31, 0x2c32, 0x2c33, 0x2c34, 0x2c35, 0x2c36, 0x2c37, 0x2c38, 0x2c39, 0x2c3a, 0x2c3b, 0x2c3c, 0x2c3d, 0x2c3e, 0x2c3f, }; static const unsigned short gNormalizeTable2c40[] = { /* U+2c40 */ 0x2c40, 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2c48, 0x2c49, 0x2c4a, 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c50, 0x2c51, 0x2c52, 0x2c53, 0x2c54, 0x2c55, 0x2c56, 0x2c57, 0x2c58, 0x2c59, 0x2c5a, 0x2c5b, 0x2c5c, 0x2c5d, 0x2c5e, 0x2c5f, 0x2c61, 0x2c61, 0x026b, 0x1d7d, 0x027d, 0x2c65, 0x2c66, 0x2c68, 0x2c68, 0x2c6a, 0x2c6a, 0x2c6c, 0x2c6c, 0x0251, 0x0271, 0x0250, 0x0252, 0x2c71, 0x2c73, 0x2c73, 0x2c74, 0x2c76, 0x2c76, 0x2c77, 0x2c78, 0x2c79, 0x2c7a, 0x2c7b, 0x006a, 0x0076, 0x023f, 0x0240, }; static const unsigned short gNormalizeTable2c80[] = { /* U+2c80 */ 0x2c81, 0x2c81, 0x2c83, 0x2c83, 0x2c85, 0x2c85, 0x2c87, 0x2c87, 0x2c89, 0x2c89, 0x2c8b, 0x2c8b, 0x2c8d, 0x2c8d, 0x2c8f, 0x2c8f, 0x2c91, 0x2c91, 0x2c93, 0x2c93, 0x2c95, 0x2c95, 0x2c97, 0x2c97, 0x2c99, 0x2c99, 0x2c9b, 0x2c9b, 0x2c9d, 0x2c9d, 0x2c9f, 0x2c9f, 0x2ca1, 0x2ca1, 0x2ca3, 0x2ca3, 0x2ca5, 0x2ca5, 0x2ca7, 0x2ca7, 0x2ca9, 0x2ca9, 0x2cab, 0x2cab, 0x2cad, 0x2cad, 0x2caf, 0x2caf, 0x2cb1, 0x2cb1, 0x2cb3, 0x2cb3, 0x2cb5, 0x2cb5, 0x2cb7, 0x2cb7, 0x2cb9, 0x2cb9, 0x2cbb, 0x2cbb, 0x2cbd, 0x2cbd, 0x2cbf, 0x2cbf, }; static const unsigned short gNormalizeTable2cc0[] = { /* U+2cc0 */ 0x2cc1, 0x2cc1, 0x2cc3, 0x2cc3, 0x2cc5, 0x2cc5, 0x2cc7, 0x2cc7, 0x2cc9, 0x2cc9, 0x2ccb, 0x2ccb, 0x2ccd, 0x2ccd, 0x2ccf, 0x2ccf, 0x2cd1, 0x2cd1, 0x2cd3, 0x2cd3, 0x2cd5, 0x2cd5, 0x2cd7, 0x2cd7, 0x2cd9, 0x2cd9, 0x2cdb, 0x2cdb, 0x2cdd, 0x2cdd, 0x2cdf, 0x2cdf, 0x2ce1, 0x2ce1, 0x2ce3, 0x2ce3, 0x2ce4, 0x2ce5, 0x2ce6, 0x2ce7, 0x2ce8, 0x2ce9, 0x2cea, 0x2cec, 0x2cec, 0x2cee, 0x2cee, 0x2cef, 0x2cf0, 0x2cf1, 0x2cf2, 0x2cf3, 0x2cf4, 0x2cf5, 0x2cf6, 0x2cf7, 0x2cf8, 0x2cf9, 0x2cfa, 0x2cfb, 0x2cfc, 0x2cfd, 0x2cfe, 0x2cff, }; static const unsigned short gNormalizeTable2d40[] = { /* U+2d40 */ 0x2d40, 0x2d41, 0x2d42, 0x2d43, 0x2d44, 0x2d45, 0x2d46, 0x2d47, 0x2d48, 0x2d49, 0x2d4a, 0x2d4b, 0x2d4c, 0x2d4d, 0x2d4e, 0x2d4f, 0x2d50, 0x2d51, 0x2d52, 0x2d53, 0x2d54, 0x2d55, 0x2d56, 0x2d57, 0x2d58, 0x2d59, 0x2d5a, 0x2d5b, 0x2d5c, 0x2d5d, 0x2d5e, 0x2d5f, 0x2d60, 0x2d61, 0x2d62, 0x2d63, 0x2d64, 0x2d65, 0x2d66, 0x2d67, 0x2d68, 0x2d69, 0x2d6a, 0x2d6b, 0x2d6c, 0x2d6d, 0x2d6e, 0x2d61, 0x2d70, 0x2d71, 0x2d72, 0x2d73, 0x2d74, 0x2d75, 0x2d76, 0x2d77, 0x2d78, 0x2d79, 0x2d7a, 0x2d7b, 0x2d7c, 0x2d7d, 0x2d7e, 0x2d7f, }; static const unsigned short gNormalizeTable2e80[] = { /* U+2e80 */ 0x2e80, 0x2e81, 0x2e82, 0x2e83, 0x2e84, 0x2e85, 0x2e86, 0x2e87, 0x2e88, 0x2e89, 0x2e8a, 0x2e8b, 0x2e8c, 0x2e8d, 0x2e8e, 0x2e8f, 0x2e90, 0x2e91, 0x2e92, 0x2e93, 0x2e94, 0x2e95, 0x2e96, 0x2e97, 0x2e98, 0x2e99, 0x2e9a, 0x2e9b, 0x2e9c, 0x2e9d, 0x2e9e, 0x6bcd, 0x2ea0, 0x2ea1, 0x2ea2, 0x2ea3, 0x2ea4, 0x2ea5, 0x2ea6, 0x2ea7, 0x2ea8, 0x2ea9, 0x2eaa, 0x2eab, 0x2eac, 0x2ead, 0x2eae, 0x2eaf, 0x2eb0, 0x2eb1, 0x2eb2, 0x2eb3, 0x2eb4, 0x2eb5, 0x2eb6, 0x2eb7, 0x2eb8, 0x2eb9, 0x2eba, 0x2ebb, 0x2ebc, 0x2ebd, 0x2ebe, 0x2ebf, }; static const unsigned short gNormalizeTable2ec0[] = { /* U+2ec0 */ 0x2ec0, 0x2ec1, 0x2ec2, 0x2ec3, 0x2ec4, 0x2ec5, 0x2ec6, 0x2ec7, 0x2ec8, 0x2ec9, 0x2eca, 0x2ecb, 0x2ecc, 0x2ecd, 0x2ece, 0x2ecf, 0x2ed0, 0x2ed1, 0x2ed2, 0x2ed3, 0x2ed4, 0x2ed5, 0x2ed6, 0x2ed7, 0x2ed8, 0x2ed9, 0x2eda, 0x2edb, 0x2edc, 0x2edd, 0x2ede, 0x2edf, 0x2ee0, 0x2ee1, 0x2ee2, 0x2ee3, 0x2ee4, 0x2ee5, 0x2ee6, 0x2ee7, 0x2ee8, 0x2ee9, 0x2eea, 0x2eeb, 0x2eec, 0x2eed, 0x2eee, 0x2eef, 0x2ef0, 0x2ef1, 0x2ef2, 0x9f9f, 0x2ef4, 0x2ef5, 0x2ef6, 0x2ef7, 0x2ef8, 0x2ef9, 0x2efa, 0x2efb, 0x2efc, 0x2efd, 0x2efe, 0x2eff, }; static const unsigned short gNormalizeTable2f00[] = { /* U+2f00 */ 0x4e00, 0x4e28, 0x4e36, 0x4e3f, 0x4e59, 0x4e85, 0x4e8c, 0x4ea0, 0x4eba, 0x513f, 0x5165, 0x516b, 0x5182, 0x5196, 0x51ab, 0x51e0, 0x51f5, 0x5200, 0x529b, 0x52f9, 0x5315, 0x531a, 0x5338, 0x5341, 0x535c, 0x5369, 0x5382, 0x53b6, 0x53c8, 0x53e3, 0x56d7, 0x571f, 0x58eb, 0x5902, 0x590a, 0x5915, 0x5927, 0x5973, 0x5b50, 0x5b80, 0x5bf8, 0x5c0f, 0x5c22, 0x5c38, 0x5c6e, 0x5c71, 0x5ddb, 0x5de5, 0x5df1, 0x5dfe, 0x5e72, 0x5e7a, 0x5e7f, 0x5ef4, 0x5efe, 0x5f0b, 0x5f13, 0x5f50, 0x5f61, 0x5f73, 0x5fc3, 0x6208, 0x6236, 0x624b, }; static const unsigned short gNormalizeTable2f40[] = { /* U+2f40 */ 0x652f, 0x6534, 0x6587, 0x6597, 0x65a4, 0x65b9, 0x65e0, 0x65e5, 0x66f0, 0x6708, 0x6728, 0x6b20, 0x6b62, 0x6b79, 0x6bb3, 0x6bcb, 0x6bd4, 0x6bdb, 0x6c0f, 0x6c14, 0x6c34, 0x706b, 0x722a, 0x7236, 0x723b, 0x723f, 0x7247, 0x7259, 0x725b, 0x72ac, 0x7384, 0x7389, 0x74dc, 0x74e6, 0x7518, 0x751f, 0x7528, 0x7530, 0x758b, 0x7592, 0x7676, 0x767d, 0x76ae, 0x76bf, 0x76ee, 0x77db, 0x77e2, 0x77f3, 0x793a, 0x79b8, 0x79be, 0x7a74, 0x7acb, 0x7af9, 0x7c73, 0x7cf8, 0x7f36, 0x7f51, 0x7f8a, 0x7fbd, 0x8001, 0x800c, 0x8012, 0x8033, }; static const unsigned short gNormalizeTable2f80[] = { /* U+2f80 */ 0x807f, 0x8089, 0x81e3, 0x81ea, 0x81f3, 0x81fc, 0x820c, 0x821b, 0x821f, 0x826e, 0x8272, 0x8278, 0x864d, 0x866b, 0x8840, 0x884c, 0x8863, 0x897e, 0x898b, 0x89d2, 0x8a00, 0x8c37, 0x8c46, 0x8c55, 0x8c78, 0x8c9d, 0x8d64, 0x8d70, 0x8db3, 0x8eab, 0x8eca, 0x8f9b, 0x8fb0, 0x8fb5, 0x9091, 0x9149, 0x91c6, 0x91cc, 0x91d1, 0x9577, 0x9580, 0x961c, 0x96b6, 0x96b9, 0x96e8, 0x9751, 0x975e, 0x9762, 0x9769, 0x97cb, 0x97ed, 0x97f3, 0x9801, 0x98a8, 0x98db, 0x98df, 0x9996, 0x9999, 0x99ac, 0x9aa8, 0x9ad8, 0x9adf, 0x9b25, 0x9b2f, }; static const unsigned short gNormalizeTable2fc0[] = { /* U+2fc0 */ 0x9b32, 0x9b3c, 0x9b5a, 0x9ce5, 0x9e75, 0x9e7f, 0x9ea5, 0x9ebb, 0x9ec3, 0x9ecd, 0x9ed1, 0x9ef9, 0x9efd, 0x9f0e, 0x9f13, 0x9f20, 0x9f3b, 0x9f4a, 0x9f52, 0x9f8d, 0x9f9c, 0x9fa0, 0x2fd6, 0x2fd7, 0x2fd8, 0x2fd9, 0x2fda, 0x2fdb, 0x2fdc, 0x2fdd, 0x2fde, 0x2fdf, 0x2fe0, 0x2fe1, 0x2fe2, 0x2fe3, 0x2fe4, 0x2fe5, 0x2fe6, 0x2fe7, 0x2fe8, 0x2fe9, 0x2fea, 0x2feb, 0x2fec, 0x2fed, 0x2fee, 0x2fef, 0x2ff0, 0x2ff1, 0x2ff2, 0x2ff3, 0x2ff4, 0x2ff5, 0x2ff6, 0x2ff7, 0x2ff8, 0x2ff9, 0x2ffa, 0x2ffb, 0x2ffc, 0x2ffd, 0x2ffe, 0x2fff, }; static const unsigned short gNormalizeTable3000[] = { /* U+3000 */ 0x0020, 0x3001, 0x3002, 0x3003, 0x3004, 0x3005, 0x3006, 0x3007, 0x3008, 0x3009, 0x300a, 0x300b, 0x300c, 0x300d, 0x300e, 0x300f, 0x3010, 0x3011, 0x3012, 0x3013, 0x3014, 0x3015, 0x3016, 0x3017, 0x3018, 0x3019, 0x301a, 0x301b, 0x301c, 0x301d, 0x301e, 0x301f, 0x3020, 0x3021, 0x3022, 0x3023, 0x3024, 0x3025, 0x3026, 0x3027, 0x3028, 0x3029, 0x302a, 0x302b, 0x302c, 0x302d, 0x302e, 0x302f, 0x3030, 0x3031, 0x3032, 0x3033, 0x3034, 0x3035, 0x3012, 0x3037, 0x5341, 0x5344, 0x5345, 0x303b, 0x303c, 0x303d, 0x303e, 0x303f, }; static const unsigned short gNormalizeTable3040[] = { /* U+3040 */ 0x3040, 0x3041, 0x3042, 0x3043, 0x3044, 0x3045, 0x3046, 0x3047, 0x3048, 0x3049, 0x304a, 0x304b, 0x304b, 0x304d, 0x304d, 0x304f, 0x304f, 0x3051, 0x3051, 0x3053, 0x3053, 0x3055, 0x3055, 0x3057, 0x3057, 0x3059, 0x3059, 0x305b, 0x305b, 0x305d, 0x305d, 0x305f, 0x305f, 0x3061, 0x3061, 0x3063, 0x3064, 0x3064, 0x3066, 0x3066, 0x3068, 0x3068, 0x306a, 0x306b, 0x306c, 0x306d, 0x306e, 0x306f, 0x306f, 0x306f, 0x3072, 0x3072, 0x3072, 0x3075, 0x3075, 0x3075, 0x3078, 0x3078, 0x3078, 0x307b, 0x307b, 0x307b, 0x307e, 0x307f, }; static const unsigned short gNormalizeTable3080[] = { /* U+3080 */ 0x3080, 0x3081, 0x3082, 0x3083, 0x3084, 0x3085, 0x3086, 0x3087, 0x3088, 0x3089, 0x308a, 0x308b, 0x308c, 0x308d, 0x308e, 0x308f, 0x3090, 0x3091, 0x3092, 0x3093, 0x3046, 0x3095, 0x3096, 0x3097, 0x3098, 0x3099, 0x309a, 0x0020, 0x0020, 0x309d, 0x309d, 0x3088, 0x30a0, 0x30a1, 0x30a2, 0x30a3, 0x30a4, 0x30a5, 0x30a6, 0x30a7, 0x30a8, 0x30a9, 0x30aa, 0x30ab, 0x30ab, 0x30ad, 0x30ad, 0x30af, 0x30af, 0x30b1, 0x30b1, 0x30b3, 0x30b3, 0x30b5, 0x30b5, 0x30b7, 0x30b7, 0x30b9, 0x30b9, 0x30bb, 0x30bb, 0x30bd, 0x30bd, 0x30bf, }; static const unsigned short gNormalizeTable30c0[] = { /* U+30c0 */ 0x30bf, 0x30c1, 0x30c1, 0x30c3, 0x30c4, 0x30c4, 0x30c6, 0x30c6, 0x30c8, 0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30cf, 0x30cf, 0x30d2, 0x30d2, 0x30d2, 0x30d5, 0x30d5, 0x30d5, 0x30d8, 0x30d8, 0x30d8, 0x30db, 0x30db, 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e3, 0x30e4, 0x30e5, 0x30e6, 0x30e7, 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ee, 0x30ef, 0x30f0, 0x30f1, 0x30f2, 0x30f3, 0x30a6, 0x30f5, 0x30f6, 0x30ef, 0x30f0, 0x30f1, 0x30f2, 0x30fb, 0x30fc, 0x30fd, 0x30fd, 0x30b3, }; static const unsigned short gNormalizeTable3100[] = { /* U+3100 */ 0x3100, 0x3101, 0x3102, 0x3103, 0x3104, 0x3105, 0x3106, 0x3107, 0x3108, 0x3109, 0x310a, 0x310b, 0x310c, 0x310d, 0x310e, 0x310f, 0x3110, 0x3111, 0x3112, 0x3113, 0x3114, 0x3115, 0x3116, 0x3117, 0x3118, 0x3119, 0x311a, 0x311b, 0x311c, 0x311d, 0x311e, 0x311f, 0x3120, 0x3121, 0x3122, 0x3123, 0x3124, 0x3125, 0x3126, 0x3127, 0x3128, 0x3129, 0x312a, 0x312b, 0x312c, 0x312d, 0x312e, 0x312f, 0x3130, 0x1100, 0x1101, 0x11aa, 0x1102, 0x11ac, 0x11ad, 0x1103, 0x1104, 0x1105, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4, 0x11b5, }; static const unsigned short gNormalizeTable3140[] = { /* U+3140 */ 0x111a, 0x1106, 0x1107, 0x1108, 0x1121, 0x1109, 0x110a, 0x110b, 0x110c, 0x110d, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0x1161, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, 0x1169, 0x116a, 0x116b, 0x116c, 0x116d, 0x116e, 0x116f, 0x1170, 0x1171, 0x1172, 0x1173, 0x1174, 0x1175, 0x0020, 0x1114, 0x1115, 0x11c7, 0x11c8, 0x11cc, 0x11ce, 0x11d3, 0x11d7, 0x11d9, 0x111c, 0x11dd, 0x11df, 0x111d, 0x111e, 0x1120, 0x1122, 0x1123, 0x1127, 0x1129, 0x112b, 0x112c, 0x112d, 0x112e, 0x112f, 0x1132, 0x1136, 0x1140, }; static const unsigned short gNormalizeTable3180[] = { /* U+3180 */ 0x1147, 0x114c, 0x11f1, 0x11f2, 0x1157, 0x1158, 0x1159, 0x1184, 0x1185, 0x1188, 0x1191, 0x1192, 0x1194, 0x119e, 0x11a1, 0x318f, 0x3190, 0x3191, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e0a, 0x4e2d, 0x4e0b, 0x7532, 0x4e59, 0x4e19, 0x4e01, 0x5929, 0x5730, 0x4eba, 0x31a0, 0x31a1, 0x31a2, 0x31a3, 0x31a4, 0x31a5, 0x31a6, 0x31a7, 0x31a8, 0x31a9, 0x31aa, 0x31ab, 0x31ac, 0x31ad, 0x31ae, 0x31af, 0x31b0, 0x31b1, 0x31b2, 0x31b3, 0x31b4, 0x31b5, 0x31b6, 0x31b7, 0x31b8, 0x31b9, 0x31ba, 0x31bb, 0x31bc, 0x31bd, 0x31be, 0x31bf, }; static const unsigned short gNormalizeTable3200[] = { /* U+3200 */ 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x321f, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, 0x0028, }; static const unsigned short gNormalizeTable3240[] = { /* U+3240 */ 0x0028, 0x0028, 0x0028, 0x0028, 0x554f, 0x5e7c, 0x6587, 0x7b8f, 0x3248, 0x3249, 0x324a, 0x324b, 0x324c, 0x324d, 0x324e, 0x324f, 0x0070, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0033, 0x0033, 0x0033, 0x0033, 0x0033, 0x0033, 0x1100, 0x1102, 0x1103, 0x1105, 0x1106, 0x1107, 0x1109, 0x110b, 0x110c, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0x1100, 0x1102, 0x1103, 0x1105, 0x1106, 0x1107, 0x1109, 0x110b, 0x110c, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0x110e, 0x110c, 0x110b, 0x327f, }; static const unsigned short gNormalizeTable3280[] = { /* U+3280 */ 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b, 0x4e5d, 0x5341, 0x6708, 0x706b, 0x6c34, 0x6728, 0x91d1, 0x571f, 0x65e5, 0x682a, 0x6709, 0x793e, 0x540d, 0x7279, 0x8ca1, 0x795d, 0x52b4, 0x79d8, 0x7537, 0x5973, 0x9069, 0x512a, 0x5370, 0x6ce8, 0x9805, 0x4f11, 0x5199, 0x6b63, 0x4e0a, 0x4e2d, 0x4e0b, 0x5de6, 0x53f3, 0x533b, 0x5b97, 0x5b66, 0x76e3, 0x4f01, 0x8cc7, 0x5354, 0x591c, 0x0033, 0x0033, 0x0033, 0x0033, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0034, 0x0035, }; static const unsigned short gNormalizeTable32c0[] = { /* U+32c0 */ 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0068, 0x0065, 0x0065, 0x006c, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa, 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9, 0x30bb, 0x30bd, 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8, 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6, 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f0, 0x30f1, 0x30f2, 0x32ff, }; static const unsigned short gNormalizeTable3300[] = { /* U+3300 */ 0x30a2, 0x30a2, 0x30a2, 0x30a2, 0x30a4, 0x30a4, 0x30a6, 0x30a8, 0x30a8, 0x30aa, 0x30aa, 0x30ab, 0x30ab, 0x30ab, 0x30ab, 0x30ab, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30ad, 0x30af, 0x30af, 0x30af, 0x30af, 0x30b1, 0x30b3, 0x30b3, 0x30b5, 0x30b5, 0x30b7, 0x30bb, 0x30bb, 0x30bf, 0x30c6, 0x30c8, 0x30c8, 0x30ca, 0x30ce, 0x30cf, 0x30cf, 0x30cf, 0x30cf, 0x30d2, 0x30d2, 0x30d2, 0x30d2, 0x30d5, 0x30d5, 0x30d5, 0x30d5, 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30d8, 0x30db, 0x30db, 0x30db, }; static const unsigned short gNormalizeTable3340[] = { /* U+3340 */ 0x30db, 0x30db, 0x30db, 0x30de, 0x30de, 0x30de, 0x30de, 0x30de, 0x30df, 0x30df, 0x30df, 0x30e1, 0x30e1, 0x30e1, 0x30e4, 0x30e4, 0x30e6, 0x30ea, 0x30ea, 0x30eb, 0x30eb, 0x30ec, 0x30ec, 0x30ef, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0068, 0x0064, 0x0061, 0x0062, 0x006f, 0x0070, 0x0064, 0x0064, 0x0064, 0x0069, 0x5e73, 0x662d, 0x5927, 0x660e, 0x682a, }; static const unsigned short gNormalizeTable3380[] = { /* U+3380 */ 0x0070, 0x006e, 0x03bc, 0x006d, 0x006b, 0x006b, 0x006d, 0x0067, 0x0063, 0x006b, 0x0070, 0x006e, 0x03bc, 0x03bc, 0x006d, 0x006b, 0x0068, 0x006b, 0x006d, 0x0067, 0x0074, 0x03bc, 0x006d, 0x0064, 0x006b, 0x0066, 0x006e, 0x03bc, 0x006d, 0x0063, 0x006b, 0x006d, 0x0063, 0x006d, 0x006b, 0x006d, 0x0063, 0x006d, 0x006b, 0x006d, 0x006d, 0x0070, 0x006b, 0x006d, 0x0067, 0x0072, 0x0072, 0x0072, 0x0070, 0x006e, 0x03bc, 0x006d, 0x0070, 0x006e, 0x03bc, 0x006d, 0x006b, 0x006d, 0x0070, 0x006e, 0x03bc, 0x006d, 0x006b, 0x006d, }; static const unsigned short gNormalizeTable33c0[] = { /* U+33c0 */ 0x006b, 0x006d, 0x0061, 0x0062, 0x0063, 0x0063, 0x0063, 0x0063, 0x0064, 0x0067, 0x0068, 0x0068, 0x0069, 0x006b, 0x006b, 0x006b, 0x006c, 0x006c, 0x006c, 0x006c, 0x006d, 0x006d, 0x006d, 0x0070, 0x0070, 0x0070, 0x0070, 0x0073, 0x0073, 0x0077, 0x0076, 0x0061, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0031, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0032, 0x0033, 0x0033, 0x0067, }; static const unsigned short gNormalizeTablea640[] = { /* U+a640 */ 0xa641, 0xa641, 0xa643, 0xa643, 0xa645, 0xa645, 0xa647, 0xa647, 0xa649, 0xa649, 0xa64b, 0xa64b, 0xa64d, 0xa64d, 0xa64f, 0xa64f, 0xa651, 0xa651, 0xa653, 0xa653, 0xa655, 0xa655, 0xa657, 0xa657, 0xa659, 0xa659, 0xa65b, 0xa65b, 0xa65d, 0xa65d, 0xa65f, 0xa65f, 0xa660, 0xa661, 0xa663, 0xa663, 0xa665, 0xa665, 0xa667, 0xa667, 0xa669, 0xa669, 0xa66b, 0xa66b, 0xa66d, 0xa66d, 0xa66e, 0xa66f, 0xa670, 0xa671, 0xa672, 0xa673, 0xa674, 0xa675, 0xa676, 0xa677, 0xa678, 0xa679, 0xa67a, 0xa67b, 0xa67c, 0xa67d, 0xa67e, 0xa67f, }; static const unsigned short gNormalizeTablea680[] = { /* U+a680 */ 0xa681, 0xa681, 0xa683, 0xa683, 0xa685, 0xa685, 0xa687, 0xa687, 0xa689, 0xa689, 0xa68b, 0xa68b, 0xa68d, 0xa68d, 0xa68f, 0xa68f, 0xa691, 0xa691, 0xa693, 0xa693, 0xa695, 0xa695, 0xa697, 0xa697, 0xa698, 0xa699, 0xa69a, 0xa69b, 0xa69c, 0xa69d, 0xa69e, 0xa69f, 0xa6a0, 0xa6a1, 0xa6a2, 0xa6a3, 0xa6a4, 0xa6a5, 0xa6a6, 0xa6a7, 0xa6a8, 0xa6a9, 0xa6aa, 0xa6ab, 0xa6ac, 0xa6ad, 0xa6ae, 0xa6af, 0xa6b0, 0xa6b1, 0xa6b2, 0xa6b3, 0xa6b4, 0xa6b5, 0xa6b6, 0xa6b7, 0xa6b8, 0xa6b9, 0xa6ba, 0xa6bb, 0xa6bc, 0xa6bd, 0xa6be, 0xa6bf, }; static const unsigned short gNormalizeTablea700[] = { /* U+a700 */ 0xa700, 0xa701, 0xa702, 0xa703, 0xa704, 0xa705, 0xa706, 0xa707, 0xa708, 0xa709, 0xa70a, 0xa70b, 0xa70c, 0xa70d, 0xa70e, 0xa70f, 0xa710, 0xa711, 0xa712, 0xa713, 0xa714, 0xa715, 0xa716, 0xa717, 0xa718, 0xa719, 0xa71a, 0xa71b, 0xa71c, 0xa71d, 0xa71e, 0xa71f, 0xa720, 0xa721, 0xa723, 0xa723, 0xa725, 0xa725, 0xa727, 0xa727, 0xa729, 0xa729, 0xa72b, 0xa72b, 0xa72d, 0xa72d, 0xa72f, 0xa72f, 0xa730, 0xa731, 0xa733, 0xa733, 0xa735, 0xa735, 0xa737, 0xa737, 0xa739, 0xa739, 0xa73b, 0xa73b, 0xa73d, 0xa73d, 0xa73f, 0xa73f, }; static const unsigned short gNormalizeTablea740[] = { /* U+a740 */ 0xa741, 0xa741, 0xa743, 0xa743, 0xa745, 0xa745, 0xa747, 0xa747, 0xa749, 0xa749, 0xa74b, 0xa74b, 0xa74d, 0xa74d, 0xa74f, 0xa74f, 0xa751, 0xa751, 0xa753, 0xa753, 0xa755, 0xa755, 0xa757, 0xa757, 0xa759, 0xa759, 0xa75b, 0xa75b, 0xa75d, 0xa75d, 0xa75f, 0xa75f, 0xa761, 0xa761, 0xa763, 0xa763, 0xa765, 0xa765, 0xa767, 0xa767, 0xa769, 0xa769, 0xa76b, 0xa76b, 0xa76d, 0xa76d, 0xa76f, 0xa76f, 0xa76f, 0xa771, 0xa772, 0xa773, 0xa774, 0xa775, 0xa776, 0xa777, 0xa778, 0xa77a, 0xa77a, 0xa77c, 0xa77c, 0x1d79, 0xa77f, 0xa77f, }; static const unsigned short gNormalizeTablea780[] = { /* U+a780 */ 0xa781, 0xa781, 0xa783, 0xa783, 0xa785, 0xa785, 0xa787, 0xa787, 0xa788, 0xa789, 0xa78a, 0xa78c, 0xa78c, 0xa78d, 0xa78e, 0xa78f, 0xa790, 0xa791, 0xa792, 0xa793, 0xa794, 0xa795, 0xa796, 0xa797, 0xa798, 0xa799, 0xa79a, 0xa79b, 0xa79c, 0xa79d, 0xa79e, 0xa79f, 0xa7a0, 0xa7a1, 0xa7a2, 0xa7a3, 0xa7a4, 0xa7a5, 0xa7a6, 0xa7a7, 0xa7a8, 0xa7a9, 0xa7aa, 0xa7ab, 0xa7ac, 0xa7ad, 0xa7ae, 0xa7af, 0xa7b0, 0xa7b1, 0xa7b2, 0xa7b3, 0xa7b4, 0xa7b5, 0xa7b6, 0xa7b7, 0xa7b8, 0xa7b9, 0xa7ba, 0xa7bb, 0xa7bc, 0xa7bd, 0xa7be, 0xa7bf, }; static const unsigned short gNormalizeTablef900[] = { /* U+f900 */ 0x8c48, 0x66f4, 0x8eca, 0x8cc8, 0x6ed1, 0x4e32, 0x53e5, 0x9f9c, 0x9f9c, 0x5951, 0x91d1, 0x5587, 0x5948, 0x61f6, 0x7669, 0x7f85, 0x863f, 0x87ba, 0x88f8, 0x908f, 0x6a02, 0x6d1b, 0x70d9, 0x73de, 0x843d, 0x916a, 0x99f1, 0x4e82, 0x5375, 0x6b04, 0x721b, 0x862d, 0x9e1e, 0x5d50, 0x6feb, 0x85cd, 0x8964, 0x62c9, 0x81d8, 0x881f, 0x5eca, 0x6717, 0x6d6a, 0x72fc, 0x90ce, 0x4f86, 0x51b7, 0x52de, 0x64c4, 0x6ad3, 0x7210, 0x76e7, 0x8001, 0x8606, 0x865c, 0x8def, 0x9732, 0x9b6f, 0x9dfa, 0x788c, 0x797f, 0x7da0, 0x83c9, 0x9304, }; static const unsigned short gNormalizeTablef940[] = { /* U+f940 */ 0x9e7f, 0x8ad6, 0x58df, 0x5f04, 0x7c60, 0x807e, 0x7262, 0x78ca, 0x8cc2, 0x96f7, 0x58d8, 0x5c62, 0x6a13, 0x6dda, 0x6f0f, 0x7d2f, 0x7e37, 0x964b, 0x52d2, 0x808b, 0x51dc, 0x51cc, 0x7a1c, 0x7dbe, 0x83f1, 0x9675, 0x8b80, 0x62cf, 0x6a02, 0x8afe, 0x4e39, 0x5be7, 0x6012, 0x7387, 0x7570, 0x5317, 0x78fb, 0x4fbf, 0x5fa9, 0x4e0d, 0x6ccc, 0x6578, 0x7d22, 0x53c3, 0x585e, 0x7701, 0x8449, 0x8aaa, 0x6bba, 0x8fb0, 0x6c88, 0x62fe, 0x82e5, 0x63a0, 0x7565, 0x4eae, 0x5169, 0x51c9, 0x6881, 0x7ce7, 0x826f, 0x8ad2, 0x91cf, 0x52f5, }; static const unsigned short gNormalizeTablef980[] = { /* U+f980 */ 0x5442, 0x5973, 0x5eec, 0x65c5, 0x6ffe, 0x792a, 0x95ad, 0x9a6a, 0x9e97, 0x9ece, 0x529b, 0x66c6, 0x6b77, 0x8f62, 0x5e74, 0x6190, 0x6200, 0x649a, 0x6f23, 0x7149, 0x7489, 0x79ca, 0x7df4, 0x806f, 0x8f26, 0x84ee, 0x9023, 0x934a, 0x5217, 0x52a3, 0x54bd, 0x70c8, 0x88c2, 0x8aaa, 0x5ec9, 0x5ff5, 0x637b, 0x6bae, 0x7c3e, 0x7375, 0x4ee4, 0x56f9, 0x5be7, 0x5dba, 0x601c, 0x73b2, 0x7469, 0x7f9a, 0x8046, 0x9234, 0x96f6, 0x9748, 0x9818, 0x4f8b, 0x79ae, 0x91b4, 0x96b8, 0x60e1, 0x4e86, 0x50da, 0x5bee, 0x5c3f, 0x6599, 0x6a02, }; static const unsigned short gNormalizeTablef9c0[] = { /* U+f9c0 */ 0x71ce, 0x7642, 0x84fc, 0x907c, 0x9f8d, 0x6688, 0x962e, 0x5289, 0x677b, 0x67f3, 0x6d41, 0x6e9c, 0x7409, 0x7559, 0x786b, 0x7d10, 0x985e, 0x516d, 0x622e, 0x9678, 0x502b, 0x5d19, 0x6dea, 0x8f2a, 0x5f8b, 0x6144, 0x6817, 0x7387, 0x9686, 0x5229, 0x540f, 0x5c65, 0x6613, 0x674e, 0x68a8, 0x6ce5, 0x7406, 0x75e2, 0x7f79, 0x88cf, 0x88e1, 0x91cc, 0x96e2, 0x533f, 0x6eba, 0x541d, 0x71d0, 0x7498, 0x85fa, 0x96a3, 0x9c57, 0x9e9f, 0x6797, 0x6dcb, 0x81e8, 0x7acb, 0x7b20, 0x7c92, 0x72c0, 0x7099, 0x8b58, 0x4ec0, 0x8336, 0x523a, }; static const unsigned short gNormalizeTablefa00[] = { /* U+fa00 */ 0x5207, 0x5ea6, 0x62d3, 0x7cd6, 0x5b85, 0x6d1e, 0x66b4, 0x8f3b, 0x884c, 0x964d, 0x898b, 0x5ed3, 0x5140, 0x55c0, 0xfa0e, 0xfa0f, 0x585a, 0xfa11, 0x6674, 0xfa13, 0xfa14, 0x51de, 0x732a, 0x76ca, 0x793c, 0x795e, 0x7965, 0x798f, 0x9756, 0x7cbe, 0x7fbd, 0xfa1f, 0x8612, 0xfa21, 0x8af8, 0xfa23, 0xfa24, 0x9038, 0x90fd, 0xfa27, 0xfa28, 0xfa29, 0x98ef, 0x98fc, 0x9928, 0x9db4, 0xfa2e, 0xfa2f, 0x4fae, 0x50e7, 0x514d, 0x52c9, 0x52e4, 0x5351, 0x559d, 0x5606, 0x5668, 0x5840, 0x58a8, 0x5c64, 0x5c6e, 0x6094, 0x6168, 0x618e, }; static const unsigned short gNormalizeTablefa40[] = { /* U+fa40 */ 0x61f2, 0x654f, 0x65e2, 0x6691, 0x6885, 0x6d77, 0x6e1a, 0x6f22, 0x716e, 0x722b, 0x7422, 0x7891, 0x793e, 0x7949, 0x7948, 0x7950, 0x7956, 0x795d, 0x798d, 0x798e, 0x7a40, 0x7a81, 0x7bc0, 0x7df4, 0x7e09, 0x7e41, 0x7f72, 0x8005, 0x81ed, 0x8279, 0x8279, 0x8457, 0x8910, 0x8996, 0x8b01, 0x8b39, 0x8cd3, 0x8d08, 0x8fb6, 0x9038, 0x96e3, 0x97ff, 0x983b, 0x6075, 0xfa6c, 0x8218, 0xfa6e, 0xfa6f, 0x4e26, 0x51b5, 0x5168, 0x4f80, 0x5145, 0x5180, 0x52c7, 0x52fa, 0x559d, 0x5555, 0x5599, 0x55e2, 0x585a, 0x58b3, 0x5944, 0x5954, }; static const unsigned short gNormalizeTablefa80[] = { /* U+fa80 */ 0x5a62, 0x5b28, 0x5ed2, 0x5ed9, 0x5f69, 0x5fad, 0x60d8, 0x614e, 0x6108, 0x618e, 0x6160, 0x61f2, 0x6234, 0x63c4, 0x641c, 0x6452, 0x6556, 0x6674, 0x6717, 0x671b, 0x6756, 0x6b79, 0x6bba, 0x6d41, 0x6edb, 0x6ecb, 0x6f22, 0x701e, 0x716e, 0x77a7, 0x7235, 0x72af, 0x732a, 0x7471, 0x7506, 0x753b, 0x761d, 0x761f, 0x76ca, 0x76db, 0x76f4, 0x774a, 0x7740, 0x78cc, 0x7ab1, 0x7bc0, 0x7c7b, 0x7d5b, 0x7df4, 0x7f3e, 0x8005, 0x8352, 0x83ef, 0x8779, 0x8941, 0x8986, 0x8996, 0x8abf, 0x8af8, 0x8acb, 0x8b01, 0x8afe, 0x8aed, 0x8b39, }; static const unsigned short gNormalizeTablefac0[] = { /* U+fac0 */ 0x8b8a, 0x8d08, 0x8f38, 0x9072, 0x9199, 0x9276, 0x967c, 0x96e3, 0x9756, 0x97db, 0x97ff, 0x980b, 0x983b, 0x9b12, 0x9f9c, 0xfacf, 0xfad0, 0xfad1, 0x3b9d, 0x4018, 0x4039, 0xfad5, 0xfad6, 0xfad7, 0x9f43, 0x9f8e, 0xfada, 0xfadb, 0xfadc, 0xfadd, 0xfade, 0xfadf, 0xfae0, 0xfae1, 0xfae2, 0xfae3, 0xfae4, 0xfae5, 0xfae6, 0xfae7, 0xfae8, 0xfae9, 0xfaea, 0xfaeb, 0xfaec, 0xfaed, 0xfaee, 0xfaef, 0xfaf0, 0xfaf1, 0xfaf2, 0xfaf3, 0xfaf4, 0xfaf5, 0xfaf6, 0xfaf7, 0xfaf8, 0xfaf9, 0xfafa, 0xfafb, 0xfafc, 0xfafd, 0xfafe, 0xfaff, }; static const unsigned short gNormalizeTablefb00[] = { /* U+fb00 */ 0x0066, 0x0066, 0x0066, 0x0066, 0x0066, 0x0073, 0x0073, 0xfb07, 0xfb08, 0xfb09, 0xfb0a, 0xfb0b, 0xfb0c, 0xfb0d, 0xfb0e, 0xfb0f, 0xfb10, 0xfb11, 0xfb12, 0x0574, 0x0574, 0x0574, 0x057e, 0x0574, 0xfb18, 0xfb19, 0xfb1a, 0xfb1b, 0xfb1c, 0x05d9, 0xfb1e, 0x05f2, 0x05e2, 0x05d0, 0x05d3, 0x05d4, 0x05db, 0x05dc, 0x05dd, 0x05e8, 0x05ea, 0x002b, 0x05e9, 0x05e9, 0x05e9, 0x05e9, 0x05d0, 0x05d0, 0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0xfb37, 0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0xfb3d, 0x05de, 0xfb3f, }; static const unsigned short gNormalizeTablefb40[] = { /* U+fb40 */ 0x05e0, 0x05e1, 0xfb42, 0x05e3, 0x05e4, 0xfb45, 0x05e6, 0x05e7, 0x05e8, 0x05e9, 0x05ea, 0x05d5, 0x05d1, 0x05db, 0x05e4, 0x05d0, 0x0671, 0x0671, 0x067b, 0x067b, 0x067b, 0x067b, 0x067e, 0x067e, 0x067e, 0x067e, 0x0680, 0x0680, 0x0680, 0x0680, 0x067a, 0x067a, 0x067a, 0x067a, 0x067f, 0x067f, 0x067f, 0x067f, 0x0679, 0x0679, 0x0679, 0x0679, 0x06a4, 0x06a4, 0x06a4, 0x06a4, 0x06a6, 0x06a6, 0x06a6, 0x06a6, 0x0684, 0x0684, 0x0684, 0x0684, 0x0683, 0x0683, 0x0683, 0x0683, 0x0686, 0x0686, 0x0686, 0x0686, 0x0687, 0x0687, }; static const unsigned short gNormalizeTablefb80[] = { /* U+fb80 */ 0x0687, 0x0687, 0x068d, 0x068d, 0x068c, 0x068c, 0x068e, 0x068e, 0x0688, 0x0688, 0x0698, 0x0698, 0x0691, 0x0691, 0x06a9, 0x06a9, 0x06a9, 0x06a9, 0x06af, 0x06af, 0x06af, 0x06af, 0x06b3, 0x06b3, 0x06b3, 0x06b3, 0x06b1, 0x06b1, 0x06b1, 0x06b1, 0x06ba, 0x06ba, 0x06bb, 0x06bb, 0x06bb, 0x06bb, 0x06d5, 0x06d5, 0x06c1, 0x06c1, 0x06c1, 0x06c1, 0x06be, 0x06be, 0x06be, 0x06be, 0x06d2, 0x06d2, 0x06d2, 0x06d2, 0xfbb2, 0xfbb3, 0xfbb4, 0xfbb5, 0xfbb6, 0xfbb7, 0xfbb8, 0xfbb9, 0xfbba, 0xfbbb, 0xfbbc, 0xfbbd, 0xfbbe, 0xfbbf, }; static const unsigned short gNormalizeTablefbc0[] = { /* U+fbc0 */ 0xfbc0, 0xfbc1, 0xfbc2, 0xfbc3, 0xfbc4, 0xfbc5, 0xfbc6, 0xfbc7, 0xfbc8, 0xfbc9, 0xfbca, 0xfbcb, 0xfbcc, 0xfbcd, 0xfbce, 0xfbcf, 0xfbd0, 0xfbd1, 0xfbd2, 0x06ad, 0x06ad, 0x06ad, 0x06ad, 0x06c7, 0x06c7, 0x06c6, 0x06c6, 0x06c8, 0x06c8, 0x06c7, 0x06cb, 0x06cb, 0x06c5, 0x06c5, 0x06c9, 0x06c9, 0x06d0, 0x06d0, 0x06d0, 0x06d0, 0x0649, 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x06cc, 0x06cc, 0x06cc, 0x06cc, }; static const unsigned short gNormalizeTablefc00[] = { /* U+fc00 */ 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062b, 0x062b, 0x062b, 0x062c, 0x062c, 0x062d, 0x062d, 0x062e, 0x062e, 0x062e, 0x0633, 0x0633, 0x0633, 0x0633, 0x0635, 0x0635, 0x0636, 0x0636, 0x0636, 0x0636, 0x0637, 0x0637, 0x0638, 0x0639, 0x0639, 0x063a, 0x063a, 0x0641, 0x0641, 0x0641, 0x0641, 0x0641, 0x0641, 0x0642, 0x0642, 0x0642, 0x0642, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, }; static const unsigned short gNormalizeTablefc40[] = { /* U+fc40 */ 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0647, 0x0647, 0x0647, 0x0647, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0630, 0x0631, 0x0649, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062b, 0x062b, 0x062b, 0x062b, 0x062b, 0x0641, 0x0641, 0x0642, 0x0642, }; static const unsigned short gNormalizeTablefc80[] = { /* U+fc80 */ 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x0628, 0x0628, 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062c, 0x062c, 0x062d, 0x062d, 0x062e, 0x062e, 0x0633, 0x0633, 0x0633, 0x0633, 0x0635, 0x0635, 0x0635, 0x0636, 0x0636, 0x0636, 0x0636, 0x0637, 0x0638, 0x0639, 0x0639, 0x063a, 0x063a, 0x0641, 0x0641, }; static const unsigned short gNormalizeTablefcc0[] = { /* U+fcc0 */ 0x0641, 0x0641, 0x0642, 0x0642, 0x0643, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0647, 0x0647, 0x0647, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x064a, 0x0628, 0x0628, 0x062a, 0x062a, 0x062b, 0x062b, 0x0633, 0x0633, 0x0634, 0x0634, 0x0643, 0x0643, 0x0644, 0x0646, 0x0646, 0x064a, 0x064a, 0x0640, 0x0640, 0x0640, 0x0637, 0x0637, 0x0639, 0x0639, 0x063a, 0x063a, 0x0633, 0x0633, 0x0634, 0x0634, 0x062d, }; static const unsigned short gNormalizeTablefd00[] = { /* U+fd00 */ 0x062d, 0x062c, 0x062c, 0x062e, 0x062e, 0x0635, 0x0635, 0x0636, 0x0636, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0633, 0x0635, 0x0636, 0x0637, 0x0637, 0x0639, 0x0639, 0x063a, 0x063a, 0x0633, 0x0633, 0x0634, 0x0634, 0x062d, 0x062d, 0x062c, 0x062c, 0x062e, 0x062e, 0x0635, 0x0635, 0x0636, 0x0636, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0633, 0x0635, 0x0636, 0x0634, 0x0634, 0x0634, 0x0634, 0x0633, 0x0634, 0x0637, 0x0633, 0x0633, 0x0633, 0x0634, 0x0634, 0x0634, 0x0637, 0x0638, 0x0627, 0x0627, 0xfd3e, 0xfd3f, }; static const unsigned short gNormalizeTablefd40[] = { /* U+fd40 */ 0xfd40, 0xfd41, 0xfd42, 0xfd43, 0xfd44, 0xfd45, 0xfd46, 0xfd47, 0xfd48, 0xfd49, 0xfd4a, 0xfd4b, 0xfd4c, 0xfd4d, 0xfd4e, 0xfd4f, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062c, 0x062c, 0x062d, 0x062d, 0x0633, 0x0633, 0x0633, 0x0633, 0x0633, 0x0633, 0x0633, 0x0633, 0x0635, 0x0635, 0x0635, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0634, 0x0636, 0x0636, 0x0636, 0x0637, 0x0637, 0x0637, 0x0637, 0x0639, 0x0639, 0x0639, 0x0639, 0x063a, 0x063a, 0x063a, 0x0641, 0x0641, 0x0642, 0x0642, }; static const unsigned short gNormalizeTablefd80[] = { /* U+fd80 */ 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0x0645, 0xfd90, 0xfd91, 0x0645, 0x0647, 0x0647, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x0646, 0x064a, 0x064a, 0x0628, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062a, 0x062c, 0x062c, 0x062c, 0x0633, 0x0635, 0x0634, 0x0636, 0x0644, 0x0644, 0x064a, 0x064a, 0x064a, 0x0645, 0x0642, 0x0646, 0x0642, 0x0644, 0x0639, 0x0643, 0x0646, 0x0645, 0x0644, 0x0643, 0x0644, 0x0646, 0x062c, 0x062d, }; static const unsigned short gNormalizeTablefdc0[] = { /* U+fdc0 */ 0x0645, 0x0641, 0x0628, 0x0643, 0x0639, 0x0635, 0x0633, 0x0646, 0xfdc8, 0xfdc9, 0xfdca, 0xfdcb, 0xfdcc, 0xfdcd, 0xfdce, 0xfdcf, 0xfdd0, 0xfdd1, 0xfdd2, 0xfdd3, 0xfdd4, 0xfdd5, 0xfdd6, 0xfdd7, 0xfdd8, 0xfdd9, 0xfdda, 0xfddb, 0xfddc, 0xfddd, 0xfdde, 0xfddf, 0xfde0, 0xfde1, 0xfde2, 0xfde3, 0xfde4, 0xfde5, 0xfde6, 0xfde7, 0xfde8, 0xfde9, 0xfdea, 0xfdeb, 0xfdec, 0xfded, 0xfdee, 0xfdef, 0x0635, 0x0642, 0x0627, 0x0627, 0x0645, 0x0635, 0x0631, 0x0639, 0x0648, 0x0635, 0x0635, 0x062c, 0x0631, 0xfdfd, 0xfdfe, 0xfdff, }; static const unsigned short gNormalizeTablefe00[] = { /* U+fe00 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x002c, 0x3001, 0x3002, 0x003a, 0x003b, 0x0021, 0x003f, 0x3016, 0x3017, 0x002e, 0xfe1a, 0xfe1b, 0xfe1c, 0xfe1d, 0xfe1e, 0xfe1f, 0xfe20, 0xfe21, 0xfe22, 0xfe23, 0xfe24, 0xfe25, 0xfe26, 0xfe27, 0xfe28, 0xfe29, 0xfe2a, 0xfe2b, 0xfe2c, 0xfe2d, 0xfe2e, 0xfe2f, 0x002e, 0x2014, 0x2013, 0x005f, 0x005f, 0x0028, 0x0029, 0x007b, 0x007d, 0x3014, 0x3015, 0x3010, 0x3011, 0x300a, 0x300b, 0x3008, }; static const unsigned short gNormalizeTablefe40[] = { /* U+fe40 */ 0x3009, 0x300c, 0x300d, 0x300e, 0x300f, 0xfe45, 0xfe46, 0x005b, 0x005d, 0x0020, 0x0020, 0x0020, 0x0020, 0x005f, 0x005f, 0x005f, 0x002c, 0x3001, 0x002e, 0xfe53, 0x003b, 0x003a, 0x003f, 0x0021, 0x2014, 0x0028, 0x0029, 0x007b, 0x007d, 0x3014, 0x3015, 0x0023, 0x0026, 0x002a, 0x002b, 0x002d, 0x003c, 0x003e, 0x003d, 0xfe67, 0x005c, 0x0024, 0x0025, 0x0040, 0xfe6c, 0xfe6d, 0xfe6e, 0xfe6f, 0x0020, 0x0640, 0x0020, 0xfe73, 0x0020, 0xfe75, 0x0020, 0x0640, 0x0020, 0x0640, 0x0020, 0x0640, 0x0020, 0x0640, 0x0020, 0x0640, }; static const unsigned short gNormalizeTablefe80[] = { /* U+fe80 */ 0x0621, 0x0627, 0x0627, 0x0627, 0x0627, 0x0648, 0x0648, 0x0627, 0x0627, 0x064a, 0x064a, 0x064a, 0x064a, 0x0627, 0x0627, 0x0628, 0x0628, 0x0628, 0x0628, 0x0629, 0x0629, 0x062a, 0x062a, 0x062a, 0x062a, 0x062b, 0x062b, 0x062b, 0x062b, 0x062c, 0x062c, 0x062c, 0x062c, 0x062d, 0x062d, 0x062d, 0x062d, 0x062e, 0x062e, 0x062e, 0x062e, 0x062f, 0x062f, 0x0630, 0x0630, 0x0631, 0x0631, 0x0632, 0x0632, 0x0633, 0x0633, 0x0633, 0x0633, 0x0634, 0x0634, 0x0634, 0x0634, 0x0635, 0x0635, 0x0635, 0x0635, 0x0636, 0x0636, 0x0636, }; static const unsigned short gNormalizeTablefec0[] = { /* U+fec0 */ 0x0636, 0x0637, 0x0637, 0x0637, 0x0637, 0x0638, 0x0638, 0x0638, 0x0638, 0x0639, 0x0639, 0x0639, 0x0639, 0x063a, 0x063a, 0x063a, 0x063a, 0x0641, 0x0641, 0x0641, 0x0641, 0x0642, 0x0642, 0x0642, 0x0642, 0x0643, 0x0643, 0x0643, 0x0643, 0x0644, 0x0644, 0x0644, 0x0644, 0x0645, 0x0645, 0x0645, 0x0645, 0x0646, 0x0646, 0x0646, 0x0646, 0x0647, 0x0647, 0x0647, 0x0647, 0x0648, 0x0648, 0x0649, 0x0649, 0x064a, 0x064a, 0x064a, 0x064a, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0x0644, 0xfefd, 0xfefe, 0x0020, }; static const unsigned short gNormalizeTableff00[] = { /* U+ff00 */ 0xff00, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, }; static const unsigned short gNormalizeTableff40[] = { /* U+ff40 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2985, 0x2986, 0x3002, 0x300c, 0x300d, 0x3001, 0x30fb, 0x30f2, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30e3, 0x30e5, 0x30e7, 0x30c3, 0x30fc, 0x30a2, 0x30a4, 0x30a6, 0x30a8, 0x30aa, 0x30ab, 0x30ad, 0x30af, 0x30b1, 0x30b3, 0x30b5, 0x30b7, 0x30b9, 0x30bb, 0x30bd, }; static const unsigned short gNormalizeTableff80[] = { /* U+ff80 */ 0x30bf, 0x30c1, 0x30c4, 0x30c6, 0x30c8, 0x30ca, 0x30cb, 0x30cc, 0x30cd, 0x30ce, 0x30cf, 0x30d2, 0x30d5, 0x30d8, 0x30db, 0x30de, 0x30df, 0x30e0, 0x30e1, 0x30e2, 0x30e4, 0x30e6, 0x30e8, 0x30e9, 0x30ea, 0x30eb, 0x30ec, 0x30ed, 0x30ef, 0x30f3, 0x3099, 0x309a, 0x0020, 0x1100, 0x1101, 0x11aa, 0x1102, 0x11ac, 0x11ad, 0x1103, 0x1104, 0x1105, 0x11b0, 0x11b1, 0x11b2, 0x11b3, 0x11b4, 0x11b5, 0x111a, 0x1106, 0x1107, 0x1108, 0x1121, 0x1109, 0x110a, 0x110b, 0x110c, 0x110d, 0x110e, 0x110f, 0x1110, 0x1111, 0x1112, 0xffbf, }; static const unsigned short gNormalizeTableffc0[] = { /* U+ffc0 */ 0xffc0, 0xffc1, 0x1161, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0xffc8, 0xffc9, 0x1167, 0x1168, 0x1169, 0x116a, 0x116b, 0x116c, 0xffd0, 0xffd1, 0x116d, 0x116e, 0x116f, 0x1170, 0x1171, 0x1172, 0xffd8, 0xffd9, 0x1173, 0x1174, 0x1175, 0xffdd, 0xffde, 0xffdf, 0x00a2, 0x00a3, 0x00ac, 0x0020, 0x00a6, 0x00a5, 0x20a9, 0xffe7, 0x2502, 0x2190, 0x2191, 0x2192, 0x2193, 0x25a0, 0x25cb, 0xffef, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0xfff9, 0xfffa, 0xfffb, 0xfffc, 0xfffd, 0xfffe, 0xffff, }; static const unsigned short* gNormalizeTable[] = { 0, gNormalizeTable0040, gNormalizeTable0080, gNormalizeTable00c0, gNormalizeTable0100, gNormalizeTable0140, gNormalizeTable0180, gNormalizeTable01c0, gNormalizeTable0200, gNormalizeTable0240, gNormalizeTable0280, gNormalizeTable02c0, 0, gNormalizeTable0340, gNormalizeTable0380, gNormalizeTable03c0, gNormalizeTable0400, gNormalizeTable0440, gNormalizeTable0480, gNormalizeTable04c0, gNormalizeTable0500, gNormalizeTable0540, gNormalizeTable0580, 0, gNormalizeTable0600, gNormalizeTable0640, 0, gNormalizeTable06c0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTable0900, gNormalizeTable0940, 0, gNormalizeTable09c0, gNormalizeTable0a00, gNormalizeTable0a40, 0, 0, 0, gNormalizeTable0b40, gNormalizeTable0b80, gNormalizeTable0bc0, 0, gNormalizeTable0c40, 0, gNormalizeTable0cc0, 0, gNormalizeTable0d40, 0, gNormalizeTable0dc0, gNormalizeTable0e00, 0, gNormalizeTable0e80, gNormalizeTable0ec0, gNormalizeTable0f00, gNormalizeTable0f40, gNormalizeTable0f80, 0, gNormalizeTable1000, 0, gNormalizeTable1080, gNormalizeTable10c0, 0, gNormalizeTable1140, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTable1780, 0, gNormalizeTable1800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTable1b00, gNormalizeTable1b40, 0, 0, 0, 0, 0, 0, gNormalizeTable1d00, gNormalizeTable1d40, gNormalizeTable1d80, 0, gNormalizeTable1e00, gNormalizeTable1e40, gNormalizeTable1e80, gNormalizeTable1ec0, gNormalizeTable1f00, gNormalizeTable1f40, gNormalizeTable1f80, gNormalizeTable1fc0, gNormalizeTable2000, gNormalizeTable2040, gNormalizeTable2080, 0, gNormalizeTable2100, gNormalizeTable2140, gNormalizeTable2180, gNormalizeTable21c0, gNormalizeTable2200, gNormalizeTable2240, gNormalizeTable2280, gNormalizeTable22c0, gNormalizeTable2300, 0, 0, 0, 0, gNormalizeTable2440, gNormalizeTable2480, gNormalizeTable24c0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTable2a00, gNormalizeTable2a40, 0, gNormalizeTable2ac0, 0, 0, 0, 0, gNormalizeTable2c00, gNormalizeTable2c40, gNormalizeTable2c80, gNormalizeTable2cc0, 0, gNormalizeTable2d40, 0, 0, 0, 0, gNormalizeTable2e80, gNormalizeTable2ec0, gNormalizeTable2f00, gNormalizeTable2f40, gNormalizeTable2f80, gNormalizeTable2fc0, gNormalizeTable3000, gNormalizeTable3040, gNormalizeTable3080, gNormalizeTable30c0, gNormalizeTable3100, gNormalizeTable3140, gNormalizeTable3180, 0, gNormalizeTable3200, gNormalizeTable3240, gNormalizeTable3280, gNormalizeTable32c0, gNormalizeTable3300, gNormalizeTable3340, gNormalizeTable3380, gNormalizeTable33c0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTablea640, gNormalizeTablea680, 0, gNormalizeTablea700, gNormalizeTablea740, gNormalizeTablea780, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, gNormalizeTablef900, gNormalizeTablef940, gNormalizeTablef980, gNormalizeTablef9c0, gNormalizeTablefa00, gNormalizeTablefa40, gNormalizeTablefa80, gNormalizeTablefac0, gNormalizeTablefb00, gNormalizeTablefb40, gNormalizeTablefb80, gNormalizeTablefbc0, gNormalizeTablefc00, gNormalizeTablefc40, gNormalizeTablefc80, gNormalizeTablefcc0, gNormalizeTablefd00, gNormalizeTablefd40, gNormalizeTablefd80, gNormalizeTablefdc0, gNormalizeTablefe00, gNormalizeTablefe40, gNormalizeTablefe80, gNormalizeTablefec0, gNormalizeTableff00, gNormalizeTableff40, gNormalizeTableff80, gNormalizeTableffc0, }; unsigned int normalize_character(const unsigned int c) { if (c >= 0x10000 || !gNormalizeTable[c >> 6]) return c; return gNormalizeTable[c >> 6][c & 0x3f]; } mediascanner2-0.111+16.04.20160317/src/mediascanner/mozilla/fts3_tokenizer.h0000644000015600001650000001361712672421600026614 0ustar pbuserpbgroup00000000000000/* ** 2006 July 10 ** ** The author disclaims copyright to this source code. ** ************************************************************************* ** Defines the interface to tokenizers used by fulltext-search. There ** are three basic components: ** ** sqlite3_tokenizer_module is a singleton defining the tokenizer ** interface functions. This is essentially the class structure for ** tokenizers. ** ** sqlite3_tokenizer is used to define a particular tokenizer, perhaps ** including customization information defined at creation time. ** ** sqlite3_tokenizer_cursor is generated by a tokenizer to generate ** tokens from a particular input. */ #ifndef _FTS3_TOKENIZER_H_ #define _FTS3_TOKENIZER_H_ /* TODO(shess) Only used for SQLITE_OK and SQLITE_DONE at this time. ** If tokenizers are to be allowed to call sqlite3_*() functions, then ** we will need a way to register the API consistently. */ #include "sqlite3.h" /* ** Structures used by the tokenizer interface. When a new tokenizer ** implementation is registered, the caller provides a pointer to ** an sqlite3_tokenizer_module containing pointers to the callback ** functions that make up an implementation. ** ** When an fts3 table is created, it passes any arguments passed to ** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the ** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer ** implementation. The xCreate() function in turn returns an ** sqlite3_tokenizer structure representing the specific tokenizer to ** be used for the fts3 table (customized by the tokenizer clause arguments). ** ** To tokenize an input buffer, the sqlite3_tokenizer_module.xOpen() ** method is called. It returns an sqlite3_tokenizer_cursor object ** that may be used to tokenize a specific input buffer based on ** the tokenization rules supplied by a specific sqlite3_tokenizer ** object. */ typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module; typedef struct sqlite3_tokenizer sqlite3_tokenizer; typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor; struct sqlite3_tokenizer_module { /* ** Structure version. Should always be set to 0. */ int iVersion; /* ** Create a new tokenizer. The values in the argv[] array are the ** arguments passed to the "tokenizer" clause of the CREATE VIRTUAL ** TABLE statement that created the fts3 table. For example, if ** the following SQL is executed: ** ** CREATE .. USING fts3( ... , tokenizer arg1 arg2) ** ** then argc is set to 2, and the argv[] array contains pointers ** to the strings "arg1" and "arg2". ** ** This method should return either SQLITE_OK (0), or an SQLite error ** code. If SQLITE_OK is returned, then *ppTokenizer should be set ** to point at the newly created tokenizer structure. The generic ** sqlite3_tokenizer.pModule variable should not be initialised by ** this callback. The caller will do so. */ int (*xCreate)( int argc, /* Size of argv array */ const char *const*argv, /* Tokenizer argument strings */ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */ ); /* ** Destroy an existing tokenizer. The fts3 module calls this method ** exactly once for each successful call to xCreate(). */ int (*xDestroy)(sqlite3_tokenizer *pTokenizer); /* ** Create a tokenizer cursor to tokenize an input buffer. The caller ** is responsible for ensuring that the input buffer remains valid ** until the cursor is closed (using the xClose() method). */ int (*xOpen)( sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ const char *pInput, int nBytes, /* Input buffer */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */ ); /* ** Destroy an existing tokenizer cursor. The fts3 module calls this ** method exactly once for each successful call to xOpen(). */ int (*xClose)(sqlite3_tokenizer_cursor *pCursor); /* ** Retrieve the next token from the tokenizer cursor pCursor. This ** method should either return SQLITE_OK and set the values of the ** "OUT" variables identified below, or SQLITE_DONE to indicate that ** the end of the buffer has been reached, or an SQLite error code. ** ** *ppToken should be set to point at a buffer containing the ** normalized version of the token (i.e. after any case-folding and/or ** stemming has been performed). *pnBytes should be set to the length ** of this buffer in bytes. The input text that generated the token is ** identified by the byte offsets returned in *piStartOffset and ** *piEndOffset. *piStartOffset should be set to the index of the first ** byte of the token in the input buffer. *piEndOffset should be set ** to the index of the first byte just past the end of the token in ** the input buffer. ** ** The buffer *ppToken is set to point at is managed by the tokenizer ** implementation. It is only required to be valid until the next call ** to xNext() or xClose(). */ /* TODO(shess) current implementation requires pInput to be ** nul-terminated. This should either be fixed, or pInput/nBytes ** should be converted to zInput. */ int (*xNext)( sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */ const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */ int *piStartOffset, /* OUT: Byte offset of token in input buffer */ int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */ int *piPosition /* OUT: Number of tokens returned before this one */ ); }; struct sqlite3_tokenizer { const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */ /* Tokenizer implementations will typically add additional fields */ }; struct sqlite3_tokenizer_cursor { sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */ /* Tokenizer implementations will typically add additional fields */ }; #endif /* _FTS3_TOKENIZER_H_ */ mediascanner2-0.111+16.04.20160317/src/mediascanner/mozilla/README.mozilla0000644000015600001650000000031212672421600026004 0ustar pbuserpbgroup00000000000000fts3_porter.c code is from SQLite3. This customized tokenizer "mozporter" by Mozilla supports CJK indexing using bi-gram. So you have to use bi-gram search string if you wanto to search CJK character. mediascanner2-0.111+16.04.20160317/src/mediascanner/mozilla/fts3_porter.c0000644000015600001650000012070212672421600026102 0ustar pbuserpbgroup00000000000000/* ** 2006 September 30 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** Implementation of the full-text-search tokenizer that implements ** a Porter stemmer. ** */ /* * This file is based on the SQLite FTS3 Porter Stemmer implementation. * * This is an attempt to provide some level of full-text search to users of * Thunderbird who use languages that are not space/punctuation delimited. * This is accomplished by performing bi-gram indexing of characters fall * into the unicode space occupied by character sets used in such languages. * * Bi-gram indexing means that given the string "12345" we would index the * pairs "12", "23", "34", and "45" (with position information). We do this * because we are not sure where the word/semantic boundaries are in that * string. Then, when a user searches for "234" the FTS3 engine tokenizes the * search query into "23" and "34". Using special phrase-logic FTS3 requires * the matches to have the tokens "23" and "34" adjacent to each other and in * that order. In theory if the user searched for "2345" we we could just * search for "23 NEAR/2 34". Unfortunately, NEAR does not imply ordering, * so even though that would be more efficient, we would lose correctness * and cannot do it. * * The efficiency and usability of bi-gram search assumes that the character * space is large enough and actually observed bi-grams sufficiently * distributed throughout the potential space so that the search bi-grams * generated when the user issues a query find a 'reasonable' number of * documents for each bi-gram match. * * Mozilla contributors: * Makoto Kato * Andrew Sutherland */ /* ** The code in this file is only compiled if: ** ** * The FTS3 module is being built as an extension ** (in which case SQLITE_CORE is not defined), or ** ** * The FTS3 module is being built into the core of ** SQLite (in which case SQLITE_ENABLE_FTS3 is defined). */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) #define UNUSED __attribute__ ((unused)) #include #include #include #include #include #include "fts3_tokenizer.h" /* need some defined to compile without sqlite3 code */ #define sqlite3_malloc malloc #define sqlite3_free free #define sqlite3_realloc realloc static const unsigned char sqlite3Utf8Trans1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, }; typedef unsigned char u8; /** * SQLite helper macro from sqlite3.c (really utf.c) to encode a unicode * character into utf8. * * @param zOut A pointer to the current write position that is updated by * the routine. At entry it should point to one-past the last valid * encoded byte. The same holds true at exit. * @param c The character to encode; this should be an unsigned int. */ #define WRITE_UTF8(zOut, c) { \ if( c<0x0080 ){ \ *zOut++ = (u8)(c&0xff); \ } \ else if( c<0x0800 ){ \ *zOut++ = 0xC0 + (u8)((c>>6) & 0x1F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ } \ else if( c<0x10000 ){ \ *zOut++ = 0xE0 + (u8)((c>>12) & 0x0F); \ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ }else{ \ *zOut++ = 0xf0 + (u8)((c>>18) & 0x07); \ *zOut++ = 0x80 + (u8)((c>>12) & 0x3F); \ *zOut++ = 0x80 + (u8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (u8)(c & 0x3F); \ } \ } /** * Fudge factor to avoid buffer overwrites when WRITE_UTF8 is involved. * * Our normalization table includes entries that may result in a larger * utf-8 encoding. Namely, 023a maps to 2c65. This is a growth from 2 bytes * as utf-8 encoded to 3 bytes. This is currently the only transition possible * because 1-byte encodings are known to stay 1-byte and our normalization * table is 16-bit and so can't generate a 4-byte encoded output. * * For simplicity, we just multiple by 2 which covers the current case and * potential growth for 2-byte to 4-byte growth. We can afford to do this * because we're not talking about a lot of memory here as a rule. */ #define MAX_UTF8_GROWTH_FACTOR 2 /** * Helper from sqlite3.c to read a single UTF8 character. * * The clever bit with multi-byte reading is that you keep going until you find * a byte whose top bits are not '10'. A single-byte UTF8 character will have * '00' or '01', and a multi-byte UTF8 character must start with '11'. * * In the event of illegal UTF-8 this macro may read an arbitrary number of * characters but will never read past zTerm. The resulting character value * of illegal UTF-8 can be anything, although efforts are made to return the * illegal character (0xfffd) for UTF-16 surrogates. * * @param zIn A pointer to the current position that is updated by the routine, * pointing at the start of the next character when the routine returns. * @param zTerm A pointer one past the end of the buffer. * @param c The 'unsigned int' to hold the resulting character value. Do not * use a short or a char. */ #define READ_UTF8(zIn, zTerm, c) { \ c = *(zIn++); \ if( c>=0xc0 ){ \ c = sqlite3Utf8Trans1[c-0xc0]; \ while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ c = (c<<6) + (0x3f & *(zIn++)); \ } \ if( c<0x80 \ || (c&0xFFFFF800)==0xD800 \ || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ } \ } /* end of compatible block to complie codes */ /* ** Class derived from sqlite3_tokenizer */ typedef struct porter_tokenizer { sqlite3_tokenizer base; /* Base class */ } porter_tokenizer; /* ** Class derived from sqlit3_tokenizer_cursor */ typedef struct porter_tokenizer_cursor { sqlite3_tokenizer_cursor base; const char *zInput; /* input we are tokenizing */ int nInput; /* size of the input */ int iOffset; /* current position in zInput */ int iToken; /* index of next token to be returned */ unsigned char *zToken; /* storage for current token */ int nAllocated; /* space allocated to zToken buffer */ /** * Store the offset of the second character in the bi-gram pair that we just * emitted so that we can consider it being the first character in a bi-gram * pair. * The value 0 indicates that there is no previous such character. This is * an acceptable sentinel value because the 0th offset can never be the * offset of the second in a bi-gram pair. * * For example, let us say we are tokenizing a string of 4 CJK characters * represented by the byte-string "11223344" where each repeated digit * indicates 2-bytes of storage used to encode the character in UTF-8. * (It actually takes 3, btw.) Then on the passes to emit each token, * the iOffset and iPrevGigramOffset values at entry will be: * * 1122: iOffset = 0, iPrevBigramOffset = 0 * 2233: iOffset = 4, iPrevBigramOffset = 2 * 3344: iOffset = 6, iPrevBigramOffset = 4 * (nothing will be emitted): iOffset = 8, iPrevBigramOffset = 6 */ int iPrevBigramOffset; /* previous result was bi-gram */ } porter_tokenizer_cursor; /* Forward declaration */ static const sqlite3_tokenizer_module porterTokenizerModule; /* from normalize.c */ extern unsigned int normalize_character(const unsigned int c); /* ** Create a new tokenizer instance. */ static int porterCreate( int UNUSED argc, const char UNUSED * const *argv, sqlite3_tokenizer **ppTokenizer ){ porter_tokenizer *t; t = (porter_tokenizer *) sqlite3_malloc(sizeof(*t)); if( t==NULL ) return SQLITE_NOMEM; memset(t, 0, sizeof(*t)); *ppTokenizer = &t->base; return SQLITE_OK; } /* ** Destroy a tokenizer */ static int porterDestroy(sqlite3_tokenizer *pTokenizer){ sqlite3_free(pTokenizer); return SQLITE_OK; } /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is zInput[0..nInput-1]. A cursor ** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int porterOpen( sqlite3_tokenizer UNUSED *pTokenizer, /* The tokenizer */ const char *zInput, int nInput, /* String to be tokenized */ sqlite3_tokenizer_cursor **ppCursor /* OUT: Tokenization cursor */ ){ porter_tokenizer_cursor *c; c = (porter_tokenizer_cursor *) sqlite3_malloc(sizeof(*c)); if( c==NULL ) return SQLITE_NOMEM; c->zInput = zInput; if( zInput==0 ){ c->nInput = 0; }else if( nInput<0 ){ c->nInput = (int)strlen(zInput); }else{ c->nInput = nInput; } c->iOffset = 0; /* start tokenizing at the beginning */ c->iToken = 0; c->zToken = NULL; /* no space allocated, yet. */ c->nAllocated = 0; c->iPrevBigramOffset = 0; *ppCursor = &c->base; return SQLITE_OK; } /* ** Close a tokenization cursor previously opened by a call to ** porterOpen() above. */ static int porterClose(sqlite3_tokenizer_cursor *pCursor){ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; sqlite3_free(c->zToken); sqlite3_free(c); return SQLITE_OK; } /* ** Vowel or consonant */ static const char cType[] = { 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 2, 1 }; /* ** isConsonant() and isVowel() determine if their first character in ** the string they point to is a consonant or a vowel, according ** to Porter ruls. ** ** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. ** 'Y' is a consonant unless it follows another consonant, ** in which case it is a vowel. ** ** In these routine, the letters are in reverse order. So the 'y' rule ** is that 'y' is a consonant unless it is followed by another ** consonent. */ static int isVowel(const char*); static int isConsonant(const char *z){ int j; char x = *z; if( x==0 ) return 0; assert( x>='a' && x<='z' ); j = cType[x-'a']; if( j<2 ) return j; return z[1]==0 || isVowel(z + 1); } static int isVowel(const char *z){ int j; char x = *z; if( x==0 ) return 0; assert( x>='a' && x<='z' ); j = cType[x-'a']; if( j<2 ) return 1-j; return isConsonant(z + 1); } /* ** Let any sequence of one or more vowels be represented by V and let ** C be sequence of one or more consonants. Then every word can be ** represented as: ** ** [C] (VC){m} [V] ** ** In prose: A word is an optional consonant followed by zero or ** vowel-consonant pairs followed by an optional vowel. "m" is the ** number of vowel consonant pairs. This routine computes the value ** of m for the first i bytes of a word. ** ** Return true if the m-value for z is 1 or more. In other words, ** return true if z contains at least one vowel that is followed ** by a consonant. ** ** In this routine z[] is in reverse order. So we are really looking ** for an instance of of a consonant followed by a vowel. */ static int m_gt_0(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } return *z!=0; } /* Like mgt0 above except we are looking for a value of m which is ** exactly 1 */ static int m_eq_1(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } if( *z==0 ) return 0; while( isVowel(z) ){ z++; } if( *z==0 ) return 1; while( isConsonant(z) ){ z++; } return *z==0; } /* Like mgt0 above except we are looking for a value of m>1 instead ** or m>0 */ static int m_gt_1(const char *z){ while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } if( *z==0 ) return 0; while( isVowel(z) ){ z++; } if( *z==0 ) return 0; while( isConsonant(z) ){ z++; } return *z!=0; } /* ** Return TRUE if there is a vowel anywhere within z[0..n-1] */ static int hasVowel(const char *z){ while( isConsonant(z) ){ z++; } return *z!=0; } /* ** Return TRUE if the word ends in a double consonant. ** ** The text is reversed here. So we are really looking at ** the first two characters of z[]. */ static int doubleConsonant(const char *z){ return isConsonant(z) && z[0]==z[1] && isConsonant(z+1); } /* ** Return TRUE if the word ends with three letters which ** are consonant-vowel-consonent and where the final consonant ** is not 'w', 'x', or 'y'. ** ** The word is reversed here. So we are really checking the ** first three letters and the first one cannot be in [wxy]. */ static int star_oh(const char *z){ return z[0]!=0 && isConsonant(z) && z[0]!='w' && z[0]!='x' && z[0]!='y' && z[1]!=0 && isVowel(z+1) && z[2]!=0 && isConsonant(z+2); } /* ** If the word ends with zFrom and xCond() is true for the stem ** of the word that preceeds the zFrom ending, then change the ** ending to zTo. ** ** The input word *pz and zFrom are both in reverse order. zTo ** is in normal order. ** ** Return TRUE if zFrom matches. Return FALSE if zFrom does not ** match. Not that TRUE is returned even if xCond() fails and ** no substitution occurs. */ static int stem( char **pz, /* The word being stemmed (Reversed) */ const char *zFrom, /* If the ending matches this... (Reversed) */ const char *zTo, /* ... change the ending to this (not reversed) */ int (*xCond)(const char*) /* Condition that must be true */ ){ char *z = *pz; while( *zFrom && *zFrom==*z ){ z++; zFrom++; } if( *zFrom!=0 ) return 0; if( xCond && !xCond(z) ) return 1; while( *zTo ){ *(--z) = *(zTo++); } *pz = z; return 1; } /** * Voiced sound mark is only on Japanese. It is like accent. It combines with * previous character. Example, "サ" (Katakana) with "ã‚›" (voiced sound mark) is * "ã‚¶". Although full-width character mapping has combined character like "ã‚¶", * there is no combined character on half-width Katanaka character mapping. */ static int isVoicedSoundMark(const unsigned int c) { if (c == 0xff9e || c == 0xff9f || c == 0x3099 || c == 0x309a) return 1; return 0; } /** * How many unicode characters to take from the front and back of a term in * |copy_stemmer|. */ #define COPY_STEMMER_COPY_HALF_LEN 10 /** * Normalizing but non-stemming term copying. * * The original function would take 10 bytes from the front and 10 bytes from * the back if there were no digits in the string and it was more than 20 * bytes long. If there were digits involved that would decrease to 3 bytes * from the front and 3 from the back. This would potentially corrupt utf-8 * encoded characters, which is fine from the perspective of the FTS3 logic. * * In our revised form we now operate on a unicode character basis rather than * a byte basis. Additionally we use the same length limit even if there are * digits involved because it's not clear digit token-space reduction is saving * us from anything and could be hurting. Specifically, if no one is ever * going to search on things with digits, then we should just remove them. * Right now, the space reduction is going to increase false positives when * people do search on them and increase the number of collisions sufficiently * to make it really expensive. The caveat is there will be some increase in * index size which could be meaningful if people are receiving lots of emails * full of distinct numbers. * * In order to do the copy-from-the-front and copy-from-the-back trick, once * we reach N characters in, we set zFrontEnd to the current value of zOut * (which represents the termination of the first part of the result string) * and set zBackStart to the value of zOutStart. We then advanced zBackStart * along a character at a time as we write more characters. Once we have * traversed the entire string, if zBackStart > zFrontEnd, then we know * the string should be shrunk using the characters in the two ranges. * * (It would be faster to scan from the back with specialized logic but that * particular logic seems easy to screw up and we don't have unit tests in here * to the extent required.) * * @param zIn Input string to normalize and potentially shrink. * @param nBytesIn The number of bytes in zIn, distinct from the number of * unicode characters encoded in zIn. * @param zOut The string to write our output into. This must have at least * nBytesIn * MAX_UTF8_GROWTH_FACTOR in order to compensate for * normalization that results in a larger utf-8 encoding. * @param pnBytesOut Integer to write the number of bytes in zOut into. */ static void copy_stemmer(const unsigned char *zIn, const int nBytesIn, unsigned char *zOut, int *pnBytesOut){ const unsigned char *zInTerm = zIn + nBytesIn; unsigned char *zOutStart = zOut; unsigned int c; unsigned int charCount = 0; unsigned char *zFrontEnd = NULL, *zBackStart = NULL; unsigned int trashC; /* copy normalized character */ while (zIn < zInTerm) { READ_UTF8(zIn, zInTerm, c); c = normalize_character(c); /* ignore voiced/semi-voiced sound mark */ if (!isVoicedSoundMark(c)) { /* advance one non-voiced sound mark character. */ if (zBackStart) READ_UTF8(zBackStart, zOut, trashC); WRITE_UTF8(zOut, c); charCount++; if (charCount == COPY_STEMMER_COPY_HALF_LEN) { zFrontEnd = zOut; zBackStart = zOutStart; } } } /* if we need to shrink the string, transplant the back bytes */ if (zBackStart > zFrontEnd) { /* this handles when both are null too */ size_t backBytes = zOut - zBackStart; memmove(zFrontEnd, zBackStart, backBytes); zOut = zFrontEnd + backBytes; } *zOut = 0; *pnBytesOut = zOut - zOutStart; } /* ** Stem the input word zIn[0..nIn-1]. Store the output in zOut. ** zOut is at least big enough to hold nIn bytes. Write the actual ** size of the output word (exclusive of the '\0' terminator) into *pnOut. ** ** Any upper-case characters in the US-ASCII character set ([A-Z]) ** are converted to lower case. Upper-case UTF characters are ** unchanged. ** ** Words that are longer than about 20 bytes are stemmed by retaining ** a few bytes from the beginning and the end of the word. If the ** word contains digits, 3 bytes are taken from the beginning and ** 3 bytes from the end. For long words without digits, 10 bytes ** are taken from each end. US-ASCII case folding still applies. ** ** If the input word contains not digits but does characters not ** in [a-zA-Z] then no stemming is attempted and this routine just ** copies the input into the input into the output with US-ASCII ** case folding. ** ** Stemming never increases the length of the word. So there is ** no chance of overflowing the zOut buffer. */ static void porter_stemmer( const unsigned char *zIn, unsigned int nIn, unsigned char *zOut, int *pnOut ){ unsigned int i, j, c; char zReverse[28]; char *z, *z2; const unsigned char *zTerm = zIn + nIn; const unsigned char *zTmp = zIn; if( nIn<3 || nIn>=sizeof(zReverse)-7 ){ /* The word is too big or too small for the porter stemmer. ** Fallback to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); return; } for (j = sizeof(zReverse) - 6; zTmp < zTerm; j--) { READ_UTF8(zTmp, zTerm, c); c = normalize_character(c); if( c>='a' && c<='z' ){ zReverse[j] = c; }else{ /* The use of a character not in [a-zA-Z] means that we fallback ** to the copy stemmer */ copy_stemmer(zIn, nIn, zOut, pnOut); return; } } memset(&zReverse[sizeof(zReverse)-5], 0, 5); z = &zReverse[j+1]; /* Step 1a */ if( z[0]=='s' ){ if( !stem(&z, "sess", "ss", 0) && !stem(&z, "sei", "i", 0) && !stem(&z, "ss", "ss", 0) ){ z++; } } /* Step 1b */ z2 = z; if( stem(&z, "dee", "ee", m_gt_0) ){ /* Do nothing. The work was all in the test */ }else if( (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) && z!=z2 ){ if( stem(&z, "ta", "ate", 0) || stem(&z, "lb", "ble", 0) || stem(&z, "zi", "ize", 0) ){ /* Do nothing. The work was all in the test */ }else if( doubleConsonant(z) && (*z!='l' && *z!='s' && *z!='z') ){ z++; }else if( m_eq_1(z) && star_oh(z) ){ *(--z) = 'e'; } } /* Step 1c */ if( z[0]=='y' && hasVowel(z+1) ){ z[0] = 'i'; } /* Step 2 */ switch( z[1] ){ case 'a': (void) (stem(&z, "lanoita", "ate", m_gt_0) || stem(&z, "lanoit", "tion", m_gt_0)); break; case 'c': (void) (stem(&z, "icne", "ence", m_gt_0) || stem(&z, "icna", "ance", m_gt_0)); break; case 'e': (void) (stem(&z, "rezi", "ize", m_gt_0)); break; case 'g': (void) (stem(&z, "igol", "log", m_gt_0)); break; case 'l': (void) (stem(&z, "ilb", "ble", m_gt_0) || stem(&z, "illa", "al", m_gt_0) || stem(&z, "iltne", "ent", m_gt_0) || stem(&z, "ile", "e", m_gt_0) || stem(&z, "ilsuo", "ous", m_gt_0)); break; case 'o': (void) (stem(&z, "noitazi", "ize", m_gt_0) || stem(&z, "noita", "ate", m_gt_0) || stem(&z, "rota", "ate", m_gt_0)); break; case 's': (void) (stem(&z, "msila", "al", m_gt_0) || stem(&z, "ssenevi", "ive", m_gt_0) || stem(&z, "ssenluf", "ful", m_gt_0) || stem(&z, "ssensuo", "ous", m_gt_0)); break; case 't': (void) (stem(&z, "itila", "al", m_gt_0) || stem(&z, "itivi", "ive", m_gt_0) || stem(&z, "itilib", "ble", m_gt_0)); break; } /* Step 3 */ switch( z[0] ){ case 'e': (void) (stem(&z, "etaci", "ic", m_gt_0) || stem(&z, "evita", "", m_gt_0) || stem(&z, "ezila", "al", m_gt_0)); break; case 'i': (void) (stem(&z, "itici", "ic", m_gt_0)); break; case 'l': (void) (stem(&z, "laci", "ic", m_gt_0) || stem(&z, "luf", "", m_gt_0)); break; case 's': (void) (stem(&z, "ssen", "", m_gt_0)); break; } /* Step 4 */ switch( z[1] ){ case 'a': if( z[0]=='l' && m_gt_1(z+2) ){ z += 2; } break; case 'c': if( z[0]=='e' && z[2]=='n' && (z[3]=='a' || z[3]=='e') && m_gt_1(z+4) ){ z += 4; } break; case 'e': if( z[0]=='r' && m_gt_1(z+2) ){ z += 2; } break; case 'i': if( z[0]=='c' && m_gt_1(z+2) ){ z += 2; } break; case 'l': if( z[0]=='e' && z[2]=='b' && (z[3]=='a' || z[3]=='i') && m_gt_1(z+4) ){ z += 4; } break; case 'n': if( z[0]=='t' ){ if( z[2]=='a' ){ if( m_gt_1(z+3) ){ z += 3; } }else if( z[2]=='e' ){ (void) (stem(&z, "tneme", "", m_gt_1) || stem(&z, "tnem", "", m_gt_1) || stem(&z, "tne", "", m_gt_1)); } } break; case 'o': if( z[0]=='u' ){ if( m_gt_1(z+2) ){ z += 2; } }else if( z[3]=='s' || z[3]=='t' ){ (void) (stem(&z, "noi", "", m_gt_1)); } break; case 's': if( z[0]=='m' && z[2]=='i' && m_gt_1(z+3) ){ z += 3; } break; case 't': (void) (stem(&z, "eta", "", m_gt_1) || stem(&z, "iti", "", m_gt_1)); break; case 'u': if( z[0]=='s' && z[2]=='o' && m_gt_1(z+3) ){ z += 3; } break; case 'v': case 'z': if( z[0]=='e' && z[2]=='i' && m_gt_1(z+3) ){ z += 3; } break; } /* Step 5a */ if( z[0]=='e' ){ if( m_gt_1(z+1) ){ z++; }else if( m_eq_1(z+1) && !star_oh(z+1) ){ z++; } } /* Step 5b */ if( m_gt_1(z) && z[0]=='l' && z[1]=='l' ){ z++; } /* z[] is now the stemmed word in reverse order. Flip it back ** around into forward order and return. */ *pnOut = i = strlen(z); zOut[i] = 0; while( *z ){ zOut[--i] = *(z++); } } /** * Indicate whether characters in the 0x30 - 0x7f region can be part of a token. * Letters and numbers can; punctuation (and 'del') can't. */ static const char porterIdChar[] = { /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ }; /** * Test whether a character is a (non-ascii) space character or not. isDelim * uses the existing porter stemmer logic for anything in the ASCII (< 0x80) * space which covers 0x20. * * 0x2000-0x206F is the general punctuation table. 0x2000 - 0x200b are spaces. * The spaces 0x2000 - 0x200a are all defined as roughly equivalent to a * standard 0x20 space. 0x200b is a "zero width space" (ZWSP) and not like an * 0x20 space. 0x202f is a narrow no-break space and roughly equivalent to an * 0x20 space. 0x205f is a "medium mathematical space" and defined as roughly * equivalent to an 0x20 space. */ #define IS_UNI_SPACE(x) (((x)>=0x2000&&(x)<=0x200a) || (x)==0x202f || (x)==0x205f) /** * What we are checking for: * - 0x3001: Ideographic comma (-> 0x2c ',') * - 0x3002: Ideographic full stop (-> 0x2e '.') * - 0xff0c: fullwidth comma (~ wide 0x2c ',') * - 0xff0e: fullwidth full stop (~ wide 0x2e '.') * - 0xff61: halfwidth ideographic full stop (~ narrow 0x3002) * - 0xff64: halfwidth ideographic comma (~ narrow 0x3001) * * It is possible we should be treating other things as delimiters! */ #define IS_JA_DELIM(x) (((x)==0x3001)||((x)==0xFF64)||((x)==0xFF0E)||((x)==0x3002)||((x)==0xFF61)||((x)==0xFF0C)) /** * The previous character was a delimeter (which includes the start of the * string). */ #define BIGRAM_RESET 0 /** * The previous character was a CJK character and we have only seen one of them. * If we had seen more than one in a row it would be the BIGRAM_USE state. */ #define BIGRAM_UNKNOWN 1 /** * We have seen two or more CJK characters in a row. */ #define BIGRAM_USE 2 /** * The previous character was ASCII or something in the unicode general scripts * area that we do not believe is a delimeter. We call it 'alpha' as in * alphabetic/alphanumeric and something that should be tokenized based on * delimiters rather than on a bi-gram basis. */ #define BIGRAM_ALPHA 3 static int isDelim( const unsigned char *zCur, /* IN: current pointer of token */ const unsigned char *zTerm, /* IN: one character beyond end of token */ int *len, /* OUT: analyzed bytes in this token */ int *state /* IN/OUT: analyze state */ ){ const unsigned char *zIn = zCur; unsigned int c; int delim; /* get the unicode character to analyze */ READ_UTF8(zIn, zTerm, c); c = normalize_character(c); *len = zIn - zCur; /* ASCII character range has rule */ if( c < 0x80 ){ // This is original porter stemmer isDelim logic. // 0x0 - 0x1f are all control characters, 0x20 is space, 0x21-0x2f are // punctuation. delim = (c < 0x30 || !porterIdChar[c - 0x30]); // cases: "&a", "&." if (*state == BIGRAM_USE || *state == BIGRAM_UNKNOWN ){ /* previous maybe CJK and current is ascii */ *state = BIGRAM_ALPHA; /*ascii*/ delim = 1; /* must break */ } else if (delim == 1) { // cases: "a.", ".." /* this is delimiter character */ *state = BIGRAM_RESET; /*reset*/ } else { // cases: "aa", ".a" *state = BIGRAM_ALPHA; /*ascii*/ } return delim; } // (at this point we must be a non-ASCII character) /* voiced/semi-voiced sound mark is ignore */ if (isVoicedSoundMark(c) && *state != BIGRAM_ALPHA) { /* ignore this because it is combined with previous char */ return 0; } /* this isn't CJK range, so return as no delim */ // Anything less than 0x2000 (except to U+0E00-U+0EFF and U+1780-U+17FF) // is the general scripts area and should not be bi-gram indexed. // 0xa000 - 0a4cf is the Yi area. It is apparently a phonetic language whose // usage does not appear to have simple delimeter rules, so we're leaving it // as bigram processed. This is a guess, if you know better, let us know. // (We previously bailed on this range too.) // Addition, U+0E00-U+0E7F is Thai, U+0E80-U+0EFF is Laos, // and U+1780-U+17FF is Khmer. It is no easy way to break each word. // So these should use bi-gram too. // cases: "aa", ".a", "&a" if (c < 0xe00 || (c >= 0xf00 && c < 0x1780) || (c >= 0x1800 && c < 0x2000)) { *state = BIGRAM_ALPHA; /* not really ASCII but same idea; tokenize it */ return 0; } // (at this point we must be a bi-grammable char or delimiter) /* this is space character or delim character */ // cases: "a.", "..", "&." if( IS_UNI_SPACE(c) || IS_JA_DELIM(c) ){ *state = BIGRAM_RESET; /* reset */ return 1; /* it actually is a delimiter; report as such */ } // (at this point we must be a bi-grammable char) // cases: "a&" if( *state==BIGRAM_ALPHA ){ /* Previous is ascii and current maybe CJK */ *state = BIGRAM_UNKNOWN; /* mark as unknown */ return 1; /* break to emit the ASCII token*/ } /* We have no rule for CJK!. use bi-gram */ // cases: "&&" if( *state==BIGRAM_UNKNOWN || *state==BIGRAM_USE ){ /* previous state is unknown. mark as bi-gram */ *state = BIGRAM_USE; return 1; /* break to emit the digram */ } // cases: ".&" (*state == BIGRAM_RESET) *state = BIGRAM_UNKNOWN; /* mark as unknown */ return 0; /* no need to break; nothing to emit */ } static inline int tokenizer_hides_wildcards(void) { static int first_run = 1; static int hides_wildcard; if (first_run) { int version = sqlite3_libversion_number(); hides_wildcard = version > 3008004 && version < 3008007; first_run = 0; } return hides_wildcard; } /** * Generate a new token. There are basically three types of token we can * generate: * - A porter stemmed token. This is a word entirely comprised of ASCII * characters. We run the porter stemmer algorithm against the word. * Because we have no way to know what is and is not an English word * (the only language for which the porter stemmer was designed), this * could theoretically map multiple words that are not variations of the * same word down to the same root, resulting in potentially unexpected * result inclusions in the search results. We accept this result because * there's not a lot we can do about it and false positives are much * better than false negatives. * - A copied token; case/accent-folded but not stemmed. We call the porter * stemmer for all non-CJK cases and it diverts to the copy stemmer if it * sees any non-ASCII characters (after folding) or if the string is too * long. The copy stemmer will shrink the string if it is deemed too long. * - A bi-gram token; two CJK-ish characters. For query reasons we generate a * series of overlapping bi-grams. (We can't require the user to start their * search based on the arbitrary context of the indexed documents.) * * It may be useful to think of this function as operating at the points between * characters. While we are considering the 'current' character (the one after * the 'point'), we are also interested in the 'previous' character (the one * preceding the point). * At any 'point', there are a number of possible situations which I will * illustrate with pairs of characters. 'a' means alphanumeric ASCII or a * non-ASCII character that is not bi-grammable or a delimeter, '.' * means a delimiter (space or punctuation), '&' means a bi-grammable * character. * - aa: We are in the midst of a token. State remains BIGRAM_ALPHA. * - a.: We will generate a porter stemmed or copied token. State was * BIGRAM_ALPHA, gets set to BIGRAM_RESET. * - a&: We will generate a porter stemmed or copied token; we will set our * state to BIGRAM_UNKNOWN to indicate we have seen one bigram character * but that it is not yet time to emit a bigram. * - .a: We are starting a token. State was BIGRAM_RESET, gets set to * BIGRAM_ALPHA. * - ..: We skip/eat the delimeters. State stays BIGRAM_RESET. * - .&: State set to BIGRAM_UNKNOWN to indicate we have seen one bigram char. * - &a: If the state was BIGRAM_USE, we generate a bi-gram token. If the state * was BIGRAM_UNKNOWN we had only seen one CJK character and so don't do * anything. State is set to BIGRAM_ALPHA. * - &.: Same as the "&a" case, but state is set to BIGRAM_RESET. * - &&: We will generate a bi-gram token. State was either BIGRAM_UNKNOWN or * BIGRAM_USE, gets set to BIGRAM_USE. */ static int porterNext( sqlite3_tokenizer_cursor *pCursor, /* Cursor returned by porterOpen */ const char **pzToken, /* OUT: *pzToken is the token text */ int *pnBytes, /* OUT: Number of bytes in token */ int *piStartOffset, /* OUT: Starting offset of token */ int *piEndOffset, /* OUT: Ending offset of token */ int *piPosition /* OUT: Position integer of token */ ){ porter_tokenizer_cursor *c = (porter_tokenizer_cursor *) pCursor; const unsigned char *z = (unsigned char *) c->zInput; int len = 0; int state; while( c->iOffset < c->nInput ){ int iStartOffset, numChars; /* * This loop basically has two modes of operation: * - general processing (iPrevBigramOffset == 0 here) * - CJK processing (iPrevBigramOffset != 0 here) * * In an general processing pass we skip over all the delimiters, leaving us * at a character that promises to produce a token. This could be a CJK * token (state == BIGRAM_USE) or an ALPHA token (state == BIGRAM_ALPHA). * If it was a CJK token, we transition into CJK state for the next loop. * If it was an alpha token, our current offset is pointing at a delimiter * (which could be a CJK character), so it is good that our next pass * through the function and loop will skip over any delimiters. If the * delimiter we hit was a CJK character, the next time through we will * not treat it as a delimiter though; the entry state for that scan is * BIGRAM_RESET so the transition is not treated as a delimiter! * * The CJK pass always starts with the second character in a bi-gram emitted * as a token in the previous step. No delimiter skipping is required * because we know that first character might produce a token for us. It * only 'might' produce a token because the previous pass performed no * lookahead and cannot be sure it is followed by another CJK character. * This is why */ // If we have a previous bigram offset if (c->iPrevBigramOffset == 0) { /* Scan past delimiter characters */ state = BIGRAM_RESET; /* reset */ while (c->iOffset < c->nInput && isDelim(z + c->iOffset, z + c->nInput, &len, &state)) { c->iOffset += len; } } else { /* for bigram indexing, use previous offset */ c->iOffset = c->iPrevBigramOffset; } /* Count non-delimiter characters. */ iStartOffset = c->iOffset; numChars = 0; // Start from a reset state. This means the first character we see // (which will not be a delimiter) determines which of ALPHA or CJK modes // we are operating in. (It won't be a delimiter because in a 'general' // pass as defined above, we will have eaten all the delimiters, and in // a CJK pass we are guaranteed that the first character is CJK.) state = BIGRAM_RESET; /* state is reset */ // Advance until it is time to emit a token. // For ALPHA characters, this means advancing until we encounter a delimiter // or a CJK character. iOffset will be pointing at the delimiter or CJK // character, aka one beyond the last ALPHA character. // For CJK characters this means advancing until we encounter an ALPHA // character, a delimiter, or we have seen two consecutive CJK // characters. iOffset points at the ALPHA/delimiter in the first 2 cases // and the second of two CJK characters in the last case. // Because of the way this loop is structured, iOffset is only updated // when we don't terminate. However, if we terminate, len still contains // the number of bytes in the character found at iOffset. (This is useful // in the CJK case.) while (c->iOffset < c->nInput && !isDelim(z + c->iOffset, z + c->nInput, &len, &state)) { c->iOffset += len; numChars++; } if (state == BIGRAM_USE) { /* Split word by bigram */ // Right now iOffset is pointing at the second character in a pair. // Save this offset so next-time through we start with that as the // first character. c->iPrevBigramOffset = c->iOffset; // And now advance so that iOffset is pointing at the character after // the second character in the bi-gram pair. Also count the char. c->iOffset += len; numChars++; } else { /* Reset bigram offset */ c->iPrevBigramOffset = 0; } /* We emit a token if: * - there are two ideograms together, * - there are three chars or more, * - we think this is a query and wildcard magic is desired. * We think is a wildcard query when we have at least one * character, our current offset is one shy of nInput and the * character at iOffset is '*'. * * Sqlite 3.8.5 ... 3.8.6 changed how the tokenizer is called such * that we can't detect wildcards. In that case, we accept any * short word at the end of the input. */ // It is possible we have no token to emit here if iPrevBigramOffset was not // 0 on entry and there was no second CJK character. iPrevBigramOffset // will now be 0 if that is the case (and c->iOffset == iStartOffset). if (// allow two-character words only if in bigram (numChars == 2 && state == BIGRAM_USE) || // otherwise, drop two-letter words (considered stop-words) (numChars >=3) || // final word wildcard case: (numChars >= 1 && (tokenizer_hides_wildcards() ? (c->iOffset == c->nInput) : (c->iOffset == c->nInput - 1 && z[c->iOffset] == '*')))) { /* figure out the number of bytes to copy/stem */ int n = c->iOffset - iStartOffset; /* make sure there is enough buffer space */ if (n * MAX_UTF8_GROWTH_FACTOR > c->nAllocated) { c->nAllocated = n * MAX_UTF8_GROWTH_FACTOR + 20; c->zToken = sqlite3_realloc(c->zToken, c->nAllocated); if (c->zToken == NULL) return SQLITE_NOMEM; } if (state == BIGRAM_USE) { /* This is by bigram. So it is unnecessary to convert word */ copy_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); } else { porter_stemmer(&z[iStartOffset], n, c->zToken, pnBytes); } *pzToken = (const char*)c->zToken; *piStartOffset = iStartOffset; *piEndOffset = c->iOffset; *piPosition = c->iToken++; return SQLITE_OK; } } return SQLITE_DONE; } /* ** The set of routines that implement the porter-stemmer tokenizer */ static const sqlite3_tokenizer_module porterTokenizerModule = { 0, porterCreate, porterDestroy, porterOpen, porterClose, porterNext, }; /* ** Allocate a new porter tokenizer. Return a pointer to the new ** tokenizer in *ppModule */ void sqlite3Fts3PorterTokenizerModule( sqlite3_tokenizer_module const**ppModule ){ *ppModule = &porterTokenizerModule; } #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */ mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaStore.cc0000644000015600001650000011047112672421600024362 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaStore.hh" #include #include #include #include #include #include #include #include #include #include #include #include "mozilla/fts3_tokenizer.h" #include "MediaFile.hh" #include "MediaFileBuilder.hh" #include "Album.hh" #include "Filter.hh" #include "internal/sqliteutils.hh" #include "internal/utils.hh" using namespace std; namespace mediascanner { // Increment this whenever changing db schema. // It will cause dbstore to rebuild its tables. static const int schemaVersion = 10; struct MediaStorePrivate { sqlite3 *db; // https://www.sqlite.org/cvstrac/wiki?p=DatabaseIsLocked // http://sqlite.com/faq.html#q6 std::mutex dbMutex; void insert(const MediaFile &m) const; void remove(const std::string &fname) const; void insert_broken_file(const std::string &fname, const std::string &etag) const; void remove_broken_file(const std::string &fname) const; bool is_broken_file(const std::string &fname, const std::string &etag) const; MediaFile lookup(const std::string &filename) const; std::vector query(const std::string &q, MediaType type, const Filter &filter) const; std::vector queryAlbums(const std::string &core_term, const Filter &filter) const; std::vector queryArtists(const std::string &q, const Filter &filter) const; std::vector getAlbumSongs(const Album& album) const; std::string getETag(const std::string &filename) const; std::vector listSongs(const Filter &filter) const; std::vector listAlbums(const Filter &filter) const; std::vector listArtists(const Filter &filter) const; std::vector listAlbumArtists(const Filter &filter) const; std::vector listGenres(const Filter &filter) const; bool hasMedia(MediaType type) const; size_t size() const; void pruneDeleted(); void archiveItems(const std::string &prefix); void restoreItems(const std::string &prefix); void removeSubtree(const std::string &directory); void begin(); void commit(); void rollback(); }; extern "C" void sqlite3Fts3PorterTokenizerModule( sqlite3_tokenizer_module const**ppModule); static void register_tokenizer(sqlite3 *db) { Statement query(db, "SELECT fts3_tokenizer(?, ?)"); query.bind(1, "mozporter"); const sqlite3_tokenizer_module *p = nullptr; sqlite3Fts3PorterTokenizerModule(&p); query.bind(2, &p, sizeof(p)); query.step(); } /* ranking function adapted from http://sqlite.org/fts3.html#appendix_a */ static void rankfunc(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal) { const int32_t *aMatchinfo; /* Return value of matchinfo() */ int32_t nCol; /* Number of columns in the table */ int32_t nPhrase; /* Number of phrases in the query */ int32_t iPhrase; /* Current phrase */ double score = 0.0; /* Value to return */ /* Check that the number of arguments passed to this function is correct. ** If not, jump to wrong_number_args. Set aMatchinfo to point to the array ** of unsigned integer values returned by FTS function matchinfo. Set ** nPhrase to contain the number of reportable phrases in the users full-text ** query, and nCol to the number of columns in the table. */ if( nVal<1 ) goto wrong_number_args; aMatchinfo = static_cast(sqlite3_value_blob(apVal[0])); nPhrase = aMatchinfo[0]; nCol = aMatchinfo[1]; if( nVal!=(1+nCol) ) goto wrong_number_args; /* Iterate through each phrase in the users query. */ for(iPhrase=0; iPhrase / ) * ** ** aPhraseinfo[] points to the start of the data for phrase iPhrase. So ** the hit count and global hit counts for each column are found in ** aPhraseinfo[iCol*3] and aPhraseinfo[iCol*3+1], respectively. */ const int32_t *aPhraseinfo = &aMatchinfo[2 + iPhrase*nCol*3]; for(iCol=0; iCol0 ){ score += ((double)nHitCount / (double)nGlobalHitCount) * weight; } } } sqlite3_result_double(pCtx, score); return; /* Jump here if the wrong number of arguments are passed to this function */ wrong_number_args: sqlite3_result_error(pCtx, "wrong number of arguments to function rank()", -1); } struct FirstContext { int type; union { int i; double f; struct { void *blob; int length; } b; } data; }; static void first_step(sqlite3_context *ctx, int /*argc*/, sqlite3_value **argv) { FirstContext *d = static_cast(sqlite3_aggregate_context(ctx, sizeof(FirstContext*))); if (d->type != 0) { return; } sqlite3_value *arg = argv[0]; d->type = sqlite3_value_type(arg); switch (d->type) { case SQLITE_INTEGER: d->data.i = sqlite3_value_int(arg); break; case SQLITE_FLOAT: d->data.f = sqlite3_value_double(arg); break; case SQLITE_NULL: break; case SQLITE_TEXT: d->data.b.length = sqlite3_value_bytes(arg); d->data.b.blob = malloc(d->data.b.length); memcpy(d->data.b.blob, sqlite3_value_text(arg), d->data.b.length); break; case SQLITE_BLOB: d->data.b.length = sqlite3_value_bytes(arg); d->data.b.blob = malloc(d->data.b.length); memcpy(d->data.b.blob, sqlite3_value_blob(arg), d->data.b.length); break; default: sqlite3_result_error(ctx, "Unhandled data type", -1); sqlite3_result_error_code(ctx, SQLITE_MISMATCH); } } static void first_finalize(sqlite3_context *ctx) { FirstContext *d = static_cast(sqlite3_aggregate_context(ctx, 0)); if (d == nullptr) { sqlite3_result_null(ctx); return; } switch (d->type) { case SQLITE_INTEGER: sqlite3_result_int(ctx, d->data.i); break; case SQLITE_FLOAT: sqlite3_result_double(ctx, d->data.f); break; case SQLITE_NULL: sqlite3_result_null(ctx); break; case SQLITE_TEXT: sqlite3_result_text(ctx, reinterpret_cast(d->data.b.blob), d->data.b.length, free); d->data.b.blob = nullptr; break; case SQLITE_BLOB: sqlite3_result_blob(ctx, d->data.b.blob, d->data.b.length, free); d->data.b.blob = nullptr; break; default: sqlite3_result_error(ctx, "Unhandled data type", -1); sqlite3_result_error_code(ctx, SQLITE_MISMATCH); } } static bool has_block_in_path(std::map &cache, const std::string &filename) { std::vector path_segments; std::istringstream f(filename); std::string s; while (std::getline(f, s, '/')) { path_segments.push_back(s); } path_segments.pop_back(); std::string trial_path; for(const auto &seg : path_segments) { if(trial_path != "/") trial_path += "/"; trial_path += seg; auto r = cache.find(trial_path); if(r != cache.end() && r->second) { return true; } if(has_scanblock(trial_path)) { cache[trial_path] = true; return true; } else { cache[trial_path] = false; } } return false; } static void register_functions(sqlite3 *db) { if (sqlite3_create_function(db, "rank", -1, SQLITE_ANY, nullptr, rankfunc, nullptr, nullptr) != SQLITE_OK) { throw runtime_error(sqlite3_errmsg(db)); } if (sqlite3_create_function(db, "first", 1, SQLITE_ANY, nullptr, nullptr, first_step, first_finalize) != SQLITE_OK) { throw runtime_error(sqlite3_errmsg(db)); } } static void execute_sql(sqlite3 *db, const string &cmd) { char *errmsg = nullptr; if(sqlite3_exec(db, cmd.c_str(), nullptr, nullptr, &errmsg) != SQLITE_OK) { throw runtime_error(errmsg); } } static int getSchemaVersion(sqlite3 *db) { int version = -1; try { Statement select(db, "SELECT version FROM schemaVersion"); if (select.step()) version = select.getInt(0); } catch (const exception &e) { /* schemaVersion table might not exist */ } return version; } void deleteTables(sqlite3 *db) { string deleteCmd(R"( DROP TABLE IF EXISTS media; DROP TABLE IF EXISTS media_fts; DROP TABLE IF EXISTS media_attic; DROP TABLE IF EXISTS schemaVersion; DROP TABLE IF EXISTS broken_files; )"); execute_sql(db, deleteCmd); } void createTables(sqlite3 *db) { string schema(R"( CREATE TABLE schemaVersion (version INTEGER); CREATE TABLE media ( id INTEGER PRIMARY KEY, filename TEXT UNIQUE NOT NULL CHECK (filename LIKE '/%'), content_type TEXT, etag TEXT, title TEXT, date TEXT, artist TEXT, -- Only relevant to audio album TEXT, -- Only relevant to audio album_artist TEXT, -- Only relevant to audio genre TEXT, -- Only relevant to audio disc_number INTEGER, -- Only relevant to audio track_number INTEGER, -- Only relevant to audio duration INTEGER, width INTEGER, -- Only relevant to video/images height INTEGER, -- Only relevant to video/images latitude DOUBLE, longitude DOUBLE, has_thumbnail INTEGER CHECK (has_thumbnail IN (0, 1)), mtime INTEGER, type INTEGER CHECK (type IN (1, 2, 3)) -- MediaType enum ); CREATE INDEX media_type_idx ON media(type); CREATE INDEX media_song_info_idx ON media(type, album_artist, album, disc_number, track_number, title) WHERE type = 1; CREATE INDEX media_artist_idx ON media(type, artist) WHERE type = 1; CREATE INDEX media_genre_idx ON media(type, genre) WHERE type = 1; CREATE INDEX media_mtime_idx ON media(type, mtime); CREATE TABLE media_attic ( filename TEXT UNIQUE NOT NULL, content_type TEXT, etag TEXT, title TEXT, date TEXT, artist TEXT, -- Only relevant to audio album TEXT, -- Only relevant to audio album_artist TEXT, -- Only relevant to audio genre TEXT, -- Only relevant to audio disc_number INTEGER, -- Only relevant to audio track_number INTEGER, -- Only relevant to audio duration INTEGER, width INTEGER, -- Only relevant to video/images height INTEGER, -- Only relevant to video/images latitude DOUBLE, longitude DOUBLE, has_thumbnail INTEGER, mtime INTEGER, type INTEGER -- 0=Audio, 1=Video ); CREATE VIRTUAL TABLE media_fts USING fts4(content='media', title, artist, album, tokenize=mozporter); CREATE TRIGGER media_bu BEFORE UPDATE ON media BEGIN DELETE FROM media_fts WHERE docid=old.id; END; CREATE TRIGGER media_au AFTER UPDATE ON media BEGIN INSERT INTO media_fts(docid, title, artist, album) VALUES (new.id, new.title, new.artist, new.album); END; CREATE TRIGGER media_bd BEFORE DELETE ON media BEGIN DELETE FROM media_fts WHERE docid=old.id; END; CREATE TRIGGER media_ai AFTER INSERT ON media BEGIN INSERT INTO media_fts(docid, title, artist, album) VALUES (new.id, new.title, new.artist, new.album); END; CREATE TABLE broken_files ( filename TEXT PRIMARY KEY NOT NULL, etag TEXT NOT NULL ); )"); execute_sql(db, schema); Statement version(db, "INSERT INTO schemaVersion (version) VALUES (?)"); version.bind(1, schemaVersion); version.step(); } static std::string get_default_database() { std::string cachedir; char *env_cachedir = getenv("MEDIASCANNER_CACHEDIR"); if (env_cachedir) { cachedir = env_cachedir; } else { cachedir = g_get_user_cache_dir(); cachedir += "/mediascanner-2.0"; } if (g_mkdir_with_parents(cachedir.c_str(), S_IRWXU) < 0) { std::string msg("Could not create cache dir: "); msg += strerror(errno); throw runtime_error(msg); } return cachedir + "/mediastore.db"; } MediaStore::MediaStore(OpenType access, const std::string &retireprefix) : MediaStore(get_default_database(), access, retireprefix) { } MediaStore::MediaStore(const std::string &filename, OpenType access, const std::string &retireprefix) { int sqliteFlags = access == MS_READ_WRITE ? SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE : SQLITE_OPEN_READONLY; p = new MediaStorePrivate(); if(sqlite3_open_v2(filename.c_str(), &p->db, sqliteFlags, nullptr) != SQLITE_OK) { throw runtime_error(sqlite3_errmsg(p->db)); } register_tokenizer(p->db); register_functions(p->db); int detectedSchemaVersion = getSchemaVersion(p->db); if(access == MS_READ_WRITE) { if(detectedSchemaVersion != schemaVersion) { deleteTables(p->db); createTables(p->db); } if(!retireprefix.empty()) archiveItems(retireprefix); } else { if(detectedSchemaVersion != schemaVersion) { std::string msg("Tried to open a db with schema version "); msg += std::to_string(detectedSchemaVersion); msg += ", while supported version is "; msg += std::to_string(schemaVersion) + "."; throw runtime_error(msg); } } } MediaStore::~MediaStore() { sqlite3_close(p->db); delete p; } size_t MediaStorePrivate::size() const { Statement count(db, "SELECT COUNT(*) FROM media"); count.step(); return count.getInt(0); } void MediaStorePrivate::insert(const MediaFile &m) const { Statement query(db, "INSERT OR REPLACE INTO media (filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); query.bind(1, m.getFileName()); query.bind(2, m.getContentType()); query.bind(3, m.getETag()); query.bind(4, m.getTitle()); query.bind(5, m.getDate()); query.bind(6, m.getAuthor()); query.bind(7, m.getAlbum()); query.bind(8, m.getAlbumArtist()); query.bind(9, m.getGenre()); query.bind(10, m.getDiscNumber()); query.bind(11, m.getTrackNumber()); query.bind(12, m.getDuration()); query.bind(13, m.getWidth()); query.bind(14, m.getHeight()); query.bind(15, m.getLatitude()); query.bind(16, m.getLongitude()); query.bind(17, (int)m.getHasThumbnail()); query.bind(18, (int64_t)m.getModificationTime()); query.bind(19, (int)m.getType()); query.step(); const char *typestr = m.getType() == AudioMedia ? "song" : "video"; printf("Added %s to backing store: %s\n", typestr, m.getFileName().c_str()); printf(" author : %s\n", m.getAuthor().c_str()); printf(" title : %s\n", m.getTitle().c_str()); printf(" album : %s\n", m.getAlbum().c_str()); printf(" duration : %d\n", m.getDuration()); // Not atomic with the addition above but very unlikely to crash between the two. // Even if it does, only one residual line remains and that will be cleaned up // on the next scan. remove_broken_file(m.getFileName()); } void MediaStorePrivate::remove(const string &fname) const { Statement del(db, "DELETE FROM media WHERE filename = ?"); del.bind(1, fname); del.step(); } void MediaStorePrivate::insert_broken_file(const std::string &fname, const std::string &etag) const { Statement del(db, "INSERT OR REPLACE INTO broken_files (filename, etag) VALUES (?, ?)"); del.bind(1, fname); del.bind(2, etag); del.step(); } void MediaStorePrivate::remove_broken_file(const std::string &fname) const { Statement del(db, "DELETE FROM broken_files WHERE filename = ?"); del.bind(1, fname); del.step(); } bool MediaStorePrivate::is_broken_file(const std::string &fname, const std::string &etag) const { Statement query(db, "SELECT * FROM broken_files WHERE filename = ? AND etag = ?"); query.bind(1, fname); query.bind(2, etag); return query.step(); } static MediaFile make_media(Statement &query) { return MediaFileBuilder(query.getText(0)) .setContentType(query.getText(1)) .setETag(query.getText(2)) .setTitle(query.getText(3)) .setDate(query.getText(4)) .setAuthor(query.getText(5)) .setAlbum(query.getText(6)) .setAlbumArtist(query.getText(7)) .setGenre(query.getText(8)) .setDiscNumber(query.getInt(9)) .setTrackNumber(query.getInt(10)) .setDuration(query.getInt(11)) .setWidth(query.getInt(12)) .setHeight(query.getInt(13)) .setLatitude(query.getDouble(14)) .setLongitude(query.getDouble(15)) .setHasThumbnail(query.getInt(16)) .setModificationTime(query.getInt64(17)) .setType((MediaType)query.getInt(18)); } static vector collect_media(Statement &query) { vector result; while (query.step()) { result.push_back(make_media(query)); } return result; } MediaFile MediaStorePrivate::lookup(const std::string &filename) const { Statement query(db, R"( SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media WHERE filename = ? )"); query.bind(1, filename); if (!query.step()) { throw runtime_error("Could not find media " + filename); } return make_media(query); } vector MediaStorePrivate::query(const std::string &core_term, MediaType type, const Filter &filter) const { string qs(R"( SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media )"); if (!core_term.empty()) { qs += R"( JOIN ( SELECT docid, rank(matchinfo(media_fts), 1.0, 0.5, 0.75) AS rank FROM media_fts WHERE media_fts MATCH ? ) AS ranktable ON (media.id = ranktable.docid) )"; } qs += " WHERE type = ?"; switch (filter.getOrder()) { case MediaOrder::Default: case MediaOrder::Rank: // We can only sort by rank if there was a query term if (!core_term.empty()) { qs += " ORDER BY ranktable.rank"; if (!filter.getReverse()) { // Normal order is descending qs += " DESC"; } } break; case MediaOrder::Title: qs += " ORDER BY title"; if (filter.getReverse()) { qs += " DESC"; } break; case MediaOrder::Date: qs += " ORDER BY date"; if (filter.getReverse()) { qs += " DESC"; } break; case MediaOrder::Modified: qs += " ORDER BY mtime"; if (filter.getReverse()) { qs += " DESC"; } break; } qs += " LIMIT ? OFFSET ?"; Statement query(db, qs.c_str()); int param = 1; if (!core_term.empty()) { query.bind(param++, core_term + "*"); } query.bind(param++, (int)type); query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); return collect_media(query); } static Album make_album(Statement &query) { const string album = query.getText(0); const string album_artist = query.getText(1); const string date = query.getText(2); const string genre = query.getText(3); const string filename = query.getText(4); const bool has_thumbnail = query.getInt(5); return Album(album, album_artist, date, genre, filename, has_thumbnail); } static vector collect_albums(Statement &query) { vector result; while (query.step()) { result.push_back(make_album(query)); } return result; } vector MediaStorePrivate::queryAlbums(const std::string &core_term, const Filter &filter) const { string qs(R"( SELECT album, album_artist, first(date) as date, first(genre) as genre, first(filename) as filename, first(has_thumbnail) as has_thumbnail, first(mtime) as mtime FROM media WHERE type = ? AND album <> '' )"); if (!core_term.empty()) { qs += " AND id IN (SELECT docid FROM media_fts WHERE media_fts MATCH ?)"; } qs += " GROUP BY album, album_artist"; switch (filter.getOrder()) { case MediaOrder::Default: case MediaOrder::Title: qs += " ORDER BY album"; if (filter.getReverse()) { qs += " DESC"; } break; case MediaOrder::Rank: throw std::runtime_error("Can not query albums by rank"); case MediaOrder::Date: throw std::runtime_error("Can not query albums by date"); case MediaOrder::Modified: qs += " ORDER BY mtime"; if (filter.getReverse()) { qs += " DESC"; } break; } qs += " LIMIT ? OFFSET ?"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (!core_term.empty()) { query.bind(param++, core_term + "*"); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); return collect_albums(query); } vector MediaStorePrivate::queryArtists(const string &q, const Filter &filter) const { string qs(R"( SELECT artist FROM media WHERE type = ? AND artist <> '' )"); if (!q.empty()) { qs += "AND id IN (SELECT docid FROM media_fts WHERE media_fts MATCH ?)"; } qs += " GROUP BY artist"; switch (filter.getOrder()) { case MediaOrder::Default: case MediaOrder::Title: qs += " ORDER BY artist"; if (filter.getReverse()) { qs += " DESC"; } break; case MediaOrder::Rank: throw std::runtime_error("Can not query artists by rank"); case MediaOrder::Date: throw std::runtime_error("Can not query artists by date"); case MediaOrder::Modified: throw std::runtime_error("Can not query artists by modification date"); } qs += " LIMIT ? OFFSET ?"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (!q.empty()) { query.bind(param++, q + "*"); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); vector result; while (query.step()) { result.push_back(query.getText(0)); } return result; } vector MediaStorePrivate::getAlbumSongs(const Album& album) const { Statement query(db, R"( SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media WHERE album = ? AND album_artist = ? AND type = ? ORDER BY disc_number, track_number )"); query.bind(1, album.getTitle()); query.bind(2, album.getArtist()); query.bind(3, (int)AudioMedia); return collect_media(query); } std::string MediaStorePrivate::getETag(const std::string &filename) const { Statement query(db, R"( SELECT etag FROM media WHERE filename = ? )"); query.bind(1, filename); if (query.step()) { return query.getText(0); } else { return ""; } } std::vector MediaStorePrivate::listSongs(const Filter &filter) const { std::string qs(R"( SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media WHERE type = ? )"); if (filter.hasArtist()) { qs += " AND artist = ?"; } if (filter.hasAlbum()) { qs += " AND album = ?"; } if (filter.hasAlbumArtist()) { qs += " AND album_artist = ?"; } if (filter.hasGenre()) { qs += " AND genre = ?"; } qs += R"( ORDER BY album_artist, album, disc_number, track_number, title LIMIT ? OFFSET ? )"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (filter.hasArtist()) { query.bind(param++, filter.getArtist()); } if (filter.hasAlbum()) { query.bind(param++, filter.getAlbum()); } if (filter.hasAlbumArtist()) { query.bind(param++, filter.getAlbumArtist()); } if (filter.hasGenre()) { query.bind(param++, filter.getGenre()); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); return collect_media(query); } std::vector MediaStorePrivate::listAlbums(const Filter &filter) const { std::string qs(R"( SELECT album, album_artist, first(date) as date, first(genre) as genre, first(filename) as filename, first(has_thumbnail) as has_thumbnail FROM media WHERE type = ? )"); if (filter.hasArtist()) { qs += " AND artist = ?"; } if (filter.hasAlbumArtist()) { qs += " AND album_artist = ?"; } if (filter.hasGenre()) { qs += "AND genre = ?"; } qs += R"( GROUP BY album, album_artist ORDER BY album_artist, album LIMIT ? OFFSET ? )"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (filter.hasArtist()) { query.bind(param++, filter.getArtist()); } if (filter.hasAlbumArtist()) { query.bind(param++, filter.getAlbumArtist()); } if (filter.hasGenre()) { query.bind(param++, filter.getGenre()); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); return collect_albums(query); } vector MediaStorePrivate::listArtists(const Filter &filter) const { string qs(R"( SELECT artist FROM media WHERE type = ? )"); if (filter.hasGenre()) { qs += " AND genre = ?"; } qs += R"( GROUP BY artist ORDER BY artist LIMIT ? OFFSET ? )"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (filter.hasGenre()) { query.bind(param++, filter.getGenre()); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); vector artists; while (query.step()) { artists.push_back(query.getText(0)); } return artists; } vector MediaStorePrivate::listAlbumArtists(const Filter &filter) const { string qs(R"( SELECT album_artist FROM media WHERE type = ? )"); if (filter.hasGenre()) { qs += " AND genre = ?"; } qs += R"( GROUP BY album_artist ORDER BY album_artist LIMIT ? OFFSET ? )"; Statement query(db, qs.c_str()); int param = 1; query.bind(param++, (int)AudioMedia); if (filter.hasGenre()) { query.bind(param++, filter.getGenre()); } query.bind(param++, filter.getLimit()); query.bind(param++, filter.getOffset()); vector artists; while (query.step()) { artists.push_back(query.getText(0)); } return artists; } vector MediaStorePrivate::listGenres(const Filter &filter) const { Statement query(db, R"( SELECT genre FROM media WHERE type = ? GROUP BY genre ORDER BY genre LIMIT ? OFFSET ? )"); query.bind(1, (int)AudioMedia); query.bind(2, filter.getLimit()); query.bind(3, filter.getOffset()); vector genres; while (query.step()) { genres.push_back(query.getText(0)); } return genres; } bool MediaStorePrivate::hasMedia(MediaType type) const { if (type == AllMedia) { Statement query(db, R"( SELECT id FROM media LIMIT 1 )"); return query.step(); } else { Statement query(db, R"( SELECT id FROM media WHERE type = ? LIMIT 1 )"); query.bind(1, (int)type); return query.step(); } } void MediaStorePrivate::pruneDeleted() { std::map path_cache; vector deleted; Statement query(db, "SELECT filename FROM media"); while (query.step()) { const string filename = query.getText(0); if (access(filename.c_str(), F_OK) != 0 || has_block_in_path(path_cache, filename)) { deleted.push_back(filename); continue; } } query.finalize(); printf("%d files deleted from disk or in scanblocked directories.\n", (int)deleted.size()); for(const auto &i : deleted) { remove(i); } } void MediaStorePrivate::archiveItems(const std::string &prefix) { const char *templ = R"(BEGIN TRANSACTION; INSERT INTO media_attic (filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type) SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media WHERE filename LIKE %s; DELETE FROM media WHERE filename LIKE %s; COMMIT; )"; string cond = sqlQuote(prefix + "%"); const size_t bufsize = 1024; char cmd[bufsize]; snprintf(cmd, bufsize, templ, cond.c_str(), cond.c_str()); char *errmsg; if(sqlite3_exec(db, cmd, nullptr, nullptr, &errmsg) != SQLITE_OK) { sqlite3_exec(db, "ROLLBACK;", nullptr, nullptr, nullptr); throw runtime_error(errmsg); } } void MediaStorePrivate::restoreItems(const std::string &prefix) { const char *templ = R"(BEGIN TRANSACTION; INSERT INTO media (filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type) SELECT filename, content_type, etag, title, date, artist, album, album_artist, genre, disc_number, track_number, duration, width, height, latitude, longitude, has_thumbnail, mtime, type FROM media_attic WHERE filename LIKE %s; DELETE FROM media_attic WHERE filename LIKE %s; COMMIT; )"; string cond = sqlQuote(prefix + "%"); const size_t bufsize = 1024; char cmd[bufsize]; snprintf(cmd, bufsize, templ, cond.c_str(), cond.c_str()); char *errmsg; if(sqlite3_exec(db, cmd, nullptr, nullptr, &errmsg) != SQLITE_OK) { sqlite3_exec(db, "ROLLBACK;", nullptr, nullptr, nullptr); throw runtime_error(errmsg); } } void MediaStorePrivate::removeSubtree(const std::string &directory) { string escaped = directory; string::size_type pos = 0; // Escape instances of like expression special characters and the escape character. while (true) { pos = escaped.find_first_of("%_!", pos); if (pos == string::npos) { break; } escaped.replace(pos, 0, "!"); pos += 2; } if (escaped.empty() || escaped[escaped.size() - 1] != '/') { escaped += '/'; } escaped += '%'; Statement query(db, "DELETE FROM media WHERE filename LIKE ? ESCAPE '!'"); query.bind(1, escaped); query.step(); } void MediaStorePrivate::begin() { Statement query(db, "BEGIN TRANSACTION"); query.step(); } void MediaStorePrivate::commit() { Statement query(db, "COMMIT TRANSACTION"); query.step(); } void MediaStorePrivate::rollback() { Statement query(db, "ROLLBACK TRANSACTION"); query.step(); } void MediaStore::insert(const MediaFile &m) const { std::lock_guard lock(p->dbMutex); p->insert(m); } void MediaStore::remove(const std::string &fname) const { std::lock_guard lock(p->dbMutex); p->remove(fname); } void MediaStore::insert_broken_file(const std::string &fname, const std::string &etag) const { std::lock_guard lock(p->dbMutex); p->insert_broken_file(fname, etag); } void MediaStore::remove_broken_file(const std::string &fname) const { std::lock_guard lock(p->dbMutex); p->remove_broken_file(fname); } bool MediaStore::is_broken_file(const std::string &fname, const std::string &etag) const { std::lock_guard lock(p->dbMutex); return p->is_broken_file(fname, etag); } MediaFile MediaStore::lookup(const std::string &filename) const { std::lock_guard lock(p->dbMutex); return p->lookup(filename); } std::vector MediaStore::query(const std::string &q, MediaType type, const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->query(q, type, filter); } std::vector MediaStore::queryAlbums(const std::string &core_term, const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->queryAlbums(core_term, filter); } std::vector MediaStore::queryArtists(const std::string &q, const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->queryArtists(q, filter); } std::vector MediaStore::getAlbumSongs(const Album& album) const { std::lock_guard lock(p->dbMutex); return p->getAlbumSongs(album); } std::string MediaStore::getETag(const std::string &filename) const { std::lock_guard lock(p->dbMutex); return p->getETag(filename); } std::vector MediaStore::listSongs(const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->listSongs(filter); } std::vector MediaStore::listAlbums(const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->listAlbums(filter); } std::vector MediaStore::listArtists(const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->listArtists(filter); } std::vector MediaStore::listAlbumArtists(const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->listAlbumArtists(filter); } std::vector MediaStore::listGenres(const Filter &filter) const { std::lock_guard lock(p->dbMutex); return p->listGenres(filter); } bool MediaStore::hasMedia(MediaType type) const { std::lock_guard lock(p->dbMutex); return p->hasMedia(type); } size_t MediaStore::size() const { std::lock_guard lock(p->dbMutex); return p->size(); } void MediaStore::pruneDeleted() { std::lock_guard lock(p->dbMutex); p->pruneDeleted(); } void MediaStore::archiveItems(const std::string &prefix) { std::lock_guard lock(p->dbMutex); p->archiveItems(prefix); } void MediaStore::restoreItems(const std::string &prefix) { std::lock_guard lock(p->dbMutex); p->restoreItems(prefix); } void MediaStore::removeSubtree(const std::string &directory) { std::lock_guard lock(p->dbMutex); p->removeSubtree(directory); } MediaStoreTransaction MediaStore::beginTransaction() { std::lock_guard lock(p->dbMutex); p->begin(); return MediaStoreTransaction(p); } MediaStoreTransaction::MediaStoreTransaction(MediaStorePrivate *p) : p(p) { } MediaStoreTransaction::MediaStoreTransaction(MediaStoreTransaction &&other) { *this = std::move(other); } MediaStoreTransaction::~MediaStoreTransaction() { if (!p) { return; } std::lock_guard lock(p->dbMutex); try { p->rollback(); } catch (const std::exception &e) { fprintf(stderr, "MediaStoreTransaction: error rolling back in destructor: %s\n", e.what()); } } MediaStoreTransaction& MediaStoreTransaction::operator=(MediaStoreTransaction &&other) { p = other.p; other.p = nullptr; return *this; } void MediaStoreTransaction::commit() { std::lock_guard lock(p->dbMutex); p->commit(); p->begin(); } } mediascanner2-0.111+16.04.20160317/src/mediascanner/Filter.hh0000644000015600001650000000400512672421600023560 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIAFILTER_H_ #define MEDIAFILTER_H_ #include #include "scannercore.hh" namespace mediascanner { class Filter final { public: Filter(); Filter(const Filter &other); Filter(Filter &&other); ~Filter(); Filter &operator=(const Filter &other); Filter &operator=(Filter &&other); bool operator==(const Filter &other) const; bool operator!=(const Filter &other) const; void clear(); void setArtist(const std::string &artist); void unsetArtist(); bool hasArtist() const; const std::string &getArtist() const; void setAlbum(const std::string &album); void unsetAlbum(); bool hasAlbum() const; const std::string &getAlbum() const; void setAlbumArtist(const std::string &album_artist); void unsetAlbumArtist(); bool hasAlbumArtist() const; const std::string &getAlbumArtist() const; void setGenre(const std::string &genre); void unsetGenre(); bool hasGenre() const; const std::string &getGenre() const; void setOffset(int offset); int getOffset() const; void setLimit(int limit); int getLimit() const; void setOrder(MediaOrder order); MediaOrder getOrder() const; void setReverse(bool reverse); bool getReverse() const; private: struct Private; Private *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/utils.cc0000644000015600001650000001064712672421600023472 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include"internal/utils.hh" #include #include #include #include #include #include namespace mediascanner { std::string sqlQuote(const std::string &input) { std::vector out; out.reserve(input.size() + 2); const char quote = '\''; out.push_back(quote); for(size_t i=0; i std::string filenameToTitle(const std::string &filename) { auto fname_start = filename.rfind('/'); auto suffix_dot = filename.rfind('.'); std::string result; if(fname_start == std::string::npos) { if(suffix_dot == std::string::npos) { result = filename; } else { result = filename.substr(0, suffix_dot); } } else { if(suffix_dot == std::string::npos) { result = filename.substr(fname_start+1, filename.size()); } else { result = filename.substr(fname_start+1, suffix_dot-fname_start-1); } } for(size_t i=0; imessage; g_error_free(error); throw std::runtime_error(msg); } std::string uri(uristr); g_free(uristr); return uri; } using namespace std; static bool dir_exists(const string &path) { struct stat statbuf; if(stat(path.c_str(), &statbuf) < 0) { if(errno != ENOENT) { printf("Error while trying to determine state of dir %s: %s\n", path.c_str(), strerror(errno)); } return false; } return S_ISDIR(statbuf.st_mode) ; } static bool file_exists(const string &path) { struct stat statbuf; if(stat(path.c_str(), &statbuf) < 0) { if(errno != ENOENT) { printf("Error while trying to determine state of file %s: %s\n", path.c_str(), strerror(errno)); } return false; } return S_ISREG(statbuf.st_mode) ; } bool is_rootlike(const string &path) { string s1 = path + "/usr"; string s2 = path + "/var"; string s3 = path + "/bin"; string s4 = path + "/Program Files"; return (dir_exists(s1) && dir_exists(s2) && dir_exists(s3)) || dir_exists(s4); } bool is_optical_disc(const string &path) { string dvd1 = path + "/AUDIO_TS"; string dvd2 = path + "/VIDEO_TS"; string bluray = path + "/BDMV"; return (dir_exists(dvd1) && dir_exists(dvd2)) || dir_exists(bluray); } bool has_scanblock(const std::string &path) { return file_exists(path + "/.nomedia"); } static string uri_escape(const string &unescaped) { char *result = g_uri_escape_string(unescaped.c_str(), NULL, FALSE); string escaped(result); g_free(result); return escaped; } string make_album_art_uri(const string &artist, const string &album) { string result = "image://albumart/artist="; result += uri_escape(artist); result += "&album="; result += uri_escape(album); return result; } std::string make_thumbnail_uri(const std::string &uri) { return string("image://thumbnailer/") + uri; } } mediascanner2-0.111+16.04.20160317/src/mediascanner/internal/0000755000015600001650000000000012672422030023625 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/mediascanner/internal/utils.hh0000644000015600001650000000234012672421600025307 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef SCAN_UTILS_H #define SCAN_UTILS_H #include namespace mediascanner { std::string sqlQuote(const std::string &input); std::string filenameToTitle(const std::string &filename); std::string getUri(const std::string &filename); bool is_rootlike(const std::string &path); bool is_optical_disc(const std::string &path); bool has_scanblock(const std::string &path); std::string make_album_art_uri(const std::string &artist, const std::string &album); std::string make_thumbnail_uri(const std::string &uri); } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/internal/MediaFilePrivate.hh0000644000015600001650000000340212672421600027321 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIAFILEPRIVATE_HH #define MEDIAFILEPRIVATE_HH #include #include namespace mediascanner { struct MediaFilePrivate { std::string filename; std::string content_type; std::string etag; std::string title; std::string date; // ISO date string. Should this be time since epoch? std::string author; std::string album; std::string album_artist; std::string genre; int disc_number = 0; int track_number = 0; int duration = 0; // In seconds. int width = 0; int height = 0; double latitude = 0.0; // In degrees, negative for South double longitude = 0.0; // In degrees, negative for West bool has_thumbnail = false; uint64_t modification_time = 0; MediaType type = UnknownMedia; MediaFilePrivate(); MediaFilePrivate(const std::string &filename); MediaFilePrivate(const MediaFilePrivate &other); bool operator==(const MediaFilePrivate &other) const; bool operator!=(const MediaFilePrivate &other) const; void setFallbackMetadata(); }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/internal/FolderArtCache.hh0000644000015600001650000000274112672421600026762 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef FOLDERARTCACHE_HH #define FOLDERARTCACHE_HH #include #include #include #include namespace mediascanner { struct FolderArtInfo { std::string art; struct timespec dir_mtime {0, 0}; }; class FolderArtCache final { public: FolderArtCache(); ~FolderArtCache(); FolderArtCache(const FolderArtCache &other) = delete; FolderArtCache& operator=(const FolderArtCache &other) = delete; // Get a singleton instance of the cache static FolderArtCache& get(); std::string get_art_for_directory(const std::string &directory); std::string get_art_for_file(const std::string &filename); private: std::map cache_; std::map old_cache_; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/internal/sqliteutils.hh0000644000015600001650000001111512672421600026531 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef SCAN_SQLITEUTILS_H #define SCAN_SQLITEUTILS_H #include #include #include #include namespace mediascanner { class Statement { public: Statement(sqlite3 *db, const char *sql) { rc = sqlite3_prepare_v2(db, sql, -1, &statement, nullptr); if (rc != SQLITE_OK) { throw std::runtime_error(sqlite3_errmsg(db)); } } ~Statement() { try { finalize(); } catch(const std::exception &e) { fprintf(stderr, "Error finalising statement: %s\n", e.what()); } catch(...) { fprintf(stderr, "Unknown error finalising statement.\n"); } } void bind(int pos, int value) { rc = sqlite3_bind_int(statement, pos, value); if (rc != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(rc)); } void bind(int pos, int64_t value) { rc = sqlite3_bind_int64(statement, pos, value); if (rc != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(rc)); } void bind(int pos, double value) { rc = sqlite3_bind_double(statement, pos, value); if (rc != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(rc)); } void bind(int pos, const std::string &value) { rc = sqlite3_bind_text(statement, pos, value.c_str(), value.size(), SQLITE_TRANSIENT); if (rc != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(rc)); } void bind(int pos, void *blob, int length) { rc = sqlite3_bind_blob(statement, pos, blob, length, SQLITE_STATIC); if (rc != SQLITE_OK) throw std::runtime_error(sqlite3_errstr(rc)); } bool step() { // Sqlite docs list a few cases where you need to to a rollback // if a calling step fails. We don't match those cases but if // we change queries that may start to happen. // https://sqlite.org/c3ref/step.html // // The proper fix would probably be to move to a WAL log, but // it seems to require write access to the mediastore dir // even for readers, which is problematic for confined apps. int retry_count=0; const int max_retries = 100; do { rc = sqlite3_step(statement); retry_count++; } while(rc == SQLITE_BUSY && retry_count < max_retries); switch (rc) { case SQLITE_DONE: return false; case SQLITE_ROW: return true; default: throw std::runtime_error(sqlite3_errstr(rc)); } } std::string getText(int column) { if (rc != SQLITE_ROW) throw std::runtime_error("Statement hasn't been executed, or no more results"); return (const char *)sqlite3_column_text(statement, column); } int getInt(int column) { if (rc != SQLITE_ROW) throw std::runtime_error("Statement hasn't been executed, or no more results"); return sqlite3_column_int(statement, column); } int64_t getInt64(int column) { if (rc != SQLITE_ROW) throw std::runtime_error("Statement hasn't been executed, or no more results"); return sqlite3_column_int64(statement, column); } double getDouble(int column) { if (rc != SQLITE_ROW) throw std::runtime_error("Statement hasn't been executed, or no more results"); return sqlite3_column_double(statement, column); } void finalize() { if (statement != nullptr) { rc = sqlite3_finalize(statement); if (rc != SQLITE_OK) { std::string msg("Could not finalize statement: "); msg += sqlite3_errstr(rc); throw std::runtime_error(msg); } statement = nullptr; } } private: sqlite3_stmt *statement; int rc; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaStoreBase.hh0000644000015600001650000000412512672421600025165 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASTOREBASE_HH_ #define MEDIASTOREBASE_HH_ #include"scannercore.hh" #include #include namespace mediascanner { class MediaFile; class Album; class Filter; class MediaStoreBase { public: MediaStoreBase(); virtual ~MediaStoreBase(); MediaStoreBase(const MediaStoreBase &other) = delete; MediaStoreBase& operator=(const MediaStoreBase &other) = delete; virtual MediaFile lookup(const std::string &filename) const = 0; virtual std::vector query(const std::string &q, MediaType type, const Filter& filter) const = 0; virtual std::vector queryAlbums(const std::string &core_term, const Filter &filter) const = 0; virtual std::vector queryArtists(const std::string &q, const Filter &filter) const = 0; virtual std::vector getAlbumSongs(const Album& album) const = 0; virtual std::string getETag(const std::string &filename) const = 0; virtual std::vector listSongs(const Filter &filter) const = 0; virtual std::vector listAlbums(const Filter &filter) const = 0; virtual std::vector listArtists(const Filter &filter) const = 0; virtual std::vectorlistAlbumArtists(const Filter &filter) const = 0; virtual std::vectorlistGenres(const Filter &filter) const = 0; virtual bool hasMedia(MediaType type) const = 0; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/FolderArtCache.cc0000644000015600001650000001132412672421600025131 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2015 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "internal/FolderArtCache.hh" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace { const int CACHE_SIZE = 50; string detect_albumart(string directory) { static const array art_basenames = { "cover", "album", "albumart", ".folder", "folder", }; static const array art_extensions = { "jpeg", "jpg", "png", }; if (!directory.empty() && directory[directory.size()-1] != '/') { directory += "/"; } unique_ptr dir( opendir(directory.c_str()), &closedir); if (!dir) { return ""; } const int dirent_size = sizeof(dirent) + fpathconf(dirfd(dir.get()), _PC_NAME_MAX) + 1; unique_ptr entry( reinterpret_cast(malloc(dirent_size)), &free); string detected; int best_score = 0; struct dirent *de = nullptr; while (readdir_r(dir.get(), entry.get(), &de) == 0 && de) { const string filename(de->d_name); auto dot = filename.rfind('.'); // Ignore files with no extension if (dot == string::npos) { continue; } auto basename = filename.substr(0, dot); auto extension = filename.substr(dot+1); // Does the file name match one of the required names when // converted to lower case? transform(basename.begin(), basename.end(), basename.begin(), ::tolower); transform(extension.begin(), extension.end(), extension.begin(), ::tolower); auto base_pos = find(art_basenames.begin(), art_basenames.end(), basename); if (base_pos == art_basenames.end()) { continue; } auto ext_pos = find(art_extensions.begin(), art_extensions.end(), extension); if (ext_pos == art_extensions.end()) { continue; } int score = (base_pos - art_basenames.begin()) * art_basenames.size() + (ext_pos - art_extensions.begin()); if (detected.empty() || score < best_score) { detected = filename; best_score = score; } } if (detected.empty()) { return detected; } return directory + detected; } } namespace mediascanner { FolderArtCache::FolderArtCache() = default; FolderArtCache::~FolderArtCache() = default; // Get a singleton instance of the cache FolderArtCache& FolderArtCache::get() { static FolderArtCache cache; return cache; } string FolderArtCache::get_art_for_directory(const string &directory) { struct stat s; if (lstat(directory.c_str(), &s) < 0) { return ""; } if (!S_ISDIR(s.st_mode)) { return ""; } FolderArtInfo info; bool update = false; try { info = cache_.at(directory); } catch (const out_of_range &) { // Fall back to checking the previous iteration of the cache try { info = old_cache_.at(directory); update = true; } catch (const out_of_range &) { } } if (info.dir_mtime.tv_sec != s.st_mtim.tv_sec || info.dir_mtime.tv_nsec != s.st_mtim.tv_nsec) { info.art = detect_albumart(directory); info.dir_mtime = s.st_mtim; update = true; } if (update) { cache_[directory] = info; // Start new cache generation if we've exceeded the size. if (cache_.size() > CACHE_SIZE) { old_cache_ = move(cache_); cache_.clear(); } } return info.art; } string FolderArtCache::get_art_for_file(const string &filename) { auto slash = filename.rfind('/'); if (slash == string::npos) { return ""; } auto directory = filename.substr(0, slash + 1); return get_art_for_directory(directory); } } mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaFileBuilder.hh0000644000015600001650000000470712672421600025472 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIAFILEBUILDER_H_ #define MEDIAFILEBUILDER_H_ #include"scannercore.hh" #include #include namespace mediascanner { class MediaFile; struct MediaFilePrivate; /** * This is a helper class to build MediaFiles. Since we want MediaFiles * to be immutable and always valid, the user needs to always list * all variables in the constructor. This is cumbersome so this class * allows you to gather them one by one and then finally construct * a fully valid MediaFile. */ class MediaFileBuilder final { friend class MediaFile; public: explicit MediaFileBuilder(const std::string &filename); MediaFileBuilder(const MediaFile &mf); MediaFileBuilder(const MediaFileBuilder &) = delete; MediaFileBuilder& operator=(MediaFileBuilder &) = delete; ~MediaFileBuilder(); MediaFile build() const; MediaFileBuilder &setType(MediaType t); MediaFileBuilder &setETag(const std::string &e); MediaFileBuilder &setContentType(const std::string &c); MediaFileBuilder &setTitle(const std::string &t); MediaFileBuilder &setDate(const std::string &d); MediaFileBuilder &setAuthor(const std::string &a); MediaFileBuilder &setAlbum(const std::string &a); MediaFileBuilder &setAlbumArtist(const std::string &a); MediaFileBuilder &setGenre(const std::string &g); MediaFileBuilder &setDiscNumber(int n); MediaFileBuilder &setTrackNumber(int n); MediaFileBuilder &setDuration(int d); MediaFileBuilder &setWidth(int w); MediaFileBuilder &setHeight(int h); MediaFileBuilder &setLatitude(double l); MediaFileBuilder &setLongitude(double l); MediaFileBuilder &setHasThumbnail(bool t); MediaFileBuilder &setModificationTime(uint64_t t); private: MediaFilePrivate *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaStore.hh0000644000015600001650000000701312672421600024371 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIASTORE_HH_ #define MEDIASTORE_HH_ #include "MediaStoreBase.hh" #include #include namespace mediascanner { enum OpenType { MS_READ_ONLY, MS_READ_WRITE }; struct MediaStorePrivate; class MediaStoreTransaction; class MediaStore final : public virtual MediaStoreBase { private: MediaStorePrivate *p; public: MediaStore(OpenType access, const std::string &retireprefix=""); MediaStore(const std::string &filename, OpenType access, const std::string &retireprefix=""); MediaStore(const MediaStore &other) = delete; MediaStore& operator=(const MediaStore &other) = delete; virtual ~MediaStore(); void insert(const MediaFile &m) const; void remove(const std::string &fname) const; // Maintain a list of files known to crash GStreamer // metadata scanner. void insert_broken_file(const std::string &fname, const std::string &etag) const; void remove_broken_file(const std::string &fname) const; bool is_broken_file(const std::string &fname, const std::string &etag) const; virtual MediaFile lookup(const std::string &filename) const override; virtual std::vector query(const std::string &q, MediaType type, const Filter &filter) const override; virtual std::vector queryAlbums(const std::string &core_term, const Filter &filter) const override; virtual std::vector queryArtists(const std::string &q, const Filter &filter) const override; virtual std::vector getAlbumSongs(const Album& album) const override; virtual std::string getETag(const std::string &filename) const override; virtual std::vector listSongs(const Filter &filter) const override; virtual std::vector listAlbums(const Filter &filter) const override; virtual std::vector listArtists(const Filter &filter) const override; virtual std::vectorlistAlbumArtists(const Filter &filter) const override; virtual std::vectorlistGenres(const Filter &filter) const override; virtual bool hasMedia(MediaType type) const override; size_t size() const; void pruneDeleted(); void archiveItems(const std::string &prefix); void restoreItems(const std::string &prefix); void removeSubtree(const std::string &directory); MediaStoreTransaction beginTransaction(); }; class MediaStoreTransaction final { friend MediaStore; public: MediaStoreTransaction(MediaStoreTransaction &&other); ~MediaStoreTransaction(); MediaStoreTransaction(const MediaStoreTransaction &other) = delete; MediaStoreTransaction& operator=(const MediaStoreTransaction &other) = delete; MediaStoreTransaction& operator=(MediaStoreTransaction &&other); void commit(); private: MediaStoreTransaction(MediaStorePrivate *p); MediaStorePrivate *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/mediascanner-2.0.map0000644000015600001650000000123012672421600025434 0ustar pbuserpbgroup00000000000000{ global: # We can do this because we only export classes that have public # methods. If they had private ones, we would need to enumerate # what to export. extern "C++" { mediascanner::MediaFile::*; mediascanner::Album::*; mediascanner::MediaFileBuilder::*; mediascanner::MediaStore::*; mediascanner::MediaStoreBase::*; mediascanner::MediaStoreTransaction::*; mediascanner::Filter::*; typeinfo?for?mediascanner::*; typeinfo?name?for?mediascanner::*; VTT?for?mediascanner::*; virtual?thunk?to?mediascanner::*; vtable?for?mediascanner::*; }; local: *; }; mediascanner2-0.111+16.04.20160317/src/mediascanner/Album.hh0000644000015600001650000000354412672421600023402 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef ALBUM_HH #define ALBUM_HH #include namespace mediascanner { class Album final { public: Album(); Album(const std::string &title, const std::string &artist); Album(const std::string &title, const std::string &artist, const std::string &date, const std::string &genre, const std::string &filename); Album(const std::string &title, const std::string &artist, const std::string &date, const std::string &genre, const std::string &filename, bool has_thumbnail); Album(const Album &other); Album(Album &&other); ~Album(); Album &operator=(const Album &other); Album &operator=(Album &&other); const std::string& getTitle() const noexcept; const std::string& getArtist() const noexcept; const std::string& getDate() const noexcept; const std::string& getGenre() const noexcept; const std::string& getArtFile() const noexcept; bool getHasThumbnail() const noexcept; std::string getArtUri() const; bool operator==(const Album &other) const; bool operator!=(const Album &other) const; private: struct Private; Private *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaStoreBase.cc0000644000015600001650000000153112672421600025151 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaStoreBase.hh" namespace mediascanner { MediaStoreBase::MediaStoreBase() { } MediaStoreBase::~MediaStoreBase() { } } mediascanner2-0.111+16.04.20160317/src/mediascanner/scannercore.hh0000644000015600001650000000174012672421600024640 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef SCANNERCORE_H #define SCANNERCORE_H namespace mediascanner { enum MediaType { UnknownMedia, AudioMedia, VideoMedia, ImageMedia, AllMedia = 255, }; enum class MediaOrder { Default, Rank, Title, Date, Modified, }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaFile.hh0000644000015600001650000000464412672421600024163 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #ifndef MEDIAFILE_HH #define MEDIAFILE_HH #include "scannercore.hh" #include #include namespace mediascanner { class MediaFileBuilder; struct MediaFilePrivate; class MediaFile final { friend class MediaFileBuilder; public: MediaFile(); MediaFile(const MediaFile &other); MediaFile(MediaFile &&other); MediaFile(const MediaFileBuilder &builder); MediaFile(MediaFileBuilder &&builder); ~MediaFile(); const std::string& getFileName() const noexcept; const std::string& getContentType() const noexcept; const std::string& getETag() const noexcept; const std::string& getTitle() const noexcept; const std::string& getAuthor() const noexcept; const std::string& getAlbum() const noexcept; const std::string& getAlbumArtist() const noexcept; const std::string& getDate() const noexcept; const std::string& getGenre() const noexcept; std::string getUri() const; std::string getArtUri() const; int getDiscNumber() const noexcept; int getTrackNumber() const noexcept; int getDuration() const noexcept; int getWidth() const noexcept; int getHeight() const noexcept; double getLatitude() const noexcept; double getLongitude() const noexcept; bool getHasThumbnail() const noexcept; uint64_t getModificationTime() const noexcept; MediaType getType() const noexcept; bool operator==(const MediaFile &other) const; bool operator!=(const MediaFile &other) const; MediaFile &operator=(const MediaFile &other); MediaFile &operator=(MediaFile &&other); // There are no setters. MediaFiles are immutable. // For piecewise construction use MediaFileBuilder. private: MediaFilePrivate *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/mediascanner/Album.cc0000644000015600001650000000720712672421600023370 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "Album.hh" #include "internal/FolderArtCache.hh" #include "internal/utils.hh" using namespace std; namespace mediascanner { struct Album::Private { string title; string artist; string date; string genre; string filename; bool has_thumbnail; Private() {} Private(const string &title, const string &artist, const string &date, const string &genre, const string &filename, bool has_thumbnail) : title(title), artist(artist), date(date), genre(genre), filename(filename), has_thumbnail(has_thumbnail) {} Private(const Private &other) { *this = other; } }; Album::Album() : p(new Private){ } Album::Album(const std::string &title, const std::string &artist) : Album(title, artist, "", "", "", false) { } Album::Album(const std::string &title, const std::string &artist, const std::string &date, const std::string &genre, const std::string &filename) : Album(title, artist, date, genre, filename, !filename.empty()) { } Album::Album(const std::string &title, const std::string &artist, const std::string &date, const std::string &genre, const std::string &filename, bool has_thumbnail) : p(new Private(title, artist, date, genre, filename, has_thumbnail)) { } Album::Album(const Album &other) : p(new Private(*other.p)) { } Album::Album(Album &&other) : p(nullptr) { *this = std::move(other); } Album::~Album() { delete p; } Album &Album::operator=(const Album &other) { *p = *other.p; return *this; } Album &Album::operator=(Album &&other) { if (this != &other) { delete p; p = other.p; other.p = nullptr; } return *this; } const std::string& Album::getTitle() const noexcept { return p->title; } const std::string& Album::getArtist() const noexcept { return p->artist; } const std::string& Album::getDate() const noexcept { return p->date; } const std::string& Album::getGenre() const noexcept { return p->genre; } const std::string& Album::getArtFile() const noexcept { return p->filename; } bool Album::getHasThumbnail() const noexcept { return p->has_thumbnail; } std::string Album::getArtUri() const { if (p->has_thumbnail) { return make_thumbnail_uri(getUri(p->filename)); } else { auto standalone = FolderArtCache::get().get_art_for_file(p->filename); if (!standalone.empty()) { return make_thumbnail_uri(getUri(standalone)); } return make_album_art_uri(p->artist, p->title); } } bool Album::operator==(const Album &other) const { return p->title == other.p->title && p->artist == other.p->artist && p->date == other.p->date && p->genre == other.p->genre && p->filename == other.p->filename && p->has_thumbnail == other.p->has_thumbnail; } bool Album::operator!=(const Album &other) const { return !(*this == other); } } mediascanner2-0.111+16.04.20160317/src/mediascanner/CMakeLists.txt0000644000015600001650000000201312672421600024547 0ustar pbuserpbgroup00000000000000add_library(mediascanner SHARED MediaFile.cc MediaFileBuilder.cc MediaFilePrivate.cc Filter.cc Album.cc MediaStore.cc MediaStoreBase.cc FolderArtCache.cc utils.cc mozilla/fts3_porter.c mozilla/Normalize.c ) set(symbol_map "${CMAKE_CURRENT_SOURCE_DIR}/mediascanner-2.0.map") set_target_properties(mediascanner PROPERTIES LINK_FLAGS "${ldflags} -Wl,--version-script,${symbol_map} ") set_target_properties(mediascanner PROPERTIES LINK_DEPENDS ${symbol_map}) add_definitions(${MEDIASCANNER_DEPS_CFLAGS}) target_link_libraries(mediascanner ${MEDIASCANNER_DEPS_LDFLAGS}) set_target_properties(mediascanner PROPERTIES OUTPUT_NAME "mediascanner-2.0" SOVERSION ${MEDIASCANNER_SOVERSION} VERSION ${MEDIASCANNER_LIBVERSION} ) install( TARGETS mediascanner LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(FILES Album.hh Filter.hh MediaFile.hh MediaFileBuilder.hh MediaStore.hh MediaStoreBase.hh scannercore.hh DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/mediascanner-2.0/mediascanner" ) mediascanner2-0.111+16.04.20160317/src/mediascanner/MediaFile.cc0000644000015600001650000001033212672421600024140 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as * published by the Free Software Foundation. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "MediaFile.hh" #include "MediaFileBuilder.hh" #include "internal/MediaFilePrivate.hh" #include "internal/FolderArtCache.hh" #include "internal/utils.hh" #include using namespace std; namespace mediascanner { MediaFile::MediaFile() : p(new MediaFilePrivate) { } MediaFile::MediaFile(const MediaFile &other) : p(new MediaFilePrivate(*other.p)) { } MediaFile::MediaFile(MediaFile &&other) : p(nullptr) { *this = std::move(other); } MediaFile::MediaFile(const MediaFileBuilder &builder) { if(!builder.p) { throw logic_error("Tried to construct a Mediafile with an empty MediaFileBuilder."); } p = new MediaFilePrivate(*builder.p); p->setFallbackMetadata(); } MediaFile::MediaFile(MediaFileBuilder &&builder) { if(!builder.p) { throw logic_error("Tried to construct a Mediafile with an empty MediaFileBuilder."); } p = builder.p; builder.p = nullptr; p->setFallbackMetadata(); } MediaFile::~MediaFile() { delete p; } MediaFile &MediaFile::operator=(const MediaFile &other) { *p = *other.p; return *this; } MediaFile &MediaFile::operator=(MediaFile &&other) { if (this != &other) { delete p; p = other.p; other.p = nullptr; } return *this; } const std::string& MediaFile::getFileName() const noexcept { return p->filename; } const std::string& MediaFile::getContentType() const noexcept { return p->content_type; } const std::string& MediaFile::getETag() const noexcept { return p->etag; } const std::string& MediaFile::getTitle() const noexcept { return p->title; } const std::string& MediaFile::getAuthor() const noexcept { return p->author; } const std::string& MediaFile::getAlbum() const noexcept { return p->album; } const std::string& MediaFile::getAlbumArtist() const noexcept { return p->album_artist; } const std::string& MediaFile::getDate() const noexcept { return p->date; } const std::string& MediaFile::getGenre() const noexcept { return p->genre; } int MediaFile::getDiscNumber() const noexcept { return p->disc_number; } int MediaFile::getTrackNumber() const noexcept { return p->track_number; } int MediaFile::getDuration() const noexcept { return p->duration; } int MediaFile::getWidth() const noexcept { return p->width; } int MediaFile::getHeight() const noexcept { return p->height; } double MediaFile::getLatitude() const noexcept { return p->latitude; } double MediaFile::getLongitude() const noexcept { return p->longitude; } bool MediaFile::getHasThumbnail() const noexcept { return p->has_thumbnail; } uint64_t MediaFile::getModificationTime() const noexcept { return p->modification_time; } MediaType MediaFile::getType() const noexcept { return p->type; } std::string MediaFile::getUri() const { return mediascanner::getUri(p->filename); } std::string MediaFile::getArtUri() const { switch (p->type) { case AudioMedia: { if (p->has_thumbnail) { return make_thumbnail_uri(getUri()); } auto standalone = FolderArtCache::get().get_art_for_file(p->filename); if(!standalone.empty()) { return make_thumbnail_uri(mediascanner::getUri(standalone)); } return make_album_art_uri(getAuthor(), getAlbum()); } default: return make_thumbnail_uri(getUri()); } } bool MediaFile::operator==(const MediaFile &other) const { return *p == *other.p; } bool MediaFile::operator!=(const MediaFile &other) const { return !(*this == other); } } mediascanner2-0.111+16.04.20160317/src/utils/0000755000015600001650000000000012672422030020520 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/utils/query.cc0000644000015600001650000000336112672421600022201 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "mediascanner/Filter.hh" #include "mediascanner/MediaFile.hh" #include "mediascanner/MediaStore.hh" #include #include #include #include using namespace std; using namespace mediascanner; void queryDb(const string &core_term) { MediaStore store(MS_READ_ONLY); vector results; results = store.query(core_term, AudioMedia, Filter()); if(results.empty()) { printf("No audio matches.\n"); } else { printf("Audio matches:\n"); } for(const auto &i : results) { printf("Filename: %s\n", i.getFileName().c_str()); } results = store.query(core_term, VideoMedia, Filter()); if(results.empty()) { printf("No video matches.\n"); } else { printf("Video matches:\n"); } for(const auto &i : results) { printf("Filename: %s\n", i.getFileName().c_str()); } } int main(int argc, char **argv) { if(argc < 2) { printf("%s \n", argv[0]); return 1; } string core_term(argv[1]); queryDb(core_term); } mediascanner2-0.111+16.04.20160317/src/utils/mountwatcher.cc0000644000015600001650000000236612672421600023560 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include "../daemon/MountWatcher.hh" void mount_event(const mediascanner::MountWatcher::Info &info) { printf("Filesystem %s\n", info.is_mounted ? "mounted" : "unmounted"); printf(" Device: %s\n", info.device.c_str()); printf(" UUID: %s\n", info.uuid.c_str()); printf(" Mount Point: %s\n", info.mount_point.c_str()); printf("\n"); } int main(int, char **) { mediascanner::MountWatcher watcher(mount_event); GMainLoop *main = g_main_loop_new(nullptr, false); g_main_loop_run(main); } mediascanner2-0.111+16.04.20160317/src/utils/CMakeLists.txt0000644000015600001650000000046312672421600023265 0ustar pbuserpbgroup00000000000000add_definitions(${MEDIASCANNER_DEPS_CFLAGS}) include_directories(..) add_executable(watcher watchertest.cc) add_executable(query query.cc) target_link_libraries(query mediascanner ${MEDIASCANNER_DEPS_LDFLAGS}) add_executable(mountwatcher mountwatcher.cc) target_link_libraries(mountwatcher scannerstuff) mediascanner2-0.111+16.04.20160317/src/utils/watchertest.cc0000644000015600001650000000422712672421600023373 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include using namespace std; #define BUFSIZE 4096 int main(int /*argc*/, char **/*argv*/) { string dbdir = "/home/"; dbdir += getlogin(); dbdir += "/.cache/mediascanner-test"; int ifd; int wd; char buf[BUFSIZE]; ifd = inotify_init(); if(ifd == -1) { printf("Inotify init failed.\n"); return 1; } wd = inotify_add_watch(ifd, dbdir.c_str(), IN_CREATE | IN_DELETE | IN_MODIFY); if(wd == -1) { printf("Could not create watch for data dir.\n"); return 1; } printf("This program simulates invalidation of query results as media files come and go.\n\n"); while(true) { ssize_t num_read; num_read = read(ifd, buf, BUFSIZE); if(num_read == 0) { printf("Inotify returned 0.\n"); return 1; } if(num_read == -1) { printf("Read error.\n"); return 1; } for(char *p = buf; p < buf + num_read;) { struct inotify_event *event = (struct inotify_event *) p; if(event->mask & IN_CREATE) { printf("Invalidation: create\n"); } else if(event->mask & IN_DELETE) { printf("Invalidation: delete\n"); } else { printf("Invalidation: modify %s\n", event->name); } p += sizeof(struct inotify_event) + event->len; } } return 0; } mediascanner2-0.111+16.04.20160317/src/daemon/0000755000015600001650000000000012672422030020623 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/daemon/InvalidationSender.hh0000644000015600001650000000261012672421605024734 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef INVALIDATIONSENDER_HH #define INVALIDATIONSENDER_HH #include typedef struct _GDBusConnection GDBusConnection; /** * A class that sends a broadcast signal that the state of media * files has changed. */ class InvalidationSender final { public: InvalidationSender(); ~InvalidationSender(); InvalidationSender(const InvalidationSender &o) = delete; InvalidationSender& operator=(const InvalidationSender &o) = delete; void invalidate(); void setBus(GDBusConnection *bus); void setDelay(int delay); private: static int callback(void *data); std::unique_ptr bus; unsigned int timeout_id = 0; int delay = 0; }; #endif mediascanner2-0.111+16.04.20160317/src/daemon/Scanner.hh0000644000015600001650000000237012672421600022541 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef SCANNER_HH_ #define SCANNER_HH_ #include #include #include #include namespace mediascanner { struct DetectedFile; class MetadataExtractor; class StopIteration : public std::exception { }; class Scanner final { public: Scanner(MetadataExtractor *extractor, const std::string &root, const MediaType type); ~Scanner(); Scanner(const Scanner &o) = delete; Scanner& operator=(const Scanner &o) = delete; DetectedFile next(); private: struct Private; Private *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/daemon/scannerdaemon.cc0000644000015600001650000002524212672421605023763 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaStore.hh" #include "../extractor/DetectedFile.hh" #include "../extractor/MetadataExtractor.hh" #include "MountWatcher.hh" #include "SubtreeWatcher.hh" #include "Scanner.hh" #include "InvalidationSender.hh" #include "../mediascanner/internal/utils.hh" using namespace std; using namespace mediascanner; namespace { bool is_same_directory(const char *dir1, const char *dir2) { struct stat s1, s2; if(stat(dir1, &s1) != 0) { return false; } if(stat(dir2, &s2) != 0) { return false; } return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino; } } static const char BUS_NAME[] = "com.canonical.MediaScanner2.Daemon"; static const unsigned int INVALIDATE_DELAY = 1; class ScannerDaemon final { public: ScannerDaemon(); ~ScannerDaemon(); int run(); private: void setupBus(); void setupSignals(); void setupMountWatcher(); void readFiles(MediaStore &store, const string &subdir, const MediaType type); void addDir(const string &dir); void removeDir(const string &dir); static gboolean signalCallback(gpointer data); static void busNameLostCallback(GDBusConnection *connection, const char *name, gpointer data); void mountEvent(const MountWatcher::Info &info); unique_ptr mount_watcher; unsigned int sigint_id = 0, sigterm_id = 0; string cachedir; unique_ptr store; unique_ptr extractor; map> subtrees; InvalidationSender invalidator; unique_ptr main_loop; unique_ptr session_bus; unsigned int bus_name_id = 0; }; ScannerDaemon::ScannerDaemon() : main_loop(g_main_loop_new(nullptr, FALSE), g_main_loop_unref), session_bus(nullptr, g_object_unref) { setupBus(); store.reset(new MediaStore(MS_READ_WRITE, "/media/")); extractor.reset(new MetadataExtractor(session_bus.get())); setupMountWatcher(); const char *musicdir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); const char *videodir = g_get_user_special_dir(G_USER_DIRECTORY_VIDEOS); const char *picturesdir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES); const char *homedir = g_get_home_dir(); // According to XDG specification, when one of the special dirs is missing // it falls back to home directory. This would mean scanning the entire home // directory. This is probably not what people want so skip if this is the case. if (musicdir && !is_same_directory(musicdir, homedir)) addDir(musicdir); if (videodir && !is_same_directory(videodir, homedir)) addDir(videodir); if (picturesdir && !is_same_directory(picturesdir, homedir)) addDir(picturesdir); // In case someone opened the db file before we could populate it. invalidator.invalidate(); // This is at the end because the initial scan may take a while // and is not interruptible but we want the process to die if it // gets a SIGINT or the like. setupSignals(); } ScannerDaemon::~ScannerDaemon() { if (sigint_id != 0) { g_source_remove(sigint_id); } if (sigterm_id != 0) { g_source_remove(sigterm_id); } if (bus_name_id != 0) { g_bus_unown_name(bus_name_id); } } void ScannerDaemon::busNameLostCallback(GDBusConnection *, const char *name, gpointer data) { ScannerDaemon *daemon = static_cast(data); fprintf(stderr, "Exiting due to loss of control of bus name %s\n", name); daemon->bus_name_id = 0; g_main_loop_quit(daemon->main_loop.get()); } void ScannerDaemon::setupBus() { GError *error = nullptr; session_bus.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error)); if (!session_bus) { string errortxt(error->message); g_error_free(error); string msg = "Failed to connect to session bus: "; msg += errortxt; throw runtime_error(msg); } invalidator.setBus(session_bus.get()); invalidator.setDelay(INVALIDATE_DELAY); bus_name_id = g_bus_own_name_on_connection( session_bus.get(), BUS_NAME, static_cast( G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE), nullptr, &ScannerDaemon::busNameLostCallback, this, nullptr); } gboolean ScannerDaemon::signalCallback(gpointer data) { ScannerDaemon *daemon = static_cast(data); g_main_loop_quit(daemon->main_loop.get()); return G_SOURCE_CONTINUE; } void ScannerDaemon::setupSignals() { sigint_id = g_unix_signal_add(SIGINT, &ScannerDaemon::signalCallback, this); sigterm_id = g_unix_signal_add(SIGTERM, &ScannerDaemon::signalCallback, this); } void ScannerDaemon::addDir(const string &dir) { assert(dir[0] == '/'); if(subtrees.find(dir) != subtrees.end()) { return; } if(is_rootlike(dir)) { fprintf(stderr, "Directory %s looks like a top level root directory, skipping it (%s).\n", dir.c_str(), __PRETTY_FUNCTION__); return; } if(is_optical_disc(dir)) { fprintf(stderr, "Directory %s looks like an optical disc, skipping it.\n", dir.c_str()); return; } if(has_scanblock(dir)) { fprintf(stderr, "Directory %s has a scan block file, skipping it.\n", dir.c_str()); return; } unique_ptr sw(new SubtreeWatcher(*store.get(), *extractor.get(), invalidator)); store->restoreItems(dir); store->pruneDeleted(); readFiles(*store.get(), dir, AllMedia); sw->addDir(dir); subtrees[dir] = move(sw); } void ScannerDaemon::removeDir(const string &dir) { assert(dir[0] == '/'); if(subtrees.find(dir) == subtrees.end()) return; store->archiveItems(dir); subtrees.erase(dir); } void ScannerDaemon::readFiles(MediaStore &store, const string &subdir, const MediaType type) { Scanner s(extractor.get(), subdir, type); MediaStoreTransaction txn = store.beginTransaction(); const int update_interval = 10; // How often to send invalidations. struct timespec previous_update, current_time; clock_gettime(CLOCK_MONOTONIC, &previous_update); previous_update.tv_sec -= update_interval/2; // Send the first update sooner for better visual appeal. while(true) { try { auto d = s.next(); clock_gettime(CLOCK_MONOTONIC, ¤t_time); while(g_main_context_pending(g_main_context_default())) { g_main_context_iteration(g_main_context_default(), FALSE); } if(current_time.tv_sec - previous_update.tv_sec >= update_interval) { txn.commit(); invalidator.invalidate(); previous_update = current_time; } // If the file is broken or unchanged, use fallback. if (store.is_broken_file(d.filename, d.etag)) { fprintf(stderr, "Using fallback data for unscannable file %s.\n", d.filename.c_str()); store.insert(extractor->fallback_extract(d)); continue; } if(d.etag == store.getETag(d.filename)) continue; try { store.insert_broken_file(d.filename, d.etag); MediaFile media; try { media = extractor->extract(d); } catch (const runtime_error &e) { fprintf(stderr, "Error extracting from '%s': %s\n", d.filename.c_str(), e.what()); media = extractor->fallback_extract(d); } store.insert(std::move(media)); } catch(const exception &e) { fprintf(stderr, "Error when indexing: %s\n", e.what()); } } catch(const StopIteration &stop) { break; } } txn.commit(); } int ScannerDaemon::run() { g_main_loop_run(main_loop.get()); return 99; } void ScannerDaemon::setupMountWatcher() { try { using namespace std::placeholders; mount_watcher.reset( new MountWatcher(std::bind(&ScannerDaemon::mountEvent, this, _1))); } catch (const std::runtime_error &e) { fprintf(stderr, "Failed to connect to udisksd: %s\n", e.what()); fprintf(stderr, "Removable media support disabled\n"); return; } } void ScannerDaemon::mountEvent(const MountWatcher::Info& info) { bool changed = false; if (info.is_mounted) { printf("Volume %s was mounted.\n", info.mount_point.c_str()); if (info.mount_point.substr(0, 6) == "/media") { addDir(info.mount_point); changed = true; } } else { printf("Volume %s was unmounted.\n", info.mount_point.c_str()); if (subtrees.find(info.mount_point) != subtrees.end()) { removeDir(info.mount_point); changed = true; } else { // This volume was not tracked because it looked rootlike. // Thus we don't need to do anything. } } if (changed) { invalidator.invalidate(); } } static void print_banner() { char timestr[200]; time_t t; struct tm *tmp; t = time(NULL); tmp = localtime(&t); if (tmp == NULL) { printf("\nMediascanner service starting.\n\n"); return; } if (strftime(timestr, sizeof(timestr), "%Y-%m-%d %l:%M:%S", tmp) == 0) { printf("\nMediascanner service starting.\n\n"); return; } printf("\nMediascanner service starting at %s.\n\n", timestr); } int main(int /*argc*/, char **/*argv*/) { print_banner(); try { ScannerDaemon d; return d.run(); } catch(string &s) { printf("Error: %s\n", s.c_str()); } return 100; } mediascanner2-0.111+16.04.20160317/src/daemon/MountWatcher.cc0000644000015600001650000002462612672421600023566 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "MountWatcher.hh" #include #include #include #include #include #include #include #include #include namespace { const char BLOCK_DEVICE_PREFIX[] = "/org/freedesktop/UDisks2/block_devices/"; const char FILESYSTEM_IFACE[] = "org.freedesktop.UDisks2.Filesystem"; struct DeviceInfo { mediascanner::MountWatcherPrivate *p; std::unique_ptr device; std::unique_ptr filesystem; std::string mount_point; bool is_mounted = false; unsigned int mount_point_changed_id = 0; DeviceInfo(mediascanner::MountWatcherPrivate *p, UDisksObject *dev); ~DeviceInfo(); DeviceInfo(const DeviceInfo&) = delete; DeviceInfo& operator=(DeviceInfo& o) = delete; void filesystem_added(); void filesystem_removed(); void update_mount_state(); static void mount_point_changed(GObject *, GParamSpec *, void *user_data) noexcept; }; } namespace mediascanner { struct MountWatcherPrivate { std::function callback; std::unique_ptr client; GDBusObjectManager *manager = nullptr; std::map> devices; unsigned int object_added_id = 0; unsigned int object_removed_id = 0; unsigned int interface_added_id = 0; unsigned int interface_removed_id = 0; MountWatcherPrivate(std::function callback); ~MountWatcherPrivate(); static void object_added(GDBusObjectManager *manager, GDBusObject *object, void *user_data) noexcept; static void object_removed(GDBusObjectManager *manager, GDBusObject *object, void *user_data) noexcept; static void interface_added(GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *iface, void *user_data) noexcept; static void interface_removed(GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *iface, void *user_data) noexcept; }; MountWatcher::MountWatcher(std::function callback) : p(new MountWatcherPrivate(callback)) { GError *error = nullptr; p->client.reset(udisks_client_new_sync(nullptr, &error)); if (not p->client) { std::string errortxt(error->message); g_error_free(error); delete(p); throw std::runtime_error( std::string("Failed to create udisks2 client: ") + errortxt); } p->manager = udisks_client_get_object_manager(p->client.get()); p->object_added_id = g_signal_connect( p->manager, "object-added", G_CALLBACK(&MountWatcherPrivate::object_added), p); p->object_removed_id = g_signal_connect( p->manager, "object-removed", G_CALLBACK(&MountWatcherPrivate::object_removed), p); p->interface_added_id = g_signal_connect( p->manager, "interface-added", G_CALLBACK(&MountWatcherPrivate::interface_added), p); p->interface_removed_id = g_signal_connect( p->manager, "interface-removed", G_CALLBACK(&MountWatcherPrivate::interface_removed), p); GList *objects = g_dbus_object_manager_get_objects(p->manager); for (GList *l = objects; l != nullptr; l = l->next) { GDBusObject *object = reinterpret_cast(l->data); MountWatcherPrivate::object_added(p->manager, object, p); } } MountWatcher::~MountWatcher() { delete p; } MountWatcherPrivate::MountWatcherPrivate( std::function callback) : callback(callback), client(nullptr, g_object_unref) { } MountWatcherPrivate::~MountWatcherPrivate() { if (object_added_id != 0) { g_signal_handler_disconnect(manager, object_added_id); } if (object_removed_id != 0) { g_signal_handler_disconnect(manager, object_removed_id); } if (interface_added_id != 0) { g_signal_handler_disconnect(manager, interface_added_id); } if (interface_removed_id != 0) { g_signal_handler_disconnect(manager, interface_removed_id); } // Clear the callback so we don't send out any notifications // during destruction. callback = nullptr; } void MountWatcherPrivate::object_added(GDBusObjectManager *, GDBusObject *object, void *user_data) noexcept { MountWatcherPrivate *p = reinterpret_cast(user_data); // We're only interested in block devices const char *object_path = g_dbus_object_get_object_path(object); if (!g_str_has_prefix(object_path, BLOCK_DEVICE_PREFIX)) { return; } UDisksObject *device = UDISKS_OBJECT(object); UDisksBlock *block = udisks_object_peek_block(device); if (not block) { return; } // Check if we're already tracking this object (this should never // be true if events are received in order, but it doesn't hurt to // check). if (p->devices.find(object_path) != p->devices.end()) { return; } // Determine whether the device belongs to a removable drive. std::unique_ptr drive_object( udisks_client_get_object(p->client.get(), udisks_block_get_drive(block)), g_object_unref); if (not drive_object) { return; } UDisksDrive *drive = udisks_object_peek_drive(drive_object.get()); if (not drive || !udisks_drive_get_removable(drive)) return; // Start tracking this device std::unique_ptr info(new DeviceInfo(p, device)); p->devices.emplace(object_path, std::move(info)); } void MountWatcherPrivate::object_removed(GDBusObjectManager *, GDBusObject *object, void *user_data) noexcept { MountWatcherPrivate *p = reinterpret_cast(user_data); const char *object_path = g_dbus_object_get_object_path(object); p->devices.erase(object_path); } void MountWatcherPrivate::interface_added(GDBusObjectManager *, GDBusObject *object, GDBusInterface *iface, void *user_data) noexcept { // We're only interested in the filesystem interface GDBusInterfaceInfo *iface_info = g_dbus_interface_get_info(iface); if (strcmp(iface_info->name, FILESYSTEM_IFACE) != 0) { return; } MountWatcherPrivate *p = reinterpret_cast(user_data); const char *object_path = g_dbus_object_get_object_path(object); try { p->devices.at(object_path)->filesystem_added(); } catch (const std::out_of_range &e) { } } void MountWatcherPrivate::interface_removed(GDBusObjectManager *, GDBusObject *object, GDBusInterface *iface, void *user_data) noexcept { // We're only interested in the filesystem interface GDBusInterfaceInfo *iface_info = g_dbus_interface_get_info(iface); if (strcmp(iface_info->name, FILESYSTEM_IFACE) != 0) { return; } MountWatcherPrivate *p = reinterpret_cast(user_data); const char *object_path = g_dbus_object_get_object_path(object); try { p->devices.at(object_path)->filesystem_removed(); } catch (const std::out_of_range &e) { } } } namespace { DeviceInfo::DeviceInfo(mediascanner::MountWatcherPrivate *p, UDisksObject *dev) : p(p), device(reinterpret_cast(g_object_ref(dev)), g_object_unref), filesystem(nullptr, g_object_unref) { if (udisks_object_peek_filesystem(device.get())) { filesystem_added(); } } DeviceInfo::~DeviceInfo() { filesystem_removed(); } void DeviceInfo::filesystem_added() { if (mount_point_changed_id != 0) { g_signal_handler_disconnect(filesystem.get(), mount_point_changed_id); mount_point_changed_id = 0; } filesystem.reset(udisks_object_get_filesystem(device.get())); mount_point_changed_id = g_signal_connect( filesystem.get(), "notify::mount-points", G_CALLBACK(&DeviceInfo::mount_point_changed), this); update_mount_state(); } void DeviceInfo::filesystem_removed() { if (mount_point_changed_id != 0) { g_signal_handler_disconnect(filesystem.get(), mount_point_changed_id); mount_point_changed_id = 0; } filesystem.reset(); update_mount_state(); } void DeviceInfo::update_mount_state() { const char *new_mount_point = nullptr; if (filesystem) { auto mount_points = udisks_filesystem_get_mount_points(filesystem.get()); new_mount_point = mount_points ? mount_points[0] : nullptr; } // Has the mount state changed? if ((new_mount_point != nullptr) == is_mounted) { return; } // Construct an info structure for the callback mediascanner::MountWatcher::Info mount_info; mount_info.is_mounted = new_mount_point != nullptr; mount_info.mount_point = mount_info.is_mounted ? std::string(new_mount_point) : mount_point; UDisksBlock *block = udisks_object_peek_block(device.get()); const char *device = udisks_block_get_device(block); if (device != nullptr) { mount_info.device = device; } const char *uuid = udisks_block_get_id_uuid(block); if (uuid != nullptr) { mount_info.uuid = uuid; } // And then update our internal state is_mounted = new_mount_point != nullptr; if (is_mounted) { mount_point = new_mount_point; } else { mount_point = ""; } if (p->callback) { p->callback(mount_info); } } void DeviceInfo::mount_point_changed(GObject *, GParamSpec *, void *user_data) noexcept { DeviceInfo *info = reinterpret_cast(user_data); info->update_mount_state(); } } mediascanner2-0.111+16.04.20160317/src/daemon/MountWatcher.hh0000644000015600001650000000226212672421600023570 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include namespace mediascanner { struct MountWatcherPrivate; class MountWatcher final { public: struct Info { std::string device; std::string uuid; std::string mount_point; bool is_mounted; }; MountWatcher(std::function callback); ~MountWatcher(); MountWatcher(const MountWatcher&) = delete; MountWatcher& operator=(MountWatcher& o) = delete; private: MountWatcherPrivate *p; }; } mediascanner2-0.111+16.04.20160317/src/daemon/InvalidationSender.cc0000644000015600001650000000532012672421605024723 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include"InvalidationSender.hh" #include #include #include #include #include using namespace std; // timer delay in seconds static const char SCOPES_DBUS_IFACE[] = "com.canonical.unity.scopes"; static const char SCOPES_DBUS_PATH[] = "/com/canonical/unity/scopes"; static const char SCOPES_INVALIDATE_RESULTS[] = "InvalidateResults"; InvalidationSender::InvalidationSender() : bus(nullptr, g_object_unref) { } InvalidationSender::~InvalidationSender() { if (timeout_id != 0) { g_source_remove(timeout_id); } } void InvalidationSender::setBus(GDBusConnection *bus) { this->bus.reset(static_cast(g_object_ref(bus))); } void InvalidationSender::setDelay(int delay) { this->delay = delay; } void InvalidationSender::invalidate() { if (!bus) { return; } if (timeout_id != 0) { return; } if (delay > 0) { timeout_id = g_timeout_add_seconds(delay, &InvalidationSender::callback, static_cast(this)); } else { InvalidationSender::callback(this); } } int InvalidationSender::callback(void *data) { auto invalidator = static_cast(data); GError *error = nullptr; if (!g_dbus_connection_emit_signal( invalidator->bus.get(), nullptr, SCOPES_DBUS_PATH, SCOPES_DBUS_IFACE, SCOPES_INVALIDATE_RESULTS, g_variant_new("(s)", "mediascanner-music"), &error)) { fprintf(stderr, "Could not invalidate music scope results: %s\n", error->message); g_error_free(error); error = nullptr; } if (!g_dbus_connection_emit_signal( invalidator->bus.get(), nullptr, SCOPES_DBUS_PATH, SCOPES_DBUS_IFACE, SCOPES_INVALIDATE_RESULTS, g_variant_new("(s)", "mediascanner-video"), &error)) { fprintf(stderr, "Could not invalidate video scope results: %s\n", error->message); g_error_free(error); error = nullptr; } invalidator->timeout_id = 0; return G_SOURCE_REMOVE; } mediascanner2-0.111+16.04.20160317/src/daemon/Scanner.cc0000644000015600001650000000740212672421600022530 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "Scanner.hh" #include "../extractor/DetectedFile.hh" #include "../extractor/MetadataExtractor.hh" #include "../mediascanner/internal/utils.hh" #include #include #include #include #include #include using namespace std; namespace mediascanner { struct Scanner::Private { Private(MetadataExtractor *extractor_, const std::string &root, const MediaType type_); string curdir; vector dirs; unique_ptr entry; unique_ptr dir; MediaType type; MetadataExtractor *extractor; struct dirent *de; }; Scanner::Private::Private(MetadataExtractor *extractor, const std::string &root, const MediaType type) : entry((dirent*)malloc(sizeof(dirent) + NAME_MAX + 1), free), dir(nullptr, closedir), type(type), extractor(extractor), de(nullptr) { dirs.push_back(root); } Scanner::Scanner(MetadataExtractor *extractor, const std::string &root, const MediaType type) : p(new Scanner::Private(extractor, root, type)) { } Scanner::~Scanner() { delete p; } DetectedFile Scanner::next() { begin: while(!p->dir) { if(p->dirs.empty()) { throw StopIteration(); } p->curdir = p->dirs.back(); p->dirs.pop_back(); unique_ptr tmp(opendir(p->curdir.c_str()), closedir); p->dir = move(tmp); if(is_rootlike(p->curdir)) { fprintf(stderr, "Directory %s looks like a top level root directory, skipping it (%s).\n", p->curdir.c_str(), __PRETTY_FUNCTION__); p->dir.reset(); continue; } if(has_scanblock(p->curdir)) { fprintf(stderr, "Directory %s has a scan block file, skipping it.\n", p->curdir.c_str()); p->dir.reset(); continue; } printf("In subdir %s\n", p->curdir.c_str()); } while(readdir_r(p->dir.get(), p->entry.get(), &p->de) == 0 && p->de ) { struct stat statbuf; string fname = p->entry.get()->d_name; if(fname[0] == '.') // Ignore hidden files and dirs. continue; string fullpath = p->curdir + "/" + fname; lstat(fullpath.c_str(), &statbuf); if(S_ISREG(statbuf.st_mode)) { try { DetectedFile d = p->extractor->detect(fullpath); if (p->type == AllMedia || d.type == p->type) { return d; } } catch (const exception &e) { /* Ignore non-media files */ } } else if(S_ISDIR(statbuf.st_mode)) { p->dirs.push_back(fullpath); } } // Nothing left in this directory so on to the next. assert(!p->de); p->dir.reset(); // This should be just return next(s) but we can't guarantee // that GCC can optimize away the tail recursion so we do this // instead. Using goto instead of wrapping the whole function body in // a while(true) to avoid the extra indentation. goto begin; } } mediascanner2-0.111+16.04.20160317/src/daemon/SubtreeWatcher.hh0000644000015600001650000000310312672421600024072 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef SUBTREEWATCHER_HH_ #define SUBTREEWATCHER_HH_ #include class InvalidationSender; namespace mediascanner { class MediaStore; class MetadataExtractor; struct SubtreeWatcherPrivate; class SubtreeWatcher final { private: SubtreeWatcherPrivate *p; bool fileAdded(const std::string &abspath); void fileDeleted(const std::string &abspath); void dirAdded(const std::string &abspath); void dirRemoved(const std::string &abspath); bool removeDir(const std::string &abspath); public: SubtreeWatcher(MediaStore &store, MetadataExtractor &extractor, InvalidationSender &invalidator); ~SubtreeWatcher(); SubtreeWatcher(const SubtreeWatcher &o) = delete; SubtreeWatcher& operator=(const SubtreeWatcher &o) = delete; void addDir(const std::string &path); void processEvents(); int getFd() const; int directoryCount() const; }; } #endif mediascanner2-0.111+16.04.20160317/src/daemon/CMakeLists.txt0000644000015600001650000000111312672421600023361 0ustar pbuserpbgroup00000000000000add_definitions(${MEDIASCANNER_DEPS_CFLAGS} ${UDISKS_CFLAGS}) include_directories(..) add_library(scannerstuff STATIC InvalidationSender.cc MountWatcher.cc SubtreeWatcher.cc Scanner.cc ../mediascanner/utils.cc ) target_link_libraries(scannerstuff extractor-client ${UDISKS_LDFLAGS}) add_executable(scannerdaemon scannerdaemon.cc ) set_target_properties(scannerdaemon PROPERTIES OUTPUT_NAME "mediascanner-service-2.0") target_link_libraries(scannerdaemon mediascanner scannerstuff ) install( TARGETS scannerdaemon RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) mediascanner2-0.111+16.04.20160317/src/daemon/SubtreeWatcher.cc0000644000015600001650000002343412672421605024076 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "SubtreeWatcher.hh" #include "../mediascanner/MediaStore.hh" #include "../mediascanner/MediaFile.hh" #include "InvalidationSender.hh" #include "../extractor/DetectedFile.hh" #include "../extractor/MetadataExtractor.hh" #include "../mediascanner/internal/utils.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace mediascanner { struct SubtreeWatcherPrivate { MediaStore &store; // Hackhackhack, should be replaced with callback object or something. MetadataExtractor &extractor; InvalidationSender &invalidator; int inotifyid; // Ideally use boost::bimap or something instead of these two separate objects. std::map wd2str; std::map str2wd; bool keep_going; std::unique_ptr source; SubtreeWatcherPrivate(MediaStore &store, MetadataExtractor &extractor, InvalidationSender &invalidator) : store(store), extractor(extractor), invalidator(invalidator), inotifyid(inotify_init()), keep_going(true), source(g_unix_fd_source_new(inotifyid, G_IO_IN), g_source_unref) { } ~SubtreeWatcherPrivate() { for(auto &i : wd2str) { inotify_rm_watch(inotifyid, i.first); } close(inotifyid); } }; static gboolean source_callback(GIOChannel *, GIOCondition, gpointer data) { SubtreeWatcher *watcher = static_cast(data); watcher->processEvents(); return TRUE; } SubtreeWatcher::SubtreeWatcher(MediaStore &store, MetadataExtractor &extractor, InvalidationSender &invalidator) { p = new SubtreeWatcherPrivate(store, extractor, invalidator); if(p->inotifyid == -1) { string msg("Could not init inotify: "); msg += strerror(errno); delete p; throw runtime_error(msg); } g_source_set_callback(p->source.get(), reinterpret_cast(source_callback), static_cast(this), nullptr); g_source_attach(p->source.get(), nullptr); } SubtreeWatcher::~SubtreeWatcher() { g_source_destroy(p->source.get()); delete p; } void SubtreeWatcher::addDir(const string &root) { if(root[0] != '/') throw runtime_error("Path must be absolute."); if(is_rootlike(root)) { fprintf(stderr, "Directory %s looks like a top level root directory, skipping it (%s).\n", root.c_str(), __PRETTY_FUNCTION__); return; } if(has_scanblock(root)) { fprintf(stderr, "Directory %s has a scan block file, skipping it.\n", root.c_str()); return; } if(p->str2wd.find(root) != p->str2wd.end()) return; unique_ptr dir(opendir(root.c_str()), closedir); if(!dir) { return; } int wd = inotify_add_watch(p->inotifyid, root.c_str(), IN_CREATE | IN_DELETE_SELF | IN_DELETE | IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_ONLYDIR); if(wd == -1) { fprintf(stderr, "Could not create inotify watch object: %s\n", strerror(errno)); return; // Probably ran out of watches, keep monitoring what we can. } p->wd2str[wd] = root; p->str2wd[root] = wd; printf("Watching subdirectory %s, %ld watches in total.\n", root.c_str(), (long)p->wd2str.size()); unique_ptr entry((dirent*)malloc(sizeof(dirent) + NAME_MAX), free); struct dirent *de; while(readdir_r(dir.get(), entry.get(), &de) == 0 && de ) { struct stat statbuf; string fname = entry.get()->d_name; if(fname[0] == '.') // Ignore hidden entries and also "." and "..". continue; string fullpath = root + "/" + fname; lstat(fullpath.c_str(), &statbuf); if(S_ISDIR(statbuf.st_mode)) { addDir(fullpath); } else if (S_ISREG(statbuf.st_mode)) { fileAdded(fullpath); } } } bool SubtreeWatcher::removeDir(const string &abspath) { auto it = p->str2wd.find(abspath); if (it == p->str2wd.end()) { return false; } while (it != p->str2wd.end()) { // Stop if we're no longer dealing with subdirectories of abspath if (it->first.compare(0, abspath.size(), abspath) != 0) { break; } if (it->first.size() != abspath.size() && it->first[abspath.size()] != '/') { break; } int wd = it->second; inotify_rm_watch(p->inotifyid, wd); p->wd2str.erase(wd); printf("Stopped watching %s, %ld directories remain.\n", it->first.c_str(), (long)p->wd2str.size()); it = p->str2wd.erase(it); } if(p->wd2str.empty()) p->keep_going = false; p->store.removeSubtree(abspath); return true; } bool SubtreeWatcher::fileAdded(const string &abspath) { bool changed = false; printf("New file was created: %s.\n", abspath.c_str()); try { DetectedFile d = p->extractor.detect(abspath); if(p->store.is_broken_file(abspath, d.etag)) { fprintf(stderr, "Using fallback data for unscannable file %s.\n", abspath.c_str()); p->store.insert(p->extractor.fallback_extract(d)); } else if (d.etag != p->store.getETag(d.filename)) { // Only extract and insert the file if the ETag has changed. p->store.insert_broken_file(abspath, d.etag); // If detection dies, insertion into broken files persists // and the next time this file is encountered, it is skipped. // Insert cleans broken status of the file. MediaFile media; try { media = p->extractor.extract(d); } catch (const std::runtime_error &e) { fprintf(stderr, "Error extracting from '%s': %s\n", d.filename.c_str(), e.what()); media = p->extractor.fallback_extract(d); } p->store.insert(std::move(media)); changed = true; } } catch(const exception &e) { fprintf(stderr, "Error when adding new file: %s\n", e.what()); } return changed; } void SubtreeWatcher::fileDeleted(const string &abspath) { printf("File was deleted: %s\n", abspath.c_str()); p->store.remove(abspath); } void SubtreeWatcher::dirAdded(const string &abspath) { printf("New directory was created: %s.\n", abspath.c_str()); addDir(abspath); } void SubtreeWatcher::dirRemoved(const string &abspath) { printf("Subdirectory was deleted: %s.\n", abspath.c_str()); removeDir(abspath); } void SubtreeWatcher::processEvents() { const int BUFSIZE=4096; char buf[BUFSIZE]; bool changed = false; ssize_t num_read; num_read = read(p->inotifyid, buf, BUFSIZE); if(num_read == 0) { printf("Inotify returned 0.\n"); return; } if(num_read == -1) { printf("Read error.\n"); return; } for(char *d = buf; d < buf + num_read;) { struct inotify_event *event = (struct inotify_event *) d; if (p->wd2str.find(event->wd) == p->wd2str.end()) { // Ignore events for unknown watches. We may receive // such events when a directory is removed. d += sizeof(struct inotify_event) + event->len; continue; } string directory = p->wd2str[event->wd]; string filename(event->name); string abspath = directory + '/' + filename; bool is_dir = false; bool is_file = false; struct stat statbuf; lstat(abspath.c_str(), &statbuf); // Remember: these are not valid in case of delete event. is_dir = S_ISDIR(statbuf.st_mode); is_file = S_ISREG(statbuf.st_mode); if(event->mask & IN_CREATE) { if(is_dir) { dirAdded(abspath); changed = true; } // Do not add files upon creation because we can't parse // their metadata until it is fully written. } else if((event->mask & IN_CLOSE_WRITE) || (event->mask & IN_MOVED_TO)) { if(is_dir) { dirAdded(abspath); changed = true; } if(is_file) { changed = fileAdded(abspath); } } else if((event->mask & IN_DELETE) || (event->mask & IN_MOVED_FROM)) { if(p->str2wd.find(abspath) != p->str2wd.end()) { dirRemoved(abspath); changed = true; } else { fileDeleted(abspath); changed = true; } } else if((event->mask & IN_IGNORED) || (event->mask & IN_UNMOUNT) || (event->mask & IN_DELETE_SELF)) { removeDir(abspath); changed = true; } d += sizeof(struct inotify_event) + event->len; } if (changed) { p->invalidator.invalidate(); } } int SubtreeWatcher::getFd() const { return p->inotifyid; } int SubtreeWatcher::directoryCount() const { return (int) p->wd2str.size(); } } mediascanner2-0.111+16.04.20160317/src/extractor/0000755000015600001650000000000012672422030021373 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/src/extractor/GStreamerExtractor.hh0000644000015600001650000000253512672421600025510 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_GSTREAMEREXTRACTOR_H #define EXTRACTOR_GSTREAMEREXTRACTOR_H #include typedef struct _GstDiscoverer GstDiscoverer; namespace mediascanner { class MediaFileBuilder; struct DetectedFile; class GStreamerExtractor final { public: explicit GStreamerExtractor(int seconds); ~GStreamerExtractor(); GStreamerExtractor(const GStreamerExtractor&) = delete; GStreamerExtractor& operator=(GStreamerExtractor &o) = delete; void extract(const DetectedFile &d, MediaFileBuilder &builder); private: std::unique_ptr discoverer; }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/com.canonical.MediaScanner2.Extractor.service.in0000644000015600001650000000020412672421600032450 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.MediaScanner2.Extractor Exec=@CMAKE_INSTALL_FULL_LIBDIR@/mediascanner-2.0/mediascanner-extractor mediascanner2-0.111+16.04.20160317/src/extractor/dbus-interface.xml0000644000015600001650000000073512672421600025017 0ustar pbuserpbgroup00000000000000 mediascanner2-0.111+16.04.20160317/src/extractor/MetadataExtractor.hh0000644000015600001650000000305312672421600025333 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef METADATAEXTRACTOR_H #define METADATAEXTRACTOR_H #include #include #include #include "../mediascanner/scannercore.hh" typedef struct _GDBusConnection GDBusConnection; namespace mediascanner { class MediaFile; struct DetectedFile; struct MetadataExtractorPrivate; class MetadataExtractor final { public: explicit MetadataExtractor(GDBusConnection *bus); ~MetadataExtractor(); MetadataExtractor(const MetadataExtractor&) = delete; MetadataExtractor& operator=(MetadataExtractor &o) = delete; DetectedFile detect(const std::string &filename); MediaFile extract(const DetectedFile &d); // In case the detected file is know to crash gstreamer, // use this to generate fallback data. MediaFile fallback_extract(const DetectedFile &d); private: std::unique_ptr p; }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/ImageExtractor.cc0000644000015600001650000001631612672421600024631 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "ImageExtractor.hh" #include "DetectedFile.hh" #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaFileBuilder.hh" #include #include #include #include #include #include #include #include using namespace std; using mediascanner::MediaFileBuilder; namespace { const char exif_date_template[] = "%Y:%m:%d %H:%M:%S"; const char iso8601_date_format[] = "%Y-%m-%dT%H:%M:%S"; const char iso8601_date_with_zone_format[] = "%Y-%m-%dT%H:%M:%S%z"; void parse_exif_date(ExifData *data, ExifByteOrder order, MediaFileBuilder &mfb) { static const std::vector date_tags{ EXIF_TAG_DATE_TIME_ORIGINAL, EXIF_TAG_DATE_TIME_DIGITIZED, EXIF_TAG_DATE_TIME, }; struct tm timeinfo; bool have_date = false; for (ExifTag tag : date_tags) { ExifEntry *ent = exif_data_get_entry(data, tag); if (ent == nullptr) { continue; } if (strptime((const char*)ent->data, exif_date_template, &timeinfo) != nullptr) { have_date = true; break; } } if (!have_date) { return; } char buf[100]; ExifEntry *ent = exif_data_get_entry(data, EXIF_TAG_TIME_ZONE_OFFSET); if (ent) { timeinfo.tm_gmtoff = (int)exif_get_sshort(ent->data, order) * 3600; if (strftime(buf, sizeof(buf), iso8601_date_with_zone_format, &timeinfo) != 0) { mfb.setDate(buf); } } else { /* No time zone info */ if (strftime(buf, sizeof(buf), iso8601_date_format, &timeinfo) != 0) { mfb.setDate(buf); } } } int get_exif_int(ExifEntry *ent, ExifByteOrder order) { switch (ent->format) { case EXIF_FORMAT_BYTE: return (unsigned char)ent->data[0]; case EXIF_FORMAT_SHORT: return exif_get_short(ent->data, order); case EXIF_FORMAT_LONG: return exif_get_long(ent->data, order); case EXIF_FORMAT_SBYTE: return (signed char)ent->data[0]; case EXIF_FORMAT_SSHORT: return exif_get_sshort(ent->data, order); case EXIF_FORMAT_SLONG: return exif_get_slong(ent->data, order); default: break; } return 0; } void parse_exif_dimensions(ExifData *data, ExifByteOrder order, MediaFileBuilder &mfb) { ExifEntry *w_ent = exif_data_get_entry(data, EXIF_TAG_PIXEL_X_DIMENSION); ExifEntry *h_ent = exif_data_get_entry(data, EXIF_TAG_PIXEL_Y_DIMENSION); ExifEntry *o_ent = exif_data_get_entry(data, EXIF_TAG_ORIENTATION); if (!w_ent || !h_ent) { return; } int width = get_exif_int(w_ent, order); int height = get_exif_int(h_ent, order); // Optionally swap height and width depending on orientation if (o_ent) { int tmp; // exif_data_fix() has ensured this is a short. switch (exif_get_short(o_ent->data, order)) { case 5: // Mirror horizontal and rotate 270 CW case 6: // Rotate 90 CW case 7: // Mirror horizontal and rotate 90 CW case 8: // Rotate 270 CW tmp = width; width = height; height = tmp; break; default: break; } } mfb.setWidth(width); mfb.setHeight(height); } bool rational_to_degrees(ExifEntry *ent, ExifByteOrder order, double *out) { if (ent->format != EXIF_FORMAT_RATIONAL) { return false; } ExifRational r = exif_get_rational(ent->data, order); *out = ((double) r.numerator) / r.denominator; // Minutes if (ent->components >= 2) { r = exif_get_rational(ent->data + exif_format_get_size(EXIF_FORMAT_RATIONAL), order); *out += ((double) r.numerator) / r.denominator / 60; } // Seconds if (ent->components >= 3) { r = exif_get_rational(ent->data + 2 * exif_format_get_size(EXIF_FORMAT_RATIONAL), order); *out += ((double) r.numerator) / r.denominator / 3600; } return true; } void parse_exif_location(ExifData *data, ExifByteOrder order, MediaFileBuilder &mfb) { ExifContent *ifd = data->ifd[EXIF_IFD_GPS]; ExifEntry *lat_ent = exif_content_get_entry(ifd, (ExifTag)EXIF_TAG_GPS_LATITUDE); ExifEntry *latref_ent = exif_content_get_entry(ifd, (ExifTag)EXIF_TAG_GPS_LATITUDE_REF); ExifEntry *long_ent = exif_content_get_entry(ifd, (ExifTag)EXIF_TAG_GPS_LONGITUDE); ExifEntry *longref_ent = exif_content_get_entry(ifd, (ExifTag)EXIF_TAG_GPS_LONGITUDE_REF); if (!lat_ent || !long_ent) { return; } double latitude, longitude; if (!rational_to_degrees(lat_ent, order, &latitude)) { return; } if (!rational_to_degrees(long_ent, order, &longitude)) { return; } if (latref_ent && latref_ent->data[0] == 'S') { latitude = -latitude; } if (longref_ent && longref_ent->data[0] == 'W') { longitude = -longitude; } mfb.setLatitude(latitude); mfb.setLongitude(longitude); } } namespace mediascanner { bool ImageExtractor::extract_exif(const DetectedFile &d, MediaFileBuilder &mfb) { std::unique_ptr loader( exif_loader_new(), exif_loader_unref); exif_loader_write_file(loader.get(), d.filename.c_str()); std::unique_ptr data( exif_loader_get_data(loader.get()), exif_data_unref); loader.reset(); if (!data) { return false; } exif_data_fix(data.get()); ExifByteOrder order = exif_data_get_byte_order(data.get()); parse_exif_date(data.get(), order, mfb); parse_exif_dimensions(data.get(), order, mfb); parse_exif_location(data.get(), order, mfb); return true; } void ImageExtractor::extract_pixbuf(const DetectedFile &d, MediaFileBuilder &builder) { gint width, height; if(!gdk_pixbuf_get_file_info(d.filename.c_str(), &width, &height)) { string msg("Could not determine resolution of "); msg += d.filename; msg += "."; throw runtime_error(msg); } builder.setWidth(width); builder.setHeight(height); if (d.mtime != 0) { auto t = static_cast(d.mtime); char buf[1024]; struct tm ptm; localtime_r(&t, &ptm); if (strftime(buf, sizeof(buf), iso8601_date_format, &ptm) != 0) { builder.setDate(buf); } } } void ImageExtractor::extract(const DetectedFile &d, MediaFileBuilder &builder) { if (!extract_exif(d, builder)) { extract_pixbuf(d, builder); } return; } } mediascanner2-0.111+16.04.20160317/src/extractor/ExtractorBackend.cc0000644000015600001650000000377012672421600025136 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "ExtractorBackend.hh" #include "DetectedFile.hh" #include "GStreamerExtractor.hh" #include "ImageExtractor.hh" #include "TaglibExtractor.hh" #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaFileBuilder.hh" #include #include using namespace std; namespace mediascanner { struct ExtractorBackendPrivate { ExtractorBackendPrivate(int seconds) : gstreamer(seconds) {} GStreamerExtractor gstreamer; ImageExtractor image; TaglibExtractor taglib; }; ExtractorBackend::ExtractorBackend(int seconds) : p(new ExtractorBackendPrivate(seconds)) { } ExtractorBackend::~ExtractorBackend() { delete p; } MediaFile ExtractorBackend::extract(const DetectedFile &d) { printf("Extracting metadata from %s.\n", d.filename.c_str()); MediaFileBuilder mfb(d.filename); mfb.setETag(d.etag); mfb.setContentType(d.content_type); mfb.setModificationTime(d.mtime); mfb.setType(d.type); switch (d.type) { case ImageMedia: p->image.extract(d, mfb); break; case AudioMedia: if (!p->taglib.extract(d, mfb)) { p->gstreamer.extract(d, mfb); } break; default: p->gstreamer.extract(d, mfb); break; } return mfb; } } mediascanner2-0.111+16.04.20160317/src/extractor/dbus-marshal.hh0000644000015600001650000000166312672421600024306 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_DBUS_MARSHAL_HH #define EXTRACTOR_DBUS_MARSHAL_HH #include namespace mediascanner { class MediaFile; GVariant *media_to_variant(const MediaFile &media); MediaFile media_from_variant(GVariant *variant); } #endif mediascanner2-0.111+16.04.20160317/src/extractor/ImageExtractor.hh0000644000015600001650000000254012672421600024635 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_IMAGEEXTRACTOR_H #define EXTRACTOR_IMAGEEXTRACTOR_H #include namespace mediascanner { class MediaFileBuilder; struct DetectedFile; class ImageExtractor final { public: ImageExtractor() = default; ~ImageExtractor() = default; ImageExtractor(const ImageExtractor&) = delete; ImageExtractor& operator=(ImageExtractor &o) = delete; void extract(const DetectedFile &d, MediaFileBuilder &builder); private: bool extract_exif(const DetectedFile &d, MediaFileBuilder &builder); void extract_pixbuf(const DetectedFile &d, MediaFileBuilder &builder); }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/ExtractorBackend.hh0000644000015600001650000000240412672421600025141 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_EXTRACTORBACKEND_H #define EXTRACTOR_EXTRACTORBACKEND_H #include namespace mediascanner { class MediaFile; struct DetectedFile; struct ExtractorBackendPrivate; class ExtractorBackend final { public: explicit ExtractorBackend(int seconds=25); ~ExtractorBackend(); ExtractorBackend(const ExtractorBackend&) = delete; ExtractorBackend& operator=(ExtractorBackend &o) = delete; MediaFile extract(const DetectedFile &d); private: ExtractorBackendPrivate *p; }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/TaglibExtractor.cc0000644000015600001650000002212612672421600025005 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2016 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "TaglibExtractor.hh" #include "DetectedFile.hh" #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaFileBuilder.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using mediascanner::MediaFileBuilder; namespace { string get_content_type(const string &filename) { unique_ptr file( g_file_new_for_path(filename.c_str()), g_object_unref); assert(file); GError *error = nullptr; unique_ptr info( g_file_query_info( file.get(), G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, nullptr, &error), g_object_unref); if (!info) { fprintf(stderr, "Failed to detect content type of '%s': %s\n", filename.c_str(), error->message); g_error_free(error); return string(); } return g_file_info_get_content_type(info.get()); } typedef unique_ptr DatePtr; DatePtr parse_iso_date(const string &date_string) { return DatePtr( gst_date_time_new_from_iso8601_string(date_string.c_str()), gst_date_time_unref); } string format_iso_date(const DatePtr &dt) { if (!dt) { return string(); } char *iso = gst_date_time_to_iso8601_string(dt.get()); if (!iso) { return string(); } string result(iso); g_free(iso); return result; } string check_date(const string &date_string) { // Parse and reserialise the date using GstDateTime to check its // consistency. return format_iso_date(parse_iso_date(date_string)); } void parse_common(const TagLib::File &file, MediaFileBuilder &builder) { TagLib::Tag *tag = file.tag(); const TagLib::AudioProperties *audio_props = file.audioProperties(); if (audio_props) { builder.setDuration(audio_props->length()); } TagLib::String s = tag->title(); if (!s.isEmpty()) { builder.setTitle(s.to8Bit(true)); } s = tag->artist(); if (!s.isEmpty()) { builder.setAuthor(s.to8Bit(true)); } s = tag->album(); if (!s.isEmpty()) { builder.setAlbum(s.to8Bit(true)); } s = tag->genre(); if (!s.isEmpty()) { builder.setGenre(s.to8Bit(true)); } unsigned int year = tag->year(); if (year > 0 && year <= 9999) { DatePtr dt(gst_date_time_new_y(year), gst_date_time_unref); builder.setDate(format_iso_date(dt)); } unsigned int track = tag->track(); if (track != 0) { builder.setTrackNumber(track); } } void parse_xiph_comment(const TagLib::Ogg::XiphComment *tag, MediaFileBuilder &builder) { const auto& fields = tag->fieldListMap(); if (!fields["DATE"].isEmpty()) { builder.setDate(check_date(fields["DATE"].front().to8Bit(true))); } if (!fields["ALBUMARTIST"].isEmpty()) { builder.setAlbumArtist(fields["ALBUMARTIST"].front().to8Bit(true)); } if (!fields["DISCNUMBER"].isEmpty()) { builder.setDiscNumber(fields["DISCNUMBER"].front().toInt()); } if (!fields["COVERART"].isEmpty() || !fields["METADATA_BLOCK_PICTURE"].isEmpty()) { builder.setHasThumbnail(true); } } void parse_id3v2(const TagLib::ID3v2::Tag *tag, MediaFileBuilder &builder) { const auto& frames = tag->frameListMap(); if (!frames["TDRC"].isEmpty()) { DatePtr dt = parse_iso_date(frames["TDRC"].front()->toString().to8Bit(true)); // Taglib automatically renames the old TYER frame to TDRC, // but doesn't migrate over the day and month from TDAT. if (!gst_date_time_has_month(dt.get()) && !frames["TDAT"].isEmpty()) { const TagLib::ID3v2::Frame *frame = frames["TDAT"].front(); // TagLib converts TDAT to an "UnknownFrame" type frame, // since it believes it should be ignored. Create a text // frame so we can get at the data. TagLib::String data; auto *unknown = dynamic_cast(frame); if (unknown) { // Text information frames consist of one byte for the // encoding, followed the string data. auto encoding = static_cast(unknown->data()[0]); data = TagLib::String(unknown->data().mid(1), encoding); } else { data = frame->toString(); } int ddmm = data.toInt(); int day = ddmm / 100; int month = ddmm % 100; dt.reset(gst_date_time_new_ymd( gst_date_time_get_year(dt.get()), g_date_valid_month(static_cast(month)) ? month : -1, g_date_valid_day(day) ? day : -1)); } builder.setDate(format_iso_date(dt)); } if (!frames["TPE2"].isEmpty()) { builder.setAlbumArtist(frames["TPE2"].front()->toString().to8Bit(true)); } if (!frames["TPOS"].isEmpty()) { builder.setDiscNumber(frames["TPOS"].front()->toString().toInt()); } if (!frames["APIC"].isEmpty()) { builder.setHasThumbnail(true); } } void parse_mp4(const TagLib::MP4::Tag *tag, MediaFileBuilder &builder) { const auto& items = const_cast(tag)->itemListMap(); if (items.contains("\251day")) { builder.setDate(check_date(items["\251day"].toStringList().front().to8Bit(true))); } if (items.contains("aART")) { builder.setAlbumArtist(items["aART"].toStringList().front().to8Bit(true)); } if (items.contains("disk")) { builder.setDiscNumber(items["disk"].toIntPair().first); } if (items.contains("covr")) { builder.setHasThumbnail(true); } } } namespace mediascanner { bool TaglibExtractor::extract(const DetectedFile &d, MediaFileBuilder &builder) { string content_type = get_content_type(d.filename); if (content_type.empty()) { return false; } unique_ptr fs(new TagLib::FileStream(d.filename.c_str(), true)); if (!fs->isOpen()) { return false; } if (content_type == "audio/x-vorbis+ogg") { TagLib::Ogg::Vorbis::File file(fs.get()); if (!file.isValid()) { return false; } parse_common(file, builder); parse_xiph_comment(file.tag(), builder); } else if (content_type == "audio/x-opus+ogg") { TagLib::Ogg::Opus::File file(fs.get()); if (!file.isValid()) { return false; } parse_common(file, builder); parse_xiph_comment(file.tag(), builder); } else if (content_type == "audio/x-flac+ogg") { TagLib::Ogg::FLAC::File file(fs.get()); if (!file.isValid()) { return false; } parse_common(file, builder); parse_xiph_comment(file.tag(), builder); } else if (content_type == "audio/flac") { TagLib::FLAC::File file(fs.get(), TagLib::ID3v2::FrameFactory::instance()); if (!file.isValid()) { return false; } parse_common(file, builder); if (file.hasID3v2Tag()) { parse_id3v2(file.ID3v2Tag(), builder); } if (file.hasXiphComment()) { parse_xiph_comment(file.xiphComment(), builder); } if (!file.pictureList().isEmpty()) { builder.setHasThumbnail(true); } } else if (content_type == "audio/mpeg") { TagLib::MPEG::File file(fs.get(), TagLib::ID3v2::FrameFactory::instance()); if (!file.isValid()) { return false; } parse_common(file, builder); if (file.hasID3v2Tag()) { parse_id3v2(file.ID3v2Tag(), builder); } } else if (content_type == "audio/mp4") { TagLib::MP4::File file(fs.get()); if (!file.isValid()) { return false; } parse_common(file, builder); parse_mp4(file.tag(), builder); } else { return false; } return true; } } mediascanner2-0.111+16.04.20160317/src/extractor/dbus-marshal.cc0000644000015600001650000000754212672421600024276 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "dbus-marshal.hh" #include #include #include namespace mediascanner { GVariant *media_to_variant(const MediaFile &media) { GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("(sssssssssiiiiiddbti)")); g_variant_builder_add(&builder, "s", media.getFileName().c_str()); g_variant_builder_add(&builder, "s", media.getContentType().c_str()); g_variant_builder_add(&builder, "s", media.getETag().c_str()); g_variant_builder_add(&builder, "s", media.getTitle().c_str()); g_variant_builder_add(&builder, "s", media.getAuthor().c_str()); g_variant_builder_add(&builder, "s", media.getAlbum().c_str()); g_variant_builder_add(&builder, "s", media.getAlbumArtist().c_str()); g_variant_builder_add(&builder, "s", media.getDate().c_str()); g_variant_builder_add(&builder, "s", media.getGenre().c_str()); g_variant_builder_add(&builder, "i", media.getDiscNumber()); g_variant_builder_add(&builder, "i", media.getTrackNumber()); g_variant_builder_add(&builder, "i", media.getDuration()); g_variant_builder_add(&builder, "i", media.getWidth()); g_variant_builder_add(&builder, "i", media.getHeight()); g_variant_builder_add(&builder, "d", media.getLatitude()); g_variant_builder_add(&builder, "d", media.getLongitude()); g_variant_builder_add(&builder, "b", media.getHasThumbnail()); g_variant_builder_add(&builder, "t", media.getModificationTime()); g_variant_builder_add(&builder, "i", static_cast(media.getType())); return g_variant_builder_end(&builder); } MediaFile media_from_variant(GVariant *variant) { if (!g_variant_is_of_type(variant, G_VARIANT_TYPE("(sssssssssiiiiiddbti)"))) { throw std::runtime_error("variant is of wrong type"); } const char *filename = nullptr, *content_type = nullptr, *etag = nullptr, *title = nullptr, *author = nullptr, *album = nullptr, *album_artist = nullptr, *date = nullptr, *genre = nullptr; gint32 disc_number = 0, track_number = 0, duration = 0, width = 0, height = 0, type = 0; double latitude = 0, longitude = 0; gboolean has_thumbnail = false; uint64_t mtime = 0; g_variant_get(variant, "(&s&s&s&s&s&s&s&s&siiiiiddbti)", &filename, &content_type, &etag, &title, &author, &album, &album_artist, &date, &genre, &disc_number, &track_number, &duration, &width, &height, &latitude, &longitude, &has_thumbnail, &mtime, &type, nullptr); return MediaFileBuilder(filename) .setContentType(content_type) .setETag(etag) .setTitle(title) .setAuthor(author) .setAlbum(album) .setAlbumArtist(album_artist) .setDate(date) .setGenre(genre) .setDiscNumber(disc_number) .setTrackNumber(track_number) .setDuration(duration) .setWidth(width) .setHeight(height) .setLatitude(latitude) .setLongitude(longitude) .setHasThumbnail(has_thumbnail) .setModificationTime(mtime) .setType(static_cast(type)); } } mediascanner2-0.111+16.04.20160317/src/extractor/main.cc0000644000015600001650000001521112672421600022630 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include #include #include "DetectedFile.hh" #include "ExtractorBackend.hh" #include "dbus-generated.h" #include "dbus-marshal.hh" using namespace mediascanner; namespace { const char BUS_NAME[] = "com.canonical.MediaScanner2.Extractor"; const char BUS_PATH[] = "/com/canonical/MediaScanner2/Extractor"; const char EXTRACT_ERROR[] = "com.canonical.MediaScanner2.Error.Failed"; const int DELAY = 30; class ExtractorDaemon final { public: ExtractorDaemon(); ~ExtractorDaemon(); void run(); private: void setupBus(); void extract(const DetectedFile &file, GDBusMethodInvocation *invocation); void startExitTimer(); void cancelExitTimer(); static void busNameLostCallback(GDBusConnection *, const char *name, gpointer data); static gboolean handleExtractMetadata(MSExtractor *iface, GDBusMethodInvocation *invocation, const char *filename, const char *etag, const char *content_type, guint64 mtime, gint32 type, gpointer user_data); static gboolean handleExitTimer(gpointer user_data); ExtractorBackend extractor; std::unique_ptr main_loop; std::unique_ptr session_bus; unsigned int bus_name_id = 0; std::unique_ptr iface; unsigned int handler_id = 0; unsigned int timeout_id = 0; int crash_after = -1; }; } ExtractorDaemon::ExtractorDaemon() : main_loop(g_main_loop_new(nullptr, FALSE), g_main_loop_unref), session_bus(nullptr, g_object_unref), iface(ms_extractor_skeleton_new(), g_object_unref) { const char *crash_after_env = getenv("MEDIASCANNER_EXTRACTOR_CRASH_AFTER"); if (crash_after_env) { crash_after = std::stoi(crash_after_env); } setupBus(); } ExtractorDaemon::~ExtractorDaemon() { if (bus_name_id != 0) { g_bus_unown_name(bus_name_id); } g_dbus_interface_skeleton_unexport( G_DBUS_INTERFACE_SKELETON(iface.get())); if (handler_id != 0) { g_signal_handler_disconnect(iface.get(), handler_id); } cancelExitTimer(); } void ExtractorDaemon::setupBus() { GError *error = nullptr; session_bus.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error)); if (!session_bus) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to connect to session bus: ") + errortxt); } handler_id = g_signal_connect( iface.get(), "handle-extract-metadata", G_CALLBACK(&ExtractorDaemon::handleExtractMetadata), this); if (!g_dbus_interface_skeleton_export( G_DBUS_INTERFACE_SKELETON(iface.get()), session_bus.get(), BUS_PATH, &error)) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to export object: ") + errortxt); } bus_name_id = g_bus_own_name_on_connection( session_bus.get(), BUS_NAME, static_cast( G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | G_BUS_NAME_OWNER_FLAGS_REPLACE), nullptr, &ExtractorDaemon::busNameLostCallback, this, nullptr); } void ExtractorDaemon::busNameLostCallback(GDBusConnection *, const char *name, gpointer data) { ExtractorDaemon *daemon = reinterpret_cast(data); fprintf(stderr, "Exiting due to loss of control of bus name %s\n", name); daemon->bus_name_id = 0; g_main_loop_quit(daemon->main_loop.get()); } gboolean ExtractorDaemon::handleExtractMetadata(MSExtractor *, GDBusMethodInvocation *invocation, const char *filename, const char *etag, const char *content_type, guint64 mtime, gint32 type, gpointer user_data) { auto d = reinterpret_cast(user_data); // If the environment variable was set, crash the service after // the requested number of extractions. if (d->crash_after == 0) { abort(); } else if (d->crash_after > 0) { d->crash_after--; } DetectedFile file(filename, etag, content_type, mtime, static_cast(type)); d->extract(file, invocation); return TRUE; } void ExtractorDaemon::extract(const DetectedFile &file, GDBusMethodInvocation *invocation) { cancelExitTimer(); try { MediaFile media = extractor.extract(file); ms_extractor_complete_extract_metadata( iface.get(), invocation, media_to_variant(media)); } catch (const std::exception &e) { g_dbus_method_invocation_return_dbus_error( invocation, EXTRACT_ERROR, e.what()); } startExitTimer(); } void ExtractorDaemon::startExitTimer() { cancelExitTimer(); timeout_id = g_timeout_add_seconds( DELAY, &ExtractorDaemon::handleExitTimer, this); } void ExtractorDaemon::cancelExitTimer() { if (timeout_id != 0) { g_source_remove(timeout_id); timeout_id = 0; } } gboolean ExtractorDaemon::handleExitTimer(gpointer user_data) { auto d = reinterpret_cast(user_data); fprintf(stderr, "Exiting due to inactivity\n"); g_main_loop_quit(d->main_loop.get()); d->timeout_id = 0; return G_SOURCE_REMOVE; } void ExtractorDaemon::run() { g_main_loop_run(main_loop.get()); } int main(int argc, char **argv) { gst_init(&argc, &argv); try { ExtractorDaemon d; d.run(); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return 1; } return 0; } mediascanner2-0.111+16.04.20160317/src/extractor/DetectedFile.hh0000644000015600001650000000247212672421600024244 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_DETECTEDFILE_H #define EXTRACTOR_DETECTEDFILE_H #include #include #include "../mediascanner/scannercore.hh" namespace mediascanner { struct DetectedFile { DetectedFile(const std::string &filename, const std::string &etag, const std::string &content_type, uint64_t mtime, MediaType type) : filename(filename), etag(etag), content_type(content_type) , mtime(mtime), type(type) {} std::string filename; std::string etag; std::string content_type; uint64_t mtime; MediaType type; }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/GStreamerExtractor.cc0000644000015600001650000001343512672421600025477 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "GStreamerExtractor.hh" #include "DetectedFile.hh" #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaFileBuilder.hh" #include "../mediascanner/internal/utils.hh" #include #include #include #include #include #include #include using namespace std; using mediascanner::MediaFileBuilder; namespace { void extract_tag_info (const GstTagList *list, const gchar *tag, void *user_data) { MediaFileBuilder *mfb = (MediaFileBuilder *) user_data; int i, num; string tagname(tag); if(tagname == GST_TAG_IMAGE || tagname == GST_TAG_PREVIEW_IMAGE) { mfb->setHasThumbnail(true); return; } num = gst_tag_list_get_tag_size (list, tag); for (i = 0; i < num; ++i) { const GValue *val; val = gst_tag_list_get_value_index (list, tag, i); if (G_VALUE_HOLDS_STRING(val)) { const char *value = g_value_get_string(val); if (!value) { continue; } if (tagname == GST_TAG_ARTIST) mfb->setAuthor(value); else if (tagname == GST_TAG_TITLE) mfb->setTitle(value); else if (tagname == GST_TAG_ALBUM) mfb->setAlbum(value); else if (tagname == GST_TAG_ALBUM_ARTIST) mfb->setAlbumArtist(value); else if (tagname == GST_TAG_GENRE) mfb->setGenre(value); } else if (G_VALUE_HOLDS(val, GST_TYPE_DATE_TIME)) { if (tagname == GST_TAG_DATE_TIME) { GstDateTime *dt = static_cast(g_value_get_boxed(val)); if (!dt) { continue; } char *dt_string = gst_date_time_to_iso8601_string(dt); mfb->setDate(dt_string); g_free(dt_string); } } else if (G_VALUE_HOLDS(val, G_TYPE_DATE)) { if (tagname == GST_TAG_DATE) { GDate *dt = static_cast(g_value_get_boxed(val)); if (!dt) { continue; } char buf[100]; if (g_date_strftime(buf, sizeof(buf), "%Y-%m-%d", dt) != 0) { mfb->setDate(buf); } } } else if (G_VALUE_HOLDS_UINT(val)) { if (tagname == GST_TAG_TRACK_NUMBER) { mfb->setTrackNumber(g_value_get_uint(val)); } else if (tagname == GST_TAG_ALBUM_VOLUME_NUMBER) { mfb->setDiscNumber(g_value_get_uint(val)); } } } } } namespace mediascanner { GStreamerExtractor::GStreamerExtractor(int seconds) : discoverer(nullptr, g_object_unref) { GError *error = nullptr; discoverer.reset(gst_discoverer_new(GST_SECOND * seconds, &error)); if (not discoverer) { string errortxt(error->message); g_error_free(error); string msg = "Failed to create discoverer: "; msg += errortxt; throw runtime_error(msg); } if (error) { // Sometimes this is filled in even though no error happens. g_error_free(error); } } GStreamerExtractor::~GStreamerExtractor() = default; void GStreamerExtractor::extract(const DetectedFile &d, MediaFileBuilder &mfb) { string uri = getUri(d.filename); GError *error = nullptr; unique_ptr info( gst_discoverer_discover_uri(discoverer.get(), uri.c_str(), &error), g_object_unref); if (info.get() == nullptr) { string errortxt(error->message); g_error_free(error); string msg = "Discovery of file "; msg += d.filename; msg += " failed: "; msg += errortxt; throw runtime_error(msg); } if (error) { // Sometimes this gets filled in even if no error actually occurs. g_error_free(error); error = nullptr; } if (gst_discoverer_info_get_result(info.get()) != GST_DISCOVERER_OK) { throw runtime_error("Unable to discover file " + d.filename); } const GstTagList *tags = gst_discoverer_info_get_tags(info.get()); if (tags != nullptr) { gst_tag_list_foreach(tags, extract_tag_info, &mfb); } mfb.setDuration(static_cast( gst_discoverer_info_get_duration(info.get())/GST_SECOND)); /* Check for video specific information */ unique_ptr streams( gst_discoverer_info_get_stream_list(info.get()), gst_discoverer_stream_info_list_free); for (const GList *l = streams.get(); l != nullptr; l = l->next) { auto stream_info = static_cast(l->data); if (GST_IS_DISCOVERER_VIDEO_INFO(stream_info)) { mfb.setWidth(gst_discoverer_video_info_get_width( GST_DISCOVERER_VIDEO_INFO(stream_info))); mfb.setHeight(gst_discoverer_video_info_get_height( GST_DISCOVERER_VIDEO_INFO(stream_info))); break; } } } } mediascanner2-0.111+16.04.20160317/src/extractor/CMakeLists.txt0000644000015600001650000000350712672421600024142 0ustar pbuserpbgroup00000000000000 add_definitions(${MEDIASCANNER_DEPS_CFLAGS} ${GST_CFLAGS} ${EXIF_CFLAGS} ${PIXBUF_CFLAGS} ${TAGLIB_CFLAGS}) include_directories(.. ${CMAKE_CURRENT_BINARY_DIR}) # Build stubs/skeletons for D-Bus interface find_program(gdbus_codegen gdbus-codegen) if(NOT gdbus_codegen) msg(FATAL_ERROR "Could not locate gdbus-codegen") endif() add_custom_command( OUTPUT dbus-generated.c dbus-generated.h COMMAND ${gdbus_codegen} --interface-prefix=com.canonical.MediaScanner2 --generate-c-code dbus-generated --c-namespace MS ${CMAKE_CURRENT_SOURCE_DIR}/dbus-interface.xml MAIN_DEPENDENCY dbus-interface.xml ) # Code generated by gdbus-codegen doesn't like all the warning flags # we have turned on. set_property(SOURCE dbus-generated.c APPEND_STRING PROPERTY COMPILE_FLAGS " -Wno-unused-parameter -Wno-pedantic") # The client code for the extractor daemon add_library(extractor-client STATIC MetadataExtractor.cc dbus-generated.c dbus-marshal.cc ../mediascanner/utils.cc ) target_link_libraries(extractor-client mediascanner ) # The backend code for the extractor daemon, as a library for use by tests add_library(extractor-backend STATIC ExtractorBackend.cc GStreamerExtractor.cc ImageExtractor.cc TaglibExtractor.cc ) target_link_libraries(extractor-backend extractor-client ${GST_LDFLAGS} ${EXIF_LDFLAGS} ${PIXBUF_LDFLAGS} ${TAGLIB_LDFLAGS} ) add_executable(mediascanner-extractor main.cc ) target_link_libraries(mediascanner-extractor extractor-backend) install( TARGETS mediascanner-extractor RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/mediascanner-2.0 ) configure_file( com.canonical.MediaScanner2.Extractor.service.in com.canonical.MediaScanner2.Extractor.service) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/com.canonical.MediaScanner2.Extractor.service DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services ) mediascanner2-0.111+16.04.20160317/src/extractor/TaglibExtractor.hh0000644000015600001650000000222212672421600025012 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2016 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #ifndef EXTRACTOR_TAGLIBEXTRACTOR_H #define EXTRACTOR_TAGLIBEXTRACTOR_H #include namespace mediascanner { class MediaFileBuilder; struct DetectedFile; class TaglibExtractor final { public: TaglibExtractor() = default; ~TaglibExtractor() = default; TaglibExtractor(const TaglibExtractor&) = delete; TaglibExtractor& operator=(TaglibExtractor &o) = delete; bool extract(const DetectedFile &d, MediaFileBuilder &builder); }; } #endif mediascanner2-0.111+16.04.20160317/src/extractor/MetadataExtractor.cc0000644000015600001650000001477312672421600025334 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "MetadataExtractor.hh" #include "DetectedFile.hh" #include "dbus-generated.h" #include "dbus-marshal.hh" #include "../mediascanner/MediaFile.hh" #include "../mediascanner/MediaFileBuilder.hh" #include "../mediascanner/internal/utils.hh" #include #include #include #include #include #include #include #include using namespace std; namespace { const char BUS_NAME[] = "com.canonical.MediaScanner2.Extractor"; const char BUS_PATH[] = "/com/canonical/MediaScanner2/Extractor"; // This list was obtained by grepping /usr/share/mime/audio/. std::array blacklist{{"audio/x-iriver-pla", "audio/x-mpegurl", "audio/x-ms-asx", "audio/x-scpls"}}; void validate_against_blacklist(const std::string &filename, const std::string &content_type) { auto result = std::find(blacklist.begin(), blacklist.end(), content_type); if(result != blacklist.end()) { throw runtime_error("File " + filename + " is of blacklisted type " + content_type + "."); } } } namespace mediascanner { struct MetadataExtractorPrivate { std::unique_ptr bus; std::unique_ptr proxy {nullptr, g_object_unref}; MetadataExtractorPrivate(GDBusConnection *bus); void create_proxy(); }; MetadataExtractorPrivate::MetadataExtractorPrivate(GDBusConnection *bus) : bus(reinterpret_cast(g_object_ref(bus)), g_object_unref) { create_proxy(); } void MetadataExtractorPrivate::create_proxy() { GError *error = nullptr; proxy.reset(ms_extractor_proxy_new_sync( bus.get(), static_cast( G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION), BUS_NAME, BUS_PATH, nullptr, &error)); if (not proxy) { string errortxt(error->message); g_error_free(error); string msg = "Failed to create D-Bus proxy: "; msg += errortxt; throw runtime_error(msg); } } MetadataExtractor::MetadataExtractor(GDBusConnection *bus) { p.reset(new MetadataExtractorPrivate(bus)); } MetadataExtractor::~MetadataExtractor() = default; DetectedFile MetadataExtractor::detect(const std::string &filename) { std::unique_ptr file( g_file_new_for_path(filename.c_str()), g_object_unref); if (!file) { throw runtime_error("Could not create file object"); } GError *error = nullptr; std::unique_ptr info( g_file_query_info( file.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE "," G_FILE_ATTRIBUTE_ETAG_VALUE, G_FILE_QUERY_INFO_NONE, /* cancellable */ nullptr, &error), g_object_unref); if (!info) { string errortxt(error->message); g_error_free(error); string msg("Query of file info for "); msg += filename; msg += " failed: "; msg += errortxt; throw runtime_error(msg); } uint64_t mtime = g_file_info_get_attribute_uint64( info.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED); string etag(g_file_info_get_etag(info.get())); string content_type(g_file_info_get_attribute_string( info.get(), G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE)); if (content_type.empty()) { throw runtime_error("Could not determine content type."); } validate_against_blacklist(filename, content_type); MediaType type; if (content_type.find("audio/") == 0) { type = AudioMedia; } else if (content_type.find("video/") == 0) { type = VideoMedia; } else if (content_type.find("image/") == 0) { type = ImageMedia; } else { throw runtime_error(string("File ") + filename + " is not audio or video"); } return DetectedFile(filename, etag, content_type, mtime, type); } MediaFile MetadataExtractor::extract(const DetectedFile &d) { fprintf(stderr, "Extracting metadata from %s.\n", d.filename.c_str()); GError *error = nullptr; GVariant *res = nullptr; gboolean success = ms_extractor_call_extract_metadata_sync( p->proxy.get(), d.filename.c_str(), d.etag.c_str(), d.content_type.c_str(), d.mtime, d.type, &res, nullptr, &error); // If we get a synthesised "no reply" error, the server probably // crashed due to a codec bug. We retry the extraction once more // in case the crash was due to bad cleanup from a previous job. if (!success && error->domain == G_DBUS_ERROR && error->code == G_DBUS_ERROR_NO_REPLY) { g_error_free(error); error = nullptr; fprintf(stderr, "No reply from extractor daemon, retrying once.\n"); // Recreate the proxy, since the old one will have bound to // the old instance's unique name. p->create_proxy(); success = ms_extractor_call_extract_metadata_sync( p->proxy.get(), d.filename.c_str(), d.etag.c_str(), d.content_type.c_str(), d.mtime, d.type, &res, nullptr, &error); } if (!success) { string errortxt(error->message); g_error_free(error); string msg = "ExtractMetadata D-Bus call failed: "; msg += errortxt; throw runtime_error(msg); } // Place variant in a unique_ptr so it is guaranteed to be unrefed. std::unique_ptr result( res, g_variant_unref); return media_from_variant(result.get()); } MediaFile MetadataExtractor::fallback_extract(const DetectedFile &d) { return MediaFileBuilder(d.filename).setType(d.type); } } mediascanner2-0.111+16.04.20160317/test/0000755000015600001650000000000012672422030017550 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/test_metadataextractor.cc0000644000015600001650000002521012672421600024634 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013-2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include "test_config.h" #include #include #include #include #include #include #include #include #include using namespace std; using namespace mediascanner; class MetadataExtractorTest : public ::testing::Test { protected: virtual void SetUp() override { test_dbus_.reset(g_test_dbus_new(G_TEST_DBUS_NONE)); g_test_dbus_add_service_dir(test_dbus_.get(), TEST_DIR "/services"); } virtual void TearDown() override { session_bus_.reset(); test_dbus_.reset(); unsetenv("MEDIASCANNER_EXTRACTOR_CRASH_AFTER"); } GDBusConnection *session_bus() { if (!bus_started_) { g_test_dbus_up(test_dbus_.get()); GError *error = nullptr; char *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (!address) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to determine session bus address: ") + errortxt); } session_bus_.reset(g_dbus_connection_new_for_address_sync( address, static_cast(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr, nullptr, &error)); g_free(address); if (!session_bus_) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to connect to session bus: ") + errortxt); } bus_started_ = true; } return session_bus_.get(); } private: unique_ptr test_dbus_ {nullptr, g_object_unref}; unique_ptr session_bus_ {nullptr, g_object_unref}; bool bus_started_ = false; }; TEST_F(MetadataExtractorTest, init) { MetadataExtractor extractor(session_bus()); } TEST_F(MetadataExtractorTest, detect_audio) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.ogg"; DetectedFile d = e.detect(testfile); EXPECT_NE(d.etag, ""); EXPECT_EQ(d.content_type, "audio/ogg"); EXPECT_EQ(d.type, AudioMedia); struct stat st; ASSERT_EQ(0, stat(testfile.c_str(), &st)); EXPECT_EQ(st.st_mtime, d.mtime); } TEST_F(MetadataExtractorTest, detect_video) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testvideo_480p.ogv"; DetectedFile d = e.detect(testfile); EXPECT_NE(d.etag, ""); EXPECT_EQ(d.content_type, "video/ogg"); EXPECT_EQ(d.type, VideoMedia); struct stat st; ASSERT_EQ(0, stat(testfile.c_str(), &st)); EXPECT_EQ(st.st_mtime, d.mtime); } TEST_F(MetadataExtractorTest, detect_notmedia) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/CMakeLists.txt"; EXPECT_THROW(e.detect(testfile), runtime_error); } TEST_F(MetadataExtractorTest, extract) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.ogg"; MediaFile file = e.extract(e.detect(testfile)); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "track1"); EXPECT_EQ(file.getAuthor(), "artist1"); EXPECT_EQ(file.getAlbum(), "album1"); EXPECT_EQ(file.getDate(), "2013"); EXPECT_EQ(file.getTrackNumber(), 1); EXPECT_EQ(file.getDuration(), 5); } TEST_F(MetadataExtractorTest, extract_mp3) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.mp3"; MediaFile file = e.extract(e.detect(testfile)); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "track1"); EXPECT_EQ(file.getAuthor(), "artist1"); EXPECT_EQ(file.getAlbum(), "album1"); EXPECT_EQ(file.getDate(), "2013-06-03"); EXPECT_EQ(file.getTrackNumber(), 1); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getGenre(), "Hip-Hop"); struct stat st; ASSERT_EQ(0, stat(testfile.c_str(), &st)); EXPECT_EQ(st.st_mtime, file.getModificationTime()); } TEST_F(MetadataExtractorTest, extract_m4a) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.m4a"; MediaFile file = e.extract(e.detect(testfile)); EXPECT_EQ(AudioMedia, file.getType()); EXPECT_EQ("Title", file.getTitle()); EXPECT_EQ("Artist", file.getAuthor()); EXPECT_EQ("Album", file.getAlbum()); EXPECT_EQ("Album Artist", file.getAlbumArtist()); EXPECT_EQ("2015-10-07", file.getDate()); EXPECT_EQ(4, file.getTrackNumber()); EXPECT_EQ(1, file.getDiscNumber()); EXPECT_EQ(1, file.getDuration()); EXPECT_EQ("Rock", file.getGenre()); EXPECT_EQ(true, file.getHasThumbnail()); struct stat st; ASSERT_EQ(0, stat(testfile.c_str(), &st)); EXPECT_EQ(st.st_mtime, file.getModificationTime()); } TEST_F(MetadataExtractorTest, extract_video) { MetadataExtractor e(session_bus()); MediaFile file = e.extract(e.detect(SOURCE_DIR "/media/testvideo_480p.ogv")); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 854); EXPECT_EQ(file.getHeight(), 480); file = e.extract(e.detect(SOURCE_DIR "/media/testvideo_720p.ogv")); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 1280); EXPECT_EQ(file.getHeight(), 720); file = e.extract(e.detect(SOURCE_DIR "/media/testvideo_1080p.ogv")); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 1920); EXPECT_EQ(file.getHeight(), 1080); } TEST_F(MetadataExtractorTest, extract_photo) { MetadataExtractor e(session_bus()); // An landscape image that should be rotated to portrait MediaFile file = e.extract(e.detect(SOURCE_DIR "/media/image1.jpg")); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ(2848, file.getWidth()); EXPECT_EQ(4272, file.getHeight()); EXPECT_EQ("2013-01-04T08:25:46", file.getDate()); EXPECT_DOUBLE_EQ(-28.249409333333336, file.getLatitude()); EXPECT_DOUBLE_EQ(153.150774, file.getLongitude()); // A landscape image without rotation. file = e.extract(e.detect(SOURCE_DIR "/media/image2.jpg")); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ(4272, file.getWidth()); EXPECT_EQ(2848, file.getHeight()); EXPECT_EQ("2013-01-04T09:52:27", file.getDate()); EXPECT_DOUBLE_EQ(-28.259611, file.getLatitude()); EXPECT_DOUBLE_EQ(153.1727346, file.getLongitude()); } TEST_F(MetadataExtractorTest, extract_bad_date) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/baddate.ogg"; MediaFile file = e.extract(e.detect(testfile)); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "Track"); EXPECT_EQ(file.getAuthor(), "Artist"); EXPECT_EQ(file.getAlbum(), "Album"); EXPECT_EQ(file.getDate(), ""); } TEST_F(MetadataExtractorTest, extract_mp3_bad_date) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/baddate.mp3"; MediaFile file = e.extract(e.detect(testfile)); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "Track"); EXPECT_EQ(file.getAuthor(), "Artist"); EXPECT_EQ(file.getAlbum(), "Album"); EXPECT_EQ(file.getDate(), ""); } TEST_F(MetadataExtractorTest, blacklist) { MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/playlist.m3u"; try { e.detect(testfile); FAIL(); } catch(const std::runtime_error &e) { std::string error_message(e.what()); ASSERT_NE(error_message.find("blacklist"), std::string::npos); } } TEST_F(MetadataExtractorTest, png_file) { // PNG files don't have exif entries, so test we work with those, too. MetadataExtractor e(session_bus()); MediaFile file = e.extract(e.detect(SOURCE_DIR "/media/image3.png")); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ(640, file.getWidth()); EXPECT_EQ(400, file.getHeight()); // The time stamp on the test file can be anything. We can't guarantee what it is, // so just inspect the format. auto timestr = file.getDate(); EXPECT_EQ(timestr.size(), 19); EXPECT_EQ(timestr.find('T'), 10); // These can't go inside EXPECT_EQ because it is a macro and mixing templates // with macros makes things explode. auto dashes = std::count_if(timestr.begin(), timestr.end(), [](char c) { return c == '-';}); auto colons = std::count_if(timestr.begin(), timestr.end(), [](char c) { return c == ':';}); EXPECT_EQ(dashes, 2); EXPECT_EQ(colons, 2); EXPECT_DOUBLE_EQ(0, file.getLatitude()); EXPECT_DOUBLE_EQ(0, file.getLongitude()); } TEST_F(MetadataExtractorTest, extractor_crash) { setenv("MEDIASCANNER_EXTRACTOR_CRASH_AFTER", "0", true); MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.ogg"; try { MediaFile file = e.extract(e.detect(testfile)); FAIL(); } catch (const std::runtime_error &e) { EXPECT_NE(std::string::npos, std::string(e.what()).find("ExtractMetadata D-Bus call failed")) << e.what(); } } TEST_F(MetadataExtractorTest, crash_recovery) { setenv("MEDIASCANNER_EXTRACTOR_CRASH_AFTER", "1", true); MetadataExtractor e(session_bus()); string testfile = SOURCE_DIR "/media/testfile.ogg"; // First extraction succeeds MediaFile file = e.extract(e.detect(testfile)); // Second try succeeds, with the extraction daemon being // restarted. file = e.extract(e.detect(testfile)); EXPECT_EQ("track1", file.getTitle()); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_sqliteutils.cc0000644000015600001650000000530312672421600023503 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include using namespace std; using namespace mediascanner; class SqliteTest : public ::testing::Test { public: SqliteTest() : db(NULL) { } virtual void SetUp() { int rc = sqlite3_open(":memory:", &db); if (rc != SQLITE_OK) { throw runtime_error(sqlite3_errstr(rc)); } } virtual void TearDown() { if (db != NULL) { int rc = sqlite3_close(db); if (rc != SQLITE_OK) { throw runtime_error(sqlite3_errstr(rc)); } db = NULL; } } sqlite3 *db; }; TEST_F(SqliteTest, Execute) { Statement stmt(db, "SELECT 1"); EXPECT_EQ(true, stmt.step()); stmt.finalize(); } TEST_F(SqliteTest, GetInt) { Statement stmt(db, "SELECT 42"); EXPECT_EQ(true, stmt.step()); EXPECT_EQ(42, stmt.getInt(0)); stmt.finalize(); } TEST_F(SqliteTest, GetText) { Statement stmt(db, "SELECT 'foo'"); EXPECT_EQ(true, stmt.step()); EXPECT_EQ("foo", stmt.getText(0)); stmt.finalize(); } TEST_F(SqliteTest, BindInt) { Statement stmt(db, "SELECT ?"); stmt.bind(1, 42); EXPECT_EQ(true, stmt.step()); EXPECT_EQ(42, stmt.getInt(0)); stmt.finalize(); } TEST_F(SqliteTest, BindText) { Statement stmt(db, "SELECT ?"); stmt.bind(1, "foo"); EXPECT_EQ(true, stmt.step()); EXPECT_EQ("foo", stmt.getText(0)); stmt.finalize(); } TEST_F(SqliteTest, Insert) { Statement create(db, "CREATE TABLE foo (id INT PRIMARY KEY)"); EXPECT_FALSE(create.step()); create.finalize(); Statement insert(db, "INSERT INTO foo(id) VALUES (?)"); insert.bind(1, 42); EXPECT_FALSE(insert.step()); insert.finalize(); Statement select(db, "SELECT id FROM foo"); EXPECT_TRUE(select.step()); EXPECT_EQ(42, select.getInt(0)); EXPECT_FALSE(select.step()); select.finalize(); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_util.cc0000644000015600001650000000327412672421600022103 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include"../src/mediascanner/internal/utils.hh" #include"../src/mediascanner/MediaFile.hh" #include"../src/mediascanner/MediaFileBuilder.hh" #include "test_config.h" using namespace mediascanner; class UtilTest : public ::testing::Test { public: UtilTest() { } virtual void SetUp() { } virtual void TearDown() { } }; TEST_F(UtilTest, optical) { std::string blu_root(SOURCE_DIR "/bluray_root"); std::string dvd_root(SOURCE_DIR "/dvd_root"); std::string nodisc_root(SOURCE_DIR "/media"); ASSERT_TRUE(is_optical_disc(blu_root)); ASSERT_TRUE(is_optical_disc(dvd_root)); ASSERT_FALSE(is_optical_disc(nodisc_root)); } TEST_F(UtilTest, scanblock) { std::string noblock_root(SOURCE_DIR "/media"); std::string block_root(SOURCE_DIR "/noscan"); ASSERT_TRUE(has_scanblock(block_root)); ASSERT_FALSE(has_scanblock(noblock_root)); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_config.h.in0000644000015600001650000000014012672421600022627 0ustar pbuserpbgroup00000000000000#define TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}" #define SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" mediascanner2-0.111+16.04.20160317/test/bluray_root/0000755000015600001650000000000012672422030022111 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/bluray_root/BDMV/0000755000015600001650000000000012672422030022641 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/test_subtreewatcher.cc0000644000015600001650000001762312672421605024165 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2016 Canonical, Ltd. * * Authors: * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include "test_config.h" #include #include #include #include #include #include #include #include using namespace std; using namespace mediascanner; namespace { const char CRASH_AFTER_ENV[] = "MEDIASCANNER_EXTRACTOR_CRASH_AFTER"; typedef std::unique_ptr GDBusConnectionPtr; void copy_file(const string &src, const string &dst) { FILE* f = fopen(src.c_str(), "r"); ASSERT_TRUE(f); fseek(f, 0, SEEK_END); size_t size = ftell(f); char* buf = new char[size]; fseek(f, 0, SEEK_SET); ASSERT_EQ(fread(buf, 1, size, f), size); fclose(f); f = fopen(dst.c_str(), "w"); ASSERT_TRUE(f); ASSERT_EQ(fwrite(buf, 1, size, f), size); fclose(f); delete[] buf; } void iterate_main_loop() { while (g_main_context_iteration(nullptr, FALSE)) { } } } class SubtreeWatcherTest : public ::testing::Test { protected: virtual void SetUp() override { main_loop_.reset(g_main_loop_new(nullptr, false)); tmpdir_ = TEST_DIR "/subtreewatcher-test.XXXXXX"; ASSERT_NE(nullptr, mkdtemp(&tmpdir_[0])); } void setup_watcher() { test_dbus_.reset(g_test_dbus_new(G_TEST_DBUS_NONE)); g_test_dbus_add_service_dir(test_dbus_.get(), TEST_DIR "/services"); g_test_dbus_up(test_dbus_.get()); session_bus_ = make_connection(); g_dbus_connection_signal_subscribe( session_bus_.get(), nullptr, "com.canonical.unity.scopes", "InvalidateResults", "/com/canonical/unity/scopes", "mediascanner-music", G_DBUS_SIGNAL_FLAGS_NONE, &SubtreeWatcherTest::invalidateCallback, this, nullptr); store_.reset(new MediaStore(":memory:", MS_READ_WRITE)); extractor_.reset(new MetadataExtractor(session_bus_.get())); invalidator_.reset(new InvalidationSender); invalidator_->setBus(session_bus_.get()); watcher_.reset(new SubtreeWatcher(*store_, *extractor_, *invalidator_)); } virtual void TearDown() override { watcher_.reset(); invalidator_.reset(); extractor_.reset(); store_.reset(); session_bus_.reset(); test_dbus_.reset(); if (!tmpdir_.empty()) { string cmd = "rm -rf " + tmpdir_; ASSERT_EQ(0, system(cmd.c_str())); } unsetenv(CRASH_AFTER_ENV); } GDBusConnectionPtr make_connection() { GError *error = nullptr; char *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (!address) { string errortxt(error->message); g_error_free(error); throw std::runtime_error( string("Failed to determine session bus address: ") + errortxt); } GDBusConnectionPtr bus( g_dbus_connection_new_for_address_sync( address, static_cast(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr, nullptr, &error), g_object_unref); g_free(address); if (!bus) { string errortxt(error->message); g_error_free(error); throw std::runtime_error( string("Failed to connect to session bus: ") + errortxt); } return std::move(bus); } bool wait_for_invalidate(int timeout) { invalidate_timeout_id_ = g_timeout_add( timeout * 1000, [](void *user_data) -> int { auto test = reinterpret_cast(user_data); g_main_loop_quit(test->main_loop_.get()); test->invalidate_timeout_id_ = 0; return G_SOURCE_REMOVE; }, this); g_main_loop_run(main_loop_.get()); // The timeout ID will be zero if it was fired and we received // no signals. if (invalidate_timeout_id_ == 0) { return false; } g_source_remove(invalidate_timeout_id_); invalidate_timeout_id_ = 0; return true; } static void invalidateCallback(GDBusConnection */*connection*/, const char */*sender_name*/, const char */*object_path*/, const char */*interface_name*/, const char */*signal_name*/, GVariant */*parameters*/, gpointer user_data) { auto test = reinterpret_cast(user_data); test->invalidate_count_++; // If we're waiting on an invalidate signal, quit the main loop if (test->invalidate_timeout_id_ != 0) { g_main_loop_quit(test->main_loop_.get()); } } string tmpdir_; unique_ptr test_dbus_ {nullptr, g_object_unref}; unique_ptr store_; unique_ptr watcher_; int invalidate_count_ = 0; private: GDBusConnectionPtr session_bus_ {nullptr, g_object_unref}; unique_ptr extractor_; unique_ptr invalidator_; unique_ptr main_loop_ {nullptr, g_main_loop_unref}; unsigned int invalidate_timeout_id_ = 0; }; TEST_F(SubtreeWatcherTest, open_for_write_without_change) { setup_watcher(); watcher_->addDir(tmpdir_); iterate_main_loop(); string testfile = tmpdir_ + "/testfile.ogg"; copy_file(SOURCE_DIR "/media/testfile.ogg", testfile); EXPECT_TRUE(wait_for_invalidate(2)); ASSERT_EQ(store_->size(), 1); // Invalidate called once for new file. EXPECT_EQ(1, invalidate_count_); int fd = open(testfile.c_str(), O_RDWR); ASSERT_GE(fd, 0); ASSERT_EQ(0, close(fd)); // No change, to file so no new invalidations EXPECT_FALSE(wait_for_invalidate(2)); EXPECT_EQ(1, invalidate_count_); fd = open(testfile.c_str(), O_RDWR|O_APPEND); ASSERT_GE(fd, 0); ASSERT_EQ(5, write(fd, "hello", 5)); ASSERT_EQ(0, close(fd)); // File changed, so invalidation count increases. EXPECT_TRUE(wait_for_invalidate(3)); EXPECT_EQ(2, invalidate_count_); } TEST_F(SubtreeWatcherTest, fallback_added_for_failed_extraction) { setenv(CRASH_AFTER_ENV, "0", true); setup_watcher(); watcher_->addDir(tmpdir_); iterate_main_loop(); string testfile = tmpdir_ + "/testfile.ogg"; copy_file(SOURCE_DIR "/media/testfile.ogg", testfile); EXPECT_TRUE(wait_for_invalidate(10)); ASSERT_EQ(store_->size(), 1); // Failed extraction exists in database with its title set based // on the file name, but other metadata absent. MediaFile media = store_->lookup(testfile); EXPECT_EQ("testfile", media.getTitle()); EXPECT_EQ("", media.getAuthor()); EXPECT_EQ("", media.getAlbum()); EXPECT_EQ(0, media.getDuration()); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/qml/0000755000015600001650000000000012672422030020341 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/qml/tst_songsearchmodel.qml0000644000015600001650000000172612672421600025133 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } SongsSearchModel { id: model store: store } SignalSpy { id: modelStatus target: model signalName: "statusChanged" } TestCase { name: "SongsSearchModelTests" function waitForReady() { while (model.status == SongsSearchModel.Loading) { modelStatus.wait(); } compare(model.status, SongsSearchModel.Ready); } function test_search() { // By default, the model lists all songs. waitForReady(); compare(model.count, 7, "songs_model.count == 7"); model.query = "revolution"; waitForReady(); compare(model.count, 1, "songs_model.count == 1"); compare(model.get(0, SongsSearchModel.RoleTitle), "Revolution"); } } } mediascanner2-0.111+16.04.20160317/test/qml/tst_artistsmodel.qml0000644000015600001650000000417312672421600024467 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } ArtistsModel { id: model store: store } SignalSpy { id: modelStatus target: model signalName: "statusChanged" } TestCase { name: "ArtistsModelTests" function waitForReady() { while (model.status == ArtistsModel.Loading) { modelStatus.wait(); } compare(model.status, ArtistsModel.Ready); } function cleanup() { model.albumArtists = false; model.genre = undefined; waitForReady(); } function test_initial_state() { compare(model.albumArtists, false); compare(model.genre, undefined); waitForReady(); compare(model.count, 2); compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait"); compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio"); } function test_limit() { // The limit property is deprecated now, but we need to // keep it until music-app stops using it. compare(model.limit, -1); model.limit = 1; compare(model.limit, -1); } function test_album_artists() { model.albumArtists = true; waitForReady(); compare(model.count, 2); compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait"); compare(model.get(1, ArtistsModel.RoleArtist), "The John Butler Trio"); } function test_genre() { model.genre = "rock"; waitForReady(); compare(model.count, 1); compare(model.get(0, ArtistsModel.RoleArtist), "Spiderbait"); model.genre = "roots"; waitForReady(); compare(model.count, 1); compare(model.get(0, ArtistsModel.RoleArtist), "The John Butler Trio"); model.genre = "unknown"; waitForReady(); compare(model.count, 0); } } } mediascanner2-0.111+16.04.20160317/test/qml/tst_albumsmodel.qml0000644000015600001650000001015512672421600024256 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } AlbumsModel { id: model store: store } SignalSpy { id: modelStatus target: model signalName: "statusChanged" } TestCase { name: "AlbumsModelTests" function waitForReady() { while (model.status == AlbumsModel.Loading) { modelStatus.wait(); } compare(model.status, AlbumsModel.Ready); } function cleanup() { model.artist = undefined; model.albumArtist = undefined; model.genre = undefined; waitForReady(); } function test_initial_state() { compare(model.artist, undefined); compare(model.albumArtist, undefined); compare(model.genre, undefined); compare(model.count, 4); compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples"); compare(model.get(0, AlbumsModel.RoleArtist), "Spiderbait"); compare(model.get(0, AlbumsModel.RoleDate), "1996-10-04"); compare(model.get(0, AlbumsModel.RoleGenre), "rock"); compare(model.get(0, AlbumsModel.RoleArt), "image://albumart/artist=Spiderbait&album=Ivy%20and%20the%20Big%20Apples"); compare(model.get(1, AlbumsModel.RoleTitle), "Spiderbait"); compare(model.get(1, AlbumsModel.RoleArtist), "Spiderbait"); compare(model.get(1, AlbumsModel.RoleDate), "2013-11-15"); compare(model.get(1, AlbumsModel.RoleGenre), "rock"); compare(model.get(1, AlbumsModel.RoleArt), "image://albumart/artist=Spiderbait&album=Spiderbait"); compare(model.get(2, AlbumsModel.RoleTitle), "April Uprising"); compare(model.get(2, AlbumsModel.RoleArtist), "The John Butler Trio"); compare(model.get(2, AlbumsModel.RoleDate), "2010-01-01"); compare(model.get(2, AlbumsModel.RoleGenre), "roots"); compare(model.get(2, AlbumsModel.RoleArt), "image://albumart/artist=The%20John%20Butler%20Trio&album=April%20Uprising"); compare(model.get(3, AlbumsModel.RoleTitle), "Sunrise Over Sea"); compare(model.get(3, AlbumsModel.RoleArtist), "The John Butler Trio"); compare(model.get(3, AlbumsModel.RoleDate), "2004-03-08"); compare(model.get(3, AlbumsModel.RoleGenre), "roots"); compare(model.get(3, AlbumsModel.RoleArt), "image://albumart/artist=The%20John%20Butler%20Trio&album=Sunrise%20Over%20Sea"); } function test_limit() { // The limit property is deprecated now, but we need to // keep it until music-app stops using it. compare(model.limit, -1); model.limit = 1; compare(model.limit, -1); } function test_artist() { model.artist = "The John Butler Trio"; waitForReady(); compare(model.count, 2); compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising"); compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio"); model.artist = "unknown"; waitForReady(); compare(model.count, 0); } function test_album_artist() { model.albumArtist = "The John Butler Trio"; waitForReady(); compare(model.count, 2); compare(model.get(0, AlbumsModel.RoleTitle), "April Uprising"); compare(model.get(0, AlbumsModel.RoleArtist), "The John Butler Trio"); model.albumArtist = "unknown"; waitForReady(); compare(model.count, 0); } function test_genre() { model.genre = "rock"; waitForReady(); compare(model.count, 2); compare(model.get(0, AlbumsModel.RoleTitle), "Ivy and the Big Apples"); compare(model.get(1, AlbumsModel.RoleTitle), "Spiderbait"); model.genre = "unknown"; waitForReady(); compare(model.count, 0); } } } mediascanner2-0.111+16.04.20160317/test/qml/tst_mediastore.qml0000644000015600001650000000340012672421600024101 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } TestCase { name: "MediaStoreTests" function test_lookup() { var song = store.lookup("/unknown.ogg"); compare(song, null, "song == null"); song = store.lookup("/path/foo1.ogg"); verify(song !== null, "song != null"); var checkAttr = function (attr, value) { compare(song[attr], value, "song." + attr + " == \"" + value + "\""); }; checkAttr("filename", "/path/foo1.ogg"); checkAttr("uri", "file:///path/foo1.ogg"); checkAttr("contentType", "audio/ogg"); checkAttr("eTag", "etag"); checkAttr("title", "Straight Through The Sun"); checkAttr("author", "Spiderbait"); checkAttr("album", "Spiderbait"); checkAttr("albumArtist", "Spiderbait"); checkAttr("date", "2013-11-15"); checkAttr("genre", "rock"); checkAttr("discNumber", 1); checkAttr("trackNumber", 1); checkAttr("duration", 235); checkAttr("width", 0); checkAttr("height", 0); checkAttr("latitude", 0.0); checkAttr("longitude", 0.0); checkAttr("art", "image://albumart/artist=Spiderbait&album=Spiderbait"); } function test_query() { var songs = store.query("unknown", MediaStore.AudioMedia); compare(songs.length, 0, "songs.length == 0"); var songs = store.query("Pony", MediaStore.AudioMedia); compare(songs.length, 1, "songs.length == 1"); compare(songs[0].title, "Buy Me a Pony"); } } } mediascanner2-0.111+16.04.20160317/test/qml/tst_genresmodel.qml0000644000015600001650000000214712672421600024260 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } GenresModel { id: model store: store } SignalSpy { id: modelStatus target: model signalName: "statusChanged" } TestCase { name: "GenresModelTests" function waitForReady() { while (model.status == GenresModel.Loading) { modelStatus.wait(); } compare(model.status, GenresModel.Ready); } function cleanup() { } function test_initial_state() { waitForReady(); compare(model.count, 2); compare(model.get(0, ArtistsModel.RoleGenre), "rock"); compare(model.get(1, ArtistsModel.RoleGenre), "roots"); } function test_limit() { // The limit property is deprecated now, but we need to // keep it until music-app stops using it. compare(model.limit, -1); model.limit = 1; compare(model.limit, -1); } } } mediascanner2-0.111+16.04.20160317/test/qml/tst_songsmodel.qml0000644000015600001650000000721112672421600024123 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 Item { id: root MediaStore { id: store } SongsModel { id: model store: store } SignalSpy { id: modelStatus target: model signalName: "statusChanged" } TestCase { name: "SongsModelTests" function waitForReady() { while (model.status == SongsModel.Loading) { modelStatus.wait(); } compare(model.status, SongsModel.Ready); } function cleanup() { model.artist = undefined; model.albumArtist = undefined; model.album = undefined; model.genre = undefined; waitForReady(); } function test_initial_state() { compare(model.artist, undefined); compare(model.albumArtist, undefined); compare(model.album, undefined); compare(model.count, 7); compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony") compare(model.get(0, SongsModel.RoleAlbum), "Ivy and the Big Apples"); compare(model.get(0, SongsModel.RoleAuthor), "Spiderbait"); compare(model.get(1, SongsModel.RoleTitle), "Straight Through The Sun"); compare(model.get(2, SongsModel.RoleTitle), "It's Beautiful"); compare(model.get(3, SongsModel.RoleTitle), "Revolution"); compare(model.get(4, SongsModel.RoleTitle), "One Way Road"); compare(model.get(5, SongsModel.RoleTitle), "Peaches & Cream"); compare(model.get(6, SongsModel.RoleTitle), "Zebra"); } function test_limit() { // The limit property is deprecated now, but we need to // keep it until music-app stops using it. compare(model.limit, -1); model.limit = 1; compare(model.limit, -1); } function test_artist() { model.artist = "The John Butler Trio"; waitForReady(); compare(model.count, 4); compare(model.get(0, SongsModel.RoleTitle), "Revolution"); compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio"); model.artist = "unknown"; waitForReady(); compare(model.count, 0); } function test_album_artist() { model.albumArtist = "The John Butler Trio"; waitForReady(); compare(model.count, 4); compare(model.get(0, SongsModel.RoleTitle), "Revolution"); compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio"); model.albumArtist = "unknown"; waitForReady(); compare(model.count, 0); } function test_album() { model.album = "Sunrise Over Sea"; waitForReady(); compare(model.count, 2); compare(model.get(0, SongsModel.RoleTitle), "Peaches & Cream"); compare(model.get(0, SongsModel.RoleAuthor), "The John Butler Trio"); model.albumArtist = "unknown"; waitForReady(); compare(model.count, 0); } function test_genre() { model.genre = "rock"; waitForReady(); compare(model.count, 3); compare(model.get(0, SongsModel.RoleTitle), "Buy Me a Pony"); compare(model.get(1, SongsModel.RoleTitle), "Straight Through The Sun"); compare(model.get(2, SongsModel.RoleTitle), "It's Beautiful"); model.albumArtist = "unknown"; waitForReady(); compare(model.count, 0); } } } mediascanner2-0.111+16.04.20160317/test/test_mfbuilder.cc0000644000015600001650000002723312672421600023100 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include "test_config.h" #include "mediascanner/Album.hh" #include "mediascanner/MediaFile.hh" #include "mediascanner/MediaFileBuilder.hh" #include #include #include #include #include #include #include #include #include using namespace mediascanner; class MFBTest : public ::testing::Test { protected: MFBTest() = default; virtual ~MFBTest() = default; virtual void SetUp() override { tmpdir = TEST_DIR "/mfbuilder-test.XXXXXX"; ASSERT_NE(nullptr, mkdtemp(&tmpdir[0])); } virtual void TearDown() override { if (!tmpdir.empty()) { std::string cmd = "rm -rf " + tmpdir; ASSERT_EQ(0, system(cmd.c_str())); } } void touch(const std::string &fname, bool sleep=false) { if (sleep) { // Ensure time stamps change std::this_thread::sleep_for(std::chrono::milliseconds(50)); } int fd = open(fname.c_str(), O_CREAT, 0600); ASSERT_GT(fd, 0); ASSERT_EQ(0, close(fd)); } void remove(const std::string &fname, bool sleep=false) { if (sleep) { // Ensure time stamps change std::this_thread::sleep_for(std::chrono::milliseconds(50)); } ASSERT_EQ(0, unlink(fname.c_str())); } std::string tmpdir; }; TEST_F(MFBTest, basic) { MediaType type(AudioMedia); std::string fname("abc"); std::string title("def"); std::string date("ghi"); std::string author("jkl"); std::string album("mno"); std::string album_artist("pqr"); std::string etag("stu"); std::string content_type("vwx"); std::string genre("yz"); int disc_number = 2; int track_number = 13; int duration = 99; int width = 640; int height = 480; double latitude = 67.2; double longitude = -7.5; uint64_t mtime = 42; MediaFileBuilder b(fname); b.setType(type); b.setTitle(title); b.setDate(date); b.setAuthor(author); b.setAlbum(album); b.setAlbumArtist(album_artist); b.setGenre(genre); b.setDiscNumber(disc_number); b.setTrackNumber(track_number); b.setDuration(duration); b.setETag(etag); b.setContentType(content_type); b.setWidth(width); b.setHeight(height); b.setLatitude(latitude); b.setLongitude(longitude); b.setModificationTime(mtime); // Now see if data survives a round trip. MediaFile mf = b.build(); EXPECT_EQ(mf.getType(), type); EXPECT_EQ(mf.getFileName(), fname); EXPECT_EQ(mf.getTitle(), title); EXPECT_EQ(mf.getDate(), date); EXPECT_EQ(mf.getAuthor(), author); EXPECT_EQ(mf.getAlbum(), album); EXPECT_EQ(mf.getAlbumArtist(), album_artist); EXPECT_EQ(mf.getGenre(), genre); EXPECT_EQ(mf.getDiscNumber(), disc_number); EXPECT_EQ(mf.getTrackNumber(), track_number); EXPECT_EQ(mf.getDuration(), duration); EXPECT_EQ(mf.getETag(), etag); EXPECT_EQ(mf.getContentType(), content_type); EXPECT_EQ(mf.getWidth(), width); EXPECT_EQ(mf.getHeight(), height); EXPECT_DOUBLE_EQ(mf.getLatitude(), latitude); EXPECT_DOUBLE_EQ(mf.getLongitude(), longitude); EXPECT_EQ(mf.getModificationTime(), mtime); MediaFileBuilder mfb2(mf); MediaFile mf2 = mfb2.build(); EXPECT_EQ(mf, mf2); } TEST_F(MFBTest, chaining) { MediaType type(AudioMedia); std::string fname("abc"); std::string title("def"); std::string date("ghi"); std::string author("jkl"); std::string album("mno"); std::string album_artist("pqr"); std::string etag("stu"); std::string content_type("vwx"); std::string genre("yz"); int disc_number = 2; int track_number = 13; int duration = 99; int width = 640; int height = 480; double latitude = 67.2; double longitude = -7.5; uint64_t mtime = 42; MediaFile mf = MediaFileBuilder(fname) .setType(type) .setTitle(title) .setDate(date) .setAuthor(author) .setAlbum(album) .setAlbumArtist(album_artist) .setGenre(genre) .setDiscNumber(disc_number) .setTrackNumber(track_number) .setDuration(duration) .setWidth(width) .setHeight(height) .setLatitude(latitude) .setLongitude(longitude) .setETag(etag) .setContentType(content_type) .setModificationTime(42); // Now see if data survives a round trip. EXPECT_EQ(mf.getType(), type); EXPECT_EQ(mf.getFileName(), fname); EXPECT_EQ(mf.getTitle(), title); EXPECT_EQ(mf.getDate(), date); EXPECT_EQ(mf.getAuthor(), author); EXPECT_EQ(mf.getAlbum(), album); EXPECT_EQ(mf.getAlbumArtist(), album_artist); EXPECT_EQ(mf.getGenre(), genre); EXPECT_EQ(mf.getDiscNumber(), disc_number); EXPECT_EQ(mf.getTrackNumber(), track_number); EXPECT_EQ(mf.getDuration(), duration); EXPECT_EQ(mf.getETag(), etag); EXPECT_EQ(mf.getContentType(), content_type); EXPECT_EQ(mf.getWidth(), width); EXPECT_EQ(mf.getHeight(), height); EXPECT_DOUBLE_EQ(mf.getLatitude(), latitude); EXPECT_DOUBLE_EQ(mf.getLongitude(), longitude); EXPECT_EQ(mf.getModificationTime(), mtime); } TEST_F(MFBTest, fallback_title) { // Fallback title is derived from file name. MediaFile mf = MediaFileBuilder("/path/to/abc.ogg"); EXPECT_EQ(mf.getTitle(), "abc"); } TEST_F(MFBTest, fallback_album_artist) { // Fallback album_artist is the author. MediaFile mf = MediaFileBuilder("abc") .setAuthor("author"); EXPECT_EQ(mf.getAlbumArtist(), "author"); } TEST_F(MFBTest, faulty_usage) { MediaFileBuilder mfb("/foo/bar/baz.mp3"); MediaFile m1(std::move(mfb)); ASSERT_THROW(MediaFile m2(std::move(mfb)), std::logic_error); ASSERT_THROW(MediaFile m3(mfb), std::logic_error); } TEST_F(MFBTest, album_art_uri) { // No embedded art: use external art fetcher MediaFile mf = MediaFileBuilder("/foo/bar/baz.mp3") .setType(AudioMedia) .setAuthor("The Artist") .setAlbum("The Album"); EXPECT_EQ("image://albumart/artist=The%20Artist&album=The%20Album", mf.getArtUri()); // Embedded art: use thumbnailer mf = MediaFileBuilder("/foo/bar/baz.mp3") .setType(AudioMedia) .setAuthor("The Artist") .setAlbum("The Album") .setHasThumbnail(true); EXPECT_EQ("image://thumbnailer/file:///foo/bar/baz.mp3", mf.getArtUri()); // Videos use thumbnailer mf = MediaFileBuilder("/foo/bar/baz.mp4") .setType(VideoMedia) .setAuthor("The Artist") .setAlbum("The Album"); EXPECT_EQ("image://thumbnailer/file:///foo/bar/baz.mp4", mf.getArtUri()); } TEST_F(MFBTest, folder_art) { std::string fname = tmpdir + "/dummy.mp3"; MediaFile media = MediaFileBuilder(fname) .setType(AudioMedia) .setTitle("Title") .setAuthor("Artist") .setAlbum("Album"); EXPECT_EQ("image://albumart/artist=Artist&album=Album", media.getArtUri()); touch(tmpdir + "/folder.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); remove(tmpdir + "/folder.jpg", true); EXPECT_EQ("image://albumart/artist=Artist&album=Album", media.getArtUri()); } TEST_F(MFBTest, folder_art_case_insensitive) { std::string fname = tmpdir + "/dummy.mp3"; MediaFile media = MediaFileBuilder(fname) .setType(AudioMedia) .setTitle("Title") .setAuthor("Artist") .setAlbum("Album"); touch(tmpdir + "/FOLDER.JPG"); EXPECT_NE(std::string::npos, media.getArtUri().find("/FOLDER.JPG")) << media.getArtUri(); } TEST_F(MFBTest, folder_art_precedence) { std::string fname = tmpdir + "/dummy.mp3"; MediaFile media = MediaFileBuilder(fname) .setType(AudioMedia) .setTitle("Title") .setAuthor("Artist") .setAlbum("Album"); touch(tmpdir + "/cover.jpg"); touch(tmpdir + "/album.jpg"); touch(tmpdir + "/albumart.jpg"); touch(tmpdir + "/.folder.jpg"); touch(tmpdir + "/folder.jpeg"); touch(tmpdir + "/folder.jpg"); touch(tmpdir + "/folder.png"); EXPECT_NE(std::string::npos, media.getArtUri().find("/cover.jpg")) << media.getArtUri(); remove(tmpdir + "/cover.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/album.jpg")) << media.getArtUri(); remove(tmpdir + "/album.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/albumart.jpg")) << media.getArtUri(); remove(tmpdir + "/albumart.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/.folder.jpg")) << media.getArtUri(); remove(tmpdir + "/.folder.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpeg")) << media.getArtUri(); remove(tmpdir + "/folder.jpeg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); remove(tmpdir + "/folder.jpg", true); EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.png")) << media.getArtUri(); } TEST_F(MFBTest, folder_art_cache_coverage) { std::vector files; for (int i = 0; i < 100; i++) { std::string directory = tmpdir + "/" + std::to_string(i); ASSERT_EQ(0, mkdir(directory.c_str(), 0700)); touch(directory + "/folder.jpg"); std::string fname = directory + "/dummy.mp3"; files.emplace_back(MediaFileBuilder(fname) .setType(AudioMedia) .setTitle("Title") .setAuthor("Artist") .setAlbum("Album")); } // Check art for a number of files smaller than the cache size twice for (int i = 0; i < 10; i++) { const auto &media = files[i]; EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); } for (int i = 0; i < 10; i++) { const auto &media = files[i]; EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); } // Now check a larger number of files twice for (const auto &media : files) { EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); } for (const auto &media : files) { EXPECT_NE(std::string::npos, media.getArtUri().find("/folder.jpg")) << media.getArtUri(); } } TEST_F(MFBTest, album_art) { std::string fname = tmpdir + "/dummy.mp3"; // File with embedded art Album album("Album", "Artist", "2015-11-23", "Rock", fname, true); EXPECT_NE(std::string::npos, album.getArtUri().find("/dummy.mp3")) << album.getArtUri(); // No embedded art album = Album("Album", "Artist", "2015-11-23", "Rock", fname, false); EXPECT_EQ("image://albumart/artist=Artist&album=Album", album.getArtUri()); // No embedded art, but folder art available touch(tmpdir + "/folder.jpg", true); EXPECT_NE(std::string::npos, album.getArtUri().find("/folder.jpg")) << album.getArtUri(); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_extractorbackend.cc0000644000015600001650000002202212672421600024441 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2014 Canonical, Ltd. * * Authors: * Jussi Pakkanen * James Henstridge * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include #include "test_config.h" #include #include #include #include #include using namespace std; using namespace mediascanner; namespace { bool supports_decoder(const std::string& format) { typedef std::unique_ptr CapsPtr; static std::vector formats; if (formats.empty()) { std::unique_ptr decoders( gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DECODER, GST_RANK_NONE), gst_plugin_feature_list_free); for (const GList* l = decoders.get(); l != nullptr; l = l->next) { const auto factory = static_cast(l->data); const GList* templates = gst_element_factory_get_static_pad_templates(factory); for (const GList* l = templates; l != nullptr; l = l->next) { const auto t = static_cast(l->data); if (t->direction != GST_PAD_SINK) { continue; } CapsPtr caps(gst_static_caps_get(&t->static_caps), gst_caps_unref); if (gst_caps_is_any(caps.get())) { continue; } formats.emplace_back(std::move(caps)); } } } char *end = nullptr; GstStructure *structure = gst_structure_from_string(format.c_str(), &end); assert(structure != nullptr); assert(end == format.c_str() + format.size()); // GstCaps adopts the GstStructure CapsPtr caps(gst_caps_new_full(structure, nullptr), gst_caps_unref); for (const auto &other : formats) { if (gst_caps_is_always_compatible(caps.get(), other.get())) { return true; } } return false; } } class ExtractorBackendTest : public ::testing::Test { protected: ExtractorBackendTest() { } virtual ~ExtractorBackendTest() { } virtual void SetUp() override { } virtual void TearDown() override { } }; TEST_F(ExtractorBackendTest, init) { ExtractorBackend extractor; } TEST_F(ExtractorBackendTest, extract_vorbis) { ExtractorBackend e; string testfile = SOURCE_DIR "/media/testfile.ogg"; DetectedFile df(testfile, "etag", "audio/ogg", 42, AudioMedia); MediaFile file = e.extract(df); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "track1"); EXPECT_EQ(file.getAuthor(), "artist1"); EXPECT_EQ(file.getAlbum(), "album1"); EXPECT_EQ(file.getDate(), "2013"); EXPECT_EQ(file.getTrackNumber(), 1); EXPECT_EQ(file.getDuration(), 5); } TEST_F(ExtractorBackendTest, extract_mp3) { ExtractorBackend e; string testfile = SOURCE_DIR "/media/testfile.mp3"; DetectedFile df(testfile, "etag", "audio/mpeg", 42, AudioMedia); MediaFile file = e.extract(df); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "track1"); EXPECT_EQ(file.getAuthor(), "artist1"); EXPECT_EQ(file.getAlbum(), "album1"); EXPECT_EQ(file.getGenre(), "Hip-Hop"); EXPECT_EQ(file.getDate(), "2013-06-03"); EXPECT_EQ(file.getTrackNumber(), 1); EXPECT_EQ(file.getDuration(), 1); } TEST_F(ExtractorBackendTest, extract_m4a) { ExtractorBackend e; string testfile = SOURCE_DIR "/media/testfile.m4a"; DetectedFile df(testfile, "etag", "audio/mpeg4", 42, AudioMedia); MediaFile file = e.extract(df); EXPECT_EQ(file.getType(), AudioMedia); EXPECT_EQ(file.getTitle(), "Title"); EXPECT_EQ(file.getAuthor(), "Artist"); EXPECT_EQ(file.getAlbum(), "Album"); EXPECT_EQ(file.getGenre(), "Rock"); EXPECT_EQ(file.getDate(), "2015-10-07"); EXPECT_EQ(file.getTrackNumber(), 4); EXPECT_EQ(file.getDuration(), 1); } TEST_F(ExtractorBackendTest, extract_video) { ExtractorBackend e; MediaFile file = e.extract(DetectedFile( SOURCE_DIR "/media/testvideo_480p.ogv", "etag", "video/ogg", 42, VideoMedia)); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 854); EXPECT_EQ(file.getHeight(), 480); file = e.extract(DetectedFile( SOURCE_DIR "/media/testvideo_720p.ogv", "etag", "video/ogg", 42, VideoMedia)); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 1280); EXPECT_EQ(file.getHeight(), 720); file = e.extract(DetectedFile( SOURCE_DIR "/media/testvideo_1080p.ogv", "etag", "video/ogg", 42, VideoMedia)); EXPECT_EQ(file.getType(), VideoMedia); EXPECT_EQ(file.getDuration(), 1); EXPECT_EQ(file.getWidth(), 1920); EXPECT_EQ(file.getHeight(), 1080); } TEST_F(ExtractorBackendTest, extract_photo) { ExtractorBackend e; // An landscape image that should be rotated to portrait MediaFile file = e.extract(DetectedFile( SOURCE_DIR "/media/image1.jpg", "etag", "image/jpeg", 42, ImageMedia)); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ(2848, file.getWidth()); EXPECT_EQ(4272, file.getHeight()); EXPECT_EQ("2013-01-04T08:25:46", file.getDate()); EXPECT_DOUBLE_EQ(-28.249409333333336, file.getLatitude()); EXPECT_DOUBLE_EQ(153.150774, file.getLongitude()); // A landscape image without rotation. file = e.extract(DetectedFile( SOURCE_DIR "/media/image2.jpg", "etag", "image/jpeg", 42, ImageMedia)); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ(4272, file.getWidth()); EXPECT_EQ(2848, file.getHeight()); EXPECT_EQ("2013-01-04T09:52:27", file.getDate()); EXPECT_DOUBLE_EQ(-28.259611, file.getLatitude()); EXPECT_DOUBLE_EQ(153.1727346, file.getLongitude()); } TEST_F(ExtractorBackendTest, extract_photo_date_original) { ExtractorBackend e; MediaFile file = e.extract(DetectedFile( SOURCE_DIR "/media/krillin.jpg", "etag", "image/jpeg", 42, ImageMedia)); EXPECT_EQ(ImageMedia, file.getType()); EXPECT_EQ("2016-01-22T18:28:42", file.getDate()); } void compare_taglib_gst(const DetectedFile d) { GStreamerExtractor gst(5); MediaFileBuilder builder_gst(d.filename); gst.extract(d, builder_gst); TaglibExtractor taglib; MediaFileBuilder builder_taglib(d.filename); ASSERT_TRUE(taglib.extract(d, builder_taglib)); MediaFile media_gst(builder_gst); MediaFile media_taglib(builder_taglib); EXPECT_EQ(media_gst, media_taglib); // And check individual keys to improve error handling: EXPECT_EQ(media_gst.getTitle(), media_taglib.getTitle()); EXPECT_EQ(media_gst.getAuthor(), media_taglib.getAuthor()); EXPECT_EQ(media_gst.getAlbum(), media_taglib.getAlbum()); EXPECT_EQ(media_gst.getAlbumArtist(), media_taglib.getAlbumArtist()); EXPECT_EQ(media_gst.getDate(), media_taglib.getDate()); EXPECT_EQ(media_gst.getGenre(), media_taglib.getGenre()); EXPECT_EQ(media_gst.getDiscNumber(), media_taglib.getDiscNumber()); EXPECT_EQ(media_gst.getTrackNumber(), media_taglib.getTrackNumber()); EXPECT_EQ(media_gst.getDuration(), media_taglib.getDuration()); EXPECT_EQ(media_gst.getHasThumbnail(), media_taglib.getHasThumbnail()); } TEST_F(ExtractorBackendTest, check_taglib_gst_vorbis) { DetectedFile d(SOURCE_DIR "/media/testfile.ogg", "etag", "audio/ogg", 42, AudioMedia); compare_taglib_gst(d); } TEST_F(ExtractorBackendTest, check_taglib_gst_mp3) { if (!supports_decoder("audio/mpeg, mpegversion=(int)1, layer=(int)3")) { printf("MP3 codec not supported\n"); return; } DetectedFile d(SOURCE_DIR "/media/testfile.mp3", "etag", "audio/mpeg", 42, AudioMedia); compare_taglib_gst(d); } TEST_F(ExtractorBackendTest, check_taglib_gst_m4a) { if (!supports_decoder("audio/mpeg, mpegversion=(int)4, stream-format=(string)raw")) { printf("M4A codec not supported\n"); return; } DetectedFile d(SOURCE_DIR "/media/testfile.m4a", "etag", "audio/mp4", 42, AudioMedia); compare_taglib_gst(d); } int main(int argc, char **argv) { gst_init(&argc, &argv); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/dvd_root/0000755000015600001650000000000012672422030021370 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/dvd_root/VIDEO_TS/0000755000015600001650000000000012672422030022644 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/dvd_root/AUDIO_TS/0000755000015600001650000000000012672422030022637 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/services/0000755000015600001650000000000012672422030021373 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/services/com.canonical.MediaScanner2.service.in0000644000015600001650000000015312672421600030501 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.MediaScanner2 Exec=@CMAKE_BINARY_DIR@/src/ms-dbus/mediascanner-dbus-2.0 mediascanner2-0.111+16.04.20160317/test/services/com.canonical.MediaScanner2.Extractor.service.in0000644000015600001650000000017012672421600032452 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.MediaScanner2.Extractor Exec=@CMAKE_BINARY_DIR@/src/extractor/mediascanner-extractor mediascanner2-0.111+16.04.20160317/test/media/0000755000015600001650000000000012672422030020627 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/media/testfile.m4a0000644000015600001650000001564012672421600023061 0ustar pbuserpbgroup00000000000000ftypM4V isomiso2avc1freeâmdatÞLavc56.41.1000@­ÿÿ©ÜE齿ÙH·–,Ø Ù#îïx264 - core 146 r2555 0c21480 - H.264/MPEG-4 AVC codec - Copyleft 2003-2015 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=9 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00€`eˆ„_þðo̲|¸ªÿ±Ú”w½Ð% ã“JÛ7’][Ë`JÀ3WjV%ò®HÂ+·¶ `܈ÞºÔ¯ëGW` ÐÙÙMÑ(³mvb›P™:¾+‡t—Ùú@ô^žÒeb 7 ½hvgýlJtö˜¤Ý¨D3Duår™îcƒÔR~"GHÛ?TB&ðS¹Ê{XX>TE4ÀŸuïý/øöHo_õŽPÚö¢=Êá%ę́Wñ¯èªÜ¶ŠgûYÀ}9¤T§Ís°cÊ`= *™ÁXˆ-l±KLaœ¡¿Ú•N›’™‚ê(šö1ôäG.Ô’–±ÕtÕ .Uü段L¯^9ÜáþMggäËN³&ø8æ6p´7lm럄cZ…&–C¹T-n¥FÆ0м¾*ÌÒoñuëíÄЭjÙˆéÏ›šÓX²&ñ|#¤Tä2y1,ܦAß³úò0›"¾Ît¬ÿh„V)¾ÒÔÐV¹[J;ßÕ˜¹p© ëãr×ÎîuN L;MPAmÆ$2ßlwŸÞ3yHÊ(’àCkRvFß䪺 ø;5Æb¿æÙјœºyÏ[hT(¦léÅÜ‚;¿{,üsÇcAÀ˜yÛ`jMœ†~v5w|ÌhÜTíÂv㟢ZÈÜÉ'jF±ÚSW‹ 2I}"©¦BÌVNnx8{‚òžYZ„ç ¿òµ• ÍX¤qÅPÀ¯—È€ꙡ³}GÁ*S¨ –¸ÃXÏ•ŽòÎ}îÃSfò$NO%×                                            šmoovlmvhdè @6trak\tkhd@ÈÈ$edtselst®mdia mdhd_UÄ-hdlrvideVideoHandlerYminfvmhd$dinfdref url stblµstsd¥avc1ÈÈHHÿÿ3avcCô4ÿágô4‘›(hß6@@¯ÈÆ e€hëãÄHDuuidkh@ò_$Oź9¥Ï#ósttsstscstszstcoA³trak\tkhd @$edtselstö€+mdia mdhd¬D²@UÄ-hdlrsounSoundHandlerÖminfsmhd$dinfdref url šstbljstsdZmp4a¬D6esds€€€%€€€@ôó€€€V倀€ stts,@(stsc,Èstsz-stco,V=udta5meta!hdlrmdirappl Úilst©namdataTitle©ARTdataArtist©albdataAlbum©gendataRock trkndata diskdata"©daydata2015-10-07%©toodataLavf56.36.100 µcovr ­data ÿØÿàJFIFHHÿÛC   ")$+*($''-2@7-0=0''8L9=CEHIH+6OUNFT@GHEÿÛC !!E.'.EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEÿÂÈÈÿÄÿÄÿÚ ·*„iÞ[Žsy Øb ÌÎcq˜œŽ qÌo"‰ÓY 1\I8.¦e8´sqZÊÉ0vÆ8ˆ´“ ó8Ÿ+fD$@k8‰†&`¬•CÒDQÎG—¤ó‚púk6ŬÜŒ “„)2LHàvç0:Èãa¼ÚFÆàÿÄ&04@p"5PÿÚý£¥aÞ¹®1 &àë4ÌDPâ$ÞõP£/W(ÇÕm[ÇRÀbkh´{iGæÒð×nÞG'%™µäÙå8E(·vÙ6´…=“Ã1V>o¥#ÜIc9Ï[EÑ\ÐÓ5ÔÄqF¦‚[£‡q¹sˆS X@15üÇøÚÞ÷Z•Xù¾•kãþë/u¦™.+Lå[EÜp]¾tvK†uÏ«ËrÜäÒ ‰ˆ&™š lBŽRl…a¶¡G‡åÇÿÄ*!1AQaq‘±Áð@¡ 0pÑáPÿÚ?!üœeÊ#AIUZ^M-ƒÇÈIÂhÝQ3fÊ„qš’‘-,MRÑ1Á¹š˜¢qÖ§Χˆ¢`Y1ÒxÀ,ó¡Õᘄ§kZZ #w"ÝᇌÇèL8ÜL÷1 $ Ȉ„›H&Oºðë¸@/÷ @ØïsêHT9Â8Pv ´ß~»²P.Aö]âMŠÙüP'„?7f$•Ï`y[õPCÜB=T9 ñpù¿º ¾Eš2€2gxmŠÆ \åbñÐÛ¹¹uËÅ@E¨<è X9ÞÀ‚ ã ÎD80“H?¹Ù4úÿ%‹)–>cèÈ™‚É $}·Qìz@ ª³cç9‹`ÚerÈ— 3µ_Üb‰ÂÂx°×H4è]B½$ Z&€ ¤y8-æÑ³¯/šðC\«4b3¡%:`UD23/gèj‡P°—“§À¨ï<Ýøg™ªj=ª;Å ®_È pB>†ì3L:@þ…óàzõtº¸Áa¾t˜ {8CÚUñ¨ðf‘‡ñÃÿÚ $$€@ $@I H  € H€$ÿÄÿÚ?ÿÄÿÚ?ÿÄ*!1AQaq‘Áð0@¡ p±ÑñPÿÚ?ýÎ+VJŸÉ»ýQ„® )Þ(£u…ÀÔÝ‚cÓ‘«t™¥ä Õt(+P¤ kHN@KËj— `ÄÃl4ÐsgËDðtîR€«­qà]ñ4=ÃO¨f?P.÷=IºÂÛ‹ˆ‡™â¼ìÌDäѤDëO!‘sig„–4Ži¼B3V­ðÍgÙÃKáE˜kø +›ÙSnhŒÜ‘Ñr- öhÁ,91P—ú$Ì#úRÛA Kkt¨UqØU‹ò)ÖitI(™²{&t©)»0:ÓVØõ†|k^3>¦m8§¥³+¢|iIF]øoIÁŽÛÜz+¹S'aÏi£@òˆÖ!Ó5|j 3«^åÍldöO³%8t8WWm ÈJù©- S ¸ü+?;Jφ”í$¥rÛ¯¶+t`fÒLŠf¡]ì¡k¥6“½=õ r% @™ž’ãV‚ß……nÚ´Å5(‰?ÅkŠhRÿ¯ù&ɶ‹Ò‹•ìD·èRw”€¨Kš`ÊÖ石X²nXï* ÷‰è)]¶'äu;Va$ ŒLÉæ‡¶Cî­U(ÌXíG¨¢ø!¨½Õlˆ ™~ÊÓ/Bt&SÍìØˆ¾/(±;ؘ}h¨¬ Xc¥>¡ ËJÃЇˆ+W ÈJ­Õr¿c AgWÜJÜK"ÀD™»= ùõ³ˆ;S‘ƒìV Økª6TŠE6T¦>äˆÅãˆ{ýŒôÅŽäÅÐë@\±¸ÂbéÝô#“±’4€Ü †gp𼄪ÝW+@€(„pÒÐÛ)áÀ~((Û1@»»¼þÜÿÙ$aARTdataAlbum Artist.freemediascanner2-0.111+16.04.20160317/test/media/testvideo_1080p.ogv0000644000015600001650000003153612672421600024214 0ustar pbuserpbgroup00000000000000OggSà)^KiXÐ*€theoraxD€8 (ÀOggSà)^xî :ÿÿÿÿÿÿÿÿÿÿ?theora+Xiph.Org libtheora 1.1 20090822 (Thusnelda)‚theora¾Í(÷¹Íkµ©IJsœæ1ŒR”¤!1Œb„!@ôád.UI´võp´˜k•¢©@–H¡ÐGÓ™¸Öd0K%R‘,”F"ƒ‘ÀÐd1‹b0”F!ƒ¡ÀÈ`, „‚ ðh0û™Ö•UTÒÑ‘PO ÍÌŒ‹KKJ ÈÈLJ‡‡†FFFE‚AAAAAA@!°¡‚ƒ3ÐÀÀá1££ÃpàÑ‚ƒ”S€áaÓ5uá!bS¤FÖtÑ‚3t‡Ãåvw—†T…Åö'Fv1!‚ö6661!Q¤&6661£†66662ô&66666666666666666666666666666666666661AÂAƒAƒAƒƒƒƒƒâü·Ð 3¾Bàꉽ¹f"ÝGà?r‰"^àØÉ}j©fWbìBä`öަKç0ÚÕ†ÆU)^}»‡äÄ4Ì®ÅØ‚È£ÃU¨62^?9w1+£Ú<“„ûª¤ ÅsTûª¤WcAÊiAíuÜàek1·ÎÅx^ yÛþŽÓhó©"cuk3)¨5ß –£û@½ xØæ]‘ w(=£Î¹M¶»Ø+‚•F_Å¢Ö9Ü"ͯ÷´†bÊЧ%r({·IzªJ„yœ'JÖlÅ®ÀÒ@äñCݺïÿ²cylƒÙàZuÇ+1‡ÀeG#F¢idÚÿêRÜìŽ,>“‚idÚ<ðõt +-1äÊÿêRÜ„Ö^vj"ÿì|¾„ÜÂv}-°V¸K&ºb€ó'wª¤@kõî^§KµçY°‰j…•†Ÿâ#¿8UIJèβbQ¯ÂÊÅääÒí#ÎÜ9ˆC¸÷6ô¶¢”W8bõ¬¢ÊÇ’/í»›j>ÌFaПý2[ E(Â,¬Ýyö¤ó?mB"\Øt-2{§Ì|H纊©V/”>÷ÀmDÿëÓ<#H2áŠnmé¼0õ¬Ô@u ¾]P§­Ï9;DzJ¢i#üÓ# ó çW`CùÓ¸¶Åsè54´8rŒ&•íy®Fæ \Ã{IÊìcpíûƒ¸3Ú%“ΪS™“è°Ü›{,‘5†öó•â³0rê=2\ÇÅT­a}{ òn5íNaV¹€ð•±DÞÉ…¬ßðª‘Kâ7þNã4 înÀ5&6£Ùd‰§E¬œÂc}BªF\g þ7Âb†Úû.Ø®Ò Ëç <‹„)#w…¡ûžV±Äã—Q!¹¸f (m¯³K•ÿ \N1fÑ2BivÖã›Îýð}jÊÁD8¸Ë¨*R´Òí&´spÎàZ~zŽ.¬+<ÂvæXð)(ÓK¬šB¿£ƒÜ¼Zaß#똳ò÷7¨,®¥;„÷‚fÚˤ™Bà¿ßBŒðíoÇ&1ª“‘&L1òw±Z:ù ¹ÄeÛY´—;×~²±U$+^Ϲ‡Ñ~²³¢ù;r0Ä6ÖlåQ”Bª[€Í£mrÆ[¾„ÃÂö &üEb´-{:>D)p=µdÓ:0¿» ЫZžâ4]%Mÿ܃‡šîžó’&ÔC:Å”ªÈ¹¡Ó°šY=óì-ˆ|áæ[Ÿ£×™Å•®4tˆK´žÑMFpZ-_óÛΊ [M¯¾ap#‹¾r¬²¥,Yº„>e…*ÝC¹ ë,´¼ S6ÖM#Ï“ÇhžÑ8}u*ŽY‰S6ÖOcx;D£?°ám…p71†qçü©¨Z†qÙí1¤Íµ–L_Ybï pEãr×r‚¢2j™¦rÐÇÝEYbžÉ{‹FèV‹Ô šfm®,&Ÿ´ ì)¨EñÔœÉ}º7œ ©_Âs yZÑßèhƒmR&—Hòô²²ï7îyÂ߀‹(n€åxH&•¶ºgݱ ²²w½T)|1$j•¤ÙåD-Ü:ô?0þ+°OÊÕÝ ¨oR˜µÑp[ÓïD%ä†fÚ¥i6LpÿÔê#YXÞ¥(xú/`ÅÛTÍ3—ÿ`§]AoÕ¼X‹±å*œ¥`6ÔÊf™ãr{D(¹€zùGë+#wÖãÊ”‹~‘ϤVVF É™4Ͷ¸ÝÿQÓ¥£Äg!tÅæ ±4Ͷ´[슠)wb´¤ÿüBâ&þ G°šQÔì8?Ëá½Æ,ù¬°"$%m6±÷åëjR\eß&•¶ºIš*/ø?u놠ܶÅd<Î*¤#$%4™¶¹Ñ{–8ùð;eözãq=Ö+€ßú~`SPµw€fi3meË‘÷aZ½}¸Üèbd˜Ó6ÚÊ£—£åMDŸVXOÛÛ‹x?ЏžOœÄª’'¿ÚYXï¨Ü¶¬šg‹ —ËwƒqúuÀÓ6Ú˜PËæ’4'²ý U"'–Ö°ï® JBäëþ÷*¤²²!9ðÛU /\e‹Lý™å±ƒ¯x¥SëA@j•¤Òg<ßrîØ2ˆOåŠ÷ÔèG‰ùW`/Žï–¨0$´˜Û\òÆ&ï]þ)¨wó«`‘ÅÊÊÏ7!~^‘ƒ¶²“4Ï+‰èà,¬‹¨TÔ¾\^LÓ9FÚㄎVÑæïGó÷ÀFå#éýUHßÈJ;ˆâÅarUãLÛja3,· Þ/Akÿ»ãÂÊÁ*á–-36Õ3 磔#…U%(ÍÝõ#‹ Ü’…Ó0¿ñ¶¦X´Ïå gðöŠ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰äˆZ¦ †ç?“˜ šfm¬¹cÝ…{…¢¿ç½_§$Ú™LÓ9Xá}Õ)€xçáDVîXãÕgqÂ)¨‡ÒÞ| #P”4Ͷ¦2Æû·ûâÅdÿ:ˆ#Ë™›j™…÷Kú¹YZš„>@n‰"‹^¸uØôä¡iüÁ–-36Õs çït¼qa·I~BŠA¼dÄ@883ȶu÷&|k£ý¹Qߥ•¼•U#xÉ!ˆ€p: pg‘lëîLø×Gûr£¿K+y*ªFñ’CàtàÏ"Ù×Ü™ñ®öåG~–VòUTŠ©8OÔ‰x&i™¶²å#!ñebÞ_z.ìŽ÷±ÃR—®_Ð,®ÍÂä† FS4ÌÛYoŸƒ¿‘tóóå*–V¨‹Öð 8$/J4Ͷ¤Ì±ÂîÑ­è,¬åkéü#p“¶þ!:?JLËæ™¶Öê"SQIä@úz;qÏ¡]‘À7ŸŒ±i™3 ͵º^¤†¡ '«+¥R¼ù2û§Z5ÑþÆ83Ê^]¸QÔdQ’¢ëX^L¢s¾Ÿýb°m«ž ±ižþùªJ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰ä€OggSCà)^Êk’Ž,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ% ƒL½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ö¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}v¾÷Þûßx½÷¾÷Þûß{ï}o¼}5Cï5PûÇÃWïI¿xù*‡Þ>j¡÷¼}ãàªxùª‡Þ>¿xúMûÇÉT>ñóU¼}ãïPûÇÍT>ñðÕûÇÒoÞ>J¡÷š¨}ãïxø*‡Þ>j¡÷†¯Þ>“~ñòU¼|ÕCïxûÇÁT>ñóU¼|5~ñô›÷’¨}ãæªxûÇÞ> ¡÷š¨}ãá«÷¤ß¼|•Cï5PûÇÞ>ñðU¼|ÕCï _¼}&ýãäªxùª‡Þ>ñ÷‚¨}ãæªxøjýãé7ï%PûÇÍT>ñ÷¼|Cï5PûÇÃWïI¿xù*‡Þ>j¡÷¼}ãàªxùª‡Þ>¿xúMûÇÉT>ñóU¼}ãïPûÇÍT>ñðÕûÇÒoÞ>J¡÷š¨}ãïxø*‡Þ>j¡÷†¯Þ>“~ñòU¼|ÕCïxûÇÁT>ñóU¼|5~ñô›÷’¨}ãæªxûÇÞ> ¡÷š¨}ãá«÷¤ß¼|•Cï5PûÇÞ>ñðU¼|ÕCï _¼}&ýãäªxùª‡Þ>ñ÷‚¨}ãæªxøjýãé7ï%PûÇÍT>ñ÷¼|Cï5PûÇÃWïI¿xù*‡Þ>j¡÷¡÷¼Þ>J¡÷¼}ãÜß¼}ãïPûÇÞ>ñ÷¼{Õ¼}ãïæýãïxøê‡Þ>ñ÷¼}ãÞ¨}ãïx÷7ïxûÇÇT>ñ÷¼}ãïõCïxûǹ¿xûÇÞ>:¡÷¼}ãïx÷ªxûÇÞ=ÍûÇÞ>ññÕ¼}ãïxûǽPûÇÞ>ñîoÞ>ñ÷ލ}ãïxûÇÞ=ê‡Þ>ñ÷s~ñ÷¼|uCïxûÇÞ>ñïT>ñ÷¼{›÷¼}ããªxûÇÞ>ñ÷z¡÷¼}ãÜß¼}ãïPûÇÞ>ñ÷¼{Õ¼}ãïæýãïxøê‡Þ>ñ÷¼}ãÞ¨}ãïx÷7ïxûÇÇT>ñ÷¼}ãïõCïxûǹ¿xûÇÞ>:¡÷¼}ãïx÷ªxûÇÞ=ÍûÇÞ>ññÕ¼}ãïxûǽPûÇÞ>ñîoÞ>ñ÷ލ}ãïxûÇÞ=ê‡Þ>ñ÷s~ñ÷¼|uCïxûÇÞ>ñïT>ñ÷¼{›÷¼}ããªxûÇÞ>ñ÷z¡÷¼}ãÜß¼}ãïPûÇÎC@¡ PÐ(h4  †€C@¡ PÐ(h4  †€C@¡ PÐ(h4  †€C@¡ PÐ(h4  †€C@¡ QTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPU@UEEQTQTPUHÒ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª*€(  ¨€ª•Á+˜áÚce^¯ÅAPW„1Ãø^¯0×sAz¼À{eà+Áí£ –•âL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+ÁìtâL¦:☯Aê‚¡LW„2˜ëŠb¼ ³—stàöÁà+Áìtá„×V’ײZôÈ€LÈ4ói:57Ó"!2M¦ªfT¤ÈŸÉl"µdÈÚ‚”«@ÂhIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AIZÒZöKdÈ€LȦ@èDX0ȈLÔ©S2¥&@çd¶HE26… ¥*д™­%¯d¶LÈÌ€Jd„Eƒ ˆ„ÈJ•3*RdvKa„S shZ R­ AI—é@ˆkG“ ufÍ–L¦@ý¥&†©™R“ sú_þ—”ÈÚ€ˆÚµÔÈ@þ—é@ˆLÕ›6Ye2¥LÊ”™ŸÒÿô¿LÍ¡h¡h d KôÈ Ä@Œ&@êÍ›,²™ R¦eJLÏéú_¦@æÐ´Fд2?¥údb F ufÍ–YL‚©S2¥&@çô¿ý/Ó shZ#hZ™Òý21# :³fË,¦AT©™R“ sú_þ—é9´-´-L„é~™ˆ„ÈY³e–S *TÌ©I9ý/ÿKôÈÚ€ˆÚ€¦Bô¿L‚ŒDÂd¬Ù²Ë)@•*fT¤Èþ—ÿ¥údm @Dm @S!ú_¦AF"a2VlÙe”È J•3*RdKÿÒý26… "6… )ý/Ó €#0™«6l²Êd%J™•)2?¥ÿé~™›BÐBÐÈ@þ—é@ˆLÕ›6Ye2¥LÊ”™ŸÒÿô¿LÍ¡h¡h d KôÈ Ä@Œ&@êÍ›,²™ R¦eJLÏéú_¦@æÐ´Fд2?¥údb F ufÍ–YL‚©S2¥&@çô¿ý/Ó shZ#hZ™Òý21# :³fË,¦AT©™R“ sú_þ—é9´-´-L„é~™ˆ„ÈY³e–S *TÌ©I9ý/ÿKôÈÚ€ˆÚ€¦Bô¿L‚ŒDÂd¬Ù²Ë)@•*fT¤Èþ—ÿ¥údm @Dm @S!ú_¦AF"a2VlÙe”È J•3*RdKÿÒý26… "6… )ý/Ó €#0™«6l²Êd%J™•)2?¥ÿé~™›BÐBÐÈ ’&Ü„-¥o0F¢%@! ! 0@ ……ƒ˜ ‰PB€BÁƒÌD¨„!@!`Áæ"TB °`ó*!PX0y‚ˆ•„(„,<ÁDJ€BB `€"%@! ! 0@ ……ƒ˜ ‰PB€BÁƒÌD¨„!@!`Áæ"TB °`ó*!PX0y‚ˆ•„(„,<ÁDJ€BB `€"%@! ! 0@ ……ƒ˜ ‰PB€BÁƒÌD¨„!@!`Áæ"TB °`ó*!PX0y‚ˆ•„(„,<ÁDJ€BB `€"%@! ! 0@ ……ƒ˜ ‰PB€BÁƒÌD¨„!@!`Áæ"TB °`ó*!PX0y‚ˆ•„(„,<ÁDJ€BB `€"%@! ! 0@ … ‚ºjµNµZ£Ñèôz;¥ô~úß[êþ‡C¡Ðݯt½yA]5Z§Z­Qèôz=Òú¿}o­õC¡Ðèn‹×º^¼È ‚.š­S­V¨ôz=Žé}_¾·Öú¿¡Ðèt7EëÝ/^dAMV©Ö«Tz=Gt¾¯ß[ë}_Ðèt:¢õî—¯2 ‹¦«TëUª=G£º_Wï­õ¾¯èt: Ñz÷K×™EÓUªuªÕG£ÑÝ/«÷ÖúßWô:†è½{¥ëÌ‚"éªÕ:ÕjG£Ñèî—Õûë}o«ú‡Ct^½ÒõæAtÕjjµG£ÑèôwKêýõ¾·Õý‡C¡º/^ézó ‚ºjµNµZ£Ñèôz;¥õ~úß[êþ‡C¡Ðݯt½yA]5Z§Z­Qèôz=Òú¿}o­õC¡Ðèn‹×º^¼È ‚.š­S­V¨ôz=Žé}_¾·Öú¿¡Ðèt7EëÝ/^dAMV©Ö«Tz=Gt¾¯ß[ë}_Ðèt:¢õî—¯2 ‹¦«TëUª=G£º_Wï­õ¾¯èt: Ñz÷K×™EÓUªuªÕG£ÑÝ/«÷ÖúßWô:†è½{¥ëÌ‚"éªÕ:ÕjG£Ñèî—Õûë}o«ú‡Ct^½ÒõæAtÕjjµG£ÑèôwKêýõ¾·Õý‡C¡º/^ézó ‚ºjµNµZ£Ñèôz;¥õ~úß[êþ‡C¡Ðݯt½}×C¡u¡Ðù~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JTÝt:Z ß§ïÕú¿O÷w]©t¥M×C¡u¡Ðú~ý_«ôÿwuÑJ—JT˜@#•F€…  @#ñøüÑ£F B…  @‘øü~hÑ£P¡B… Hü~?4hѨP¡B„$~?€š4hÔ(P¡B?ÀM4j(P¡ Çà&5 (PÇãðF€ (@Gãñø £F@…  @#ñøüÑ£F B…  @‘øü~hÑ£P¡B… Hü~?4hѨP¡B„$~?€š4hÔ(P¡B?ÀM4j(P¡ Çà&5 (PÇãðF€ (@Gãñø £F@…  @#ñøüÑ£F B…  @‘øü~hÑ£P¡B… Hü~?4hѨP¡B„$~?€š4hÔ(P¡B?ÀM4j(P¡ Çà&5 (PÇãðF€ (@Gãñø £F@…  @#ñøüÑ£F B…  @‘øü~hÑ£P¡B… Hü~?4hѨP¡B„$~?€š4hÔ(P¡B?ÀM4j(P¡ Çà&5 (PÇãðF€ (@Gãñø £F@…  @#ñøüÑ£F B… ‰$H‘"܉-û‘"E½$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$WMÈ‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰Ÿ0ù‡0áÃæ8|ǘpáó>aÇÌ8pù‡0áÃæ8|ǘpáó>aÇÌ8pù‡0áÃæ8|ǘpáó>aÇÌ8pù‡0áÃæ8|ǘpáó>aÇÌ8pù‡0ÐBe–P„*b” í¶Ò"ÁB,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò B,²Ë„*R”¶Ûm ‚„"Ë,±B¥)Km¶Ò ‚"ÊR”¶ÛFAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÚAJR–Ûm¤A¥)m¶ÛCdPd                                                                 €ÌÌ»´’Y™œ…ww™™wv’Y™Ë»¼ÌË»´’ÌÌn]Ýæf]ݤ–fcrîï32îí$³3—wy™—wi%™˜Ü»»ÌÌ»»I,ÌÆåÝÞfeÝÚIff7.îó3.îÒK31¹ww™™wv’Y™Ë»¼ÌË»´’ÌÌn]Ýæf]ݤ–fcrîï32îí$³3—wy™—wi%™˜Ü»»ÌÌ»»I,ÌÆåÝÞfeÝÚIff7.îó3.îÒK31¹ww™‡)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffffR”¦fffe)JffçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú…óçϨ_>|ú&&&&**(ÆÆÆ¥¢bbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbbbbbb¢¢¢£cccccccbbbbc££”dllkr::::6665¹ÜŽŽŽŽnGGGGFÆÆÆ·#££££ccc[‘ÑÑÑѱ±±­ÈèèèèØØØÖättttlllkr::::6665¹ÜŽŽŽŽnGGGGFÆÆÆ·#££££ccc[‘ÑÑÑѱ±±­ÈèèèèØØØÖättttlllke€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€†’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I$’I&$¶=c4’[DZìe‚Il{DZ– %±ì{ÆX$–DZì{`’[DZìe‚Il{DZ– %±ì{ÆX$–DZì{`’[DZìe‚Il{DZ– %±ì{ÆX$–DZì{`’[DZìe‚Il{DZ– %±ì{ÆX$–DZì{Jºèú ~ h hèú ~ h hèú QTv‡a1°ì;ÂcaØv„ÆÃ°ì; ‡aØvðì&6‡aØLl;ð˜Øv‡a1°ì;ÂcaØv„ÆÃ°ì; ‡aØvðì&6‡aØLl;ð˜Øv‡aÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv§iÚv¦¹òI$’I$’I$’I$’I$’I$’I$’I$’B´2@OggSJà)^wy*tOggSKà)^§v?Rmediascanner2-0.111+16.04.20160317/test/media/testvideo_480p.ogv0000644000015600001650000001524512672421600024136 0ustar pbuserpbgroup00000000000000OggS EåSV*€theora6Và (ÀOggS E²Aà :ÿÿÿÿÿÿÿÿÿÿ?theora+Xiph.Org libtheora 1.1 20090822 (Thusnelda)‚theora¾Í(÷¹Íkµ©IJsœæ1ŒR”¤!1Œb„!@ôád.UI´võp´˜k•¢©@–H¡ÐGÓ™¸Öd0K%R‘,”F"ƒ‘ÀÐd1‹b0”F!ƒ¡ÀÈ`, „‚ ðh0û™Ö•UTÒÑ‘PO ÍÌŒ‹KKJ ÈÈLJ‡‡†FFFE‚AAAAAA@!°¡‚ƒ3ÐÀÀá1££ÃpàÑ‚ƒ”S€áaÓ5uá!bS¤FÖtÑ‚3t‡Ãåvw—†T…Åö'Fv1!‚ö6661!Q¤&6661£†66662ô&66666666666666666666666666666666666661AÂAƒAƒAƒƒƒƒƒâü·Ð 3¾Bàꉽ¹f"ÝGà?r‰"^àØÉ}j©fWbìBä`öަKç0ÚÕ†ÆU)^}»‡äÄ4Ì®ÅØ‚È£ÃU¨62^?9w1+£Ú<“„ûª¤ ÅsTûª¤WcAÊiAíuÜàek1·ÎÅx^ yÛþŽÓhó©"cuk3)¨5ß –£û@½ xØæ]‘ w(=£Î¹M¶»Ø+‚•F_Å¢Ö9Ü"ͯ÷´†bÊЧ%r({·IzªJ„yœ'JÖlÅ®ÀÒ@äñCݺïÿ²cylƒÙàZuÇ+1‡ÀeG#F¢idÚÿêRÜìŽ,>“‚idÚ<ðõt +-1äÊÿêRÜ„Ö^vj"ÿì|¾„ÜÂv}-°V¸K&ºb€ó'wª¤@kõî^§KµçY°‰j…•†Ÿâ#¿8UIJèβbQ¯ÂÊÅääÒí#ÎÜ9ˆC¸÷6ô¶¢”W8bõ¬¢ÊÇ’/í»›j>ÌFaПý2[ E(Â,¬Ýyö¤ó?mB"\Øt-2{§Ì|H纊©V/”>÷ÀmDÿëÓ<#H2áŠnmé¼0õ¬Ô@u ¾]P§­Ï9;DzJ¢i#üÓ# ó çW`CùÓ¸¶Åsè54´8rŒ&•íy®Fæ \Ã{IÊìcpíûƒ¸3Ú%“ΪS™“è°Ü›{,‘5†öó•â³0rê=2\ÇÅT­a}{ òn5íNaV¹€ð•±DÞÉ…¬ßðª‘Kâ7þNã4 înÀ5&6£Ùd‰§E¬œÂc}BªF\g þ7Âb†Úû.Ø®Ò Ëç <‹„)#w…¡ûžV±Äã—Q!¹¸f (m¯³K•ÿ \N1fÑ2BivÖã›Îýð}jÊÁD8¸Ë¨*R´Òí&´spÎàZ~zŽ.¬+<ÂvæXð)(ÓK¬šB¿£ƒÜ¼Zaß#똳ò÷7¨,®¥;„÷‚fÚˤ™Bà¿ßBŒðíoÇ&1ª“‘&L1òw±Z:ù ¹ÄeÛY´—;×~²±U$+^Ϲ‡Ñ~²³¢ù;r0Ä6ÖlåQ”Bª[€Í£mrÆ[¾„ÃÂö &üEb´-{:>D)p=µdÓ:0¿» ЫZžâ4]%Mÿ܃‡šîžó’&ÔC:Å”ªÈ¹¡Ó°šY=óì-ˆ|áæ[Ÿ£×™Å•®4tˆK´žÑMFpZ-_óÛΊ [M¯¾ap#‹¾r¬²¥,Yº„>e…*ÝC¹ ë,´¼ S6ÖM#Ï“ÇhžÑ8}u*ŽY‰S6ÖOcx;D£?°ám…p71†qçü©¨Z†qÙí1¤Íµ–L_Ybï pEãr×r‚¢2j™¦rÐÇÝEYbžÉ{‹FèV‹Ô šfm®,&Ÿ´ ì)¨EñÔœÉ}º7œ ©_Âs yZÑßèhƒmR&—Hòô²²ï7îyÂ߀‹(n€åxH&•¶ºgݱ ²²w½T)|1$j•¤ÙåD-Ü:ô?0þ+°OÊÕÝ ¨oR˜µÑp[ÓïD%ä†fÚ¥i6LpÿÔê#YXÞ¥(xú/`ÅÛTÍ3—ÿ`§]AoÕ¼X‹±å*œ¥`6ÔÊf™ãr{D(¹€zùGë+#wÖãÊ”‹~‘ϤVVF É™4Ͷ¸ÝÿQÓ¥£Äg!tÅæ ±4Ͷ´[슠)wb´¤ÿüBâ&þ G°šQÔì8?Ëá½Æ,ù¬°"$%m6±÷åëjR\eß&•¶ºIš*/ø?u놠ܶÅd<Î*¤#$%4™¶¹Ñ{–8ùð;eözãq=Ö+€ßú~`SPµw€fi3meË‘÷aZ½}¸Üèbd˜Ó6ÚÊ£—£åMDŸVXOÛÛ‹x?ЏžOœÄª’'¿ÚYXï¨Ü¶¬šg‹ —ËwƒqúuÀÓ6Ú˜PËæ’4'²ý U"'–Ö°ï® JBäëþ÷*¤²²!9ðÛU /\e‹Lý™å±ƒ¯x¥SëA@j•¤Òg<ßrîØ2ˆOåŠ÷ÔèG‰ùW`/Žï–¨0$´˜Û\òÆ&ï]þ)¨wó«`‘ÅÊÊÏ7!~^‘ƒ¶²“4Ï+‰èà,¬‹¨TÔ¾\^LÓ9FÚㄎVÑæïGó÷ÀFå#éýUHßÈJ;ˆâÅarUãLÛja3,· Þ/Akÿ»ãÂÊÁ*á–-36Õ3 磔#…U%(ÍÝõ#‹ Ü’…Ó0¿ñ¶¦X´Ïå gðöŠ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰äˆZ¦ †ç?“˜ šfm¬¹cÝ…{…¢¿ç½_§$Ú™LÓ9Xá}Õ)€xçáDVîXãÕgqÂ)¨‡ÒÞ| #P”4Ͷ¦2Æû·ûâÅdÿ:ˆ#Ë™›j™…÷Kú¹YZš„>@n‰"‹^¸uØôä¡iüÁ–-36Õs çït¼qa·I~BŠA¼dÄ@883ȶu÷&|k£ý¹Qߥ•¼•U#xÉ!ˆ€p: pg‘lëîLø×Gûr£¿K+y*ªFñ’CàtàÏ"Ù×Ü™ñ®öåG~–VòUTŠ©8OÔ‰x&i™¶²å#!ñebÞ_z.ìŽ÷±ÃR—®_Ð,®ÍÂä† FS4ÌÛYoŸƒ¿‘tóóå*–V¨‹Öð 8$/J4Ͷ¤Ì±ÂîÑ­è,¬åkéü#p“¶þ!:?JLËæ™¶Öê"SQIä@úz;qÏ¡]‘À7ŸŒ±i™3 ͵º^¤†¡ '«+¥R¼ù2û§Z5ÑþÆ83Ê^]¸QÔdQ’¢ëX^L¢s¾Ÿýb°m«ž ±ižþùªJ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰ä€OggSE EÎÚK’ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿH? ƒL½÷¾÷Þûß{ï}ï¼Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷ÞûÀ}ï½÷¾÷Þûßx½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾ð{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷€ûß{ï}ï½÷¾ð{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}à>÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷ÞûÚùªüõ~Z¡ñˆ>j¡éT>ñðUùêüµCã|ÕCÒ¨}ãà«óÕùj‡Æ ùª‡¥PûÇÁWç«òÕŒAóUJ¡÷‚¯ÏW媃檕Cï_ž¯ËT>1ÍT=*‡Þ> ¿=_–¨|bš¨zU¼a|KåOçNiêûÇÅWï PûÇ…PûÇÞ=*ýãáªxðªxûÇ¥_¼|5CïCïxô«÷†¨}ã¨}ãï•~ñðÕ¼xU¼}ãÒ¯Þ>¡÷ ¡÷¼zUûÇÃT>ñáT>ññê§ËO@€4h4 A  (ª ª¢Š*‚ª¨¢Š ªª(¢¨*ªŠ(ª ª¢Š*‚ª¨¢Š ªª(« ¢¨(*Š  ª*‚‚¨ª ¢¨(*Š  ª*€ª–ô`AÈfÜ@B„48À, AC d„´a€!šÛmÊÛä4?–Ñ€à@B€€À@ƒF9f€Ì0ŶÛhÀVß-òÚ0  ƒà#@È`À,І8¶ÛmÊÛå¾[Ft|` Aåš0ÀÛm£[|·ËhÀ€‚€Œp €  Œr͘`‹m¶Ñ€ ­¾[å´`@AÎ p !@@` A€#³@fâÛm´`+o–ùmÐAð€  d0`–h Ã[m¶Œemòß-£:>0À€„€  Œr͘`‹m¶Ñ€ ­¾[å´`@AÀF8€ 0 À€Y 3 qm¶Ú0•·Ë|¶Œè øÀÀÈ`À,І8¶ÛmÊÛå¾[Ft|àÀ2‚0Ë4a€-¶ÛF2¶ùo–Ñ€à@B€€À@ƒF9f€Ì0ŶÛhÀVß-òÚ0  ƒà#@È`À,І8¶ÛmÊÛå¾[Ft|` Aåš0ÀÛm£[|·ËhÀ€‚€Œp €  Œr͘`‹m¶Ñ€ ­¾[å´`Hˆcé©°Dmˆ M àÉ™¶¡h-k´/k¶È™ h­Ù ^Ùmvû`¼:_©I)4$ÈÄC X"6IJfm…hZ Zí AvÈ™ ¶B×¶Zõ°Kõ)%)2qȱ,™›aZ‚Ö»BÐ]²&@d-µí–½lÒýJIJLœD2‚#l@`K&fØV… µ®Ð´l‰™ d-{e¯[ô¿R’R“ g `ˆÛÉ™¶¡h-k´-Û"d&BÙ ^ÙkÖÁý/Ô¤”¤ÈÄC X"6IJfm…hZ Zí AvÈ™ ¶B×¶Zõ°Kõ)%)2qȱ,™›aZ‚Ö»BÐ]²&@d-µí–½lÒýJIJL,2ƒn,›p»BÐ]¼ ·™kÖçö)I'ÿ²TÏý±µ±®ždD[#Ýl€)@Óe²?ÿû½°Od¶ZÒ[]LŸýŸ³ÿlE­l\2" d¥(VÈÿÿÿ¶Élµ¤µ¦@ÏþÏÙÿ¶"Ö¶. 2R”«dÿÿÛd¶ZÒZÓ gÿgìÿÛk[ ˆ„È)JU²?ÿÿ탲[-i-i3ÿ³ö툵­‹†DBd €¥*ÙÿÿöÁÙ-–´–´ÈÿÙû?öÄZÖÅÃ"!2@ R€lÿÿû`ì–ËZKZd ÿìýŸûb-kbá‘™ )@ ¶Gÿÿý°vKe­%­2½Ÿ³Û…±pȶB€o?þÜìÅ®ß*a¬²Ë8Ü`oe–Xae–XañŒoe–Xae–XañŒoe–`ÂË,°ÃãÞË,°ÂË,0ÆøÆ7²Ë,0²Ë,0Æã{,²Ã ,²Ã oŒc{,²Ã ,²Ã oŒc{,³Ye†ßÆöYe†Ya†7Æ1½–Ya…–Ya†7Ùe–Ye–c|cÙe–Ye–c|cÙe˜0²Ë,0ÆøÆ7²Ë,0²Ë 1¾1ì²Ë 4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z=Žé¬Ök5—BtA|µ.¯–¤È!ѳY¬Ö]4z7õšÇDýòÔ¾A¬Ö?tÑh´Z+µù¿.¯Íù»ñè´Z-Ò""é¢Ñh´WKó~]_›ówãÑh´Z+¤DEÓE¢Ñh®—æüº¿7æïÇ¢Ñh´WHˆ‹¦‹E¢Ñ]/Íùu~oÍßE¢Ñh®‘M‹E¢º_›òêüß›¿‹E¢Ñ]"".š-‹Et¿7åÕù¿7~=‹E¢ºDD]4Z-Šé~oË«ó~nüz-‹EtˆE¢u~oË®=‰ôH‘"¿¿¿¿D‰$[l¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰ËûûûôH‘"E±ËûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘l¿¿¿¿D‰$[l¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰+ûûûôH‘"D‰+ûûûôH‘"¿¿¿¿D‰$H‘"¿¿¿¿D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘"D‰$hÑ£D‰$H‘"D‰$H‘£D‰+¢$H‘#F¢D‰Ñ$H‘£FŒÑ"DŠè‰$HÑ£Fh‘"EtD‰$hÑ£4H‘"º"D‰4hÑš$H‘]"D‰4hÍ$H®ˆ‘"D4f‰$O¢D¢Eîïîïîïîïîïîñîñîñîñîñîïîïîïîïîï1Œk,X1Œb„¡B…ƱbŃÆ!J(P1Œk,X1Œb„¡B…ƱbŃÆ!J(P1Œk,X1Œb„¡B…ƱbŃÆ!J(P1Œk,X1Œb„¡B…Ń„¡A$’Ie–Y$’Ie–Y$’Ie–Y$’Ie–Y$’Ie–Y$’Ie–Y$’Ie–Y$–ZI$’¦HU¤•ÝÞfe2™™™wv’Wwy™”ÊffeÝÚI]ÝæfS)™™—wi%ww™™L¦ff]ݤ•ÝÞfe2™™™wv’Wwy™”ÊffeÝÚI]ÝæfS)™™—wi]æS3.ó3! 3330B³333! 3330B³333! 3330B³333! 330B̦H@ÄÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÄÄÅEEEFÆÆÆÇGGGDÄÄÄÅEEEDÄÅEFÆÇGDÄÅEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGl{DZ¤’Il{DZ¤’Il{DZ¤’Il{DZ¤’Il{DZ¤’Il{DZ¤’Il{DZ¤’Il{Il;ð˜Ø¶-‹b1´í;NÓ°ì;Âcbض-ˆÆÓ´í;Nðì; ‹bض#NÓ´í;ðì&6-‹bØŒm;NÓ´ì;ð˜Ø¶-‹b1´í;NÓ°ì;Âcbض-ˆÆÓ´í;Nðì; ‹bض#NÓ´í;Ãý‹bþÓ´í;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;NÓ´ì[űl;ðí;Nűl; ’I$’I$’I$’I$’I$’I$’I$’6Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ÙZ@J?§›Íæóy¼ÞoÁQù%º)ÙNÊvS²”ì§ÁP( @ P( C 4hÑ£F4hÑ£YðOggSK EØü<Œmediascanner2-0.111+16.04.20160317/test/media/testfile.ogg0000644000015600001650000003474712672421600023165 0ustar pbuserpbgroup00000000000000OggSÎFÍØ-µvorbisD¬w¸OggSÎFºIHÅÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉvorbis-Xiph.Org libVorbis I 20101101 (Schaufenugget)GENRE=Progressive Rock TRACKNUMBER=1 ALBUM=album1 TITLE=track1 DATE=2013COMMENTS=This is a commentARTIST=artist1vorbis)BCV1L Å€ÐU`$)“fI)¥”¡(y˜”HI)¥”Å0‰˜”‰ÅcŒ1ÆcŒ1ÆcŒ 4d€( Ž£æIjÎ9g'Žr 9iN8§ ŠQà9 Âõ&cn¦´¦knÎ)% Y@H!…RH!…bˆ!†bˆ!‡rÈ!§œr *¨ ‚ 2È ƒL2餓N:騣Ž:ê(´ÐB -´ÒJL1ÕVc®½]|sÎ9çœsÎ9çœsÎ BCV BdB!…Rˆ)¦˜r 2È€ÐU €G‘I±˱ÍÑ$Oò,Q5Ñ3ESTMUUUUu]Wve×vu×v}Y˜…[¸}Y¸…[Ø…]÷…a†a†a†aø}ß÷}ß÷} 4d  #9–ã)¢"¢â9¢„†¬d ’")’£I¦fj®i›¶h«¶m˲,˲ „†¬ iš¦iš¦iš¦iš¦iš¦iš¦išfY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY@hÈ*@@ÇqÇq$ER$Çr, YÈ@R,År4Gs4Çs<Çs=:<;<=:H14&,H;<<767:<=L´.H=;> Jñ¯F„ÿ«!*® ¤€Yi¬gf-?º%#EQEccdzíGã Ôà ±ßF\ƒ›\2/ojدétê`©ÿ‹~â-oÃÛ®¥T†ùWã*Ü9P‘ÍmE’ìýtÀ.g,ª­ƒÏ4‘om"úŽñ"‚ <òÁ-áG†Œö¡‡ávw<$ðO«M¶”þ½—æîb9T|d«™™3ANÁã`V§p®:âe"îîîsèî > N‹ ê¹\é+ŽÃG“~5ÐÏzP€×ZX>׿ïÏõ¼þÿÅìXOê > N÷‹ ê¹]5ÒW\&ýj Ÿ ô×ÊX_jí`ÙϾ>õ¥t?Ñ~çÖ{-7> N‹ ê¹l#é+Žë£I¿ôà.Õ°~M–ƒ“¸È \é¶ V X‡†ü> N‹ ê¹\ÒW,¯hÒ/@?ëwj°~ «Î=Žb×þ]2™Ì?ž> N‹ ê¹\Òw(Ž£Š&ýªPe°NvX¹¼ÿõs{®÷Yl3FmKT> N‹ ê¹\Òw(®àŠ&ýªPe°žUX °òòñrÖ‹<æ™x3Fwá> N÷‹`ê¹]ô•A£I¿ €J:€w•%€õ¡ÆD‡ÁÙ'<cÅ–> NÏ‹`ê¹\é+ŽC£Â2* èo^#¬›Wx‰Ãø‰rÔ½€`Jo\× Žà"R€ˆîÏHøÙäòBÛ]Ÿß¹3ªëÒ€¥‹tÎïjìï@ßV?%–ßv[lèꪪXEQeî£5’MMM…Ý}D†ÉW®>¿–ra îm0¿ý˜ë^D†‘ß¹úì[šŠ î~S€uÆÁwvfèÃT†ñÏ\yömäÀ×W,—þ1¬¢É<†ÁÏÆ5ø6r` îÊ`¼<`I-L†ÙWã \K30Æ-W6@ûó/E+×$†éŸkðmä†1nɲà¿úæM5^$†©ï\ÿlí&a8ñ´€kð/O)'ŒŸQñ×ÅåúÛÕJow]\‘5&-\^u÷„».Xõmn#î<4yøËWŒé%:sl¼ë/yª#Öð!pû—dåþ‡<ÎõQç ¼½ïžžÕÝ §×,nàé÷Å«}#VÛÿ¾¹ÆŠÎÄ™·ÔmÍjyKŸeê_û¬$nÍWã¯AÛLqÚR$aÿÕW^Jêc¯%V9Îz â ÚŽè%¢cží)ãÉæùÍtžçyFQ Ðévê'ÿ#ý^ ÝÍ—¿¿¿èÞᬮ”R]v¦Á¹ÅÅ¿ÿ÷W¿> .—_°R/c$=E)c¤_ Ð?] ð´`µÝ~Ÿ>ÔÖ{rµ;Øgú9> ®§PF/ãBzŠÚ˜~ÐàåËî{·ŒKwúÆכžI¾•.´> .—_°R/c$=E©c¤_ пSàfWË~öõ©¯K/ë¹€£z7> .Ï—Ÿ©—VIO1Ž‘~ Àp%Ç}9ˆŒ"2ˆÌqȹ¦ ™;Ý > .—p©—M%=ÅÀ:FúUÖßÔP›´Ú¾xkuùá‹'ûjµš> .—0R/cGzв‘~U€u™V5r×óç÷õ\ßî;¡ØP9y¥> .÷—ß©—M$=Å#ýªë[U€‡áÈ•—·_—^š÷:—6H&> .—Ÿ©—1“ž¢Ô1Ò/Ücy^ëÈ:Š; Â`îMEµó 4>Þ Žð%RpaÎÇJyïrù6æu}çΨ®kôy ° Âðžv¯ýôÓó=ÐÓ÷#ëÞ{ï§ßr4b¯^½¤¿ÿ $†ÁGþðì,Å cpçV€xgÐ[—†IDŠÙG®=?[?P™Áý5pþûëŸAC64ŠñGþðìlý@ew·•@öÁ©·z&T†åg®>?ƒ{Œ‡¾~zèÃD†Ég®=»6ºP•Á]o·\‡†TŠùG®>;_¨Ìàþjëü7gÃú°D†ÉG®=»6z 2ƒû[ ¬ÞMÖ¡‡#D†í[ãzÔ7ba îÊj£ý÷–aŸê×lb©žØ–A…±YºUܱÙ[n‹±yÏ?˜½·ëò·¯üÄTxbWþZ‹Ÿ‰ÓTá‰õÙ­Tý ‡ @Þ4àÎÍÙ܈9[ýVuE)¼åüCVûŸ½wu]¯Coé=d¦sî=  ¤½ßv»¸GåâæÕi`uæ?’äØúéSÿ"ÀñåÔA/4þsÎ>câî>zI|bßFk· cßÃ1XgJ\”yŸ.—Èu±Ôy÷ï+*–ÏÏúŽöMrhôû[$ü¥ŽãìüæÍ<Ïìü§sð«³KP  ®·ÈÍ‹¿ç|aÁS6Ûÿ4¬€¬ 4¹`væ"> Nëå·UÏgHßã‹?Ú~À›ú™ „eX†Ö.ê<ÕÚç´€åqç> Nûåw©ž÷6’¾_4ƒŠö ¸Ÿ þòØÏjZ+JW¬èÚe µ€eÃÝ> Nûåw©ž÷6’¾_4ƒŠö ¸Ÿ ~xœ€[5 Ʒ˶às'- Ví=> Nûåw©ž÷&¾'/Ó¢íü<p? „eXR7tÀa fk@Š6µ> Nûåw¥ž÷h¤ïdj´_@ÜÏ€õýñ ÊT0!UJsu‘vM¤pºÍ > Nëåw©žÏ&¾_$Ó¢ý¸Ÿ >?MÀ\.€oû_n:”C Eum·> Nëåw¡žÏ&¾_$Ó¢ý¸Ÿ ž]p¿†VdÅ,9²2*bûg> Nûåw¥ž÷ÐHß/Èè£ý Ÿ ýú5ðPÇ‚)¥J&œ<ï&Ug“> Nûåw©ž÷¶‘¾_$ƒ¶ðú™uzÀ’nm'ൎ àU€õ¤Á öŽáEš{t>6„Ÿ¢4hŽãõíÛã8ØãÏ_®à©”>0áLž¶6.¹÷—ç/;—o¾^½‡Ü¿¿¿¿|né}Â1S_ò²˜x wŠƒ·Íy2“QÚÿ?Æ'­¶õÿ·fJµ÷÷ƒ·IvóÿôËf϶EUÁô Cºè‡š6Éüðá’÷ÇgeËå2Ä_O4ðlú¦E¶Xîâs™â ô©w–³1L3—"„'ÓÆx±]ÙG5¹«u68\ô‡E‡ÝÚLNùÏ\<÷;‹f®ÁÍÅ#‡ãÙ®:þÙVò.i´ê¯×¡…ÍtN2l~Íw#Žî¥¨ÈøúŠà=ŒfT;)ÚöáM²ÕèýzEøK[>îÞ½; =ü~!>¬(ûe\~í¾ü7]æþÍ4ÔTšÍf××Ë…ÿþÂ,”$> Nëå·UÏgÓHß/’¡Šö 0 ŸÀókýçî Àöm¯-Æø-@˜5âA> Nëå·UÏgIß/’¡Šö 0hY¾:x ÿÝ ²"+³oäz&M‰ > Nëåw©žÏ`¤ïdÐh¿€ ´,€õá“ ¿ì˜*¥‚¹Úþ Lý‹Ä> Nëåw¡žÏˆ¤ïÉËP£íüФJX†ehÍÜ€~»µ`‚™È> Nûåw©ž÷ÖHß/Zö 4 |ë±ýe˜ÈªXÑþ»œk`‚i´ > Nûåw©ž÷ÅHß/’¡Fû~x¬@ÙŒo—mÿúz«u`V*> Nûåw©ž÷h¤ïdhÑöÞZ”a–„­õEô—]ŸÑ€ Ѽüñ> Nûåw©ž÷&¾_¤*Ú/ -‹À÷—€þwWÀ„T)Ì·Ï|­zÛ®5> Nëå·UÏgÓHß/’¡Šö @?€ÏOè?÷¤ãÛ¾íÛ›T×”fç=¾ø5ÜM¢{æ‰[Ò[ö>Z¿X‘loaæø§—6ÿ³:ÔžýÅC³w€cq{è9z×-?w_gZrˆ(º±Öà þÖDÃþ\1½óãâ¸ÃÏ‚û°üW—`\Qkµ¬),G®gÛ³‡ï¿–míÂCýâÖz ©Äé$«ùùáõ/>ãDQw1[>öÏ0A±dbK¦Õ£v4KÞî {ÃÊk?^‹|cŠi"¿ŠÿÖ&ë¹ÈÏÚÔÒ&òk˜ºµ©ý9§ŽfMKi‘Ÿ‡©;ŸÚÝù9÷¿Ü“ýyð¿ò©]›’Ÿÿ‹-™VŸS‹ìÔ­Mdoð/LA^|D2Á¯z× ›¾=¸»{ù‡S‰uT•¯È·W¾"ß^™õ·©ÌÜמÎDvá߯ôߨDlewK3çíýËhS$ÚŽÁVòF£Ï_ÂOjhÌoÞ¼™ç9˜¿Ç  éî„?˜ûДlþÿýåïög³Ùl6«”ÊöÎG/¼¸¸¸¸¸(> Nûåw©ž÷¶‘¾_4ƒö ÐÏ?ß}ÀSM[0Á.g|ûÔ žZ º±÷.> Nûåw©ž÷¦‘¾_¡¶ð~ý €P†ehf¼žjØ#µ@ô®Óó> Nûåw©ž÷¦‘¾_¡ö h€~ô¯w@(O Á”R%LÖ¢Ñ׉ŸOggSÀ³ÎFdDì¯c<<=:97Mµ1#K<8=>:==::<=:=F«1P==7779=9=L¶/L;=:==;:85> Nëå·UÏg3¾_$Cí`@?€Ïžèï{3Û·}[Œ9S- <¼è+> Nëå·UÏgÓHß/’¡Šö 0 ŸÀÛSýçî ²"}$f§{$„YùŽ> NûåwM=ïÁHßÏÈ ¢ý*вÖÓû ÿÝ0!UJ³ï°G*zÚm> Nëåw¡žÏÆHß/ÈТíüq@Ë"€èNh•­ô—] Lðçç‹> Nûåw¡ž÷%¾_$Cö Ф¾uý@™¬ÈªXÑþ?ŸÑÀì‚> Nûåw©ž÷Iß/Zö 4 üù €~X@0~¦l‡ë–Ѐ ©oÞÞøuÊ]J÷èøí…ð–[ µ_¬Ø^²P9ìý?ëôðÿò|êÞƒŽL7Û™5s´Î‘%¢ªHbüöù[õÞÉR 3”âþ:þæáŸ'5Ë#\l÷¡{`Œ”³¸Õ_ý]€Õ¥µÖZ+ ËŸãw¯I¤ä࿯ü·å×ùÃÿ_Ù$“éH›¿ùûŸUø¼tÎ}—/xF¦¿¯¨X¨X2ý}¶Äà©õLÖ3¦äEàÛ]¦ø¥¾ÝÃ?kƒürSû³6‘_Údê~Ö¦ä—6ñßÏ4Å/šò¿žË?Ó”üz¦ø™¦øÙ=LíÏ4ÅÏÚdj¹áÇs[UüÅOãx/¼ÈX²¯Y%¸Ô³ÁS‰óÜ:MÕü$Oú$¿Ûïöÿ‡ ?þIª-þþÿg$já?ëðÜSŒÍ²@®\><9$:ÿ—¿.B™šÊ¶_ÏÏ ÚöÊMŠÕèó÷-áOUøÐÃp÷îx² g 8*²€ôßÜæ…ìÔãÿKËlþüÜÌÌLC©T¶÷>­ªªõ:®"V> NûåwM=ïÑHß/ȼh¿€¤ŸýëÝ`«*SJ•Lx°¦6u„,:¨> Nëåw©žÏIßÏȲhû_ ¤ŸB–aIöf%d­ (4Re2> Nûåw¥ž÷&¾_4Ó¢ý ¤Ÿ~»û€“Z"+JGÖé!ƒ4²õB > Nûåw©ž÷6’¾_4£Šö H?üó®n×1À»œñÍÃ.[´ äÒÃæ> Nûåw©ž÷&’¾'/ƒŠ¶0 ~@X†e¨fCÀó:Và|-ݾ;> Nûåw©ž÷¶‘¾_$CíÐý ðï£ÀS [¦”ª˜ðºÞÓZ ºáî,> Nëå·UÏg;¾_¤>Ú/À€~øøtý¡Ü°}Û·ýžEÕ úº“g> Nëå·UÏg3¾ŸQm?àÿè'€Š^CÉdfˆ@/Ï>‘&ü> Nûåw¥ž÷Iß/È ¢ý*@?Àúþýçî 0!UJsõ›.fiÞøuÜMryôýû–ô–ûu?è:€ã/@ Þó÷ÿ¤µÅúéo“°FÀÍüºHë,+ô-¨–kä¢å{íc€T"|Þ%6ò÷leÝ¿^ûqÄ-ªp†huü6FµÒº´V€Õ?_¿žy÷õ'ku鋼ìÖÿ|=£«ù¯ \qw ö¸`¾ ½}½èKJðòù™†©) Nûåw©ž÷¶‘¾_PíPú°¾ß)øRn&¤Jé`®Þõ•&Ø_/> Nûåw©ž÷¶‘¾_´>Ú/@ý ðùi^kؾí;|{Sî8 öãN÷> Nëåw¡žÏ&’¾_$ƒŠö  ý ðìÚ€·u¬€Y1K6>B€<óåš> NûåwM=ï!¾'/SEû4 ý èׯÀ½uL¦”*¥ÂÌ•·)rÞç­> Nëåw©žÏ&¾_iÑöþ` ý è%Ýž€­ªÀ %B}¶> Nëåw©žÏ&’¾_$Ë¢ý ¤Ÿ~»ë€¬5€ÈЬÈ:½Æó*©â\> Nóå¿UÏg ¤ïé—iÑ~~øó®ÀIUÀÛ·ƒù~ÂZ:²õ > Nûåw©ž÷&’¾Ç/£Š¶PH? ”aÊ4¸]Çœk@È¥/> NË塞ÏI߯‘AEû4 ý ðÏÇÏëX ˜ÈŠ¬ðæ•½u@tãòÞvüMrÓÌ·¤·ÜbÔ¨ëýû~&|>›ô€Ÿž§k‹Ó/9¢ïlÝ~]Î6ÖëuQ$o«µÖ@k­µÖZcÅù_Ë.¶{“=Ã"Xý*øCZk­5á€Õ?¿¦Ígÿ] w¿þzÛÁãÓ‡§uµ©A»ðÕëãÃø¼tdUaú»Õû\|ö 4),.JcSÅë™LM1°žŸ”©)¯çg“©)ÏÏÏ„255åÇýüüüŒajjj ¯ççg7&SSS~ Nëåw¡žÏ&’¾_¤*Ú/PeøËcÖŸCÁD銮]–X+˜17ïU> Nûåw©ž÷6¾_´*Ú/Peøáqëû@°ýLÙöÜé¢@0™W?> Nûåw©ž÷ÆHß/ÈP£íü<¨¤B–aI=íÀúP?£€`PÔ± > Nûåw©ž÷Iß/¨FûT ¢`}<¬›LH•RÁ\]¤© @0¥×n> Nûåw¡ž÷ÖHß/šáFû¨(ðùiëià۾׳®Ì)&Œ±j> Nëå·UÏgHß/’¡Fû¨¤ž]XêX‘³äȨ‚±²kY> NûåwM=ïÁHß/È ¢ý¨²úõk°þ¦”*™pò¼kª“>oÎ1> Nëåw¡žÏ&’¾_Pm?àd ô€’´¶“Xÿõ*ÀŒ¼àô)> Nûåw¡ž÷e }¿H†>Ú/À€~¿>°~ "+JGÖéoÖ³?ßo¾ø5üMRóèøýŠôEùh€/U`TŸÛ—–Ïâ§C™a‹›_s‚>œ ô®€Yyœ³­ÿ^QÓXô] ã+÷×ׄƒíÖñº8îØÍU>ÀgæìËÌQQk¹´Ö€rà·©VûÙÝnÓl¶SÀ5yÌ¥ÕL¥Ô(ÿ;ó’î_Ùö×Õç&ðÏf%[2ý}ZÆ$ͧ˜&òË <Ó£Lá|»‹|Ó”ü¢©õLþ妖6‘_îÉþ(Á‹y¦ÅÜô•¢¢i¨Z ”L‹©Û=™ºµ)ùY›ìÏùÔî¦n÷ ?»'òs>ÅÏÅ?ù¦‰ ¼¤ ßAw-œÞOÀ ˆS…ÅéÆPöÓÑé N·ÔmÍjýç–oQig¥¬WR¦|rÛw#¼X›P™mΠ{8ymæ3ˆ"f ÌÏÚŽÊUJæÑùy?éÁ~zîÞ½; Cêü ý~§OÁxØO=¿ü?ÏíVž›ëõ}¿¸¸Øëû~×›2wwwwwww> Nëåw¡žÏ¦‘¾_¤>Ú/À€~&x{JÀk­ Y‘‰Ù[tÖ“‚û> Nûåw©ž÷¦‘¾'¯>Ú/ ô3`}x\X_ªv`Bª” fŸ3­j`½_ç> Nûåw©ž÷¦‘¾ŸQm?à§ú ”P†ehí­ëKuÓsý¯> Nûåw©ž÷6’¾_´*Ú/@ý¾õ8€õïÄA¬(]±¢ýSгÂùË> Nûåw©ž÷6’¾_´*Ú/Peøëc%ÖŸÃ@0åLÙׇWi3æÃK£ > Nûåw©ž÷HßÏÈPEÛx;¨²„–aIøR,`ý9ä:À4ÿ²Ð > Nûåw©ž÷ÖHß/’¡FûT ’øó.°>Ô&˜R:˜onÓ @0(ŠZ> Nëå·UÏgc¤ïÉP£ýTøütëfðmßöíMóïVLéÛ:> Nëåw¡žÏIß/¨FÛø§ˆ^ ]éß5°n‚)½uÔOggST]ÎFoVnÒ1K´/&H:<<<<:==9C®(,&P<:>;><<<=NgÞøuü]Š{ôùÛ á-·õð}$û]gè§è¾¿ùFïÓx7ÙCzâdÅÙÿ.(ÓãXR‹Zk ´S×`Zk|Þö¤¿ö(k{½Æ®Î-|ÌaHè/î'v^´ÖrÔZª¯—sútq6¤[Wí§nÄ¡\…ªF T6êÝÖZkàq:Ûß-ìŸQÊ´è¦øåÆ@ñãÙ=LɯgššZÏÚ0…—Vüx¦a ÏîaJ~=ÓÔÔý¬Mük•˜¦²V ûw çuN-Ÿó)ù~Æ”üz.Sþ×3MM­g÷0ÅÏî2…gm˜âgw™’_Ï4åÇ3$¬=_ÛYR~/ìï À%|Æž6ª­=¾œN‹'ÍÄ?ýÿ9ã;Ï„ôýÏD/„bÍ¿õ®Õït¯ ƒ2eF³¶æÎ`ặ¯¿[«.¦öü úŽÖMŠÑèÏç†ðW”˘ç7ÓyÆüõ•Ò°ÕñGòk^¿øùÛ]$wƒýýYZç\mÊ8Œ¸»ûrº3> Nûåw¥ž÷ÐHß/ÈòÑöÞp?”° ËÐÌßN@Ô4€¥µ …j> Nëåw¥žÏ`¤ïd^´_@îg@ÿz0— SJ•LxßÖ@Šê]…> Nëåw¡žÏ&’¾_$£Šö 0p?||ö€óš`û¶o‹E礘ÿo> Nëåw¡žÏ&’¾_$ƒŠö 0p?¼=%à~M ­ÈŠDïþó$ˆ³ÿ/ > Nûåw©ž÷¦‘¾'WíPú™°žž€×Z0!UJ•̲•º °žL}> Nëå·UÏg3¾_Pm?àèg€6Ð O×|©Ú`ýt{> Nëåw¡žÏ¦‘¾_¤>Ú/@ý¾õX€õ¥:°"+²¢ý;UsýâW> Nóå¿UÏgŒ¤ïéWíÐýþ} XÿNÁ”mß„·WìÒ fEûç> NÇåw¥ž÷&’¾Ÿ‘¡Š¶PÈ"ʰ KXƒÀúwHM­`V¨]UÞvÊMrxvÿ¶!½åQ? ®ã?¿¤àï }Xï“ûËÓ­-žþæ-Ÿ e2b&#Âzÿ9Ã& >U¢íïþ¥¹Ÿ]¨uüö•ãTâ;„9†^ÞÌÆ(´ŽZ+@õõòϬ_Ì<ªÀ‰õ›Dð;S#i®N¥êý­WÁ|:sŸþþ‰æÏ^çxqqq‘->»©=»L¦p»nvømÜÆ!š‹ª ýëIŸ<øõÃÖ̳K›ö_ñÏ‹¦»ÕߟÍf³º¥ÍsÎ9眫\ ö—àa¹\ê[»AúÕÀ¾€þÇZàø ¶ÛïÓ­¶ÓÇï+"[0 ö—àa¹]õ=Šb ¤_ ì èK——Àòyÿ÷ý¹øž ƒ: ö—àa¹\õ-Še~5°/€þšæé€ÒϾ>õ¨ôY‡]YD?­“ ö¯—a9m‘úÅô û5lí¬†i¸àæ ÐwU•R¥Kf ö—àa¹\õ-Še~Õ€}ÖßÔ °> Õûob­îÿvEr Pðg ö—àa¹lõ-ŠÓ ýªÀ¾€õ0——@.Ÿ?¿¯çú¶þZ] €ó—; ö—`a¹\õ->¬ Ò¯ ì XßVŽïV^Þ~]ziN~2!ù] öÏ—a9]ŒúÅH¿ öepÆ’ãõYaT¢Œƒ¾¿ŠU@²õH öÏ—a9]õ-Š! „e°/ ÿ¸f€‡€×òñJìõè°5Ý €Ù§Vþøò)iã–‡]Qßb ¡_°=èŸÉ ðCƒ—‡ïm¥œÿûÿUß§&ø2ê®±NXï!Éêß?ÞcAuUªª¢(ªD_×Äê¿ßM×¶_WÎÙFÏÜÁnaVk€ 4á¨{ÿgN¦ûŸÿ_ËÂÙ?©oñP_ÍÏ·ÖšT[Íg?þ3[ðò1”˜ h²#qŽM§*þ¥a=Ï^îÉrOd SÀmediascanner2-0.111+16.04.20160317/test/media/baddate.ogg0000644000015600001650000003464112672421600022723 0ustar pbuserpbgroup00000000000000OggSÎFÍØ-µvorbisD¬w¸OggSÎFÇqÍìÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉvorbis-Xiph.Org libVorbis I 20101101 (Schaufenugget) ARTIST=Artist TITLE=Track ALBUM=AlbumDATE=2015-13-33vorbis)BCV1L Å€ÐU`$)“fI)¥”¡(y˜”HI)¥”Å0‰˜”‰ÅcŒ1ÆcŒ1ÆcŒ 4d€( Ž£æIjÎ9g'Žr 9iN8§ ŠQà9 Âõ&cn¦´¦knÎ)% Y@H!…RH!…bˆ!†bˆ!‡rÈ!§œr *¨ ‚ 2È ƒL2餓N:騣Ž:ê(´ÐB -´ÒJL1ÕVc®½]|sÎ9çœsÎ9çœsÎ BCV BdB!…Rˆ)¦˜r 2È€ÐU €G‘I±˱ÍÑ$Oò,Q5Ñ3ESTMUUUUu]Wve×vu×v}Y˜…[¸}Y¸…[Ø…]÷…a†a†a†aø}ß÷}ß÷} 4d  #9–ã)¢"¢â9¢„†¬d ’")’£I¦fj®i›¶h«¶m˲,˲ „†¬ iš¦iš¦iš¦iš¦iš¦iš¦išfY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY@hÈ*@@ÇqÇq$ER$Çr, YÈ@R,År4Gs4Çs<Çs=:<;<=:H14&,H;<<767:<=L´.H=;> Jñ¯F„ÿ«!*® ¤€Yi¬gf-?º%#EQEccdzíGã Ôà ±ßF\ƒ›\2/ojدétê`©ÿ‹~â-oÃÛ®¥T†ùWã*Ü9P‘ÍmE’ìýtÀ.g,ª­ƒÏ4‘om"úŽñ"‚ <òÁ-áG†Œö¡‡ávw<$ðO«M¶”þ½—æîb9T|d«™™3ANÁã`V§p®:âe"îîîsèî > N‹ ê¹\é+ŽÃG“~5ÐÏzP€×ZX>׿ïÏõ¼þÿÅìXOê > N÷‹ ê¹]5ÒW\&ýj Ÿ ô×ÊX_jí`ÙϾ>õ¥t?Ñ~çÖ{-7> N‹ ê¹l#é+Žë£I¿ôà.Õ°~M–ƒ“¸È \é¶ V X‡†ü> N‹ ê¹\ÒW,¯hÒ/@?ëwj°~ «Î=Žb×þ]2™Ì?ž> N‹ ê¹\Òw(Ž£Š&ýªPe°NvX¹¼ÿõs{®÷Yl3FmKT> N‹ ê¹\Òw(®àŠ&ýªPe°žUX °òòñrÖ‹<æ™x3Fwá> N÷‹`ê¹]ô•A£I¿ €J:€w•%€õ¡ÆD‡ÁÙ'<cÅ–> NÏ‹`ê¹\é+ŽC£Â2* èo^#¬›Wx‰Ãø‰rÔ½€`Jo\× Žà"R€ˆîÏHøÙäòBÛ]Ÿß¹3ªëÒ€¥‹tÎïjìï@ßV?%–ßv[lèꪪXEQeî£5’MMM…Ý}D†ÉW®>¿–ra îm0¿ý˜ë^D†‘ß¹úì[šŠ î~S€uÆÁwvfèÃT†ñÏ\yömäÀ×W,—þ1¬¢É<†ÁÏÆ5ø6r` îÊ`¼<`I-L†ÙWã \K30Æ-W6@ûó/E+×$†éŸkðmä†1nɲà¿úæM5^$†©ï\ÿlí&a8ñ´€kð/O)'ŒŸQñ×ÅåúÛÕJow]\‘5&-\^u÷„».Xõmn#î<4yøËWŒé%:sl¼ë/yª#Öð!pû—dåþ‡<ÎõQç ¼½ïžžÕÝ §×,nàé÷Å«}#VÛÿ¾¹ÆŠÎÄ™·ÔmÍjyKŸeê_û¬$nÍWã¯AÛLqÚR$aÿÕW^Jêc¯%V9Îz â ÚŽè%¢cží)ãÉæùÍtžçyFQ Ðévê'ÿ#ý^ ÝÍ—¿¿¿èÞᬮ”R]v¦Á¹ÅÅ¿ÿ÷W¿> .—_°R/c$=E)c¤_ Ð?] ð´`µÝ~Ÿ>ÔÖ{rµ;Øgú9> ®§PF/ãBzŠÚ˜~ÐàåËî{·ŒKwúÆכžI¾•.´> .—_°R/c$=E©c¤_ пSàfWË~öõ©¯K/ë¹€£z7> .Ï—Ÿ©—VIO1Ž‘~ Àp%Ç}9ˆŒ"2ˆÌqȹ¦ ™;Ý > .—p©—M%=ÅÀ:FúUÖßÔP›´Ú¾xkuùá‹'ûjµš> .—0R/cGzв‘~U€u™V5r×óç÷õ\ßî;¡ØP9y¥> .÷—ß©—M$=Å#ýªë[U€‡áÈ•—·_—^š÷:—6H&> .—Ÿ©—1“ž¢Ô1Ò/Ücy^ëÈ:Š; Â`îMEµó 4>Þ Žð%RpaÎÇJyïrù6æu}çΨ®kôy ° Âðžv¯ýôÓó=ÐÓ÷#ëÞ{ï§ßr4b¯^½¤¿ÿ $†ÁGþðì,Å cpçV€xgÐ[—†IDŠÙG®=?[?P™Áý5pþûëŸAC64ŠñGþðìlý@ew·•@öÁ©·z&T†åg®>?ƒ{Œ‡¾~zèÃD†Ég®=»6ºP•Á]o·\‡†TŠùG®>;_¨Ìàþjëü7gÃú°D†ÉG®=»6z 2ƒû[ ¬ÞMÖ¡‡#D†í[ãzÔ7ba îÊj£ý÷–aŸê×lb©žØ–A…±YºUܱÙ[n‹±yÏ?˜½·ëò·¯üÄTxbWþZ‹Ÿ‰ÓTá‰õÙ­Tý ‡ @Þ4àÎÍÙ܈9[ýVuE)¼åüCVûŸ½wu]¯Coé=d¦sî=  ¤½ßv»¸GåâæÕi`uæ?’äØúéSÿ"ÀñåÔA/4þsÎ>câî>zI|bßFk· cßÃ1XgJ\”yŸ.—Èu±Ôy÷ï+*–ÏÏúŽöMrhôû[$ü¥ŽãìüæÍ<Ïìü§sð«³KP  ®·ÈÍ‹¿ç|aÁS6Ûÿ4¬€¬ 4¹`væ"> Nëå·UÏgHßã‹?Ú~À›ú™ „eX†Ö.ê<ÕÚç´€åqç> Nûåw©ž÷6’¾_4ƒŠö ¸Ÿ þòØÏjZ+JW¬èÚe µ€eÃÝ> Nûåw©ž÷6’¾_4ƒŠö ¸Ÿ ~xœ€[5 Ʒ˶às'- Ví=> Nûåw©ž÷&¾'/Ó¢íü<p? „eXR7tÀa fk@Š6µ> Nûåw¥ž÷h¤ïdj´_@ÜÏ€õýñ ÊT0!UJsu‘vM¤pºÍ > Nëåw©žÏ&¾_$Ó¢ý¸Ÿ >?MÀ\.€oû_n:”C Eum·> Nëåw¡žÏ&¾_$Ó¢ý¸Ÿ ž]p¿†VdÅ,9²2*bûg> Nûåw¥ž÷ÐHß/Èè£ý Ÿ ýú5ðPÇ‚)¥J&œ<ï&Ug“> Nûåw©ž÷¶‘¾_$ƒ¶ðú™uzÀ’nm'ൎ àU€õ¤Á öŽáEš{t>6„Ÿ¢4hŽãõíÛã8ØãÏ_®à©”>0áLž¶6.¹÷—ç/;—o¾^½‡Ü¿¿¿¿|né}Â1S_ò²˜x wŠƒ·Íy2“QÚÿ?Æ'­¶õÿ·fJµ÷÷ƒ·IvóÿôËf϶EUÁô Cºè‡š6Éüðá’÷ÇgeËå2Ä_O4ðlú¦E¶Xîâs™â ô©w–³1L3—"„'ÓÆx±]ÙG5¹«u68\ô‡E‡ÝÚLNùÏ\<÷;‹f®ÁÍÅ#‡ãÙ®:þÙVò.i´ê¯×¡…ÍtN2l~Íw#Žî¥¨ÈøúŠà=ŒfT;)ÚöáM²ÕèýzEøK[>îÞ½; =ü~!>¬(ûe\~í¾ü7]æþÍ4ÔTšÍf××Ë…ÿþÂ,”$> Nëå·UÏgÓHß/’¡Šö 0 ŸÀókýçî Àöm¯-Æø-@˜5âA> Nëå·UÏgIß/’¡Šö 0hY¾:x ÿÝ ²"+³oäz&M‰ > Nëåw©žÏ`¤ïdÐh¿€ ´,€õá“ ¿ì˜*¥‚¹Úþ Lý‹Ä> Nëåw¡žÏˆ¤ïÉËP£íüФJX†ehÍÜ€~»µ`‚™È> Nûåw©ž÷ÖHß/Zö 4 |ë±ýe˜ÈªXÑþ»œk`‚i´ > Nûåw©ž÷ÅHß/’¡Fû~x¬@ÙŒo—mÿúz«u`V*> Nûåw©ž÷h¤ïdhÑöÞZ”a–„­õEô—]ŸÑ€ Ѽüñ> Nûåw©ž÷&¾_¤*Ú/ -‹À÷—€þwWÀ„T)Ì·Ï|­zÛ®5> Nëå·UÏgÓHß/’¡Šö @?€ÏOè?÷¤ãÛ¾íÛ›T×”fç=¾ø5ÜM¢{æ‰[Ò[ö>Z¿X‘loaæø§—6ÿ³:ÔžýÅC³w€cq{è9z×-?w_gZrˆ(º±Öà þÖDÃþ\1½óãâ¸ÃÏ‚û°üW—`\Qkµ¬),G®gÛ³‡ï¿–míÂCýâÖz ©Äé$«ùùáõ/>ãDQw1[>öÏ0A±dbK¦Õ£v4KÞî {ÃÊk?^‹|cŠi"¿ŠÿÖ&ë¹ÈÏÚÔÒ&òk˜ºµ©ý9§ŽfMKi‘Ÿ‡©;ŸÚÝù9÷¿Ü“ýyð¿ò©]›’Ÿÿ‹-™VŸS‹ìÔ­Mdoð/LA^|D2Á¯z× ›¾=¸»{ù‡S‰uT•¯È·W¾"ß^™õ·©ÌÜמÎDvá߯ôߨDlewK3çíýËhS$ÚŽÁVòF£Ï_ÂOjhÌoÞ¼™ç9˜¿Ç  éî„?˜ûДlþÿýåïög³Ùl6«”ÊöÎG/¼¸¸¸¸¸(> Nûåw©ž÷¶‘¾_4ƒö ÐÏ?ß}ÀSM[0Á.g|ûÔ žZ º±÷.> Nûåw©ž÷¦‘¾_¡¶ð~ý €P†ehf¼žjØ#µ@ô®Óó> Nûåw©ž÷¦‘¾_¡ö h€~ô¯w@(O Á”R%LÖ¢Ñ׉ŸOggSÀ³ÎFdDì¯c<<=:97Mµ1#K<8=>:==::<=:=F«1P==7779=9=L¶/L;=:==;:85> Nëå·UÏg3¾_$Cí`@?€Ïžèï{3Û·}[Œ9S- <¼è+> Nëå·UÏgÓHß/’¡Šö 0 ŸÀÛSýçî ²"}$f§{$„YùŽ> NûåwM=ïÁHßÏÈ ¢ý*вÖÓû ÿÝ0!UJ³ï°G*zÚm> Nëåw¡žÏÆHß/ÈТíüq@Ë"€èNh•­ô—] Lðçç‹> Nûåw¡ž÷%¾_$Cö Ф¾uý@™¬ÈªXÑþ?ŸÑÀì‚> Nûåw©ž÷Iß/Zö 4 üù €~X@0~¦l‡ë–Ѐ ©oÞÞøuÊ]J÷èøí…ð–[ µ_¬Ø^²P9ìý?ëôðÿò|êÞƒŽL7Û™5s´Î‘%¢ªHbüöù[õÞÉR 3”âþ:þæáŸ'5Ë#\l÷¡{`Œ”³¸Õ_ý]€Õ¥µÖZ+ ËŸãw¯I¤ä࿯ü·å×ùÃÿ_Ù$“éH›¿ùûŸUø¼tÎ}—/xF¦¿¯¨X¨X2ý}¶Äà©õLÖ3¦äEàÛ]¦ø¥¾ÝÃ?kƒürSû³6‘_Údê~Ö¦ä—6ñßÏ4Å/šò¿žË?Ó”üz¦ø™¦øÙ=LíÏ4ÅÏÚdj¹áÇs[UüÅOãx/¼ÈX²¯Y%¸Ô³ÁS‰óÜ:MÕü$Oú$¿Ûïöÿ‡ ?þIª-þþÿg$já?ëðÜSŒÍ²@®\><9$:ÿ—¿.B™šÊ¶_ÏÏ ÚöÊMŠÕèó÷-áOUøÐÃp÷îx² g 8*²€ôßÜæ…ìÔãÿKËlþüÜÌÌLC©T¶÷>­ªªõ:®"V> NûåwM=ïÑHß/ȼh¿€¤ŸýëÝ`«*SJ•Lx°¦6u„,:¨> Nëåw©žÏIßÏȲhû_ ¤ŸB–aIöf%d­ (4Re2> Nûåw¥ž÷&¾_4Ó¢ý ¤Ÿ~»û€“Z"+JGÖé!ƒ4²õB > Nûåw©ž÷6’¾_4£Šö H?üó®n×1À»œñÍÃ.[´ äÒÃæ> Nûåw©ž÷&’¾'/ƒŠ¶0 ~@X†e¨fCÀó:Và|-ݾ;> Nûåw©ž÷¶‘¾_$CíÐý ðï£ÀS [¦”ª˜ðºÞÓZ ºáî,> Nëå·UÏg;¾_¤>Ú/À€~øøtý¡Ü°}Û·ýžEÕ úº“g> Nëå·UÏg3¾ŸQm?àÿè'€Š^CÉdfˆ@/Ï>‘&ü> Nûåw¥ž÷Iß/È ¢ý*@?Àúþýçî 0!UJsõ›.fiÞøuÜMryôýû–ô–ûu?è:€ã/@ Þó÷ÿ¤µÅúéo“°FÀÍüºHë,+ô-¨–kä¢å{íc€T"|Þ%6ò÷leÝ¿^ûqÄ-ªp†huü6FµÒº´V€Õ?_¿žy÷õ'ku鋼ìÖÿ|=£«ù¯ \qw ö¸`¾ ½}½èKJðòù™†©) Nûåw©ž÷¶‘¾_PíPú°¾ß)øRn&¤Jé`®Þõ•&Ø_/> Nûåw©ž÷¶‘¾_´>Ú/@ý ðùi^kؾí;|{Sî8 öãN÷> Nëåw¡žÏ&’¾_$ƒŠö  ý ðìÚ€·u¬€Y1K6>B€<óåš> NûåwM=ï!¾'/SEû4 ý èׯÀ½uL¦”*¥ÂÌ•·)rÞç­> Nëåw©žÏ&¾_iÑöþ` ý è%Ýž€­ªÀ %B}¶> Nëåw©žÏ&’¾_$Ë¢ý ¤Ÿ~»ë€¬5€ÈЬÈ:½Æó*©â\> Nóå¿UÏg ¤ïé—iÑ~~øó®ÀIUÀÛ·ƒù~ÂZ:²õ > Nûåw©ž÷&’¾Ç/£Š¶PH? ”aÊ4¸]Çœk@È¥/> NË塞ÏI߯‘AEû4 ý ðÏÇÏëX ˜ÈŠ¬ðæ•½u@tãòÞvüMrÓÌ·¤·ÜbÔ¨ëýû~&|>›ô€Ÿž§k‹Ó/9¢ïlÝ~]Î6ÖëuQ$o«µÖ@k­µÖZcÅù_Ë.¶{“=Ã"Xý*øCZk­5á€Õ?¿¦Ígÿ] w¿þzÛÁãÓ‡§uµ©A»ðÕëãÃø¼tdUaú»Õû\|ö 4),.JcSÅë™LM1°žŸ”©)¯çg“©)ÏÏÏ„255åÇýüüüŒajjj ¯ççg7&SSS~ Nëåw¡žÏ&’¾_¤*Ú/PeøËcÖŸCÁD銮]–X+˜17ïU> Nûåw©ž÷6¾_´*Ú/Peøáqëû@°ýLÙöÜé¢@0™W?> Nûåw©ž÷ÆHß/ÈP£íü<¨¤B–aI=íÀúP?£€`PÔ± > Nûåw©ž÷Iß/¨FûT ¢`}<¬›LH•RÁ\]¤© @0¥×n> Nûåw¡ž÷ÖHß/šáFû¨(ðùiëià۾׳®Ì)&Œ±j> Nëå·UÏgHß/’¡Fû¨¤ž]XêX‘³äȨ‚±²kY> NûåwM=ïÁHß/È ¢ý¨²úõk°þ¦”*™pò¼kª“>oÎ1> Nëåw¡žÏ&’¾_Pm?àd ô€’´¶“Xÿõ*ÀŒ¼àô)> Nûåw¡ž÷e }¿H†>Ú/À€~¿>°~ "+JGÖéoÖ³?ßo¾ø5üMRóèøýŠôEùh€/U`TŸÛ—–Ïâ§C™a‹›_s‚>œ ô®€Yyœ³­ÿ^QÓXô] ã+÷×ׄƒíÖñº8îØÍU>ÀgæìËÌQQk¹´Ö€rà·©VûÙÝnÓl¶SÀ5yÌ¥ÕL¥Ô(ÿ;ó’î_Ùö×Õç&ðÏf%[2ý}ZÆ$ͧ˜&òË <Ó£Lá|»‹|Ó”ü¢©õLþ妖6‘_îÉþ(Á‹y¦ÅÜô•¢¢i¨Z ”L‹©Û=™ºµ)ùY›ìÏùÔî¦n÷ ?»'òs>ÅÏÅ?ù¦‰ ¼¤ ßAw-œÞOÀ ˆS…ÅéÆPöÓÑé N·ÔmÍjýç–oQig¥¬WR¦|rÛw#¼X›P™mΠ{8ymæ3ˆ"f ÌÏÚŽÊUJæÑùy?éÁ~zîÞ½; Cêü ý~§OÁxØO=¿ü?ÏíVž›ëõ}¿¸¸Øëû~×›2wwwwwww> Nëåw¡žÏ¦‘¾_¤>Ú/À€~&x{JÀk­ Y‘‰Ù[tÖ“‚û> Nûåw©ž÷¦‘¾'¯>Ú/ ô3`}x\X_ªv`Bª” fŸ3­j`½_ç> Nûåw©ž÷¦‘¾ŸQm?à§ú ”P†ehí­ëKuÓsý¯> Nûåw©ž÷6’¾_´*Ú/@ý¾õ8€õïÄA¬(]±¢ýSгÂùË> Nûåw©ž÷6’¾_´*Ú/Peøëc%ÖŸÃ@0åLÙׇWi3æÃK£ > Nûåw©ž÷HßÏÈPEÛx;¨²„–aIøR,`ý9ä:À4ÿ²Ð > Nûåw©ž÷ÖHß/’¡FûT ’øó.°>Ô&˜R:˜onÓ @0(ŠZ> Nëå·UÏgc¤ïÉP£ýTøütëfðmßöíMóïVLéÛ:> Nëåw¡žÏIß/¨FÛø§ˆ^ ]éß5°n‚)½uÔOggST]ÎFoVnÒ1K´/&H:<<<<:==9C®(,&P<:>;><<<=NgÞøuü]Š{ôùÛ á-·õð}$û]gè§è¾¿ùFïÓx7ÙCzâdÅÙÿ.(ÓãXR‹Zk ´S×`Zk|Þö¤¿ö(k{½Æ®Î-|ÌaHè/î'v^´ÖrÔZª¯—sútq6¤[Wí§nÄ¡\…ªF T6êÝÖZkàq:Ûß-ìŸQÊ´è¦øåÆ@ñãÙ=LɯgššZÏÚ0…—Vüx¦a ÏîaJ~=ÓÔÔý¬Mük•˜¦²V ûw çuN-Ÿó)ù~Æ”üz.Sþ×3MM­g÷0ÅÏî2…gm˜âgw™’_Ï4åÇ3$¬=_ÛYR~/ìï À%|Æž6ª­=¾œN‹'ÍÄ?ýÿ9ã;Ï„ôýÏD/„bÍ¿õ®Õït¯ ƒ2eF³¶æÎ`ặ¯¿[«.¦öü úŽÖMŠÑèÏç†ðW”˘ç7ÓyÆüõ•Ò°ÕñGòk^¿øùÛ]$wƒýýYZç\mÊ8Œ¸»ûrº3> Nûåw¥ž÷ÐHß/ÈòÑöÞp?”° ËÐÌßN@Ô4€¥µ …j> Nëåw¥žÏ`¤ïd^´_@îg@ÿz0— SJ•LxßÖ@Šê]…> Nëåw¡žÏ&’¾_$£Šö 0p?||ö€óš`û¶o‹E礘ÿo> Nëåw¡žÏ&’¾_$ƒŠö 0p?¼=%à~M ­ÈŠDïþó$ˆ³ÿ/ > Nûåw©ž÷¦‘¾'WíPú™°žž€×Z0!UJ•̲•º °žL}> Nëå·UÏg3¾_Pm?àèg€6Ð O×|©Ú`ýt{> Nëåw¡žÏ¦‘¾_¤>Ú/@ý¾õX€õ¥:°"+²¢ý;UsýâW> Nóå¿UÏgŒ¤ïéWíÐýþ} XÿNÁ”mß„·WìÒ fEûç> NÇåw¥ž÷&’¾Ÿ‘¡Š¶PÈ"ʰ KXƒÀúwHM­`V¨]UÞvÊMrxvÿ¶!½åQ? ®ã?¿¤àï }Xï“ûËÓ­-žþæ-Ÿ e2b&#Âzÿ9Ã& >U¢íïþ¥¹Ÿ]¨uüö•ãTâ;„9†^ÞÌÆ(´ŽZ+@õõòϬ_Ì<ªÀ‰õ›Dð;S#i®N¥êý­WÁ|:sŸþþ‰æÏ^çxqqq‘->»©=»L¦p»nvømÜÆ!š‹ª ýëIŸ<øõÃÖ̳K›ö_ñÏ‹¦»ÕߟÍf³º¥ÍsÎ9眫\ ö—àa¹\ê[»AúÕÀ¾€þÇZàø ¶ÛïÓ­¶ÓÇï+"[0 ö—àa¹]õ=Šb ¤_ ì èK——Àòyÿ÷ý¹øž ƒ: ö—àa¹\õ-Še~5°/€þšæé€ÒϾ>õ¨ôY‡]YD?­“ ö¯—a9m‘úÅô û5lí¬†i¸àæ ÐwU•R¥Kf ö—àa¹\õ-Še~Õ€}ÖßÔ °> Õûob­îÿvEr Pðg ö—àa¹lõ-ŠÓ ýªÀ¾€õ0——@.Ÿ?¿¯çú¶þZ] €ó—; ö—`a¹\õ->¬ Ò¯ ì XßVŽïV^Þ~]ziN~2!ù] öÏ—a9]ŒúÅH¿ öepÆ’ãõYaT¢Œƒ¾¿ŠU@²õH öÏ—a9]õ-Š! „e°/ ÿ¸f€‡€×òñJìõè°5Ý €Ù§Vþøò)iã–‡]Qßb ¡_°=èŸÉ ðCƒ—‡ïm¥œÿûÿUß§&ø2ê®±NXï!Éêß?ÞcAuUªª¢(ªD_×Äê¿ßM×¶_WÎÙFÏÜÁnaVk€ 4á¨{ÿgN¦ûŸÿ_ËÂÙ?©oñP_ÍÏ·ÖšT[Íg?þ3[ðò1”˜ h²#qŽM§*þ¥a=Ï^îÉrOd SÀmediascanner2-0.111+16.04.20160317/test/media/fake_root/0000755000015600001650000000000012672422030022600 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/media/fake_root/do_not_find_me.ogg0000644000015600001650000003226212672421600026250 0ustar pbuserpbgroup00000000000000OggSø¿2WJª?ÜvorbisD¬w¸OggSø¿2W©þ˜“ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉvorbis-Xiph.Org libVorbis I 20101101 (Schaufenugget)TRACKNUMBER=100 ARTIST=DJ JP DATE=2014GENRE=ElectronicTITLE=Goin' upvorbis)BCV1L Å€ÐU`$)“fI)¥”¡(y˜”HI)¥”Å0‰˜”‰ÅcŒ1ÆcŒ1ÆcŒ 4d€( Ž£æIjÎ9g'Žr 9iN8§ ŠQà9 Âõ&cn¦´¦knÎ)% Y@H!…RH!…bˆ!†bˆ!‡rÈ!§œr *¨ ‚ 2È ƒL2餓N:騣Ž:ê(´ÐB -´ÒJL1ÕVc®½]|sÎ9çœsÎ9çœsÎ BCV BdB!…Rˆ)¦˜r 2È€ÐU €G‘I±˱ÍÑ$Oò,Q5Ñ3ESTMUUUUu]Wve×vu×v}Y˜…[¸}Y¸…[Ø…]÷…a†a†a†aø}ß÷}ß÷} 4d  #9–ã)¢"¢â9¢„†¬d ’")’£I¦fj®i›¶h«¶m˲,˲ „†¬ iš¦iš¦iš¦iš¦iš¦iš¦išfY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY–eY@hÈ*@@ÇqÇq$ER$Çr, YÈ@R,År4Gs4Çs<Çs°/ÀÐ Sj,¥fu^`žYž3/)}²¸açì @ôK»l‡æ5ª ¾I^ì·ÔÖÙ߀ýàÆÀ¾@ßu-@0á¢_¿R¾Y^—”ÖYÚ°ÛÊÀ¾T€‡w—€íÛ¾#gƒß<žiž2oɽ³4a§ö <œ©RªÔx#v•žiž2o)½³4`§ö \CR¥T©1uvlµ¾Y^2/©½³¹agõ}¨ë[wÁ.Û¾½šy:m¾I^­—Tûv®ì €ìL)¤³þðžYžÃ§äjâ9qv¶ö@øëß,ßömëŸA_ÞY^3i½Ø÷ày²<°/¸ú € éÞ¦JÓþ;‰¾Yž³O)½Øs“Þ'Ûû ñÍ6Áª¤+VofÑ~+¾Y^2©­Ä¿ïÉ5À¾h€ñÖ`Bª·©Òxùaë¾I^í‡Tœƒß“Kö@Xw@°}Szòòn¾I^²Oi=8òà{ê}`}ý>ÀDVd<'®Ò žYž²O©=Äÿïö€~Ü: €J5U•Á®†¸¾Yž³O)=Ä¿‰ïÉòÀ¾Ðß÷Klo¶§Ãï¯mœs_žYž2O)-$8ð=¹ØôÓÙéuxtø¿½–ºžIž3O©å>ðsŠƒ}`€mPé*EÜ·áõžI^‡TT"áçû€Ïÿ`|Ûßü¯» žYž2/©ÍÅÿ‚ï û@ïL”UI÷æ]|G=žYžì§äpñðs2ó`Ø( „ÔÇéc9nÿتžYžÃ‡TüƒŸì ˆë"¦ÓWŸûx^¾Iž³OiÝ9ªá{þÐý !]eEé?oöžIžÃ—Ô®¾§*Ø*Àr°*¦so¾IžÃ§Ôæ•ð>BþPÖ7?„t%Yégò&žYž¬—dTBðÝÀ¾`€3Î ©×é.øêÕ8žY­—äR‰„ïú S‹„Ô'ÔÓÕtw[ E¾Iží§TWŽŠøžBaa_0À—Ã@”®ÖGÖÜ|/ÖžIž”—U‰ìðÞµaa_¨ ðìgÀÌɯžïžIž¬—Ô Ï]û€Ô½&€³"«ôʨ†žY­—dW ¯xŸRXØ pò ÂötxŠIÿ|¡žYƒ—ä&$Þ§ú ˜ßwÀös‹^[ûHž žIÕ—dUŽìð>a¡ß@Q€ü±] ž¯íO~¸¦2žIžø—!$ÔãÞµ¡Â¾ €ùïˆÈŠ Ùði-JžIù·U -¸O† û€8¾H`|Û+EUþm¾Ižø—U _pŸr¨°/h€ã®SR•tÅ„¯Ï¾I¹·¡ZpŸêÐÐo èß)‘U•U¥Š¿«¤¾Ižø—U_pŸê°°/èú×Ù€®Êª²¢3»^MžIá[²Tñÿ³«Á¾ + oÿ1‚)¥J&4Þ>Xž9釩âï…³À¾À]“"Œ:]«žIîèC .þ?°wÕè7 þ¹{@0åLÙöí5÷¼¬¾Iñ‡d©âÿgè7 þØÀ”»S¥ÔÆK OW¾Iá‡dT‚/œ]ôÐùòêRÝ£½©Ò¾AŸÜÓžIîìC²qñ÷‰¹O=Ðo@X͘éÉ”íYÙ÷_‘~9îñCâèò0ö÷ô{€8}Á,u²^$žIîôC2.þ}`œóôÐÇ5@°Ëßö矛SžInìS’Qñïæ>yàhôoå³ 0!Ý›®¤*ËÖMžInôS’QñïsŸ=ð@ÿuÿ±Ò½éŠU)žzžIîæKRrþ 0¿ Ðï пîRÝ¥T©û™œq~9nþ[âàü`þf‚?àðüå7ßöíp`ü4ž9îé[ÒèüÓ`~UÀà¼ýñ)¦”&˜5êžInî[bîìû;ÌSè÷Ø R¯ÓW}å:*žInþ[ÂÑù/Àü*ÀàŒ÷øŠ) ¤BUCUE©2û)žInñ[âèü`~ ð¤Z@”60BêãT”ŠØ¯§~9nå[ åïóû þ€¾€~ïi`B:²"ËÕ!~9nã÷ÿJùÓ`+àPÖK—„RHvüË žIné[â…óÏ»*øêÀÃÎ}Sj,¥Jº®žI®ñ[âFùw€Yü*Àå…Z&¤z›*V¥iL›ÆúžInå[ÂkÊ?öÙ¿€ °¾O`Bª»©’®¤3—Qß~9®Ó÷ÿšò{€yú? þXßR|2åŒoöw^)®ç·_ Êï Ì£ö& Œû0É~9îË—?ç÷æ€_Ïÿ¯ãgг½¶hùð?žIîãÇÿZÉg@9U]öýge{ßµ÷ûœ?ÚÀš»ž9§ÿ@Š? ãTóH¿V­ |Ùv§ÝÍï¿Ý€‡® ~9÷—?@—÷å=~\¯¸šœMÚ~Ê=ŽÀyky^)î··.·k”ã_ë€ÄÛË}Ëwù~9·? .Ï Òù,ûß°²J+eåÚÍ lÍWž9ç·¸¼7H§:èc?ài“b¼/vÊŸ®K,ÞО9×· .ï Ò©øèÀ^ÃõðâÔzï÷çµ >º½ž~9îû—?@å÷f ô à¯qOÃeï篻¿=ßÐ^)î÷/? Ÿ^ÃúðPq›ß¼T€÷/g^)î×/¿ ß®a„^ß'Àú?üµgz(~9îÛ/¿À?.0é×õ ²ÂˆÈˆ0ºÁmK(~9îÇ—?‚[˜%@ Àü4\a‰†qxè¶~9îÛ/¿À?¶°é×ù–@V„‡ñk¿¢*^)î/? Ÿ^`é€õö€Í¿\ãúçÚ\l^)î/? _®`O€®º0$×9`XÇ~)î÷/¿ ]®aÒ¯èß™€åÎþyöRúŒu¦0~9î÷/?`_¶°BXðbi`Ý_V9N7}9!-~9î÷/?‚/[Ø!,xÚ ÛâK§ÛØšŸ¤%^)n/? _.°éWøõË]ÿ<[îZ÷Aw^nÏ/¿ ïûá!œž¿Wðk^-^)n/? _FØBé—xýÃXîú÷zò1ÿ{¸~)î÷/?Ðe„=Ò/üèååí×§öR9éÿ¯—~)î/?Ðe  „e€üñu@·Å—þN7«½ò‚˜^)®Ï/? ”/Øs8~©ë¥¦Àòåã×ÙK©L-,^nÏ/`o€ýŒð€Nô5Cø‹KÌÈÊ’^nÏO¿ o„³~h€þß¡à︫™ ã:ÿã ^)®¯/¿`/ÀžÃé8[%`…qQÊIDÌãúëkJ~)î¯/¿`áìQ ý"o‡€¬0Š(£ˆ ”Ì{E)^)®¯/¿ /À^GÒ/ –ãNâN¥Ú £Î¤^nÏ/`/À~:¢@èàÏÇúi¬ì¿x-m^nÏ/`à!ìÕÈBîxº üþdSdC$^)®¯/¿ Ïà¬# Ò/¼þÙX>Ÿ?Ÿ?7?s³û ƒ^)nÏ/¿`a~ƒ=G¤_üÃ`¹ëß?¿ßŸÔÆ.Ng^)n/¿ a~‹8û‘ý€Ê—ÝãÛã_ ´¬ÝËÓøOggSÀø¿2W#ÉÒ6b,',-.,&,---'*...+),.-+',-,+$*//,(),--*(,,+'$++,*%*+-+&(,++*&*,,*$)++*')++,)'-+,*&+)))#'(''&%,)+'$+^nÏOà¿Ã~Fd€ô ë§îj»½:·Ù册(>®Ï/¿``^pžy€ë=öX?0àÕÔ^®¯/à {Y ý@¿Pò!vP*úèÞ^)®¯/¿`a~ƒ³Ž(H¿Ð– [½Ÿþ}ºÙ~›ñßòù^nÏO¿ƒ$óœÕH‚ô ýa²Õû¿O7ÛŸ{øå|>®O¿Rx$ì'# ¤_àäz X±CÔ)Mþ8 .Ï/?)ao#ÿ@Ww—¤¨héê>./¿`ú„ýdÔÛ ý€ùî°ÂC”RT¶h›J^®Ï/ƒjÃ^GH¿ ÿg@¶Ûý‹ß§‹3Ŷ¾Í^®Ï/ƒrÁ^ „…ðÙéûퟟ>2=c~ Â>./¿ƒº„ýdÔ³ ýÿ»´ÚN¿%æÛå„çO§ .Ï/¿ƒZÁÞF}áÀ#W¤¯þG > .Ï/ƒ£àìG= „ ßÔ@›ËLo‹ª« l>No¿ƒrÃ~2êH¿Ðý[ä®çãŸg¥T©´æù>./¿ƒ4ìýÈéø¡f wöòöÿ{»’®¼éøB>./¿ƒBÃÞ:¤_hÿžrù|üó£’*½™ßµ` NÏ/¿C„°ÎõÛ@è€ß;Ðæò…-ÛaÌ< NÏo¿Cî3G}„p0ÀÕ$à¨#S±‚Y±]/>NÏo¿CÚÄÙF]éàüS[ì”xO¥–Ž>NÏ/¿C…0&Î6²@ú`ü{¨íöêËo²½¶?í/Ël>.Ï/¿CvÁY"¤_ÐþÏZm§ßœ[ÔáÛ 'Ú¤ N/?ƒ…(ï3Ǽ ¤_0€}˲ÂU…^æþøOßâ7œAÔÛ@àRw*Àº/á&F  Ž/¿CÎÁYÇl ¤_`€Ý5rôItL/­Ó—¿¢S> NÏOÃaNÜKDAúô·j&Xá!þØI!ÅS_Ñ!> NÏOÃå8K¤ô ðaߡøc'¯R|à»h Ž/?à ÊÏÁ=G¿€ô °I@.GqqºJ…ºÑáþøO?à ½ø[T9P|ÿT‚€›óÙù/?ÃQþî)ª=€þy–˜^ŠÞå÷¸ ŽO?êü{âÌQÑ ýNÞ7Pm±7çÖpòÚÞÞ-[% ŽO?êüÝ8sô H¿€ùÇ€Úê«sßäzm?3ÕæÛ Ž/?Ã!Ê¿÷UÒ/ ß'ˇxO‡~Á¡v”þøO?Ãz©7nѯXß°88²$‘Ž.~HþøM/?Ãr©7Žèk „ú‹Àß0~Ù†Jþèïo?÷|éoqD=€ºµðW§¢T(õö¸áÚþøm÷O?ÿeäâolA@èú© œyz¹ž¯Ãÿévz7þøm÷O?ÿeä’ol€ÐxÚ“áìO/·¨ÃkûWžlç^þèí÷?Ã7|é7Ž@@èèý¥ª*J…­>ó ÞØí÷?ÿcø2ß8"„€ËÝÁŠ,Iå‚“Þèí÷?ÿeä2¿Øb{€€ùú€R¡TH]V{’þèí÷?Ã7|é7Ž` ôú¿@›óæ´*g|Û_}´ þèíÞoA ý4ÀzÓ°ÜígLOs9SÊ\["¯oþèí?Ã3r©Gh ôÀ·ÏÌù]³‹3¾íWM­êÞØm¿Ã™o‘B8Sj@—B©P 9à‹$ÞØmOÃJYòÕ á Ð÷Àz0¥L0ÈɪzÞè­O¿ÃJYò½ =Ÿ¨†©/«*+²¢×ú;<þèmOC¤Lƒ¼EwB '•àŸ_1Ý8}Lµô:$Þèm¿Ãލd@€›ý™˜¾*+fEMáK[¾Ø­O¿C¤Ìy‹j„p€ðï7€½^J•RÁ„}3Gj¾È­ÏO¿ÃH^Nòµ ä pûH!…„ÔyóúÞØ­ÏOƒ¥Ìñˆ„p ðú¥°¾^nîS6ÁèãŸÞè­ÏO¿C¤Œ“|Dׄ@üýØëR•t%LIgÍû ÞØ­ÏOC$ƒxDõB |ƒ õß-5ö6ULH¹¹¨¾Ø­ÏO¿`¥Œ…|Daá*À*.üE¦ÜÜ“ ¦ÔüËp žÈ-O?àR.,Ä# »! ¦ëÀ,ôa %¾È-ÏO¿`¥Œx‹ÂÂ4ÀqÕÀ…]ÎôØ>üœúe¾Ø-ÏO¿àRøKÄ#FU „h€~[À_\¤J©îT0aŸ…3¾Ø-O¿`¥Œ…xD¡á ÿ³©JSo:˜0«Ájç¾È­Ï¿ ðñ…T „h€¾÷>þ"UjìNÂåWØžÈ-ÏO¿ ¥ð/oQØ_ äàöé&€ô%°bÝÉžÈ-O¿ ¤ðñˆÂcáøòà„T· &0M×F¾È-Ï/¿ ¥ð/àˆÂZ á ® G&ÒWµŽY‘U¹¹FI¾È-ÏO¿ ¤ðñ…lcá €9¦éãôRHý‘\¤žÈ-O¿ Œð?àˆÂºcሗŸ82‘¾ªuÌ’Vô÷: ž¸-ÏO¿€RøVâ- ãs !@ôW&˜’ &¤n+ô•ž¸-ÏÏï5©|QÈÏ„\·¤B )"e7 žÈ-O¿À¥ð—‰·(¬w !€ °þZÀµv9»k¶o»ëz }žÈ­·_ Fù.8bü;†@Xð©R¦'LH}op džÈ-ÏO¿@¥ðÓo1ê;†€õȔ3=ß“îXË ~¸-¿Fùö8¢ØX@€ûCÐ¥o{B yæÈ=´~¨-Ïï(•ó[àv !·vL0ÁXçÕò~¸Mïu©|Â- €1„p÷À€JU…Ò mkJž¸-Ï丹Â- 0Ch¼|)À)§Ê)§_=^p䞸-Ïï•TÞ/Â- °1†ÐÈb€rzMwÊ©j+óHÀu~¨-Ï×w+•ûEzЬCC€)¤B©P )dÍ<^˜-Ï××JeÿNzŠÌ;†Ð_Ô€ë¾^¨M×g#ûƒôø=†Ðð¶ ”° K(¡±à€ ~¸MÏ÷÷ãþEzŠÿz ¡°ÿ-€SN9å”S̶6Ç~¨MÏ×w‹rÿ"=EƒŒ!4öï8U¥•rÊ)qª4€u^¨×7Ç8¿Sž¢€›, 4<8åô@”Sos>˜^˜-ÏçWJ«‰”§(ÀðBÄÞB )¤øê7^˜Ï×׫”§(`sÆ2\ÿ§œrŠPyÙº^¨×7ÆùòüÈB8X/paBª»©” &¤Ž¬{0^¨×7V)oQ@§ÇüÑX±/Æ‹¢­Éï£À:^˜Ïç7 «‰”·(à¦G€.LHu§+éŠ &÷ÿ‹>˜Ïça5å- ˜õBøjlßöí@˜ým•>ˆÏçW…WÛPÞ¢€î!€_8;°¢=€ƒâ >˜õÏ×a5GÚ[0ª áÀ³ €)¤ïð:¼!Å¥†+OggST]ø¿2W”™¤`+'($),,(&(*,)&')+Aª>˜õÏ×× «ioQ@k€§"€íÛ~¦œ)Û¾í¯xóœ >˜õÏ×WªÕHoQ@©À¿îgÀ)§œrºS¶Û{mDˆõÏçG¯¶¡|E£ „ÐHH!J…RHl7ã¼þwõ¯Ç§„W¹&½E…ŠJÈ/ âjçˆõÏÇ×…WÛRÞ¢€FE%ä@b:)¤B©P )Âß;z>ˆÏçW¯±¡|Åþ.€þ¦º¶ïð:¼¶bèÏ(ˆõÏÇW…׸PÞ¢€€E%„>%¦°}Ûëð:ùRÈé~*xÏû×5ªl)_Q@«¢r€}JH!…R¡THyÎ$—þgÏÛ·iTûŠòÿO¢r ¬o> ZŽ'þwÏÛ74ª¼¾âãÏŠJÈî¤B )” ) ñ…&þwÛWhÔ¸&}ÅÞE%ä@ô¿¥Œoû¶oû¶›ëT»þwÛ×5ª]“¾âCŸJúÏ©o|ÛÏ”3e;À n\þgÏÛWÕ«½"}žA#ä®›Û·}Û·¬ØýnmÞgÏë·iTzEùо‹ŽØ"!…°|ÞWÏë·«Wù†òÅûÑr€/ß&ؾ „.ÜNÞgËW=j¼&ýˆb[4Bpø &˜`"+˜`J¿Ÿ¿ÞgË·<ª|&ýˆ½ //LdEV”³"° \?žWu·ÛgëQéšòÿfÔXÅ/DþèƒpøT¢.¾š·÷Ú«–χ^ë^¼hÿçeFï£N¯*ú+.>¶ñósÍ­Òk|ýÏì'þ›ø÷•cË?¯wN­ÖÂUÿEs1¯À_Tôlœh¶d)-âÅ⿵ ?ÿ­Mø¹øomàüKøÿÒö /*/šà¹øomjü/mjü/mjäM-÷ ¿hj¹ùES·{"?çS·{"?çS·6áçâ_Ú`$mediascanner2-0.111+16.04.20160317/test/media/fake_root/bin/0000755000015600001650000000000012672422030023350 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/media/fake_root/usr/0000755000015600001650000000000012672422030023411 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/media/fake_root/var/0000755000015600001650000000000012672422030023370 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/media/playlist.m3u0000644000015600001650000000004012672421600023112 0ustar pbuserpbgroup00000000000000# This is a playlist file1.mp3 mediascanner2-0.111+16.04.20160317/test/media/image1.jpg0000644000015600001650000014023112672421600022477 0ustar pbuserpbgroup00000000000000ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿáLpExifII* †Œ›£(2«i‡¿%ˆ#û#CanonCanon EOS 450DHH2013:01:04 08:25:46š‚9‚A"ˆ'ˆ@0221I]‘’ q’y’ ’ ’ ’‰|’t‘†’ø!’13‘’13’’13 0100  °   Ý#¢#¢#¢¤¤¤¤ 8 2013:01:04 08:25:462013:01:04 08:25:46`7 /qy"ÅÔ  ì P˘  v€  &0ƒ“t•@¤–ä—ô˜ô™5ü Ðªì´Ðàø@Ë@° @¶ @ ¼ @üÜ @ Ø!^ÿÿÿÿ07 Lÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ7o·"!D ìÿ l˜ lSøÿÿÿÿÿÿÿÿCanon EOS 450DFirmware Version 1.1.0ªªS0S0hSS˜’pw7»»@¶q #ÚìkôhûÌÌÿP òð0[07‘u’1.1.061ŽddQ dedûžï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þ ’æP‚º%•VEF-S18-55mm f/3.5-5.6 IS Ÿp` ° ° sss¢È¢sss™™™iÇi™™™™û^ý^ý¢¢gA¿þ[¥ýA¿þÿÿÿ0ÿÿÿÿp¶qEF-S18-55mm f/3.5-5.6 ISK0336838Ô 8,8ÿÿP ž<"Ø<  Ï6 œ <x<·ïîù¿¼,&çå±ÿÿ÷µ Üêé$qtt8¥ ¢ 0òâ Û V Ïèè${[Y×xX U ia7ia7eý^7¼½P ÒXe ;pÈhhn € Ô À¼½E©  ¦-2.ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}~þdä”*–þoÄ'Ïþˆyl íþ—RX'ÿ´pBÿÂïàdÿÕÊP•ÿó›\Ìÿhh@7Ø9f ¬ x™Þ€ £ÁÁ¸ Õó£ð Fvi` ô"ÿÿÿÿ{D=5)+6:"- ,) 5B G>70*#'$O®^L8'(>/-BEF@Q:H@6HU"'(«”‚o]JP?>:6Af®^M9&(B-,AFDAQ9I@3HQ$*)®–ƒo^JP=:51:dc3&"! &/!%  "q\NA5(* ' " Èèg  .±<#]â‡c€¨³ }Ò"õÿˆ.Ýÿ {ÿ"2ïW‚Ué¼ÿÿ£âÚÔŽ~&yÌ  &Ø  dyýý×;'€/Anu+H S‚#Eš#²#º# Ò#Kq™ › â›.2013:01:04R980100I$Q$(Y$(HHÿØÿÄ¢  }!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÛ„        ÿÀx !ÿÚ ?üÓ ü^cŠAáÍ_d 27Ø`z}*ܼu-ëZ'…uŸµ ƒ €†àx>€ô—2 ¥øc⸴ M¹‰ˆðÃ}y5ÏÂOÙÚµÍ׆õhíÔn2´'hÇ_­C³('€¼C#*¦‘xKg/§Zºÿ |aÀ‚OjË)çi„þ¨¢è,:ïá/Œ¬7¼ðæ©È.è»ßçUÃoº£ÿ~È,I?ÂßÛEæ\x{T7ÜÑ2;U¸~ xæâš k±EqÚXg#?ü¨ºVG{ðƒÆšl¾]ÿ†µkwÎ6É œ‘ëÐþU·Â¯ÞMVÞÕI3±DG-ޏõ¢è,ÆÜ|.ñeª–¸Ð5$P@$ÅОƫ·Ãÿ¢3>|ª§)Œvþ†‹ ±ðN¼ J¼Èê6Rk¡.ïi$·¸¦+€ð^º@#K¼ ÷ÙMÿ„;[ ƒ¦]çÓeÉ$ð7ˆ!Ž9%ÒoQ$ÉBÉØãŠ»7ÂßÛ˜ÄÞÔÔÉ•”rÈ3“ø`þT›IÙ“*‘ŽìûÎ róQ¼û_YY@‘\îr)÷W‚N8Ý’3Z_k½óo§¾K2H#Žç!ä`l©ØU”p ‡?6-3¥”|%i ÞÝêâ»›¿6xä‚ò LHX´{UЍ+‚ÄÎF:V?‹|#§ý³Q»´ÔìnæYGm:²ÆŠÙ GRê1Ôg9¹6<óI¾Ó|9¨) #<2 G;pÇæê>§Ú»Í=×ÄQÛÜ[¬‰o¶ö¡TÉÏL‚v©lc9ŒU‹WztbÕ/.ŸÛ :³+ ¶ìOÎr{¾µaË"[\ÝìIH;g #Aó¬}I=xÁÅv&ð¬ûä›JÔ/tèmíÝäY”îi@2Æ{1 NNpqŽ+NÛÆ“YNmmc„»‹y’„‚v’Hã,ÀqŽ(jà´W9]C]—WÕ¢ºh–S}¬B|àŒ$äqÔŒúòhÐïd¶q6¢žk͹£\€’¶vã'$óÓ©iX–ú˜~'ÔØ_ÉåÍsN3"°ÈR3ƒÈ<}zú×-fÆ[çžá£eÚÊÄã¦È«Š"^FÀÐÛê"î’HÈòÛ ž½FGù›."Êͳ$Q°UD ’9Á<ŒààPž­¾†dÖ¯”m“Ë-ò“œ'¯{ŽÈ“ìÛI  ÃkþÙ«a+–$ÓüÛT’A…;v‡;@öÁãéõ¯D·Öîu¿Zi°¬ w§Å-¤mŸ1¡g1dÎÆAlg“XâaË&þù¦¾[˜×ŒZR}?]?S6_Nc–í¬$Hc d,£|œÇ·nAÀ8Éã©,¯­šçTº¶‚Wýã@ʤ2¡Pûz`å—$dgÜ‚—‘×{&¨é’ßYªj„vªÜ( Ìãp«Ø(ÈÉé[ðèöö³ýžâ;f†e\}ža•¥àœd ® ìAë¸ÎÛš+t9¥±Rº[+8 ¢“,pÝÛŸ;nÜ•F=ÿ“×4K¹ ™l`¾xVá{kl(HÔüÀ6NQþqNä²Ö¿ª˜Œ¨Ñ†DÚK9$ƒ’3êHgÿ¬E?Ãpɪ¡¶·Ñ,昰Î/ ‚2ùÈééÛó5c5 ð~4%µ JÜÆÏ‚ö çr± 1÷™¿‹ß‘ 5ë&/³Üµµ¢#I[¹ÝŒ›qÿtsÎ=3ʽô&ÚÂë1KqqËLÒ‡P'ŽbYHl0qÎz{ãÞ·&¿ÑÖ(‘¬òoòÞÙ 6þpÛñÈcÇAÓ4Ærþ*ÕÅÍÒo¶‡í “#¦r¿1#±9$óÜT-Ob¢c‘!] ¢žI9ÉÎÆ{ ­ŒÞ¦Œ‚KˆÚàCij­±8—hŒõQ*¶ lÿ¾‡¤þ’¹½É ‚kÃ4H¢½[s¬Å‡ƒÈO÷jÜ–ëa2‹×ÜXÐÛd1$œp8ÈÊuÇ5*â[’ÛH°\Kû½’*²¢D Ýó½A#hØ;g®Z5,–â{{‰m„[£$¹Œ•ã 0psëØ{UnXö»}JâK¤Ùo±œcÌ}Øn=î½°{Vž‰©Om4žTLŒòBÎ9 GR‘ƒÏ¨ª±w=;ÃØèÍe{©Ì·lnÅÁ“c9RèÜqœÉ<A¬/ˆšXm[Ì šdfó#U;ä wc•*¾ýG­JZ’ô<ËSÓþßmq"—tn¹Æþ2„ƒ×“ƒïÁíÊYY›Ý.Ò/‘1¸–?0`OàðÃ'ÐV—Ћ–æ€ÀÍ”*…\ï=ƒòôQÛ·<Óá·–hÕŸ~ÉäS·‚1ƒØÿ,÷¤ŸpHé8džéQvM$›"Vȃê:gùþ×[‹ÛˆcŽ.§;J,7€õ­$ÂçGá…þ$ñ=Â.™¤ÎìŽßy–2Xž n#ÔŒW hÿ³ Åû\Ÿx¿Hð³"±—e¼·NIè6€rpMeZ³§Úæu'ËaúìÕáinLÚ׋.µDc…`rÙÁ\¶ïáàõíéÿ ü#¦kWú—6³˜@ûN¨Ì¨™8ÝŽ3Œw¨¥R¬Ó¶Êß3–¥iz=ÜØùP¤¶®-–_0À>Ñ”ãqÁ㎨3(7 d¢E’c ƒï*€äàc*רÅhÙßan´y,á¸1M=ºÌ<„*°“'nì.zŠ©™p&wh$KŸõHޏP–àü¡±žsŒŸÂ¡ åc¤k/Tónd‘o„C+dŒ— 6†`GãôàÓÔï¬a‚FYâO˜³£+6ÿ˜Ž6žS9ý £E¢Ô¥¥xŽÛZ²’Ù,¤yi½ÕJ¨$ãw,zÇ=Ew^³—HÔÙ£åÃállœÆ}½ˆïCVQêz~(e†Ým†ÆfHÛ÷{¶SqÝ€9^ÈN9ÍsÌé4‰.ŸV€(]pÉŒVÀ`œg‘yà)£Üó½bÙ<£Ù ,]Úa€Ä€8-Ô÷ã¡þqé“ÙYÙ*šxÔ|Ì\©%Ž2>¼ž¾µh†v~Ö.¼9/‰ Ñ¯Ž€eÁ¾-ûµ)òœ¶:dŽ ®çƒþizÖ¡¨êöðÀA!–B ã*\( O©ÏJN$Nj²Iáÿ†~Ó^ææØÞy*Éz†nBãjªwôâ½ ÁZo†<5ª®Ù§‡äžØ½¼ñ!TV\gžA#¡ÅTn2ŽÇ›*“¨ísñ·k¦xƒQÕ<1{,…‘EòŽÀC#ìx8Ï­]»™¾(­¦—¡\Aa«Íq<1>á÷6†O3<<‘ŒðN9®\L!);õz|ŸùšÔ’P·¡äSx¾ÃáìÛ\¤òjos#M(Á\¤…0r~éÃò=s]Ä]PÙ[èš–5åí•ê6ËdM!ÎÂO\nzhœ=¯,¥ßOš±¥H§¹ãZœ‘E½‚X 1_–LnÀó»Üçï ­™Ò: $’M–Ú.wà/R@''ZIo±ÄÚ¤:¬÷[ Ñ¥®žÊÓ"K1ÆFâ„ȱ9®]m¡ŽÍ®$”ù¹yÈ]á—,Ã$öÆN?ÚýkUm šaÛ¥øÄ<µ8aßÜã?Zößø¶ßJÒžÂkd½µ$J³nċ؂HìpAëÖ©«‰+žÉ‰ôývÒs£$Hž\³ÆÉ»xå8nß0#ÈçÅ×ñøtêCÚKÁžCÓø†=øcž•{-™àG«qöŸjÿÙzr&ø­à_6îlôU8Lî$4„ `€ÕÓxÒõ­¤ÿ„oI·»ŸÉ)öG÷Òœr }ÕCÀŽ3É5†;57Üú.ኹÅ^KÚ?‹=àWǯ‰?ÓVÓäÒí5ŸÉ,³¶™ª!Q’6XÆê8ËJà©9àšï—Ç—¾?Ñtïè׺/‡ok.M“$/#bTÀɌDZ‚‘òØÀ 3˜B¤9)?{uò×ñíÜú|ÕÁ·S¿t×,—[7kù%£oµÏZx´}o´{{½—Ñék©Äc`ñJýO;ƒÈé×¥bøû\Ôܲ«nä¶Aã&´²³ä‡0Dî¯åÆü‡æÎO`Ä Œü¹Æ+KÝžÅùŠ·¾"¶Ó,­”È2ŠíÁ *í+߃Œ‘pxÀÀÔ|JfšÞ4´G. 0AÊ‘*9'åëý R‹ëð“âˆôéöi)óÎ&ÂHƒnY¶Ÿ›¦:û{ÔšwìÓãÜj3i·%ÒZY5äø¹#ÎW?Öp~^à8ë…'c)b"9Õü ¤é‡Q¾6)n¹ EÔAÕÆ e·ö»Ö‡Ãýz 7–&‚ µ¹ŒÆÁ˜‡ ƒÐã§óéYÎ.ƱšzÍ¡üAТ¶‡ûJD±G¾&fê S¹Žr %¸ÆqŸQ^GñSâÞ6Ô-m<6HÓ¢ÜLÎm3|Ø<ã#8ïÀï1j>ôŽœ6¦&|´Ö¿ðls¶tvN‰1%±—\nÈ?ýzô?øÊïÃPý›KA4Í»æ‘I ßùW'öדU4‹þ´>ãÅ´¸k•a’•dš³Ùy»}éo¯CÓì5ýQôdŠïK}RãQ&ßÉÀ“qêÄ6GÌp=ú×±ü<ðퟄÚÓ]c~|L ™a%#\`ìéÔdzä;áˆÈ¡„ÅÆ¼ºµ·gÓäyõ|MÄãrYá+¯ÞÉò¹ig¾ÙÓ£º³4m/ôËP_A£ÛéwFÀ^$n® &ð23“Ç=ÿkMv?Ése ”‚í­š_´¤8û z¼wȯJ“Œäžš6­ý[Ÿ™ò®tG[žÃÂöº“[‹ÍêÌy·vò¯œµ²O €¿…s_>%Èö2[èóÞÝév÷1ζ’1s¹)Ü•$úqDáì›qí¯ëú\±åvè3Å?´¦…ñ ú×ÄgP–xO—å¦>a|¨êÄûæ» ℚä:x7ÐéQ0T’tÊvÅÜ å\`óžq^n6„”¢®Ûg=XòÆ-õ>{Ð~ø£Åö6™±„<&Q¥Ã6Nxq÷ò99üz÷øh×—ZííË\E¶WÓíöFaà.ÌrØÆxû½x¯QÑqWîzXŠ—)™ñ3À~±– }+K¹·—Oq-ÌÁžo0àVgÈV ÀŒ{ƒUõ«Khµ­ü?¥$÷׬–웲 ]ÜÀb:ùUÑ¢´ÔÑÞÖ3¼iðçÄ_¼nÚ-ûèriòÝ%ìÇÎIn¦F݈ÁH*€Ùå˜ wóHÿcíb;yåÕ|X`Cn'A«Èeˆ#ïCÏÐw­\”w& š.Exd(Òþ[{ßÇIÒZÙT±aœà¿cÚ¤ø³û%ÅðŸËÔ[Z»Ô4Ë%ø…aÚN>\n<óÇ<Ъ]6¦ÔZÔðˆÌÒ^Jš2Êm™¶¬“ Ôdþ•Ò$‘ªDúË+Xb@¤öÀ©ëÖ›§Ó‘SÎ+᩺4myi²ºë¹è>ø]⟣\hº\†Ø>Ø÷}2GOÀ~8¯E›áOˆt .Êáô© Go-­Ñ×|ޤ†ˆÀéxïéÔ«rûÖ>ziÖ¬Üúõ}OGѾ&ßO‰<=´¶ó6S)+HÍK¿VïߎÕÑÍãí{þèSH{“zDK q!ˆÆÞãrHùMpÖ«Rræ{ÿÃZ>Í$…ºÕo®üau©ZÁ âñ­šO37‡rù‡ÏŒ ÉÀqŒcÔàM®ø“SÕït­ G—X¸’%ˆ´Ñ6mFàžG*¼õÀ&±övšqIžß £ñ5±o@mSKÒídñ Ù[Z]—&Ø\+µ¼›‚êÇê+Oá­œ¾»¸0[ZÙßÞÛì´pë!–|`sÑc`Ã$ô†(›ä’’{Ûñÿ&Dê|^v8±¢ø‰ÎÍ/Òîêá÷²rŽHÜñ5þ½àÿÚéñÛ5Å´,!Æ"TLn §Bû×-‰Ý'ªjÿžŸq7æv]D±ñ…–—¥èI¤[‰¤ÃIæ;ì9_”‘Ðð@÷ðÏÿ׈æýoï$•e´Ù¯”¶º–ÈŒž¸È¯UÉik ÒŸSÏôˆüQáLèsÛêWš‹Ì!|M™|¹©%cÆG\ãµeEýŸ­ÜéZ§ƒ¦šM-guÔdf.­9Aº=Äî!¸ô=JìuÆ£rw=›X†o ÜãÃ'ÉÓîÍ»?y—Ám¹?žzã5ÎE®Ì÷¶óX\ÛÁpTHmåÎøI9TuaÂço?E[+ö:b”)¶Ñ[Å^wñ&ŽòȺ‹I=Ün®VcÄ™ÜW¡ê0}Ei¯Ãm+ã>w¢xÕuWžÒ$žÒÂ+‡ ØL¬Œ¹ÈS…÷>µ­4å¡Á]¶î·Lâ›á¯„†‘¤ÜÿÂifÐ[Éi*CoóË"¨Û)ÎræÉÎrqéXÚw¿¶gK=;K‘Õ0ŒÀF$„… Û–=IéYFé{ÛP‚¥inß|7Ô>ø–M[ÂqÜM¤Í K2!UpyÎCÍ{6¤n¤mâai ðø³Ôcñ<+·}BÏí$;. 9ʶ*TôÐÚRqn,åµ½›{›Íbs:5ºNᥠ•HÆ3Ã|§€z×›xkÅ7SiòýºÖâ!>8Ù1¿“Ç^¼þu†#H¶üÄê{º§„üIŸqª8±¸·–Ɇ8Ø”•שà=ë¢ð®«ªeZý¡næîYÐ"ï€0n lzw=ë–toÆ+¯æíýzóJ&=—ˆ5/ xžÚëF¿‚öìÝ…ȹy€F= ç¿Ò­|Vð-׈µ»mxÝ”ÐÓÍl’U,€“&ÓÈþ"ÃùVXjnkEkmëm¯!{D§Ì-=RßÃr}¢+ø–ß÷Ð9 ±Î=*~¦¯øNmB-B?°¶‡†ìàxç´¸o”³r¨ ü?½Þ½:'ÌÕÏOË2›[lsZÌw×ZÛ[Xê–º6©peÿA™Š"à‘”+ÂFïXRh·:/Úu{é–ât¼¶/f“oH×nÒW=ˆÎ3ÆjÒ1³8’Ú]÷ÿ3”ð…¯¼ñ*å$ûM׆µ&7ú\ÑÈUa9ó23ÀP} vú¦‰©Øî·²6zÅ¥ÃÝ™”3—óC¼ç¦YGQÒ’k™£²nðM¥ëS½õì·“mÐý¬É,@'¶NOzÆñv±Н¬ìü7 XË|¹¦} bV’0[qÇZn[¤¯sÂ똓â$:®¥¥iÑdÛ Jþ‘ö•Þ TÚ2äŸ_j¡àï \iwW§Æ7ÛXƒ˜Ð»FÊe HÇPkÅÊ¢SC§6ôòfµÞƒwâYA¯êǤÜA¶$1eŒ ·R٠ɬá¤ÞÙ͉2ý®âòFÜÑJ̱Æä‚3ZÊ“•Ú}I¨ÖÌ䮬5‹ÏTÕåþÖ±Ò”ºÛ6UÐäb@F~QǺÝ;Ä7VÞ »¾ÔK6êåÛÞËòÄJ’ƒÈŒ¬õ…j5%¡Ï^øoûKIÒæheÓ㽻Ȼ¾UÈÚ1¼çõ5½ª^húMæ”lô›S × Â¨VWïH«ÉVäýMiÞë]'v‘WÇ7·º‰Ìt—7ͧ¿“ ¢±`Ž8ï\–½ñZÇH3XnõƽÁò¼Â¬¤dOr ¥DæÓ{ëo›ÿ#IÚkÐî>øGL¸]÷\å‡R•™-ãNCp z–7#÷®³Æ6šž¯¤XÇztû8R)P¦÷uÁa°ú`®G±¬¤Ôa'n¶ÿ‡8ç.ZœÖ¿õÿá4Ÿ\¯Â4닦ډp‡Ç&† 2¶zšä¼o¥Z·_JÖu x…¤æÞÕôsí! ŽG<jÚSwG°æœbºÁýÓBÕô»keâ]m/u]‹mn.­ñ*¶s¹_$Ÿlg5_Åž(Òu_Oe«_YÄbŒj£ dœJp j1¼u犾y4ÕŽhòsz·„4-)të=[U¼Õ¤°Âñ±ˆÐ“–A»yH÷úV‡“º¦•«Á§k•ƼmYn bˆ’ÚØ‰ŽœÇ=>•)Iô:ã6™ëÞ[k/‡koªßJm5…íæ¸‘RCòÆÉä`uÅÝxÒ4ø¥éæ·E‹eÅÃLd/1È ÛÏ@z©JJ×0ƒ‹¼OvñE®¹wá«›]cGÓe‡•\¦à6œ¤n*æu{ =%ê^4Y´ðAxlìrf*û‚îôÀ§^²‹wf ™»àÔ|/â AD–±ä¬L ÛRûž;öí\^¯„ôiÓÅ?ޤ¿!Öf„Ǹ’­Ûå$’ØîH5MG˜Ò¥Ö­˜–sxføý„Ûx€K,þ[Z™G˜Ä6~`ªGB{SîÆúÖúîïI¸šÊÒha¤»pPŒ…] ÀäÒiµbb¡ÔïÆÚN«¡¤2XÚÍ{a3Fèä¨'¢ŒŒ€»NMxwŽ<]q¦øÒ'A°µš2Þ^ñ¹fSјôóª«Oí“ {KE.> êZ£Çkig¥:ÄÌDid,HÎ'Šé¹a Ÿµ%½ŠÍ,Éçe(ÄŒsшìZÌñ–“càý¶§~y¾Z¼JYSq88ëšÖXx¸Ýœ°—¼•´G¡hÐiÂ9´mVöÆk½ñÁr_<¬IÆp; ×Ôìn¼Imk§k–ÍqxCGu(#0¾Wsq÷‡)ô"³ån7:Ýn][<öâÇUÒõËO±\ïÜ4qÉ*²ª•bœwÁ5ÛxƒU[?ÚéþhxžužãU •$`†éÇJÊpæN î:Ðç÷H4ý: SÆš®•er5%‰Y¤Œñ!ÝwÂõ:U‹ú}–‹âhä·¶KûËåK9rèn`£ âºj'$íÜÊ“ÕÜiW„ãÔt=+O[ïž6I74Š ä•ã¿þ:EgxWÇ:Ö«w{£¥²$²/ú#KnŠÑÌH\Ðdnü«™Es+oþeÅ©6™F¸Õ¡ úŵÂjg˜Šª…;9çœ?¥t·ºOˆ|gö9lü?qªj©‰¬âbYàäñ€ëÍa&£>nÛ™Ô”cnSó2ïãŽoØ›ßø‚b{½ä‡¾}}y¨bø±ã8"»Šk‘Çv1p«vàKþ÷<×iÛ)9nAÄŸÃ|·°øZKÅ9 tâ@ÞÎ{ ³gñwÆÚwÚþÁâ½~ßím¾àGy"‰O\¶'ëM6¶3qOt%¯ÅŸXÜ ìüQ®C09—N:¸ÿ¾!Io,ã_%É’?·IµÓÚFpF8Å Ml} ûoŠ>/³º[›_ëq\*”¥Ó†õÏz->(øÂÁå{k–í)̆+¹×žô]”õÜÞöø§ ÃÄ?$1Œ$cS›jŒcn㊥ñ×â&©—©xÛÄ÷1î¶[ùXn'­M©ÅlŠÒücñÔö±ÛMâï<±dŒÞɵIê@Ïvo¿ngóçñ׊^lmóP—v1Œg>œRòŠ{¢™øÇã¦*[ÅÞ"%I#7²pO^ôÙ~0øây¤–oøå‘B³µä„°‰ÍO*½ì;+Ü[OŒ^9°¸Ž{/x† £9I#½‘YO±¥¾?“PKé¾´È>1xæ×þ=¼]â¾mß%샟^µ<Š÷°¹ïbÓüwø‹"“ÆÞ'e+´ƒ'N˜ëì*Ü_´gÅ+{…žˆ>.Že&¥(`1޹¦âšk¸8¾‡ÿÙÿÀ@à"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ϲÒl£³ßrÎ0¤L›·Y4Á4³%þ_¹¹H$c­N.‹ùvW "ŸÖ^Hú$׆n.PØæ¦åXzi°ÚMyªrAÚ}}*MRÖÕ¥‰¼¶²1ØÿœÖ_|.ãiÏÌà ¿¿­[¹¿u -Ô‘ˆ†qm’O¸¤+KhD%­¾dmʃŒb·¾ÕæÚ[¹F   㱿çµUÒõ->Wu›ËQ¸4lzûŠ‚òšþK{I¤HK8$Êå±·–ä´³B7žù棸IštX# ®J‚­ž*9,$½¹dŽv,qßÜô"®hºj[_Gö³óY>o•¿ZÇŽÍøŽH_ ‚ rMkIªIk¿|`¨v‘ÆGzÚm6T¸3ÆU¡F7^k]†yïGPW¿½C¯ùŒ±…,ÙÇ¿ÿ^²õ'–Ei!‰b:ƒZ6Ö;cifˆ‚9hòzzÕW´˜ÀòõEŠžEgÅ\ìçh=søÔž]ä¦<ÇÐtcÅ=¬$Yü¸ÁTÀ''¨«BÞH›ËórÊ2M0±jÆÚU·$½%Q¹#Ó¢öðITSæ?'œú}j½œ1,ýãœç®1ÔRÇ ð^3Å'PIÜ:žÔ€£uh…ÌÒ«‘òô?^·¹Æ-ÌkóÜ‚¨o¡‘îc33ã;‡84á NæpEñÕ/,§xm∦ܓà1Ç|Va¿¼»`s‚6ÄÕ›y·¹îsÔRÜ@ê¢uÙŒ$»ï@Éo ºåÙ2Ø@ô¶3̪ cÀ>Æ­\‘âs¼à`r+2i&„º²1=>´¢–73Ý) Æ}qU¦°º¶¹ýâ© qóg5%»\>›·vû„œZs\3iò[˜Ë°mÁùÊúŠ`>?*âàJÐ(í<“Wo!ŠXÒ6à„;H·Ö°£•¢‹t|¾rA=Ek¶¤ ™£O20Ixé@\­š¢ð4„¤d|¸=sWíã[k¿2"$2ù1ýj;)¾ÜX”DüÆLtúTŠ‘[‡þc³äzæÑ£†öÚèÜ1… ŽŸJlW-Áu‘E`8àzUf¸*ë6ÔP7}}qVnoâyÒÝNì2Å9 ®ïB\,¦,‰å¶äVÅÇÍ*BFG$à Ô¾Ô­ãÛ¬qd(ô®~K¯2ä·–[qïM š6q4°“;¨PxÞ ñU‚ä™eb@`xÇj!¾Ù#G,9]¼…â©O:O.!fUÎá“@‹užP³:˜Q·#=ñé[:|‚pæ+…Û´£Ÿ\W=è”»Øä0=Gz½ivʯ¶ Í'CŽ‚ ;ºoDpœÛÞ›¹E ˆ‹€¿Ö£¼‹Î‹ _#=9Åe$ϱ៦zñL[”„‡À, ^™4Çhcf“kŽjfä2ã<‚;dÀ‘¹˜ƒŽ}é,7çaV<±ãÚ˜Û¢q–ܹëU¡Ú²a—"´$v«"¨b=OJr©l°ÀÈ'9¨°ñ¦r2*Ïš¬á‘’öˆ¿Án~¢“ÑÜOGr“÷ÈëŠ"iU‹ «¢Ð3yaɦ…îê àôÍPÈZw¸Æ÷8^ÅB#É` e™€#žMYU޹'ž¼P"¢nnzŠ‘Ýv•ã)“"’vüSíã&3SÉïLcC`c’E&ìäœm=¨ÙûÂAã8©•ãoN) ©9Á¥I’Iaϵ=_)ŽÞ´Õç€êhbÆt…‹Ê>èÇzŽêáçpÎzÔª»ˆ8ÅDñ…|`àu4Åa‘Nc‘] k²Ñ¯¡¹bÏnà3Œã5È~ìªPk ð¾  ½û;6Ø¥2}¬1n<ËtgR7WCo4³m©Áglë<²áJð0iúå¯Ù®ã¶Ü Û•än¡{k¦µóÁjEUËIRÏ5Êa¦æë‘žÔBò¦¤ÿ8ÚÎçFÚpº–)#òÓ9!ŽrO¥AioêIjS“¹ÑÌGµ-ÅÃÈfþjƒ–`võ¤w¼·»û*ºT“ÆTëZ'KkbÆ14OlÈ1ÉóbZÈ’ÊvÔ¤†{e1ËÊ—ÁÛøÔ 7œa¹P|±œ*ÅΡ%º/Úei§8d(Hú­ .N4õ1¼2;w(Èõªé3Ê„J’H6b9¢9ÿ&¥ö¢¹…Ú6FùaÜþ;ƒEœ·–öÿhµ’²î“?>ž”€¡ý±-¬â8â&3“†>¿tZ^¡kum,“:G*üü`W4Í=Åì“Kn±Är®ê™Ú;ê)"i,îaIeFŠã%Ãþ†ŽÊïQÒ.ÂÁ$Á¤l Ÿðª£[ ©™Ý£óy:-s·:ŒÓƒåBÂ8ú÷ª‘êšÜŠgÆ/åL‚ò†}’ª…Ú2’1ÇJϹŠÌaQ¤ #|æ*šáåFš\·!=¿­KpNÆ $ Éã4Bæybo”«"9ÈÛfuÃ{ûSHW_ âAÓ5µ•7íS{`[ŽòUì;AÈÁéõ«j²:bÃø»-b*È£æG¨>†´­wÁÌO^´ªcÔγ"œ®x8«Ê‘,†{<¡Â‚Íë×­$™¢ù_n ’@êinn"Ù–G’u'÷yÀÇáI€Øla2ïŽ6UþÇõjKpÃp*ñlÃ)#ÞªÈÞJá º‘ž2[¦KpªNïºYOZ`FÑ"GæFví8ÝœñøÔvÆl)ó òFüTq^¸Îàd–]½j6Ô›–/ºyV  äÃa°Î¤ÃÒ´­íÓìñ´8%¾bÁ³šÃK×,Æh¥ÿ„qý)ö×·´Š’¡€P°Hg?¾“q¶ã*îgm$‚i˜´„p@GLÕi¦••äóIPãôªæyíü£fÜsœP:íMá†ßlQ„‰Ô6âôö¬–EI%”­su>µR-J;°‚äÊv ­Ö¦û<2áYØ´ŸtgP;”¦Ýö€¢åËä}:Õ¤†HQ¤h²Ûr6ö¨o,­¡8߆SÓwéSÛ½œ‰²YåO÷[€=èG|W„3Æ#áð§Z]‹9ˆ0©,8,:U•¥±ó ²!+HªÓD­2ˇ ($÷)y1‘‚À@;J÷¤ƒÊˆ±XГ€2¿{ÞþŒcØ y óŠŠS†sª6€=jȪ‘‚…xÇõ^ ¥E| Gõª’Mt±«LìSÛõ¥‡d“ÂŒ\FO!½h ’K4Žë*9ùGBk.i>óqÏR+N[|4øÉQÀ`q²ÙDkʃžyÀ$ž$€®7g¡ôª¤‚C1ãúTï[pèxÀ¥ £ù Hö¦„Bî† U˜×vôä` pÄËaÓévnŒgç­Lö¬T¬q’Ÿw'®iK‘sÏÊÇ­[Žúâ5A(;Gc½Nndl ÏqŽ)5pµÁŒ„Æ )Þ €0}+6xœç$“È­s,â7}€ ƒŠkBÑÜr²J,IÜ¢#“ìîv““ŽGJO6l)a’;b¯Àåcpä³ò¯cQ) ?xFöíž‚¨eY¾ݪüÝÎ* 3®Ò¥F>µcx–@@/Þ‰Éïé@Î3èWõ§ºß.Oÿª•¢ZEéé’ʤ•ÇOC@º‘ … Ž´¤xÚy§˜ëÇN:ƒN‚"c| çœyÁ>øô«›WËhœ1à ÌŠT`ŠtÓl$â˜È¢ˆªì`{÷©ÀÙ22a0r1ßɼÙÀ?­.ÿöO¶}(`u æÖY$H‘Äd+パްâ¥D©–-ƒê "^ÞE 1X³EjÇvšŒÞ-æ?|¨ûÿcyCG±Ž±E(®§l­h®¸mÇý‘銢r÷²’ YˆÉSéSM¨,´; åH=?ƯD³C¥Å!Uh\‡iXŽZ³ ¡,Ⱦ[™C]¸làT«$¥`t–3–ÎO8>õ ÐLŒ±«gr°7éPÈdLÅnáÐŒ29¤å•w• yÄýñÒ¤ŠÎöèþí7.ބ㊥xYFýÈÆ5Ç~•z UªÕ#–5å³Ô{Pˤ›£’7Ø Š}ìXm6ÈIpPäccg¡«RêS=¬K(’7ÇÊÑQ3«A™.·äïÀ#9ô4 ¸Ò =¢B¤€˜çpöªsÝÿ»ƒ0¶v•<ƒÚ®«[IfÒÆÑã))Ï×…Jðì£h6E#±VŒ©ÅAif$…'g$ï*Ø©‘¾VXÑA8lô>õ^H¥´†E¶¹Üà tõ© “PÓíæ`I óÜc·ô  :…´öì¦F ¸çôªí{6ª9(þtëíFMF`ÒíD9ã¶êˆÃ,O ;Éà‚8ö  ¼·’-ž[¨#5™qr2FÜð dSåkbÉFPÇ –žÕ,h£óÄ8üèBÚæâ8‚/¯"¦´Ž[Ù¥wÁcÛm+Ý+%‘Xãåç§øý*X’Xä•¡Ûà0Åy÷‹HŠEÚ二òz ¬²‡fó‚ Ç‚ ëSI!Údi6‚~¾õ™‰"$«ùQ÷±XæïsÈJmõ úU¸ï­ ¸T·8éÖ€+) $x%ûÕǶÖò<Ð!*wGórI§yks•p$P?‡¦;Õa~·A “6ÈóG|phືhe8;v‚ÃÁª x„Y[p$qÐÔYV»–é€1×ë2YK•e cëLL˜Kzï<þ5vÖAnÄí,p ž•›æ` žx5q˜y¨cÂñÔw ¹Ißrµ2ãH’E±6ÒÑ«d€qš]Šd£jÉÏZ­!u‰â*8~}h¨úª[˜ÒØ´ŒÛ¾cùUH_ F>RO4ÒBI$(é´ô©Å¹eK¼ôÀ&‹’7¸GëNXF{ŒÐp´Œ óõÅ8mòÆÜ©8àÔ»­PšièWi9\=ê7A4øÉïšÀZ\2Ù©¡‹“ÀéTÁ%O&Q—ÀÓ¥ù–7cËMX#fÚÇ ÏJ‚MÍlŒì•¡%¨}LÍ€ÓŸZÍžë|)lƒ¡ÜN>ñ§°¬DRâ8Ѥ%†Fqõ÷ª×1Ü© ²eͰ7\TÍqs3¨Ëì'’`jÕ>Ìf T†är=ë2È “s º\);wO¥\†ÎÛçV¹u ƒG½SHüÈcšITzµs˶‰6—à`šgMnw™ÃHcÏ"«Á É>ØÛ¯LŽ j¢F‘ˆåˆå³‚£¡úSä‚·EbD„x¡1•Ü\aLB6,¼†ù}iWdƒ,{|¹«"+~ô`t5f8 ìsÀ eØ€`ò})‘ÂÑÇÃô8=êÂ>%‘>XêGZo‡s2¾Ðy'¥0†È84%¾Ø¤´s‘Ú¥¶ŒŠÕŽø¦p«å†$2O¥Ukf’0ZæM£ Ï¯iš-Ô®%†6(ŒH~êýj³®ä*ÿ ѳÖf·Ò>ËíÃGcšVBqMzΗ=¼vE6I#hûŒ}A®Zâ"EX9Ò©ši~VóüªséZ:Ä/ew«ÊÌÑ foBGJ¶îM¬Êv-¦Ý Ä«j#¶¸$¹ö=…6dKæ22Û¸ÁP3»ë6î+XíU¢,Žs•=i`¸™cŒJ“#nñŸËÖ&¥çŽÄ¼Ü/Ý>ŸZ̸H‚ÄÂ3òä±dÕ¿°ŸžYœ þèêGkº@ë9Û±~^Æ€+H¸mÐåãÚsÐÒˆ^V\!MÀü¤ÔË ÌaÊäü?ZŒaŒSää—aïî(–¨"ù—)dç¥Md#«>Prªy*=¨)$è«æÇ\¯z–9#‰ ›vdpÎ)Å”HÙÞr¼(=Å[£2¯9e#îñéT®pàˆâè6 MY”ù£‚äõǠIJ Ä®ó€N*XìZ\OüQäœsE».æ'ËÔ‚ßÊ—Í]òO(àóÉ=©Œ©•;NG#4‘]5»”Qæ$f SûŒðFèE~…–·E‘VC†žÇBGfEhÆÓ¹Ï 3Wîàû\‹‰v¾ÐíÒššhû<Ùf2$e“éLv+$¿è«%UË6θæ¦vw.Y€×¥T…O–@Ûž#¥XR¥ mng>ÔbÜ3\.fA ÁéþMnéúJ¤óD!ÚI;²yÿýz„M<3áÆcc†£LTFk¶¹°`YJƒÇÔÒ\K(f˜ˆÄd·®h •ï-V;’û”®Ï—pç â#¼îâ´^ßí,—ä23üª!m f‹ÎÃ<„2( Û3œIæ:J~sŠˆÝ·. S·`ÁãéíD·ª"ƒ89ÓŒf¤Ó­–VBî¥# Ðu eH%&DŒ/A»=ûÓšwKÉeÁ¬¬Ê@““Ç?ç5 "G}ó«„.vžÆšdK'šŒåo˜z7åW…ñ’O*\å—nøÛúšÎ“ æ#!,8<}jx ²'™”e^2i P-ÁY$œa›©¶„µ‘šXÚhQFHÍ>ÊÞ&ˆN‘+ÁvîjÅÃÇof$ffˆ‚%@½O§ë@$[µYfÒƒÂí$¹Û´§5M‘îmÚ`ì g”Ú0yçŸZ]2öIU"¶™BG–ÝHôÍJ±< u˜B1+ÛÚŽjîY&º]㉠×=ê•4jQd–û¤ö«ò¸[‘4Y]¸Î{‚ø«>U8Î@éÏÀ¯>Ô†,"«äå@þuŸuƒDë‰3¸84â­$þYmì¼Lõ©m­^HÞ-ꓵ…1\Æ’3æªY1Î;UÛEv;It?¥4Û¸ÛŒr2)-•Ž@^Aûþ”î›Ø Rî-†nHªâC'%FÑ‘ŸZµ,y”IpKÉúv¥¨l¢àm$qßÒ HWÊÚç 0@ÎriT bcÎ:T¢tRª“Q4‹thøÝØt¤!«"œ«(ãqÞ™*䂲¨$ç=ϵ#œ"ñß=1D*Þi«1 öÆIÜÿ¼È ;޵*FYÎÙ€úÒóäçc(É-Ò¥A#:ùŒ¡W H÷  gX„çrØ$÷©ã„Å6íèO¡¨$|À®QÁÆ2qSE Xý®qŽæ 6"du]Äœ~•ó<“æ0å‹téSŒ„1É8ö¨•—krKm $¡YÎE&X'ï $œàÓ&xÓ.KpAIp$ôÀrag.x!rÔÙSΑX“‡<NT.Xݸýïj½w4¨ ·¨½hÙ4ˆ½x¦!ß#JÀƒœýkj êWGG' šÓ¶ð}ºen®\û"㉥{ÓS‹yvNÀ¯8ãž>µ]ħ®O9W²i^ðÜ.Ke›óÔçô®ŽöMÞ×÷VöˆÍÓdj eí×7-ˆç>{ò.6#r1Ù{Õ»MQ¾`"¶r}ƯXŽM=ÝÔÆ‡#ŒŠŠÐGîb6<{V¼Âælâ-¾ê±{ˆ¢=Á9Í9|êŒg›%zªŽ+¼{¨àbàuÍgêŪ@|–ïY©M» ™˜^‰a ß¼ƒyÞµug?èðFvE—ÝÕÙ&5ÀïÅ^¶ÓZPæW9=FhšîÈ~ló(ÖH‚—‘¦ÇíO–1< $Œç`þjÔÖ ‰%,H'nä’*#9µ‘ Ús¯ùæ´:Æ“ÛG"2Çs'æ¿Ï½Y{Ÿ;MQ»t˜Û³¨ñªÑù¨y3Ì\Gè(½`×l³ŽŠ”@.c£”,±|¬¬zÕ9´·RZU!˜’9ê)è|…™W8|Zµ=ã[v@ÒÀn߇zA¡I6-»‰Q¶ŸºzàÔâÞÞf†— ÉÞG$ãM¹Xå¶Ì!•‚îun2}¿:,¡(Šæ0ìNQ³·a=hó´–ö˸ƒ ùN'š.nøS%w6üŽÇRôÊ/œ;–Œã¯<вg3@‚UÐgäã€)¤‘Ë |(lc¾sÜÕ˜bH¦ ¼ºÈ›‹:Óí¬‘gÚ^=€©Î0iªn.a@„+#99ÉíŠ`EŠòy#*U˜:þÄì“Ánû ùÖ„š]Ôqd“v ·8ú§J&ThÈ‘G^½ó@Ñ£4Q;•‘Óî ƒÓ5I‰¶˜É*6ÑКÐDF„32eryãì*´±ì*²I¹÷=(馆Y`„GV#€AéT¦#iTvÈJ·µX¼¶H¼™cË69èW‰„•Ü˃ÊÉÈÀí@‚ÒI%f`PŒH=…K IåÅ$m"£®òO¥G ÉÄ,œ6r}*ý°ŽâI¹Ú°ò¡–n¹úPØŠ2b`®ÀÈÙltëÅ"V,a_—Ï=Oó©?=‚îß½ÀÇz¡¹$c ó“GzËѲ°`䎅O$VŠZ™-÷¬aŠðTðoΩÛa:íÊðxæ·,.íâ‰Rcìrzc?퓈"‰!±Í_”Ç -4‹ÈóÒŸ´2:y{üÀƒ–ß±Ëä´ñÈdòyÎ?DðIjÂHÜ,Š˶¬Grd³šk¤lã ƒÏÖ¦¶r°™€RÇ=)nP¯o YD«³Ÿà V0ë™<܈œ7ÓÖ”.è&8!gÓÒ§¹”ÚÈWjº¸#ñ¨îhh™¹a2=( w2d•„ª"@îäw÷§„¸‰“#<¹îjg„[È­œ“ÔõÅ7rÊEºÜg=鈡&ØØÄäÆzç™i ŠBž‚i³Ú21“86÷«VVí¹q€1Ãc&€,v…”aŽw7··áHwÃ³à“·©š$i#D`®½{.*Ä–‘˜@]­óuÏZ£rûD~SqóH¡7þýØ ä ÍIwm±Á'v>¢¤(L`a†F7j@S"Y7 °û»i‘ÈRD‚Áº†< Ðû#‡ùHòˆÁlT/ ƒyj/Cë@Å’YàÆ…cõ§2>o0ÈÀð¸ÅY¶‰3æ1 Î:U»]*]Bî4·ˆù ñ€ýh›Z#‚ÛƒSPHžYN¸Ø¹¥iéx •8«×V2Y|ÈNÁÚ® K‹œEî‘iav¾P8ÜIþu¥§]°"<ñWµ­;u¨¸L–Æp+žÓ^xî <(ïQQ4‹WµÎÎÊtŽF8"«Ï‰îåäúV-­óÜjcê:Š×[Ø-îãYX'TBíY•S¹™$¾HhÎTƒT.ÎéCzzÖ¯‰mÀ–9cŸJä5 ®Rì§iΔjY˜A{Äï©ù¤œV¤±¸µáJ‘Þ°­ ó®ãßÛžx®Šh£RV(ÀÈ®‡$jÒ&@ÓÆ<ϘŽ;XÖGEÆMA¥\J²´.cSO°ÝoSÅeU;]/"¤>íãqבWPT]êk#S–G¼@ÀgÖ«Ëkv_ eOLZÎ)‘Ë¥Î~ÜÊÒ\3µûšeë;O–8ü¦£qŸZ³km)ucØwúiô¡ôÿ5 ºã* vþµ±ÒFU ¼(Å~`øý*š„{Î[wN9« #8rX+7É‘Î=ñU.VWHÃc€Y„Z¹ŒR¨ÊK|ÙÅUP÷**+)É$äŒN•jÎá¦YšUù`ñ÷¸íWí¬òÆîH‹E.m<(ÇCúP R´!ne/ P²ÀtÏãô¨.Úq!óÛj‘Çž•§©i,>u¼Š0I<þV­îä r¸™“…p@Rò¤4U•a¸Kx˜¦ð£/ŒsUŒFñ,A ޏ#5a<å ´ÿx’·WâvT!Õ0vç9õ [dÂ#|¬ œíèF}ªHc¹X|Ø”²dCßß4ï´£«HÊn InCtÎ1SÅ?šÐ¦ ¹¦2¹Š[§›d)1â}p¶Â0¿1ÞêsŽEÈþt³£†PyLz÷¬è„’ÆrW&RI'ô¡ ѽ¶© Æe#9H¾i#¸†ÊFIÆàùØgo^*Œºm¼S4¼kÃgî:±˜æáO›/€G|b€Û³33"“ÂgŒçÚ¨^\ª†)"a‡@¦M§1‚fv%£ÁR‚3Ò¨ÜÛ,D(R8ëœÓ Û³ƒæ7=qZш¾Ò|ˆвð `ƒXF‚yÇ`(Ü6ñy!6åÂäñžzšH ž[•‡XÛ’­J݃„JN qŸQüêÂÉ$öòFHŽL <瓊Ê7‚XíÚh¤ q̇Egy‹4 s¼,Äœ®qZ²Üª²@æEà¹ç¿FKD3GBGÌŒn͹Ræ7ó~oº9ì}åL0Ãs(“vªIÁÍL¢e˜‚㚬±Û70'¯ÒnBêdÜ9àëTf‰„áugö«ªÍ+Å“ž* îbFwÏ#€GøP"Äí¶! ͘·oŽyÊ¡b5T¾fý*…ÅÓL¥ùqŽiË UY#Þ€¸›˜,€aÈ ¾ÕtŠycéU¢šVùJä8^I­té\üêWº†¦í ‰ºF#zÕ–ÓïbŒ1†P§$Š»àéâ·ñä0Ý:¨åSråI#ŠöDzI­ŒRF­¸W±—Q®ç„›G\ŸŒê~µ~ÇJ²‘Ašä°=GJè¼E೬·:|³#Œ»#|ÇJäô›?µÎ‘£IÆÖ8"“’Žã9Ô|±Gkjš%˜M‰@êy4ɯ¬SQŽX\ðÓ‘gi"Cw£¿G<©üj–£ ý’å$ŒüŒqIÍN§ñ££“U†',ÎJã­PäÖn§®ÏÙòq€}kFñÞ+«‰”õÅs·m!iN@cžEi©{Æ»ê^¶Õf¸X¶Ó]þ–Ö—ZkE9az¼ +DãûW¡xCNºÔm¤,H 8ÏzÒÝ‚KC Ä1Ïfà‚ù\Vm¦¯xÒªã#8¯HºðsÞé’~ø™E"¹Í+Ãqj¦€S‚*_º½íæÐÂYq´òÉ%CdŽ{VL× «rˆ{rN3Öµ&ˆG3»L¤…Qކ¨!Ý‘Íõ'±íê(6Ø-óq·I?}œÄÏÈÆh{+Ÿ)–fÌN^ù¨ä€Bbçæ^˜8ÇçWQ÷ØÉn¼ûrqÿë¤%‡eºJ@;\ÆÊ¼1×ëSZ«4¥ùñÕ= öÿ ‚I Å0òÉt#O¨©4ð÷I¹ ‰n ‘Ôòhj!´éd`0‡œã$ŠÈžçý ZH„Ù¹¸+SYͺW$"ÜÝ»Œ¯Õ{·…çDÀ |¸‰ bIem¿&Há‚“Ž*˜Œ›–š2›ˆÉ?("™pfŽáRH˜«¦Í¹éV¤¶Üî2¸ˆÄ Ÿ^8üúRR ÈŠKt\?Î0s“Ó‘Nµ‰‘‘ ùÉ?"‚«"õbÅ|€3g?äÔ?iݨ¢#vŒþtÆg^™índÚ¤ü¼úUuºŠçt’0W È#¥k^ÂZgTc”9òÃÛ”ÅÑ¥‘66YˆNœ~”Ðñß=ÄP«°‘XÃcWR*#a7€‡ü+°ˆËåq‘‘´2œñǽjCsäDÑJø•[•qÅB^Dò‚¸€Œß?¥d9,BNþõ¾|¹>O9Yø+†ã¦²î,%„É· ¸ÁÁZ̦U*\nÎqŒZŒÜ3ÉôùÁ r9ÅDiˆ½XqN;½f²X̳ü§¨¬Û{¶‹r2’ {Ö¤!]†× ŠÅˆÆGµv67‰v·G$Cs«ž ïRIÚeSh6íÁÇŸJä,§{iæÜ…£”àzÕ›k‹q YY‘3€Û0¹¬;“ W ‰±Ë/#ÐÓXÒ‹¨oVOl`â²-o¥[im¢»71Ü71Ü£8üüªÝµýµèqå+ò†Æw{P XzH%RìËû§'’~Ÿ­ ‡ÍØÈãh%F@êjÌ3*±†U.½W¿Ö›k&.BH͇Nƒ×&•ÄY½³Ž+•òäV†8µƒ4ÄNïÓ r=ëzC¸lCÈÎI=}+š¿*ŒJ«¨ÇÊÔÐÙ•-ÛE¹°x9ªN¯$à“R$WWÌÐÚÄó7\(­Ý7ÃvZ|ÑÜøŠö(Ôr-PîcìqV‘&$:v¥©Z™,ìå–þñUâ¯išä‰/åKh—®y?‡jØÔü_ˆÍ¦‘¶¶^c§jç–y »–'©cS)¤´:(ágS}–ÇUÒt‰Çn$aŸ›'Ò‹ÿ6 ÿºµHGfcÍeÁeæ¡%pqR_鳋%òã>­ŽÕÍíW6§£,¥ró+ÜÆº¾¸}Qnp±Ë+í^…á_вZ:Úë1–€ÉÉZó£jêyÕË+ÇozÖSŽìç†oD}‰´;ÛXŒ:„,²0AƒÎON+Ä^ [‡“SÓÃ}ÎÑÂÊ=ýëÅFäÝFeÁã5Ûiž)Õìç7/) ŒHr*'^(Ò–]^÷ZX£s®O|Dr–‡ §Œ]3ßé‡q‡'Ò¸nÞ}Q¤2v2³Á$×gáÂV;€P®W¦Ÿs£éÔÁMu‹üNŠ+h¦%ó>u8Å8éS_{iìì{ÔþTøtÉ t8õ¬ÏjòË4N§rkÐjÉ]\ùƒ2÷΄´N»d^¾õÐhô¿gXg\2Ž=é|S7&…@qÖªèðG<¹zšÁK•ÛtO‘w[Ô-µ s±€˜Eyõ¬,5€9'uo]Ú=¾²%ÎPõ½2x’;±pƒúVÜËšÌÖ-- ú¶“(‰$ר®~Sw Ûbèx9é]D’É5²1lñLÓôѨ^m“åP3õ§9F.èi¤sqè÷w,Ò62k:ëN¹¶bdBTw¯C¼³}<‹æ¨¶ÓÅí©iÔsÚ¦7Ûg¤‰à‘M¦w?®’ËIš[Ÿ:ìc‰ì`³º¡ùYNp;ÔòÞ]O Ž,(Ïzæ”5×s&›z“ßÚ@!1ùyb28ëY)¤=ÚyRÇåÆ=º×_nc¹‚#"5G5ÜE‡î×Ú·mGDRguáëx™YQNZÙÑÍÕƒ—·ÇÑ…6X.3Ê63Æjñlm×ÌÀcØTÆ|²WfnÚÜ4 ¾6稩¼ø"%ð {V¦¢d¿SV1Í’çÚ´?i¨¼çtuxò©„õü*Df CÈ¿8b0Ò®EËMl»N2}ýê¬71ÝÈ"‹Ì›o#úæ™Ô-´±Î$Êl ˜`çÓ&œòìH± ‚s’¾ƒŠS¦-¸–ñL1Ê’xúûÔÏ£ÊíÂH”aÂŽœ:Bê$F4»BÛ sw¢™4ñÏ‘Çû´ÆFó¨aHŒS”bC.ÕÜq·ž?SYÆn|Á–0pÙÅvˆ‘…¥‘–7'¦äÇ8«0FðÝÇxŠn›p22sJV6Rà¯M¬ äjÂGj¬ #G¹0ðäó@Ìë帞égX 8%v¶CTOêéà¢nŒ ÎX}ߥ^Žî q3»•ÏÊ2=(–ý’Ù¡€‰L §¸Ç4Œ×ETˆÎ¤Bøg Æ}* D±îwO(d7p3Vžõ¼±A„cÆ?‘¤ÔŒsZHÑó†8ê8íL,\yREŽFÉÂçzyÆéYš”OåÈ’mRW*Øû⢊hÞÜí* 0OÞÏZ¯%ôoç1#¢‚•2®El›g!Ù6бy(v¤BäúÕ/1¥“ÌGbP dRÉ;”<Çï1šdmÀ8±N7RÙËnàæ©DÇvc=9©Ë°0¥`"‘A? #=ª2ó©˜e2¸2‡lgÔÀfIæ¯ÅrØ*~ó*‰_¯µM‰H†­5­7JȧvåàçùÕ– ʲ,ª3ÁÎA:Z܆˜Ï{Ö­Ù3) € åSq°³q0•g‘UÆGÍÁ_óŠ¿§ÇšZFpC’ì=*Ŝ֑i¸’4RÌÇ<àqQB^k=ª<Œyg^âl›ÌI¥2ãp:õühŽàZM•"@A\íàJ’þΙÈT(ùpCzUfœÂ›™6àÇÜ3LE{íAaVAä€$×){yö‡!bÔr}GÖêÍwvJ€½ÔV1c&Õ vŽÞõIiq¥wcIu‰!„[X ·OâuûíîM@¿?,ÌX÷5QrjmŒ§*3íYJW=:~Uv„*ca•%Ozд*HSÔö¨烕#>´¶jË19ák)jŽêISš·SªÓàê}+oÉ"¸®zÚñÑ·—¸nã[VB‘‚=År{ •ˆõjcðØx~òJýºŽ›J‚Ir@©-ôäˆUU5 Knp:WCg<0ƒ¸o•ïZV¡RšîŽ\.g†ÄJËGæaÏjäƒÓ¨©d81ëÖ¬ê;VD*GÍP$S]2C ™$nÊÛ•JÐI²Œ­—†8Ô{+Ò4¯ ¤>óÒqç•ÜÞ™ô¬m;ÆÅË\ÅæJFF9 ZÖ÷fÞ6„îUô¯KIGZ›Ÿ%šcaS÷Tvëæb­ÝŨa¶¾A¦±lµ +T3¤lAEz žÎ{EÀPËT_ì¬rÑ®{dVí%ÔðìM¨IöŽ·° W$W5i©Mg±®IcÁ­kÀX¸Àäf³­p%HÝWòk9Æ/^ä¤>ÜÜÝ ¾I÷£>œ©HÍÏz–;ˆá—bãÒªjW·;}+Uµ{š(Ý–J©¶R‡É©m™¡&D'$uªÖXh2Ü->;ÔŠ@7â¡Áߘ|ºÜ//%ËLÇ‘ I¡kÖÓ#ÛÊÁ\60j®£g.£*Ê­„ýkŸ¸Ó•d2Äå¹§ ¢­¡èÞ›lmî#ó/+[jÖâ$°žåÈV\¾`œ¯©ÍÙø~kÉLQË`:?¨¥ðî©KÙ:•8íÍV+š97Åk9Ï$šžë‚ F1ÂO.…6ú'Fö¼ezŽi#ÈåxªW:•ÝÛ––\’sÀ£[¹AêÖ—+1²²~‘ïZ*pw36Gšç¢Ô±€ñƒŽâ®G­l\ô©qc¹ÓZ]ȾT/*¤QU— ±«¶wê×:…ÈÀSÇá\dÚÂ?̱¶ÿ¯a×~8äsùÑk-F¢äìŽìj0†a4ûbC¹™¸Ïø×;©xŸÏ ´~a< ð>‚°/..gÊ<ŒcÎqK ±1n ŠåW7£AÎV} o&üÛz–+):óŽçÒ xðrsß&¦K„ÜzÒr“Z4èÑŒ›™:[:C½\ŽÝéC‰Èà 3#)uˆGŸ|S æbM(Ñ”·ÐªÙ…*.Ñwò&Ža°‚?XÊ),Ã?¥0ÇáHx+XÒŒO.¾iV®‹DMö’ÃãÓµXˆ3€i"°-Ïu«ðBò:C³18ëuyîWwØ.áW®!kD”³,„o*8ÀퟭX° Ýē̼eÈSì;ÕÝOI¼–[ÒÞ[²ç“õöª•£K‡3i£šÓ'Ô5Kí¡ §v좻M)¢´¸R’G 楰ÓÊÜÉAQÀ´ôû(er«‚sšâ•(^éYž„±uåOÙ¹hY›YTS´x¬Ùõ^D[œŽÕvçHî£I$¬{S§²¶ƒt`ùˆÒ„f·ØäWG==ìr…òIö«qŒÃŒpz‘G—œDQdŽx4e<ßÞzÎS|÷HwÔµ'ØÄEdfF#ŽzÕH¤ŒyRƒ9õ©/ Sº;cå=*ÅÚÃöd’, Œb¶sMY-ˆoSžƒYÅ×–äî*kýGhRÈvžõ-žêÅ^"ÎÊvñÞ¢¿Cæ=´©€jâïšÇ]Ž‹M‹ízk±9ãåÛXw§ÿº¡êE&¨K¤¸RÙ„ñ´ž•Ò__`(„Ã86¬¼NNmj{HÄYíÖ¨Áªo“l„|Ýêö­bo-‹Æ)Ô×0Öì‹Ë|³Ž×.-4uº}Ç•#»£Ç\t©4ùž[¹b De²UÐâÖÜyñï†S€zâº+½l,Íì2ç<‘éJp—Ù2žš#Ä”ºuÊÆÝx5ϧˆ. ²¿^zÖÇŠöµ¼.ƒæAÖ¹tÑgvv3Wîî‹ƒŽ‚> ¾DÌ?:цñž?³FÆ5#’µÌÇ¡Io e–k~ËL¹û:\0>^y汩O™Ý+7¡§‚ËM’=Ň©­«ôøôA4’)Œ±cÞ°e¶.ȤϮh»ÒÓìxŽr[®j!M¯y¼Î&q2³aÚ«pÊÓ:ËUÎpN1[:VŠ÷ǺP q9ÅmÅàÛ)$iöf 9k£•<ÈâãXу9'#?ݤº[‹‰‘l÷•©<Ÿ§Ò»;ß ÁkaæÂ$ èÍš—F°òcŽls¤bŸ+[‹Úv“ªMnàZ•,¸,~\þuz×ÂÒqöÉv°òÌç5ÛZÚܬŒdæ6ç"³®ÃGxV&ÈÇ Ô¥qÆIîTµð–“mj×4²8a#ñúV/Štx °†ãNˆ"Ž¡k¤¼—ËÓ$n®xê@£çÌ 8í¨$Ì}6Îy¦I¢9r+¦Gun±6<Ä=kÞg³ÔÀ€á[ªšÓ7{/p8ÈÇ­h3ÄJöó¢6­5´B$R£ zÖf·8»`— †tÕ]:Õ¡e—s¦ÓIjL¯¹µ£N×l©"1\óí\ÿÄ)t¹õÞäÍmÄGŸÆ­éšƒ[ë[U>V;p}ëiü'¦Oxn%µC¸åf˜¯©âì€qIôëúφô«sCc ©88Eá­=æÎ!‘…¢å¨Üò§ÒèkÙ´]+K·¥’Î ‘†@[¸,R´…c~8@(º°žŽÇ¤þé§ yOHØþëz¥¢XcP¤Ž•b=5MŸ™µ2Þ‚“Ò[’):Ž˜ÒÐô]*ÞÇLÃÄQå#—<Ô××"qÃ1ŠókiµH˜ä·xêwm!‡\ÖR”™¥@ƒPŠ=5¡™y fi·[n¤T“iìk‘‡Z¾—äxÉÛZÖ7K#‡d*ÇŠM·dÄâlÍ4†ì™¦.½…Q›Ux¥Ø§(O~Õ=ÕºÄêË(mÃò®cRûZHÁ²ç‚-v!jwZV¤º5ç™´3(ù‡;iÚø´»í–.ªP;×c%õòGÃÚº­cÃRh:|w"处ÿ¬ZÑ;Å«l5¡ƒ ÊX–$­[šõźU<}½Í•Ä/íT5{ݶ«Qàõ„bùœìÛ¹§o«5´‰p¨¤¨ÅAy«Ãype`¡s¾}ňãàÒe_ÏÙ z×È­§çy²…ão­_´½q?”>lð3T|9 ]êS4wyE{Ö„Ú%Å…ëFÓ.Gè”WDKRÕà{kVÝÎ+&–Ù¤ÛšÓ‚8 eÃÁÍeGa¨Û3 }Г€qQ%%ª]ÝxD?g‚vJ³~Öµ¼Ž|§(5™¥[G¦] f$ާ5¡¬A/ˆÐ}Ž1ˆÇ&ïgª!êÎpʱ!mÃËÏJ‚MVÙH F}j­Ö›shٛ௡¨‡n|¯;æÞ²Ñ|L´Õµ7,/a¸“Ëc®ÚêíklöÓ¦Ðßv™áM2Ùí¦†p|â~SíVüCdˆc…G̼ýkXÝÆè›Ýèe-úÆÎPü®è.·ÊÎr•Î]ÄÐÌ['‘Ò‹-FkÜrÝ (-nÆÑÖèKonC*.Õ8óšÕ†(ãÔœÈr’r=ª == äj£r“’â´5(aYØ´'8¶IÚå7rGh¡ÞŽCÚ°î£Y%Û‘ڵmo,/l%@ÃpîkŸÊ.°’‡õ§-Q)'¹£¥ê·0Î ‘çNM^ºdóDËpr¹{}êß^@Ó“n¹ï]UìfÔì,Ysš…"ãîjrzæª%Ô‰ ºŠž+ gdºDÇÅb_^ˆu£àbºÛÁ· ©CŠ["æúœªùrêåÔac?1¦ê—0.£Ñ>åÎÅ-¤°CªÝ GîßZðÀ©ö¸¿x¼ ÈÍRÕf]úÇ4é¼SƒšÚY Žâ„}ìt®kY’PÉ,Ž+cà ³0†äçoF4-Áír-cm\ÉùQ¿:í-"‹R³*§˜ÝUçÑâ‹çˆæ³_Rm5ÛÈ{p†¬JIšz~èLnsƒÞ›c"à « ¾è¨možæ1,ù O5É®”£íNàw©6ŠcmàStÊx È©£Ò>ÙrKñ}ýj”Ór›«pj[J±Êà£3JÄJ:™ºÓ¯œbÝ€¼fŸ •ÍÅ£­¼«…\œš·¨è©p­4LXžO5–[GÞ\da…5Ë}DÚZ‘i·sÇ;Ç7U8Îk¶·¸„ÚÅ&ì•ê+‰Ž%îrrÇ“[p„„mYr¾•J÷3¨?\¸Òíôë‹Û—È"4X׉ê77…ÓI+`tQØ ö3á/øI.dþLcŒ65‡©x*×L”y;Ô¹Í&ùUÚd’<ÒÞŦ`ϰâ¬YNÅP¤q€2½ÃA:chí"Y”•'`ÍcÞÙÃöõ†8c {í_D4¹2[ ¢’Cî8v öæ/25L´ØÅzö”#¶*ø9éÅfÁ[…z·4&?džçq ^«ˆüØÁ>üRKáÆ²PóJ¯ß ]†¡§I¸1pqÜSEÛc‡éÖ‚ýœvG/eqmhêÏläõÜ蚥½íÌi ,c°¬ OGKkPvóíQivòÚ ºL€jQ•ÉŒWDz›*Ã"ï!”Ô×ɽl T­/ º°I‹àzÖ¡¹{ëXÔ(i¡œ•ŠZõª­²ù@Õ™e¦fîœÕûÛ“ˆ$!€àUÈ¥Yclp Ôµ©q•ŽB}‰vË´`rj´ÍnQ¶(Ü5zçN™®¥däõNÛKdwiOáRÍ.-„àƒ5hŒ¡Ð §Ž)‘ØF¹a&=iÀÁ ‘nõ¤üˆ”nFNùvï8'jè µ· ·Ïަ±£¶/;ò· ÚG2ÉçN@;iÓM²6)\µºmò-ž1֬귗Òi±Ewó!ÚzÔÄi¥±\n} UÔo.Zê$“@íOU{±s#°pãnzàUû‹$¼¶EȨ£˜O!RØ«1³FÅcçjCR*Ge“+íÉ»<›ÍæŠ5 «ÓÇ^I!f§Yj—0[ÉGƒÚœd“³ ®©,.îŸ+©ìzTÒ]O¨F$òG­s²É?ÚXȤdóÅli»È-»ä+9JËBXÉ`žÝÈÏzÑ´Ôe–Õc‘1´u=êìŒ&ò¨éŠŠö5 6.늩]+¡t¹Jì¼ÄdàVÆ‹uö&8FᾕÏ]\3°T\³›¡¹cYÁÚölhêöÐMºÐïgä¯^ióÃ8ÓÄ2E‰1ÐT:d ¡ç‘ÔƒWµmv5R6€Ê:ÖUa'fÖ„½L;ž+øÒ/•³Î{WI-©¼™¤l¬­$Õæ’â>¯jÖŽwµ]¯ƒŽµR榭ю:Ω¥°gf\x¥‚ËO–ØCpHIïMñ>­<ì#µŒŒõ®b«‡¹Ã+jÖ)®¥õ„áíEÆâ޽j¦±ªîX¤f;Ž«Ø`´òþó·AV—I-’q½ñÓÐÖüÎÖ¬¬G¥Ú¤nø$¤œõéšµ=²Á,qDÄä皇G·óo$¹AœJÜÕ4yÉnAiᇥBÜM3›Ô/e‚郫.ÑÁ÷¦6©uqe$Ž UjåØk˜ŸÌN@êk>=òZFmüj®]ÜŠZLºeþ«æ\‘æ…ƒë[2…²I™rTŽ+Φ³žÏW|±CÝ]´2K5„k1ùeÀßO¥…5fU¿´ˆ,S:í3š½e®Å¿ÙƒSÂóZ·6Öwz8ÓÕƒLW My¾§áýSD”JÌÏnHíE¬4û›ZÅÍÔé3(Û‘Òµ­$1EóçjÎk+ý 7IWÌPóšŠÞÕí4- ¶¶:´ PÌ޵ÌÞ[o»fƒÎkyÖY¬•ц?Š¡º¶Q`$CûÅçI•‘‡=ÉM/ 6ÇŠ“JšÖî‡{ÔZ…Ì"È+ °#8ª—@öhð¯’øÎG‘v.ÏeLW~àM<Ú »TEzȰžV'™Üô­˜d¸‹oÝàÔ;§u±œïsZÎòÊØ­¼§ån >µÎøŠ;k]Go•zع²·ˆ$™ Ù­Wº³¶’ค‘ß"©Ü‰ZåF°T²K‡|’?*®ÈʪØeŒô5§}$i¦$`}ÞÕ5¬ú$ºíÅU^æ¶{VªˆÙr1ÇzMji-¬ZY`$7CŠÅÑÅp¹8Ç"ºÓ{þ›<3¨=jÒRZ“É­:°¾–Î屨ç?JÔÖcX°±bÀf£°Dì+¹ÅK¨\[CcƒaéYÚÈÛ–Îè¡yª4–¨J{Öv¥;H‘ʪTðGµ\šú'"8ÐŽ«â XE07CB-ÚÆu’òM¬ç®C¦O§Ý;dºcú$6þZñ‘YTŒžÌÎ~æöHИ>v=…gKwvï•v–+¤]Å’.[ë“ÔÚàÝlƒÒŽ…kl‹\DᘌŒÖ}¶è/ÜHr™â»èðßÙ…S‡Ç&²5]­5,œŒõª¶— ¡Ëxª˜åÇåV´›KÝRèýŒ•U囵gÝÚÇh£kÄs]…î/4«)&X7C!q^ö¦r/j±-¥ GÇœ;ûÖ[X4¶Â]§8ëZ:ýì2¤r–¸&«I«G%†"*?ZrI¶…mi¢•.È\â´,î ¼ß¼ERŠôµÙgCŠ™fW¸,ÃŒqX5Ê´•G¶7™Xñ#[ÜcÐÖÆ›z‘£’3Ò³µIRæB!'µ¶ýDÈ//–àykÏN+KKÒehÁ#ä#5‡kA+™A Œ]-ލð¼Qª’‡Â¥Y¿x–V¼ci '#TÑH.ØFGf´5Kh. ¿<‘Qé’[E+åC€Š¹û²µÃR¥äQ8úJ©£„ó³€ lÝBí! )íNh‡Ùv°Âµ`½ÖR²+Í{iöeàI޵ÊßÃqpëb£½lK º9‰1»Ö§Ò™mù«ò[ÆqnÏ`hÕð¤riv¡psšŽK¤m]ÕÎPô­›«C{ 2Ä äã½a}‘%Ô% pbë5R1NÉš·k áÍorÉ­•5¹zðìA™ù®bËKWlá·FÇ&·®tøÍ· ;±ëRÖ—fÎs]…­cŽe Æx5yt¸gðúÜ—ËõéUîà–îÑ#—$gLšâK;lÏò€=(M “kc/ÃêÉ©¹È*šê,2÷û$`r§Þ¸« sw"¬›qÉ5Ó¬±y #“(È¥bºãìúÜÙÚr3ÐÖªjv—2ŒÍ‚1\ØYnq*£15]Ó-c7ˆY¶œô§c)¡òi·ol²K~‹Wà³¹J1ÌëŸá•© Öù¼¸Ø£ƒëXÒÎò_K‘•Õh‚;X”XËœ7üÎ>õKö˜Í¹ŽF í׫M«m ¼ °ë 6óTI  2žA5ÝôA9v6gðÇ™hf…‡MÕÍk“ ÍÓ Úº=.[èŠÙ™“òœö®{[µšÇÄ"9$ 0ÜïWººES}̻ȥ±º+œ[º.œÚ¡Þòí)ÈŸ‘¸p%ù—ŸJ­p—º5Ðx9…"„Õõ*mÞÇ[c¨½¼âÝ”¼gÖµZãÍÉè=닃P2Ÿ7¡ïŠÛÓ¯à¸]†@ÒûÊ6Emarãµ ð±1ù3ùUýNÑÂq’¦¹ôD¦¼ÒL#®‡A©­³À ¾Hµ“fϹ÷/ÉÚ˜e]ûIÍHy ¢á¡nÔÆ¹-òŒæ¬ù°`O­ZGtŸ;mÀ¬=ZÞK)ârééMÝZE¼¶Ñ|­ ç¶j®µ¡ÛßZ<М ‚+˜',®ÎzdU»_Kf†ÒhØ–TúÔ©.£i¦Oákáktm[åqÁ>µ7‰¢d¸Kƒ’¹æ¢Š- Üm äæ·íÚ NßËÀwªé`æ9y´Áwn³¡< ÖŒ:¼°icò‡ Š¥±¼û;¯î»¯©Z±•d…‰?Ý“³ºÊrBn¬—vCrpj;{f@vr;ŠØŠÆI­ÝEVµM¯(çŠMjT^†|ñÆag ‚=e¥à.@µüA/ö}‰ -Îqv×R3p¤i?1'u©Ö[\ƒBò?Z‘Ú)ÊíùdïYÖ7­jOšœKö“öŸ1!«9½‰rîjN"†Njõ½›ÍÜÀÊv¹‹‰d2Ÿ•»V¿‡¥™ )—Å6'f@LDÊØã¥sÑÝÍÉ[›¥?VÔnÚ28ö¨ô3‰‘ó·­)ë ZÈÞ7O/€1Oi'{3òä⢼8±vS‚S°¿»6k˜ØÅœnÅgxI™×"Ê_aÈ4ë›¶{uT;Xžµ­qQ¾ù@Ã3Y÷Ø@ÛF[±©rMÚgQáï0éóòä`Z¯âžÊùî¡<‚£½sšJL/hÏïä}+ªÏ©[3ÊùpxªNPåfw³<ÆüË%ë™‚Ç V¶‹`èç_”ôÍt¦„VᨇVý¦³…UÓèEƒµ–Æœ×Z[ óÆ?t{ŠÑ0B Ž É£n:i;ˆ¦Vi0Ò¶7¤ˆuiš3û”,íG†.e¼iZõŽáTÖŒ-l¶û$PdÏ~õ› mG|µ.ö² °sZ·ÞívœzW5ªøl™h‹lcƒŠl¾![]F5‰~VãµØÛêpÍhc’0¯×¢1²0‹p™ÀCáÖ·Õ–O=€ À5ÒZ]$M²}¡“¡õªÚÜËpÊŠÁá”Ö$’JŒžpbÆhv£Íª:«ûË{›'Œ¢°ÅÒiöQäÊqɬÛERáT”õÝf5»· í8éT´Z 1³±bñ!»‰î¸.EW‹Lž;)&O›ŒñOÓ¬¥‡O-pw9­M²±÷Çœ=¨I_SFúœ­†¾EÇ—4„8æº{]o÷Œ¡B¹¼óÄÖ’Zk’´hUäVß…§Žçýa‡aƇsD“Ôîôëˆæ„»¨8c\¾¿/›{ ‹€Ojß²²þϲ˜<„®þ2im,ìîéÕ[<ŒóIv´Ôâ®t‡Óš)]Ÿ÷˜Ïã]&›cf$†C+sŒ‚zŠÚñŸÕ•éYv“Gc4"â,§Ý$•\º’§tn U²4$<Š„Ú ËpÙ}úS'¸ó X[÷xÍIö¥¸‰#^XñŠSD?yhj@ˆtõbAqÐÖ¼·Ûµ¶v Õ•zÖ¥¸hNÙÁ xë[Sjpé¶Ó&vðj¢¹–¢WG hg‚Øùñ•b9ÝÖ§Ñïã‹Te.P¸?J××n¬¯`Šks0t°ÿÑŠªpäTËÝSjöÎXž;ÈŸ£n“âe’øÃu·çŽº+ ¨¥³X¤ì1ÏzÌךÞs»_Bàì`Er„1°Rsš}ÝÄóÂä ŒëP$EsØUY500+&ìÇ)j­>Þ'YÜŽEI§Aei¬ù’±àä ñUôèÖç ’{fµ“HUI“9ïZ(»Ü'êojWBâò@#«‘¿¶ó“ÆãéW.®Í•©X˜•ͦ¡-ÕÎùNNxM÷K èÞHä`Ç©âº;{Ø ´Ub3Ч>ž%.#a‘Îg3)¹D`zô¤ô%ùL7–²FFyÅaÝÜ~ñ£bvÞ¯40Ã:ŽqYÚ…­ŒÛòþ‚—3ØQnö4m­ìJ~±µÛ_³Îqòò+?N»¸@ÒÆ„=M5µ /ïn T£}Í·F¾›{-í£ ⺠îÂÚ³º‰GRkŸ†ÞÖÚ ÖòrzóÖ©Ü@IórOãU{5cµÔRÞð–‡ænØ®bíå²[–Ï«š5Ä‘EœO“Ò¥Ô§·GY%Á>ô×q3)ôºK'ÛAŒnÀéQÇ;ÜÄÜ=+SNÒ>;Gû§KVôì­áÉ褕Wíyi²|2¨µKýDFÔR[É ³Š–µ¹QV(åJÊÝ,//dßn¤ý;T’ãspGZêt]FÒÊÙY‡>Õ\©èÉ‘Ÿl“À#ŽAÈ&ªk5Û…*vÿ:ê.¡ó”J©Á9J˽háLœIÁ-G0úlºNõjãFWe)ˆúâ¶­ák¸ÄÞI+WnâY¬ü¥á±ÓÒ¡EîÆ™‘m£B"Y­¥e=ðkunR;0gÅTÒàe´–ᇠV ô©z†ÆìV6wPŽÈ½kŸM,®ìùaúUûºsó .8ɪÂð5ä«#lO\õ«v#ÌǾ¹›Í0¢çf¬éð,V´Ñ’íÜÖlº¤Pê;†zšê-e‚M™Ô"’ZÜJNç5o `ä'¦*ìú," Æò=* mA!wV^Aã5¡m™ûáòcŠ•©|ÚŒó=³•ðksM™®`ù«?UH¯oÛìë\Ví¢Ùé¤#;sJÁÌ‹ˆãÄ]=kVY6.÷˵©&¶ƒOù11šçcg¿¸;Û#5Ú‘¡¤‰ÇÌqW5Ky¼®9Åjézlp[®ð2yª÷Šé;FTо[FÄ­Ÿ£Âޝ#Ÿ™F«4Àˆæ©Â XôÙo.-dÞ SNzY‡5Ä×eŸìª®iºÛYG,E·yœïY׺뇄sW-]¾YV#qW}nkm rÀ¯{óÃ8È«ž)Ña²´YáëÐÖB‹›J6teŒ6O­âLÍn–ÈŒÇ+$Û&öw0téãˆ/š1Íh‹›k…“q@õ¬fÊFAF'éT'3”ÂFàjÊ µªÕjX¼ÕÌql±´àjÍ]Bçymä–ëM:Mü®…€ëÈ­+}"x¢H²Æ­ m,t¶·¹DyÈf^Þ•¶ä¥è#åzVkö]E¼&3z»â[Ñm˜\`žÕ2·7™6lëtÍN)&dÛÈôªž!¼’Í Æ0Ö³ü?¬XÇg’8ó¡âVŠ}<ÎJզṛ‹F”QÜ¡gç=(—P]%¬‰ò»ZvŸÀ‹8êioí­’ñ ]kšÒµ˜Ô\]™Ï1D¹ÉÇœ©«×7/6$ŒlÛÆk›\MnbZöW°ù;&'=É­îuEÙܴׯòÑiùläâ¨ë*²@^3ÂóR[ØYÞÏ1 ÄöÕÙô£žä§ÊzR¦îô8ä½1¶’"Àõ4—zcG’Ñä ²<´ttU^pAï[kz0"Š.37K¹¼‚% ü¹Û[šEÏÚfy&‘‡"™ou 2n•BñéZ)¨Ø<;•ÔnS¹Ôf¥/“Ÿ WœÖ—AÔeºó–æ-‰ØšlWIs•mÊ•»‰÷F®š|Í"l¯bürZÜ m|ÍÄt5Âß[½¥Ô©å“‚+ Òí¢±¸žiæÞ9#ØPÚ†–fiD™çÚ¥ïsDbév“Ë 0ÀÊÇ®k~ÏFÔ–èoUXØäÕ›}^ÁG™nêØëTî|^û¿t7p1JìJ*å-~Öþ+¢_h‹ëX–šMÞ£)–؆ÇI­KUk¤ýðm§­Co­Ùh‰Qs»’)¦Í5°hš¤!æƒå^N UÖl&½"Uùd¥u6^"þ×Úb_•‡5­M-­ÖLD&zѯbävv÷–‘ÇæÃÚ»:G—N° Æ9®wMy.ìÖ_+*:؇t¶e˜” â©s¢w35 2XgG(È«–ÚpšÜÈ÷ “éI,A¡`%Ë95‡vfµ·hÖfšNãMn.V+ù-ƒç­í>k[xÞæ Ç8݊󸡯Z_?æÏzô xç²ònYX¯ Ð¥f7vñIÕ˜( )Ú¸ÍqBLËÚºÝ&8šÇ÷MÇz¡¨Øés8If"B½W4d£vaiú•ÌVÂ$L¨ïQ>£qÆã {×[i¢éÑC½eÜ1ÜÒÉ•ù•=y©rЧ€—W»7¬°!Œt­ÊòK¦¹v9#Åt¾v• &}qPM¯YD¿ºT©w yŒ’ÍÎIvô²ÿ³®^bç¡­C¯G´>ÀGÖ˜5·ûB˜”ïFåråðæ ñ)Œ6áÓ&ªÝøGTº¶8uY=3]*ë²…ÉQÏAYòxŠé™ü¥åFMV¤ò£‹okÛ #9ûÛº×Eý‹©E¦¬m·!@<Õ)üuqÁIcäÕ2ø¦ê{fDÇðàT&ºã%ðÝÙ!žD\þ5!Ð& A¹>‚³b×oîîDeö.zúVŒÚ¥½²á§.ÿZz”£2ßÃb~ÖÙ'9û«Ž? Ü1í’iërN¬-á;GVª7NÙcŒÑ­‡§CVßHÓZÛk>ãßšlZVŸnûÐñš©¦ZÌa’FÜSµWK‡yšÉÁõéBOpNçi§\C,Ë.ò>•bñ¬•‰xÆñ\Þ—¨Ç¥$Œä½^´¹K¸šI›ærHôvº&ZkbÁžÅˆ+üª„·ºxfR>†ªKv-î*çÒ¹Ýf+ƒ!˜)º`Ô8‰1úŒð›²"PA­]~(áXvª+‘ "±,pÞôûxy2Z“ضݎ¹u äá”bªM¯!ÂXRD±¶ÐÕÝïµy4•ú†½Mø¼F±ŸšÙ«Ï¯[H5¸_¹u…„‹(d”ÕäÓ®#|Ýdt©¡ÜÚkòîdM¡;Tö’ÕØ¶HäV2D‘€žgËŸZèa[h­K!#œUsÅègªÕ™¡•žYû5®òîå”tÍ>HÖO˜ ãšš dòÊão=+ž§2Av_ðæ‘m&¦ÈÇÍÍuÚݼÜ ª1\†”òAr× aº ×’êq<¸7jÞ‹\ºŽúw¾$‡H›ìÖÿ3‘ÎJf™rÚµáyØŽ™¬ëï K%Ü—ûð84Ý;í^ÆŠ>Wà:T·šD8ÜÛû6¶¤à{ÖþDŠŠ¸ŽkBú;˜ÀIjS¸‰. f0+C¦6m´W‚d±¹FCò÷ªú߉&‘^Þ»·–ª×î±7øFk›]Ad’a'ÝÇ¥Å1Iud {,ÙýÖÀ®§D2yR4Ì Êý+ξÜRáÑNì×a¡ß)…ÚI6‚»E=ŒÞÅç×mïä’]x¬ÝfG¶·‰íœ§'Ÿ©lŠo6> H«VsC4‰ 1`ãóÍ7¨8étmxoV’.©Ê“Þ«µîÛ—ŽL«ƒZZ6‚4xî/Xãå;sL‹FMZ & ‡EJz’­r¬—ÍåÐ[‚h²ðôêϸîÎj ­Ô–wÞ§Œ÷¯@Óçµ}' z½j·ùN*[}9RBCH8ú×!ow7ö©U-·wC]ö¥=¼×dàOº eC¥A;µéPœy468I\ŠþêY-öìè85‘ulg¶ »·zß¼Hm&\üÑ‘X³Kù‘©À&‘»#Ó¯g±QåI‚+uµ§Ô,™.p[W,cVÏcVDrFëó}ãŒS¹.(îü3ª[ý˜A,ØeàéVµ½IŽØ­=Z¸«xã´¾ŽIŽÖ®\ê+9"#ž(»#’îæ´W4ç%óÏ4_¬·°n¶ù›¸®dy¾nýÇé]ˆí¸¶üà÷¬äÛd=ÌÈ4 »Œ¸ùXv­«+ìÄÌŠY‡ZÖIr¤ÆG5C#îiTÕ-V‚sl·¥jþm×—ž^áÈ4Ýg³¬Ò°sê Pµ‚×팾fÖúÓu{wdÀvp: ÕkËf;»ê\K¹V>‚°ªÖó;\5ÉAÏÖ¤°ÎÓ~aó'½C±`¡xÍG&¢“èM4…‰Dè}jÜŸ¼;ÖÄ–qÑû²£ƒXÒ©Ú)_u'L¨ÁngG¨±¸6û€L֥™Joäõ—ö«¦u%W±õ©cÒ.c¹%[åkÈ´ÖÇ]±dÇN„Ô7âCäò_‚ssÝÙ̯¼„}kZÊêYvN+Ži·q+'©™©éŠ·›¶óžž´Xë1ZHmn Èn‡­›¨£žådó:f±o¬£yË¡åjb¬^„W!f•Ú0O§pA.íË㩪Ï#éž@ªðJK3àQr%½N·ExÚÏÉjý«Cpª$ž•RÓSŠÞdÙÎ+rc! rÉ»¸ªè+jhc·ÒBÛ\ÜVòI1x—*zšžîF¹\–Ú£µK§ Œ »”.8²R”§¦Æn÷²*ê°G #ceûóF—rò2†à)æ–Kf–FBá˜÷÷¨m`•fxq[2žªÆÅÀ‚âí&˼ÕMrê;HãI'§J­ ¼¶·Â@IçŸzÚÖ¬ÒöÒ6Ûóc½;è¶g 4&êpã…ô§:µ²ákÍj–Ê #ŽÔ[iAÜ2qÖ²ÜÐÀIÜFzÔ¨ÞTëhXžø®¢ÓL³w.Çõ«s.Ÿit‰qžZ‹\OSÒ6²u”&éH榚;›Û‘æ| Š]CT‰Bý˜}«:ãV¸L1Sg IÝøzõ›¤©ÁÅjÛÇ$ZhÆžišwˆtŽ­žj–­©ÏÁQµwì6›Eà ‚-Ê2i°_ÅœsY6ú£2˜Ø{æ¯ÛÆfˆðy©”n„¡ÜÚ¶ºó‘€rbÉ`2Üç5ÎGvÖÈqZ¶÷Éðj5‹ÐM4Èå×<è¹ã©å],J›ŠTw–†;åã-×ÐTöZ|0¼¦ITÈÝêÔSEèΆaÆb•\ÕX®ÒßJ‰yÈüjexRèJ1¸àÃc;Ú˜rq¸‘ô­ºŠŽÃ¯n Ô#Œ ãu(¤˜*äJÝ´_²ÌË/ÿ®ªê¢[{…ÆV'õE+‰êŽ:;I¦“!p ®‚ÂX­´ù-§IÕMoØxsûP,€óŠ/m-tÝA#ºP0ƪÆs2)µÃ†8îiÖŽÕÑsƒž+CTX–B±:˜XqYVM5¥ÈGOܱÎìT¡ß±×M®›9mœÀÁ÷¬ÍV›K½tB^6è3Òµìí´«øZ$ 0sŠåf†};V+è‹‘¥y,“j-u´†Îk{JñD0Y4.Ÿ¼b¨\•zãO_²$€åÊä×7%ѵ¸Ë¯+Ò•ŒÚ¹ÚiÑLÒ’ÇE^Šô$M¡8'æ¬m;^µ¸³ [k޵qï¡[a|óÖšŠKCX¿¤é¶·—M<ŒN:cµjMc}«Ï¥E¡Æ±[‰.d[¢ŠÓ™!²ýERBl’ k{2[­TtkóñŒæ¬Erƒ!È#Tgº3(~J§kCo¦Kp_ø@é\òßi·ï Fž+7ÍT´Enî]Šûfªj—«©Ä‹Ulö|€©àõÅ>eqrèvmj%pÀ*åå«o@*>ê;‹Aù©“ÞH-YÌÃTOC—ÖöÄ…Y¹¬DºØBÇÞŸ¨¦¾líÝÐ×Hú•ÍŒOn?x@ Š‹ÙCO¼òŠ´ãä&µõh!Žh˜Ür>µ–tÉí²$9QÒ”´³Â" £ŠRj1Ô‰w{íg*>íT’òG8"¶Í©žà™øTõïUg™‚œã¥Ji¤Ø&º•ôiJê@žÖÅÝ´·ðÉ2.@=j m$B®åÀfé[ºT‹mföò°=H>µq³4æ²9[h'Y€‘x©óÛžÅn} OZçIk‰(ÂŽO½LôDó·FâDÞ!…>Î;›X<ìpOJÑ– ²¤<àŒV¬šc›>ÈZJœ¤®Kf ÜSÏh&ˆæB8ÅbO£x„~ûÉp‡»µÛÛ*A±Qrî+­[Kf¤ªð*\œ}ÒS?ÿÙmediascanner2-0.111+16.04.20160317/test/media/testfile.mp30000644000015600001650000002420012672421600023067 0ustar pbuserpbgroup00000000000000ID3 3TPE1artist1TALBalbum1TIT2track1TYER2013TRCK1/10TCON(7)TDAT0306ÿû0ÀInfo(!C $$$**00066===CCIIIPPVVV\\bbbiiooouu|||‚‚ˆˆˆŽŽ•••››¡¡¡¨¨®®®´´ºººÁÁÇÇÇÍÍÔÔÔÚÚàààææíííóóùùùÿÿÿûPÄÀ¤ 4€LAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ]ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTAGtrack1artist1album12013mediascanner2-0.111+16.04.20160317/test/media/testvideo_720p.ogv0000644000015600001650000002227712672421600024136 0ustar pbuserpbgroup00000000000000OggS 1DHÅ«*€theoraP-Ð (ÀOggS 1áÛâ :ÿÿÿÿÿÿÿÿÿÿ?theora+Xiph.Org libtheora 1.1 20090822 (Thusnelda)‚theora¾Í(÷¹Íkµ©IJsœæ1ŒR”¤!1Œb„!@ôád.UI´võp´˜k•¢©@–H¡ÐGÓ™¸Öd0K%R‘,”F"ƒ‘ÀÐd1‹b0”F!ƒ¡ÀÈ`, „‚ ðh0û™Ö•UTÒÑ‘PO ÍÌŒ‹KKJ ÈÈLJ‡‡†FFFE‚AAAAAA@!°¡‚ƒ3ÐÀÀá1££ÃpàÑ‚ƒ”S€áaÓ5uá!bS¤FÖtÑ‚3t‡Ãåvw—†T…Åö'Fv1!‚ö6661!Q¤&6661£†66662ô&66666666666666666666666666666666666661AÂAƒAƒAƒƒƒƒƒâü·Ð 3¾Bàꉽ¹f"ÝGà?r‰"^àØÉ}j©fWbìBä`öަKç0ÚÕ†ÆU)^}»‡äÄ4Ì®ÅØ‚È£ÃU¨62^?9w1+£Ú<“„ûª¤ ÅsTûª¤WcAÊiAíuÜàek1·ÎÅx^ yÛþŽÓhó©"cuk3)¨5ß –£û@½ xØæ]‘ w(=£Î¹M¶»Ø+‚•F_Å¢Ö9Ü"ͯ÷´†bÊЧ%r({·IzªJ„yœ'JÖlÅ®ÀÒ@äñCݺïÿ²cylƒÙàZuÇ+1‡ÀeG#F¢idÚÿêRÜìŽ,>“‚idÚ<ðõt +-1äÊÿêRÜ„Ö^vj"ÿì|¾„ÜÂv}-°V¸K&ºb€ó'wª¤@kõî^§KµçY°‰j…•†Ÿâ#¿8UIJèβbQ¯ÂÊÅääÒí#ÎÜ9ˆC¸÷6ô¶¢”W8bõ¬¢ÊÇ’/í»›j>ÌFaПý2[ E(Â,¬Ýyö¤ó?mB"\Øt-2{§Ì|H纊©V/”>÷ÀmDÿëÓ<#H2áŠnmé¼0õ¬Ô@u ¾]P§­Ï9;DzJ¢i#üÓ# ó çW`CùÓ¸¶Åsè54´8rŒ&•íy®Fæ \Ã{IÊìcpíûƒ¸3Ú%“ΪS™“è°Ü›{,‘5†öó•â³0rê=2\ÇÅT­a}{ òn5íNaV¹€ð•±DÞÉ…¬ßðª‘Kâ7þNã4 înÀ5&6£Ùd‰§E¬œÂc}BªF\g þ7Âb†Úû.Ø®Ò Ëç <‹„)#w…¡ûžV±Äã—Q!¹¸f (m¯³K•ÿ \N1fÑ2BivÖã›Îýð}jÊÁD8¸Ë¨*R´Òí&´spÎàZ~zŽ.¬+<ÂvæXð)(ÓK¬šB¿£ƒÜ¼Zaß#똳ò÷7¨,®¥;„÷‚fÚˤ™Bà¿ßBŒðíoÇ&1ª“‘&L1òw±Z:ù ¹ÄeÛY´—;×~²±U$+^Ϲ‡Ñ~²³¢ù;r0Ä6ÖlåQ”Bª[€Í£mrÆ[¾„ÃÂö &üEb´-{:>D)p=µdÓ:0¿» ЫZžâ4]%Mÿ܃‡šîžó’&ÔC:Å”ªÈ¹¡Ó°šY=óì-ˆ|áæ[Ÿ£×™Å•®4tˆK´žÑMFpZ-_óÛΊ [M¯¾ap#‹¾r¬²¥,Yº„>e…*ÝC¹ ë,´¼ S6ÖM#Ï“ÇhžÑ8}u*ŽY‰S6ÖOcx;D£?°ám…p71†qçü©¨Z†qÙí1¤Íµ–L_Ybï pEãr×r‚¢2j™¦rÐÇÝEYbžÉ{‹FèV‹Ô šfm®,&Ÿ´ ì)¨EñÔœÉ}º7œ ©_Âs yZÑßèhƒmR&—Hòô²²ï7îyÂ߀‹(n€åxH&•¶ºgݱ ²²w½T)|1$j•¤ÙåD-Ü:ô?0þ+°OÊÕÝ ¨oR˜µÑp[ÓïD%ä†fÚ¥i6LpÿÔê#YXÞ¥(xú/`ÅÛTÍ3—ÿ`§]AoÕ¼X‹±å*œ¥`6ÔÊf™ãr{D(¹€zùGë+#wÖãÊ”‹~‘ϤVVF É™4Ͷ¸ÝÿQÓ¥£Äg!tÅæ ±4Ͷ´[슠)wb´¤ÿüBâ&þ G°šQÔì8?Ëá½Æ,ù¬°"$%m6±÷åëjR\eß&•¶ºIš*/ø?u놠ܶÅd<Î*¤#$%4™¶¹Ñ{–8ùð;eözãq=Ö+€ßú~`SPµw€fi3meË‘÷aZ½}¸Üèbd˜Ó6ÚÊ£—£åMDŸVXOÛÛ‹x?ЏžOœÄª’'¿ÚYXï¨Ü¶¬šg‹ —ËwƒqúuÀÓ6Ú˜PËæ’4'²ý U"'–Ö°ï® JBäëþ÷*¤²²!9ðÛU /\e‹Lý™å±ƒ¯x¥SëA@j•¤Òg<ßrîØ2ˆOåŠ÷ÔèG‰ùW`/Žï–¨0$´˜Û\òÆ&ï]þ)¨wó«`‘ÅÊÊÏ7!~^‘ƒ¶²“4Ï+‰èà,¬‹¨TÔ¾\^LÓ9FÚㄎVÑæïGó÷ÀFå#éýUHßÈJ;ˆâÅarUãLÛja3,· Þ/Akÿ»ãÂÊÁ*á–-36Õ3 磔#…U%(ÍÝõ#‹ Ü’…Ó0¿ñ¶¦X´Ïå gðöŠ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰äˆZ¦ †ç?“˜ šfm¬¹cÝ…{…¢¿ç½_§$Ú™LÓ9Xá}Õ)€xçáDVîXãÕgqÂ)¨‡ÒÞ| #P”4Ͷ¦2Æû·ûâÅdÿ:ˆ#Ë™›j™…÷Kú¹YZš„>@n‰"‹^¸uØôä¡iüÁ–-36Õs çït¼qa·I~BŠA¼dÄ@883ȶu÷&|k£ý¹Qߥ•¼•U#xÉ!ˆ€p: pg‘lëîLø×Gûr£¿K+y*ªFñ’CàtàÏ"Ù×Ü™ñ®öåG~–VòUTŠ©8OÔ‰x&i™¶²å#!ñebÞ_z.ìŽ÷±ÃR—®_Ð,®ÍÂä† FS4ÌÛYoŸƒ¿‘tóóå*–V¨‹Öð 8$/J4Ͷ¤Ì±ÂîÑ­è,¬åkéü#p“¶þ!:?JLËæ™¶Öê"SQIä@úz;qÏ¡]‘À7ŸŒ±i™3 ͵º^¤†¡ '«+¥R¼ù2û§Z5ÑþÆ83Ê^]¸QÔdQ’¢ëX^L¢s¾Ÿýb°m«ž ±ižþùªJ©@F,¬q¿d®DÊ'6ÕÿÃ,Zf]k úô‰ä€OggSC 1«EûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿV0 ƒL½÷¾÷Þûß{ï}ï½÷¾,>÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þù;ûÇß÷ŸÞ}ÿy÷ýçß÷ŸÞ}ÿy÷ýçß÷ýï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½ç‡Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß'xûþóïûÏ¿ï>ÿ¼ûþóïûÏ¿ï>ÿ¼ûþÿ½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¼ðûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{äïïÞ}ÿy÷ýçß÷ŸÞ}ÿy÷ýçß÷Ÿß÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷ž{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï|ýãïûÏ¿ï>ÿ¼ûþóïûÏ¿ï>ÿ¼ûþóïûþ÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷ÞóÃï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï“¿¼}ÿy÷ýçß÷ŸÞ}ÿy÷ýçß÷ŸÞ}ÿÞûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷ÞûÞx}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½òw÷¿ï>ÿ¼ûþóïûÏ¿ï>ÿ¼ûþóïûÏ¿ïûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{Ͻ÷¾÷Þûß{ï}ï½÷¾÷Þûß{ï}ï½÷¾Nþñ÷ýçß÷ŸÞ}ÿy÷ýçß÷ŸÞ}ÿy÷ýÿ{ï}ï½÷¾÷Þûß{ï}ï½÷¾÷Þûß{ïyá÷¾÷Þûß{ï}ï½÷¾÷Þûß{À¿{ï}ïÀ÷Œ«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSïxð«÷‹T>ðªePû¨}åSØèv:¼}ãÒ¨}ãïU¼}ãÈß¼}ãïPûÇÞ8ªxûÇ‘¿xûÇÞ>:¡÷¼qT>ñ÷#~ñ÷¼|uCïxâ¨}ãïFýãïxøê‡Þ>ñÅPûÇÞ<ûÇÞ>ññÕ¼}㊡÷¼y÷¼}ããªxûÇCïxò7ïxûÇÇT>ñ÷Ž*‡Þ>ñäoÞ>ñ÷ލ}ãïU¼}ãÈß¼}ãïPûÇÞ8ªxûÇ‘¿xûÇÞ>:¡÷¼qT>ñ÷#~ñôˆ|C° O½÷¾÷Þûß{ï}ï—À 4ûß{ï}ï½÷¾÷Þùpì O½÷¾÷Þûß{ï}ï—À 4ûß{ï}ï½÷¾÷Þùpì O½÷¾÷Þûß{ï}ï—À 4ûß{ï}ï½÷¾÷Þùpì O½÷¾÷Þûß{ï}ï—À  ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ   ª*ªŠ  @ª  (  ª€ª  (  ª€ª  (  ª€ª  (  ª€ª  (  ª€ª  ([Ð!€À ˜À )ˆ@C ¢R¨0£3yGŒÌ0•¶Ú› £$°-¶a€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€*@ ¤ !@@`€<Tfo¼fa€­¶ÛFQ¶¶À´` žr80€„€ðp`Ffñ›ÆfJÛm´``[l F©0€„€ðQ™¼fñ™†R¶ÛmFØÛÑ€€`¡€ @ð€ Æo`‹m¦ß-LDXÔßLˆ„ÈZ‚”«@ÂhI(Û X:™·Kõ)%&ˆ™&º´–½’צ@… 4`™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™È)‹ "´-)V… ¤Èí„rdKõ)%)2KZK^Él™™ÈŒ[›AVèu¹ê·KÖæA2ŸöOgý2[-kÅ5äÈY²•é!h2”Í×%ri‰"—é(5!Û¦AKþÏÙÿLËZöZd ,ÙJÍ”ÈL¥3uÉ]rd¿KôÈa°Šd¿ìýŸôÈlµ¯e¦@¢Í”¬ÙLÊS7\•×&@‹ô¿LFÛ¦AKþÏÙÿLËZöZd ,ÙJÍ”ÈL¥3uÉ]rd¿KôÈa°Šd¿ìýŸôÈlµ¯e¦@¢Í”¬ÙLÊS7\•×&@‹ô¿LFÛ¦AKþÏÙÿLËZöZd ,ÙJÍ”ÈL¥3uÉ]rd¿KôÈa°Šd¿ìýŸôÈlµ¯e¦@¢Í”¬ÙLÊS7\•×&@‹ô¿LFÛ¦AKþÏÙÿLËZöZd ,ÙJÍ”ÈL¥3uÉ]rd¿KôÈa°Šd¿ìýŸôÈlµ¯e¦@¢Í”¬ÙLÊS7\•×&@‹ô¿LFÛ¦AKþÏÙÿLËZöZd ,ÙJÍ”ÈL¥3uÉ]rd¿KôÈa°Šd¿ìýŸôÈlµ¯e¦@¢Í”¬ÙLÊS7\•×&@‹ô¿LFÛ¦@Ïûs—n¬Û©ºÝ{u†Ý’&Ä! i²„B€"F¢m!D"…ªªÚ"$BˆD" ‰‰´â$LD"…DDÚ"$BˆD" UU´DH„!ˆD"5iÄH˜ˆD" ‰‰´DH„!ˆD"ª6«hˆB!ˆD($j&Óˆ0!ˆD"5hˆB!ˆD(TmVÑ „B!ˆPHÔM§ `B!ˆD($j&Ñ „B!ˆP ¨Ú­¢"@!„B! ‘¨›N"@À„B!ˆPHÔM¢"@!„B! Qµ[DD€B„B!@#Q6œD€ „B! ‘¨›DD€B„B!@*£j¶ˆ‰„"„B€"F¢m8‰„B!@#Q6ˆ‰ˆD(ÔM¢£ÑèôwEëÝ/^jÕºV¬ïñ¾7ÆèAt¾ß á|Ñèôz;¢õî—¯5jÝ+VwÆøßãt ‚º_ï…ð¾èôz=Ñz÷Kךµn•«;ã|oñºA]/ƒ÷Âø_ôz=Žè½{¥ëÍZ·JÕñ¾7ÆøÝ ‚.—Áûá|/ƒú=Gt^½Òõæ­[¥jÎøßã|n„AKàýð¾ÁýG£º/^ézóV­Òµg|oñ¾7B ‹¥ð~ø_ àþG£Ñݯt½y«VéZ³¾7Æøß¡EÒø?|/…ðG£Ñèî‹×º^¼Õ«t­Yßã|oЂ"é|¾Âø?£ÑèôwEëÝ/^jÕºV¬ïñ¾7ÆèAt¾ß á|Ñèôz;¢õî—¯5jÝ+VwÆøßãt ‚º_ï…ð¾èôz=Ñz÷Kךµn•«;ã|oñºA]/ƒ÷Âø_ôf¼Õñ2øwuÑJ—JTŸõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›LÿwuÑJ—JTõ¾·ÖúÜÆ™ý6›Lÿ“¾¯éŒ(P¡B… hÑ£P (P¡B…€4hÔ @ B… 4hѨCA (SABÀ 4j @B… (P°F€$(P¡`5h @¡B… h(X£F@(P¡B… hÑ£P€ ,Ñ£F  (P¡M 4hѨ@ (P¡BÀ 4j @¡B…€4hÔ! … ) ¡`5 @¡B… (X£F@(P°F€4 P¡B…4,Ñ£F (P¡B… 4hѨ@@… hÑ£P†‚ (P¦‚…€4hÔ @… (P¡`5 HP¡BÀ 4jÐ@B… ÐP°F€ P¡B†hÐa"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$H‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰Ñ$H®ˆ‘"D‰$WDH‘"º"D‰$H‘]"DŠè‰$H‘"EtD‰+¢$H‘"D‰¢4H°áÇ8páÇÇ8páÇw8páÇ8yÜ8páÇ8páçpáÇ8páÇÇ8páÇw8páÇ8yÜ8páÄBˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`Bˆ ‚ ‚(P B±Œ`D X¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9¶ÛmR¥Jœç9µN`Á™™ÝÝ ÌÎîê33¯º ™ÝÐ`ÌÌîî 33:û Á™™ÝÝ ÌÎîê33¯º ™ÝÐ`ÌÌîî 33:û Á™™ÝÝ ÌÎîê33¯º ™ÝÐ`ÌÌîî 33:û Á™™ÝÝ ÌÎîê33¯º ™ÝÔgrI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffbI]Ýæf$–fffb¼Y™™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wy™‘ÑÑÑ×wyoŸ>}@>|ùõùóçÔçÏŸPŸ>}@>|ùõùóçÔçÏŸPŸ>}@>|ùõùóçÔçÏŸPŸ>}@>|ùõùóçÔçÏŸPŸ>}@>|ùõùóçÔçÏŸPŸ>}@>|ùõù÷øØØØØ˜˜˜˜èèèèØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØØØØ˜˜˜˜dᓆN8ØØØØ˜˜˜˜ØØØØØ˜dãbc[„DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD@Øö=c0’I$ccØö=ŒÂI$‘cØö3 $’F6=cØÌ$’IØö=c0’I$ccØö=ŒÂI$‘cØö3 $’F6=cØÌ$’IØö=c0’I$ccØö=ŒÂI$‘cØö3 $’F68”I$’I$’I$’I$’I$’I%@Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð4 @Ð;NÓ´í%(!Ûm¶Ûm¶Ûm¶Ûm¶Ûm¶ØhZJ?7‡Âø_ á|/…ð¾Âø_ ðß~©Nâw¸Äî'q;‰ÜNâw¸àüÀTOggSJ 1‘b€“OggSK 1&˜Dmediascanner2-0.111+16.04.20160317/test/media/image3.png0000644000015600001650000012163712672421600022516 0ustar pbuserpbgroup00000000000000‰PNG  IHDR€±‘Fr pHYs  šœtIMEÞ &¤ÑltEXtCommentCreated with GIMPW IDATxÚìY{Md‹µY$½ÿÿWÎØ4Ý€˜‹3œ/]È2Ö™B‡dÝ—7óù|:~#á• /‡¦"9Ó)###ø* —,±¸æõ¥™tt®‰\TÄLÂK™ßC¦¼¢% bvúkð pŸº®CùÌñxMÓð*÷ýWÖË¡¢›©œäœé”‘‘‘|É5ÚJ­äU8Vj_\—ýYVyZi-¦2È/6óh§LaUT£¡ÖæZØ0´l<þÜÀ¬l)…ƒÿ¥8TÏ7ß}A\®×…-è‹«WÉ™N À‰*oÌŠÌMN8®Ë"4UÞŠòJ|qpzÛ¶=ü~~!Ž³Ù B O§ÓÁ`0›ÍÀ<€9Ÿ üΘžõSP±æ³³ é»p_®#×wÆK°âsM§ÓÙlVr¦SFFFðI.Œº>±"ï÷{ è`0`ufeÕÄÝ1@\¼”™¤[%¢hÏHƒÍfsʃû¾')ê+¸Vˆ?¨8Sóù|·ÛñÞÄø&cÙ;ʯäÁ’{Èñ·;뾌Sd¶„WÇâß9YÆ+ÔöôëQÁÍWNoÅz…ÕXÝ óÏk…Ðât$Íy¥2€odex@PL ÀW¸Ql“åûM5À›ÍHöM×uH±Ê±HLÙ–3º.^ /‰Ð¾ïÇã1[v³Ù 8™ÏçÏÏÏöÿ| K9PÊ:7HÌiᛯÙúÊD:„´³ùg÷ŠÌm®Æ:%⚈Š<ÑTÑ"g»„–÷S*ÌFaÿïæhI‹ã{q:^¸„áŒà!ÁÀ•BØåOtr¿ðBœYÍqŒ"ÝuËyZ«¶¤—ãªäof›iê,í£|ÚŸµuõýûHpÙFHvMM÷}ï^ùÂ/Ð+ýµÅY›âóœéTNÚDY«q‹£‚×Ñóy“Kaíµ‹‰håWQzÜ'ñj-&a8#ø£¯* “`¨'tv<ƒÇ–xum–¶Ûí`0 ½ `¥¶ö²d°±TéÁ }ßF#SÁ ¾tÆ"¼ƒ­±¶KÙ²yu?;3¬Â‘*]m2*ß÷=Ú+j W9NŒušN§Øjþ³#ÝeF%?w xì­^qÔúÅr¬ûOo¹ò«æy2™Ø÷$Ç[Nß ú"K”^›ÍÎÕ,#ø›mü#s²XL¦‹Ê¨‹$•%À,™›tRd¸ØªK°Ìp¾H8(X’ëF“Çå篘Ü~f˜R¶eyµZ‘¶õ/–PÌ‹"Øë¤¿¢/§q³Ù<==Ãö¯\¦JD_ë0™Y™t:ƒsh¾Gd­p^ÅéR&œ£hKô5í4(µDµ³ßÕ|³Mz6ÈUê¹\Ð2€¿S€.¦aᾫÕÊN$žù®ë¢?³šm¡Ì9Ãt KŒtôúþ]Ö,èo×uóùäà/®V+ŠÁ,7,ˆül´«”p¿ €E_eJ*ÀãÆÂÕÓþB 1—ú’yƒ©¯sš¦q¦“ÖÿúF}»Ö1”!Öëuât~T”eQSç•ôhꆵú»ñ÷”c›@ ²çøõXx†ùKp.‹éë\Ó2€¿G˜7cõQ§S ¡»®ƒ¿Â8Å]8® K‘X…Ð;âÏâ¨öû=Y¸¦i¶Û-|„»£ÑhµZ•ÐÔ÷}9¤ÉÿmåÒb3«-IZÉ¢F]§ªÔ*mpU—’]×QÙ3åè“Éd6›-‹ÅbÁtEº«YÄÿ)âK '–ÿ¹ë8ª|FÌ1ÈM£‹ˆ»ç#­2(º»D– ôº½£¢*¶@2UŽišI`ú—e$³ˆ†"°ÚQÞn·,ÙÖ™J)€.¼Ü‚Z]«”öT‹5‹üÛóó3+‹Xüx•¦f©âPÁi8œôî¯Rµ‘þêP¡C²E5”/ͽ(WVåI`¬×k“Ïd2ʱôËéƒaÀ þµ[öéŒ-²Iüè^Þu]¬é ¥´.ÁÃŽ8‹/å8ADo ™ä¾Qù_Í"Si-Ùì7X»ÙlسºÁº~k¶ŒàŒÿy$‘×¥Yåññ‘¤%®œzÄ"ô. Á˜ÕÇ5½z­V™¥ÉÀét !¦Þlvz³Ùà™E–r CÙ¿GôÕž‚ßvê´ezV+ÁrM"¬¸Ÿ¨¸/ Ílžàåry?ŸÏÉ"°=ú×rÎ:–¯×k6^ÑBÕún9é ;¹uceè53}6â¦S(UÕ/6ÇMð8Ò¨Ä.¥î–£Û.•+[FðµGœz ­V«ÇÇGÖna8N¤íì)Â’W2™0œµªHÓTª«F ýE‘Ø­×k(¤é AåXaê@l¸2Ùn•”ÿ•¯Ã†¡¿¿Ë5I±*Ùóv»µ˜ Ã3óFÛ? ¹ªDd-—E/û˜‚+HÕ°© øæYoÌ÷ò±Xîc+VŽãF@\?ÈàSÛ)ÛvãO±ƒ°5Ó¨r'Õ‡S@eÚ¶- î_¤/®º™“ûf$_5÷åY•íA|-‚’·d%²4 P`µX,–ËåÿýGèÕ¿ðc¹”‰S»,@‹Øˆ© ÂÂf Б÷(Õ.ÇÊ\ “$tã’µ\*…[%T¹ ì¢ͲvÛòKêP¹mô­ŒeÜl ½Ð_Ù0Ú3ÌäçÝü-‹ê>QÍ WæÁ‰Ù 8ˆPÕU Ó*ïÝü‘Œ)a2DÜŒF–Ÿk]Fð5¢¯éV½ M¾ÙüªÈÓXöþóùüááá¿ÿþ{xx¸¿¿WÂã’ô äJŠØpçÉTƼgælQRñ±Áâ•Q»Äf’hLxfÞûóè/¬.⊢ئ¢ÛÛ¦%umGWÙbD\ͳ¥+©j˜-ÏÁÝÆ½Ñ©Ê´³›×Óá¾Ñ-.ö ùÚÖùý~ùÆt:5[SŽuè˜N!tFðu¡¯ Áçê&¢ùG™¢7aÿÞ4ÍÝÝ©KX”EDÓΟ÷À„¬zѧ)ŽV-ÁØï¯N‹H¬Ø•^d-‘yì0†S«ýË2`íÀ†ƒT“þRýuüÎíÝí1álÖG&µË5ÅˆŠ»HÖË ww s·Wžk]Å6q‡8Váw¯1³â1ˆÐlÍsðYØã,]Â\‡r4ÈÌVàŒà+JÄiÚLrè%ç  aܽÕP³þûû{$<ø6|ö".ÊjoÉN§Ójm´À=“Ç.¬ú;×Ô4^ ÖðâYCr.Å9-<éÚ*Ç®ßétŠhŽëh!ó&ÑW?gx­­íº‘ˆ¾qØÙ]›ëôÇF`h ÒVÏ^Õ 7…çßçÕįpl$fô¡«nu…ÐqÔR.} À¦ÊO̹­×ëŸ?ƒáÄ&ß ÎÉA‚öJе\­Ÿ‡Áj^íµ-±;èo‰x5ÊÆì<æX¬K¨ÏEÍjùÅٛї‹…ëºs³“@oeÝ÷V¹¯6œ2EMUÕXñu7^â¨Ô…?%Ž2óõçM0öÂÅÇA=DìHNôÍH¾ŠõȺ/kÐãã£J+¤¿ {mV.AŽZ¯ÒY}¾æ9—a8…m:R‘^Õ‚u&ôF¿!®J)`06(`›’Z'A]$ïge!ްA‹ j*ÕB¦nM·w·Gy¿+3óÈ-—rÛÇ‘}5é}Ýç]䣽Xs)aîgÔm]P‘‘œñ¿¨¬‘0˜ŒîHŽR`‰Q“Iß-Èí¨öØ}; ?{û n £ËãüÔßÙl½¾5‰s^£NÕîO¾R-¾v}D%6›•KAYV?».\âVeÏV¸ÕBAb@îëH+wln%§Ç`—iš}ØÅÓõJ¼÷JðŽ®¦8d+pFðå }®fž£ì™„j)Eƒ Ök~°mÛH4³µCCæ—}œ8BUŵ¯5¦jöˆu\U3P"ð-1.˜] Q¥²¬å%vë`ÚYÿ&»V"‡“ûÞ7âò9Å‹Íåãã#EÝÍ,‘Zã×É–`+…üÎÙlÖuî,ë,ýÃápµZ‘´Œ.†ŸºV.Ö;ã÷úòþÆJ¿¿™$HkûÓÓGì«Ø b¬ÁRÞ,ƒ…ÞkC¯Ói KÈ9G;ŽT`e$_ËÓ‘ù‰M¬‘‹Ž’øÜB)4°-!a¡ë+óÏî!´ÑG>7úÿù;írQ+G1—M)b0†D ™h ŒF#lÅü~ ?}U>CÁ)#¿âÌ8°Y¿É[²œ4óL¶¦ë:3Ϻ:Kçóùp8¤ÂÂ*¾qnft¶ºÂˆSÊÑxR6Çç%ÎH¾üsk÷Û| €+Ü—Qì ý¯h¦Ífcÿc°M®›®ët„„øcòÐMIÅ( zÆÊÑâC?ή뚦ñÊQÖußF1X¹Ö' (ŒÎ*öv“ƒÕ|ƒ+ûŽÜÜÀ]míÀ6_ÐwµZًʼn2ÃL'4E ÀQgwýpwÀ\e2.<›\Y3©ÀÊH¾.z ŽÃ;Úgãhr[¥¶âÃ_y1~Í£.[•öÉ€á¬f+ºðú ®ˆ”÷Ñ’O*kÑg³”+æ«YòhôBONÙøóœ.â8ÛXúó ^©nÞŒïUdÿd›c晫ÃmÌçÞŽØ• ü»0EQ–g¹rá{¼åƒ3€/ÿ¸*nšZ 3›L&TÂÌÈ9v¾{ÓY,jq ï—íúã¨'ê¨PMðýã’ª†«¢Ô–uãb§k#UÕù|§µãdÂÜFNéf³a}Ý?<(ýå°uX¤™±ƒp#aÆæ¨ÈIºarzæz½~||ä=À 29¼ ܽ¿¿§àâ€ï²#©¦ŒpÌV‘L8»;‰mÁ™…ÎH¾ðÓë@=*ad)MGË ÌOêoåæ:*°Ê¯Ã^¾à zwËâ˧8|Ž.° ‹æÉqÊMÔ3SÍÍf»Ýn¹\’»F~V¥%$øIPý¸K Ý…Ä×î£/s ýlôMímÛbµAæ™+ÈÇ䯀‘÷Ç6w6(ß:•Ezæt«Z‚h+§!e$_˜Ãw1·Q4\Ô]’GšÇÕy¢ZmÄ„s5-æ¾>oÍÕ0’#Š›‘&“‰2é3;€ÕsQ/Ýd2{%$±«ùKóù\cm'Xè¶­sçv»…8ïäœsð@%O˜Ÿ ØØzsâUƒø>==ýüùÓ!ƒ¨áÐöóÁ™½¨ÄÁ½È·;±Ù=f¤xœÙ(«äWœ• 8#ø*°ø\ñÞ&×AB«EÍ7*$._Òìoº¤qÎ<×8 .¤+=Å`sÎq&]åÅhÕx†ív;ŸÏ9šŽÊQ(nyÒ ¾ÜGٶα^ ­q41þî ØM˜;°Õjõøøˆ»ªÙ ¥Ihû—Ǩ$Wßñ D ôªñÝM³èëî9ÀŒà “àhXAѨu:–y˜c»‘¦Œ£Ñˆ‰„•¹Ä<Þ¬¹,»h\YpµÔ—þ–` ô:)ÃÍBËâC$×>EÑ\`XE´nˆ€"—ÏDôF˜*c5VtƒuÚ+7˜<ÿøñCÙ3•®×pßûû{þyiíQ£+ª4ס ÚRf:#ø*žÛh¯(Ç&Ú¸ÌE«w‡®HFß3rü Üâk³ è öвl:ïÌ×Ónúù]¤ìà¾zGïI+…J£÷û½¼Ùlxåë¶$Íçs+ÁäG­ƒîì`Ž®drß8°ùäW’þ8VD)j¶0~ê¾ôûƺï·> n¦#ýÕ,Ó ÷ékzFFðž[)]NQEKž%wÝeKš«ßù©Ënås¤Ç‚ÝGL4ü£¸&zêFG{hY£=Hmâ„È2iår›®G©ö‡{ Eqq £…Þ¦iõ­Ãë¥å$ìð >> pʽ±è{ùØ8þË$NϬ%%úf$_ŸBæ‹uÜ 0lΑˆ8tèSòXOÐk…UYŠƒöÀà?ZQjŽÍ¯òT˜o¯<°ªßH;QWÀ[J›•ù[ÑB$šŒr]Ø€7|Šhÿ}q’+äWQ»ŽüÛ9¾ß½î[=Å<›£_ƒ!˜§ÝwòàœK˜‘|E0üÊW"æ5.^‘;´àc—Zõ̇Ä3Þúø‘xŒ“n¡zŽDÍ×*:ë–R¢‘þZ=­R—¶kñ8NíG*¡Q¦N?ÒGUã$‚î¢ÜÌãGÙÎ)ºõW+´èHêÐLeêŽUˆ“7ƒ@qJ&ë:´~æ,—䢗‘|í0|ºËŽZ¤(§¬jÀñ|ËQEÂR«Í>£Óë,zW‘øô7ö5Y÷-/ÎUçR°k}äÊ ¬iRÆ6ô}O‚šúôG%l8¶ðlªÜI·ÎÔ»Ôkü±f³5l‰1âVìÆøŸ—S6·bÑvôöæ=g$ÿa+mt¶Ã»ãw øü^CYµ?nBUñHœñG«ÉÏŸ?¡¿TþœoÃZúÎçóØgòÊ1(+‹ƒ• P¥£Ñòh4}Šfªán#“ÃÒÕy™$|?ºoˆú¸(Cs¼Žß7Ó‹bV¿‘8›+&0Ü6ÝùIqµãÖµ"ãÔÒC¶!e$¿5.:ZTSb¿é9 8vD¸€Æ‰i@y`•äq8Âh^O‹är¹t¨œ“m^‡IªÏŠæ$éì+Ò®ƒ|W²ÅA‡µÙl0Ïrúò{®Ž›Š zOrN, Z¾ þgöEá7¾cqrs¼q‚ä9QØq®ôo8R”)ÇÙ*v ê32€¿ ¶2Z‚ØJµÑpTW”%¢²J"ðUgfúŽVÇ úËaÀ8µ72ÿüG¯ 5¢¸VEßàr|}ŒêÙÓ%,6U›íœN§Ž´` •ŸÏçNnxó‚È‘G«K7+%ŽŸ7 âëCEæQl©ôUw“üÏq¢Ü< qûèàç¬g$×,ŸÀãúÎ7ÄéC%x_TPQŽãâcD$V¯d*UÜ5tÛ•ù~J³˜ÝßßC‚AÁ?®8Fê©iŸ¢ªSîûâ:nÊ.‚×QÊÑi‹,ôûkÀž(Í(øCQA¦*-¶Š~÷¡W-š†òE=dÜÅÉ7ù„: )ÊîÜAzßLþ##øŸcÀ®z>ðÕó/Žž2`S»À›ƒƒôœ€áWJQ-ìÐЫm[­"徨,À¸цtŽ·¾þq¨ƒ)ÜrljBÉ¥åÐï’tZr³êÆ9‰$Ø–¡7wmÅmоÇtà˜ŠP|ÄB|K°’oyîÏ쮀•Á“}5}{5àxû•R_æ£êlã„ÞŒàïÊ€ãÔ3m°*ì|¤j`°#”j¶¬ã£K¼Všh‚1þS›xÃñ9º¿¿W~õGŸ£Hm,‰©¥dvïð _ù–]í\RãD ±CâÞEœI' évÉk¸xÃcGµ¶\5n'÷F¤\•Û&Áøo`Á³2Ñ95´ŒàoÉ€m b™s-C¬s²¸É +& ÁI­^ ŽÝn0¸•¶× IDATÝ®ëbî‘=~Ó4‡Ã–äóÿýwbùƒ- ‚^®æ«ÕÊYÂV‘厯cXôlš¦m[¿â'õ³Ë€ß òܲÀþÉ_kS²ìð~Šn_LÜl6t„óñí~†û"`è­6 bp Å  ›s)ËHþÞ ø©& €²C%©NªA__ÐYÊÛ¶e†­MÛí–OGVœ`΃S5mêãßn·H ÍB¿«Ü6©Ó°åXT;¦ç›.ÄqÞ"wÑjµzzzzzzbW§¹YL¯À`[Èþ…&œÓë›Ð›‘|S Øéôå¨ÀŒ ˜:.ðàXV”SÎ -lZå§b{ŽÝÑÃlJ˜!·Cö|wwgÒÁ2}a½pÛ¶¸dŒÇãjìÄ9+¸¹zÁØ}I)…?m)µKüÛëbï2çÓQÄ•VdÀß~"ú²abâ/S´#ålSô%ùÌVÌ6¤Št3€oŠ›JE"æŸ"Ív»‡àK$»’`'E`º"´ûG#ªÉ/‘;‘fÁ}½S¨ZÓ¥¿?~üøñãÇz½þùó'lƒÐýý½ÛÏÆOWLD[³Ì™a#bkÐ_ÙóVÉXÇ<Ø[¬k&ÙW Àßw]ŽS°B£;g[nš¦Y,÷÷÷÷÷÷nÈR‚”‘‘üpìÙ ¸Õ¿®¤ˆá¾úfà¡¡ðªÊi“ìõ}3gJ‚`‡üü•Ó¯ÐÕ¶-†rñp¤­OÛë\Î0ï”û:9ŠßƒIšÃîû^nú·,¶}ÙˆX$`¿/ý­ÒÔ}¹º®ãºÀ}Ù‡Q†@ýï$Ÿ32€oœó ,¡*Qv ÷¥âûãÇMˆšâ&øŠ‚a}šmFé“ÅT{twЉÖ8§è.² «åiÛÖM†òìò÷#žìýˆÉp5eL ®LzÏÄ`kFLk—£ÿ† Nø¾Õ_«ÝQ¬Î?IÀ° ƒñ#Ç£lõ7 (22€¿=¶&*¥mÛÂb7› ùg2„öŠhpát<§ÄCd*aX”u @b‚·²Z<cT>£¡}|||zzÂßCUvÿÿ¨XŽ£+\ŽézxµZEeu”zÁ SÕ÷gß@÷‘ ÜWd&4ŸÏïîîtC“þ&úfd${ìjˆìÉuGœ'“ €]3u_a[x`qtFºsÐsyNÕuÌxœDä«6@gn&¨•’!zz²/Y÷+þtdIœÉxÎI‹;6…àÁmÛZê¶ÆLµò|Xr̃ŽÐü×l63‘`Îàû¢¯û ÇýjÊ¦Ö ú*ƒÏ@ À7€e!q¡Œä“E_xp)…Š/¯ä{­æâ-—ËXè-G=—ìP_ß½%ˆ¶Îÿ,±)™#¤î½'`´]p)ͤþØ[\B8Îg…òÚ Œø™zyÌœ ó¯ã¥¶ª#ê£öŠîß„ [Ý`…x‹ÅâááÁû*K¿ À7€5Ú<Àà YJéºn±Xl·Û¦i@bh¥’+p…TsÓ4ôì"UU?%Ü–c¾×÷1ªû[.e#)Q ¯šdž‡Ã!D [it=MÓœƒ¾Ÿ…Ú$º\¨*N[X‹ ƒõzÓ •´íw—þ&»¿tù@ŽäàïîAýXôSC…à.‡ýÜýý½Þ“‰¾ À7Å€Y£›2ÔRŠvÍ TùuBm9΢MHè}µ‰0ÿ­¤£‰Ï;Q¤r1Dù ÷…MÚ,;™Lpµüï¿ÿþûï?xÕ_yÙ‹|ÎHG$ŇҮy³ÙTMϱëw®ÅѱÄb°˜mÕYbý»é+¹ýÜd¸…â9pÜÕô*ë¾ À·z6Án¡¿T‚‡Cß÷³Ù £M±•€U¯fÈ%Ég'Õ€¾ÑˆãGÇKÝChÈ.RB†£# ÅÕk-—õ3F™÷áphšÆüŸˆfhrª¶6E0çL¼ˆÁÒ_‹¾V¢ŠÍÃø¾ÐënCW5‡O+”CÊGEÁ|>³ À7€%µ¤£Ã3ÿÛ÷½©NW@ðƒÅq¹\B%ÿûï? «åK+#â~`òP%göÑÇý´P-¡ »;†—ã¿*¦FÝ“è -Ç~$_ÉD$–þjþU1`eÏ‚.5QkÏ"ñ·¦¿„#³J0A¬'èFÇ«|f32€o$by’÷ŽÒ‹eH“® Y9º!FT{xx b*?~trpœ0òlè/;ÒÅÿý÷bZTµjT_ŸÒTMÏçóJ5v8ùà §hägI1_=nPÄéraEý3§]õõÇ7}l‘›Q?±Íð(лÚÏR~~ÒÑÆœŠÖ1ŸýG3€3>åaŽ«eΩ;1Û\Ïçs2Ïrß8+÷³¾rÞ yݸìHf‹Sg£€Âöm+»ù€HC]É%DAo9vÅÄ2¯;Á2p´¹„ª_ë6r0l¤*³Rã:Û£‰ºOû3\\ qžØß¾º-SÿbÔ1$ g$Ìó,¤ð†t¹”zAsAÙ8Z/ ,ÇÓ~Ù`€j$"® NB„QÁÑ9~rÎvÉ ßv´¬¤t^¹êÉÈMéWƒ«?ç“«¶Æ:½ÑƒÔäÄ+õ­×Y ÐÅyˆ»hµ}m\_ÖëÅ2i ÃK0o)¡£øñüWÑWq- f§”é a8#øÚÁ(¯ÕuûªRŠÜÓ^M…^M›‡¯<~õ;q1úm=¶tð'íìP‡÷§ìXéÈÉ»"ë)ÁÌ´é«v1§]ˆí,‚%ë„ÅR /Œ]Ô±·øû.²ÕœJ‡dœnV®­Ú]A/Úª¿”Ôfª¨›+¿*!*Ž{Š»n‘cË–#ãˆLLÄ’•jFFð5†O)XßÏ碚aeÃÐGm(¬Ë̾}MÕ’‚fm²ô.—Kè»6çXBžÁ¥”ÉdÉœKdœNÁ¶€ðDqæñB¡oåXôu^$›†7§Í¯0û!-vô²º#¹¶vg…÷Nääöãÿl(aì¦=xÂí“ÏQ+Øãÿªa»c’é×z~~¦s!»¥3€¯…Dk'ŸdÖLˆ€Þ‡‡‡ÿþûO#À8šþü&Ú=r’rv*³<©s¤’ÅéÏ›+‹…—,—K–cSÐXsà&õ)ÇÙSt;HH¼Ûíl~uTÔ·öÀŠû§x)£Œ|<ǙӣÑh±X”+˜†é/²æL ŠB,ð>,Ñ]UÖŠšÃ+y%ÃL1‚›ÜNwþºÃ³u,±xÁã\r~pFð•cpN6,kµ\.MpÅ©|\ÁÕlÇ:œóÛ!ôdç>µ•…_K0‰ÿ‹5¯¬›ãñøéé©i†Å×ý[ ´6¬ÝöU߆ 2·gƒOÄŽŠØ4 éî9 vO¼ÇZÎqOOOL›æÒó‰xRì+Á/½ÂÅßI®JðªÓ†™Q€± =ÅJ)Ëå’cøãˆ‘ŒŒà ¯ƒö~°êÀ ¯ÓßxÚòŠÓ×#~b=;Nþl%3B€‡Ãáãã£e] :<ȧ§'y-^(|®ŸÑ¾#Í(.’uøŒF4G«öR’Řö:^TN«¿L½D~¯} ÒŠ8³2îY•Fó餿þꕬ °ŠÃ l[£7n¤åréóùœ"•‰Á À×› ƒE‘rœ[@. úhJížg}üÕ;¶/*S¢@ìSRkN€)Gý*óK)PÞáp¸Z­(T·m {ÆB KK¨Œk·C/nà YdUƒfa•í”þ”P=®//"ÜÅ÷zio.ÐËEœN§ÏÏϲFq™×±RœE•8½m:œG°ç±µþBÁ‚»¿å’ÄàŒà+]#n¹´Á·bß‹Ü÷Jö .¬>åXÕvN<þ/8l»ÈÛTW 'ƒÁ ïûñx¬„‡ H‹¾¶–º™ˆ£¥¾5W;±.³FÉ.ñb±ÀTáÂU}"îL¿MÍœûì—®`Xq€À¯,Kå~Ⱦ¸C…ò}]K3€o9¢û’ÿ‹= §ßyÙcÖÚBS‚h˜\J‘ø~%\Ùý)îFkòÏð¤ÉdgÚl6è¨éf’•¹jæ÷C7@hÜíÅF>5n*nGP¶[áZÓÿ&þâ³G2³ÛãðÄNRÍÐP Àï駪œI¬ʼnãñøçÏŸœÒÅb¡gŽè\î2€¯Ž‹¼2‚·r¹»’Eß «ÒP'ª.Vóõssµø@¸ ¥kÛÖL ¥;šGÛétJ®Ž^"pð&Jп5{G¤+Ø[Hve~1`˜f¹ª(þ‡ÍÉg Ìž½ ÷[ôáf°žß¿rN¢8Üû™ôIÕÎS÷£Ñ]ôz½nšæéé ÝFß÷H±R•‘üÍ ùj)» Ø9².‚æÒµŽúzSÓàûý~6›Áq‰õz­ŒM 5`ô8h\‘Ø”£ñ¤6 ªjo`%•ë—ÅJT‚ËqBƒÀÇn·[À v†Ò×À0Å…ªq9Îß&«“𺋚.§‚ܶ-…ðÍfƒôZå³Â}ß?==Ñ€Çÿ~ÙÉÉHÎxôFsãëôZªä²8óÉŒY c¡ô"‚aêÁûý^.îÖƒ§7i>Ÿ·mëZŒ¤–L5WD{2Õd7з)åÜÃÞèçÑ C161~vHpß÷X›¹Ù‚ÞMB«³ãøõ!/¿ŽL8­þžî)_iCâ+Ö2Ø·­V«õz=™LžžžTÎóûmFòÍwŸ‘üO ¯ËbÕªxm6¼åhB©¸©•Ʊ;ÙÝ¥Ž_ªjnÜeq0 3"ÿ\Ž¢n”®|(œ´:’ç]ð}ønO~¯8Ó$uÓÙ±”Bݲt°išÝn‡%*g©„rég“`GCb ¾X,žCT ‘µ2ž|ňï{“S#¿»»ûùó'i6pzfÁ$ ´‹3µ22€¯wYôYuÎüu2à¦èÀ–ÔòTÚ«KQÆSJÉJ9z¶m o3­JS“  %¦Óo¦’Çg¤¼Í„fN‚W‹û¤Þt]Ãìøî“ÎU4/SŒ­q¦ X |¥^Œ3‘α¢ŒÈ­îáé鉆RϵŸ:ÞöUN+W¹Œà«^«7×IÖU]Ukœ5à膑ã´GY#æ5!¿B“ÅŠI–•ï$nÑÎÉ:yÝÒê®åréŽÊ ºÀV‹ÉH»§aïEÖºišý~4z2™‹þ$q–âDý¼(Rº—IU*øÌa ñÙy8ˆkh’%ÎÀV1P bÊÈH¾öˆ øj9ú!D5…FW¯Ó\úÓ_\ I¨¢·‚®µmk¹z½^£²ÁN!"¯8ýFGÃ˸à¿ÇËã&¶\.µøvôžæž+³ú³Ù¬ïûÅb$ã×ñ©[IaXÕ•ÓäG$¯q¾Â_#ÔÚ§ØÖ4 §lÚTËß’sxFp2à«3xV¼t±àìÒ'èR™7Ëêc)dÚ]â©ÝqÌÿëUû€o²›“«É [®Sœõ{Š%as­jÙàÄpÁÙlfÏ’µOÛ…?oqzwq7F@ùØêœ´3\Já¶éº®Í=ØtÎçsÐz4aËlX¯±äÁ ÀWQ¯qâIQJ”Ébž-2à V²£6N'”²C݀ؾïmYv8ôW+Ð[m#¨Ìº“U¦"®Ý·¢ÈBƒI¸%Û›¤KsÌG¶ÿ+ÏÞi?ýß¾FJ>Z}¸Z­˜Â “E vÎè\ÜmOôÍH¾vè-G‰ýqÿ~U 8î°Ù‹+xU¾ öD¢ffc<©þ ÖDI¢2HQú{Ãkh%*Þžµsà‡oˆHìkdÀ‘Û.ü-j¢ÑkÚŸ½ï{zÐhØ¢žœÑ×ËåòááÝ<—¸ŒàkÑ¢±ñ2`5¨KÅÿU„uAlµÝ %¬ðÁØLAÛÇ[Ò»mc7YSrË’Z°g½^3•NŒ~M€0ì|ð‰ qœ+UŽ–묳ÄI_Nzz}¯cšÓÉ—ÜÝÝ=<<ÜÝݱ‡‹ºè\ß2€¯zßk#¾¶ƒ4¯K5r´²ªR/Ë€e´G騉D‡5ËÜ ™ðº\.aÀÿÈD9qÂ.XRˆÔ8}ßóºÙl`~`0d1Ú”š”¦6<›Íl#–tIûz»´ß†Ù4€¯Ø¢éú:‹Ð(ȤL¸m–Ë¥%9Ž0#øzA7ºÎúT» /¿Ö2¯„«v.¥ õù§ÿ—bÀÑe§¤Ùl†BÕɉ6PñO ClÿS·%lØ-´T'…µ'6º¡éaÉ Œ4xæ G£43%‘”d)>Y‘ÝÆ1Y%ô,©î&ëî>==aÏb[¼´A7nõw>ŸßdßZFðmrŽ?2à+ÉbÙéþ fÇfñïR X&GbFûøø¸^¯­VF ¦ó•œ3<æîîŽÞ|øõ›3AÉ?ÇcI°÷l˜:qÙÛuùX²M£‰t5h¨šDr:4ìwêe 7U3’ÿ,AáÈÿÒhÄÑ_°ÇÇGöv9+`ßFØÛææË Àß;\"ö¿*| %aÙƒ‹£ãذ Æ ª A_ÖA–EØ ð ‹ˆ¦Œì@k$¬š:ýËËhÄ`§ÀbÕÀ°™g ÑšãdIÇŽS˜xï/÷W±sž Û>©jµ«‹¤V Vcóº[ÃïQŽN“0è«78ô7 uItᾊŸó¶ÉHþ!åuzZdÀ6ûó × XÑ›^™+‡Ç²ÈªyÆkÀöò.‹®ëîïïñï¥ÇF¹| +ë)ô—2^º(Q(.ñVù/.ôz½.¥ “6£Ë9ÇmÛbí(ÃèY!îÆÑL±<\Íõ{‘ëÛìk5‡CÛ…Øæk[³¸‹ä ½7&¾WléëÁ}Ý·eÝ7#ø;1`Aí’kˇ w |J¡ÑcÅÚc ø‚.%Ì­3 AaŽLŽká;—Ë%½°YÃûÝ–Q¼cÎèÛujÇVj¡fóýîØTfYcŽ©i¥|àêG«ÈÓ$s|ˆªæ(ÅwV‚Ù1P·¦ª ·X ,—KäWDßÜ´e$W ܵ1à¨UÑØl¹Ð{ÊK.ÈÛXÓÉBS±‡°G“ï¡DôÍ•ôÅMì®q³UU‹I3ÄÁJœOH§w;¯$NNSÓ&rãHSD>51GMu_àšü8žˆr·gŸÅ4éŰ4ÝMC\à€4Iä•däX¥¼’àÙlÆÉd‘Õ«2%æ-z~Xv¤§ˆåÕÃÜê]×áÚ¡üXa”$5æœ+€|Qâ‡íÍNG£ÍºªØ@hNmZoUô% -ñ½f;ëŒàŒ?ãY ÎïÑ%@´sZ,_êhc-*·#•4‘Í~/Ž Š­ÐÑЖÊÿ:²Æ˜r5¾cß+Lé»›N§±!Ø[Ý Jß÷óù`&J¨¶Ä.óRÊv»•`RD1cQÕƒc&Ú‰Dµ¶ß;"›S)‘@[ŸÎ;$#ø{3`“]%t.FÀcéqàŸy}£J»N&G2”‹ÝkX¤$=¨« /MÓÐî ËÁJo0©˜÷çÛö:%”Q‡Ãt:­²&l(eÆÔ‰Õ¨ÕŠå¾eÌ^\/™”—{’¯T@}.«>fa›)Üän+ ‡*fd$ãˆN“-‹ˆÏ¿ÿe=øâƒ¢Á®F .2¦(qºTà€•ÒD/zÖ@a/oÎwžp»ƒ¼Å`EX1íklXò§üAÝ3¬éúÈ ÚªÐô´GY²ëQ<Ž2 ¿ßÝdtÊÌKœ‘| Xxˆù[–Ήf×ÐS[޾Ž.€ûB€7`ìzúhM*F9+VÆ0`<Œ8ò8°6ã§=ˆi J)oìS¢7)f›Åàèc³Íü~글±„ø4ù\B÷”¯§ŽÓñ—G³ëDߌà›â ¦ÂÈŽ2â»”Â?qºM“«X;ŸÏY°š¦aAÄŸÇ«j‹”!ÍçsÖ}Noß÷ób±xxx€¾sÂSYó±û6›­Ë±¶Âþòù×éFQtõúâd¤rÌ~ŸÖƒc 8:gùj9YC(Ý*¿–“ó‚f$ßÔÚ§DŠBë¶|Ö&  Häâ´* èŽF#çú5!®ÍÅÂ4Ãl6»¿¿ÇØÈ³¥‘ 8×Ù/¸‹Jð·ªÜ›O×*L5»Ê—“‰Â~1îªd’?þ¢jÞ  À7KÉßRL],0 ãYÞ¯ÁÂÜ„˜©~>ȵù8Âià[zŠáTÌ'Šý)p½—`ò\9DÚõ-8Lû«N‘¸ªé¾HÊO¿’¸›‘ü/Üñøùù™z*]˜L/²¦‹k¯]ÖJRµZˆÀŒm·½6­ŠeH?…ýÖšdÙÜ™ù狤‚¼a´ ÑŸ|uXayiR’׌àŒ¿×”R º*¢íî®A¤Zõ©G[ ƹÎSíñÓxmRÝ“DßkƒäsÐ4ÉkFpÆ×‘UeJ “ϯm*mÔ¶Äš\%u¹ÂÝÎÜŽ4–ø¦ eFFÆÕ-_•ì0ãcC¬8Ø “©+±”Šñ»[âšÑKm¦Õ–"Ñ7###øŸ†a¿rÐû­#ŽÊ‰œÙËŒŒŒà„á_8OÈ×Ð÷„ÞŒŒŒàŒŒŒŒŒŒŒ#ËS‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘‘œ‘‘‘‘‘‘‘œ‘‘‘‘‘q½1ÎS‘ñ»8¾Æ ¾fddd$gd| èBïóóó`0à= ;}MÎÈÈHÎÈx#Á_ŸŸŸàçççççg¾è·£ÑÈׄጌŒàŒŒ³"ª¯ƒÁ`¿ß‡ø ƒÇ@ïp8‡£Ñh<???F£Ãᜑ‘‘|I"EüË«°çá"'á”Ξ~Cd· l)…×Ýn???ï÷{¿ò¡`½£Ñh:î÷ûétÊ×ù£ù ddd$_`ѯ¾þO-ÇàS!°ê ÎPZ~-Öú:yõ2U°ÊkDY؈ î;N·ÛmÓ4~½¤,+###øë¡W †ÃaÄ¡þø±PÊ ¢G߆Iç¤Ì ¨ñBŠ*§xõPŸŸŸw»]À¥”ív{8ü/~gß÷¤¦ùPÃáôåã}322€¿~|-!) ßÞ¢ìä³J:¥¿*ŽV鱓¯dÉRUPóp8l·[èx$Áþl¤¿»Ýn»Ýò†ñgý…嘔Þívà+Šïácv]7:Æ—‘þošÊ“ó½®ÝÍSˆàï®æ¬ã0Ë4ïys˨@(b cPF:JM&“ñx\ÎëÕ©Ò §Õ\+µÐP€3¾Ê†ùqÀ2nàµþ“Ì»ÝÎ\ºéhÿîp8Üív“É„ ~GÉt>ñRF_Œ[¼f\-¯¨Ø„áàëºM¥_  ÄʲßïÇãñd2±GåÛݾ§"ažIåÁûý~³Ù‡®ëÄ3¹ït:N§“^߈Tô4*a±. 1‡,ÿæ˜=Žºåøûýñø³1bnèÆ IDAT/]ý!—©[¢ì™Ð{ª!í[¦ë†/˜xý‘÷ž7‰•0œ|]74°ïû¾ïáC0ðí®'§ôú#$ñZ5ÍË„;îv»Ífãg~~†ï6MÃ? ¿¸§>ýÓlh8¥§äRnÊ_ßï÷ >hÊW*4-'Ê,OÌ^Ø}É®Œ­ƒyf¶VãñػВODܘ¾(aóò&©ð×\” YOaØçŧƒû™ËDZ++ ÀWDY\ú¾„ú¾'5:YtÜ?~= ®’¢.§*åø(Fö—ѪÎÊk×u@oß÷]×ùy§Ó©É—Ú×Sñ®Ú$´ùÍ»căç`,÷ò ’`þËœså]1]´Øólêœù£“ɤe¤4à÷MÓÌçóÉdÒ4 ` QøÇ‘˜‹å.ŠÛƒ›Çs;™L¦Óéh4ò|¾ˆÁQ=÷þ×ùt¯ü‰(÷;óà?ð´¿ø'ܰº^ý†ùåf,Ìfñð\äúŸ|E x»Ýn·Û¶m7›BÙ¦i€–æ¯9±Ä7²:çp8ŒF#··Q¸k#¬¥P$ÁRÌRJß÷¼>??w]ÇòÊÒF£1'Aüæ¨~÷ôº@¸›iÛ–ßJÁ¥­ŒÝ1˜ð,¿V¯#ûŸW'¸–Çc^aóÂlúÛ4 0Ì?£ëÒŒç/ñ­y³ú&.ôÜ›ÍÆí×Eú;›Í€áÉd2™LܻͱÏ{=S¬îƒóú/<íyããõcéÌ‹M%n£cf(*Ic‡ÿåöÞívÓét6›ñ\|Ç‚Zð 2`ך®ë6›Íf³{À9ëý~ÿ«ó©Y±p[y9UÕÐAˆ“‡jƒÁ]ÈX[UxE˜û¥;¥Òéß=“3m6›õz½ÙlÀਯæ3FoH×”SÆêŒ!+ÁÌ^K¶YÐ…“ù*7qʸït:­rÑŸ¨¿ã^ñ›_iƒ®Žßà ‰5‚·©oªKÙu]Û¶mÛz“ð 4M³ÙlÈP¬©ú¸N_OO‹¨óúœ~Ã98÷ÊWu ÆE bÃï?¤s,æœOSY±–;ã}äÙd£ba‹¹\.yº«zpFðe˜„8äBw]WJ™L&<‡l·Û­å“÷'ÖªºfÅ EJ…ʦž\ Üž—_UEƒÁ@è_c^׿?ò+¥RÒʈsì̉ÀÏ’½Z­ÀàÍfmTcÅóàïa—yðžÿÕ-ÒB5…M=«?H îF¸•¢Yö½ßóQÐ{Z®û Æ,b<'¯´AW7¡§h¬ö%¾žÃqçsÁ%LDÇã¶m§Óéz½¾»»ƒàÖ wþ“RqÓ7€Ü+€çF!&uÍ÷Æóæë‡ÒïŒGÛÇß¶z˯–pÖq(ý˜É[,‹Å‚{Ì›ŸÛ/Ipðg¥pã"xšíñ–%õºÛí€ Ø›%R’Ĉ?ñžÔY\…_¤¹QöâVí4Ö}ã>½JUù¬*펙«È8cçF>vr 2ðçh”Læ‹Álk<슫ù*f«NGàÓyÉâ‚pÚ-&‚ ½â«_4ùf|à0†J&sªz‹©Žª/+&˱AëÅ6èsÎÕÍ?”깿jgHl ˜äÛ5N¦Å °g»*‹Ä#´lïŽ!> /~½*sV5Úßm,⼘M‰ýxƒÁŸ–¾ï=ïÆñxÌžã‡ô·¦X©„¹:׊*Íλë:vN ød2¡ ÄSÐ4Ín·û²‚Zð¿…»Q=k&­Zéb†‡;u}ŒÍf³Z­6› ˜{ÝX~ ÙÛkqCP™`œ¦…OÁ¸zä*zZ¹"÷}ïWø åWÛ©ø³¢Oìt:Ïçóù|±XPáÓ‹ãw©Y¬)¨S æ¯Ë*¬#rØòQà_øŒš¯ØMqz̲ØêG!üb•½ÿ8 k£ût¬&¼b´©â,j¹£z›ÍÔ(ý±÷É=„+…úçrFÙWíbÍ1ˆ”“É„£åÞ˜0ô‹fž-ôšaŽPdjúw‰ô÷teÄMR”•ÅM•µƒÓ*£wf¼%¢)ÛiþE·êxZ@×6îª{í6þ¶*ÁÀsaâ„Æ>í¼FÔ6ÒÜñxÌÏV¯‘Wþ-/ÒÍÓ3^Õ­¤Uܨ ¹iùßÙl6 æóùápà4B‚ßvHu`æŸãîÍs7ñ>õ¬]H0g˜Ã6)ýâM•‘üvâ+Pñª5R †Jât…Áܲ_ —°OˆK?É·¦i*ƒè¿J¬UVƱÜXIp9ê™+oäX.eŪOW]OÕ{:—vgE­³ÙŒW`8ºQ¾žX‹el2Ï|"«³(ž”"û×5gV¨Ì1³‚ˆÁ®k•„oªÒ]ÌZ¬´*:°Ï€è#~‰U§²Õ(Yg‡änÏï‰ eU@9Ý~y•ù°$-¸‚±å2ݘ=Aù=Ì«°œAò™ç Lâ½[Ÿ3룧YôòûÒWF¤ü.çtZñyQådó›ÅEF›Í†×ªÝù‡ôÇ{ñ™Š*ŠÄõÄšI¯¾ïm)Œ:ŒlyOþÈÐr”Ê‘èÎ/W °·^¯Û¶}||D7$óKø+¨´@ˆ7'ÖbòÙG"Š,xøYÝbÅוZZ·V”c–²*vFº)ôZå¢Î óÞ¼b•þc*‚ ZÇŒ=>&ÓžŸJŸ\¹OŸ2ลPqWåêiYîï=¥ãH“Húé=b¡Ž7±„ÿ;TŒ«ï¯2./®ïlV€ 6OìÞb¢þ›kw6ph"³lFã×cª©Ê£¾"{½›¶ •“†´s¸æi­=‚½_V Zý< ŒªD’Ym/þöÎ<°Óÿõ•ër*þˆ ÍÊ- NÞ4Q”ô7ø]“´ê3 –=˜ª—êv÷ñ£f À¡¸ˆ]”ÁoH¬ÅV“˜6­t*öPyÒx~âs%¢û "–%– «f¥|õ„w£ET,þ}c*Bi.^.—6®DIGXAoÔxWã]nʯrßsÛï/yxãQçF»×¶mÕîÅE÷5n˜ª¹âz|óú¿ÙSÄ9´X Ó5ÞׯôpŸ’`d±ô®çFß÷_r¶1‘þ½l>cëŽbLçsxk)¿úbÊè³ïWªÚ]×håxFt¯ã{b§û_í§3€_~f\ëA\ s½^ë%©«b,¾hM@É ›‚.¡‡[¼iÄ„¯§ò~—X«¬‰+©vE|<¢¬4²Ûø;«â®}±¬¡§Í¯>Ã|Õ_€Ðü|åŽWDù˜™ >Âd2Y,Ëår¹\. m§\Ù£h9þÑj·[¥#‰½›å3möÔ¥Gé >IÔ^©Ý{Q£Ó­Uæ¶*7¼˜ð1'äJÍî§ò;é÷&™ÍfÑJ©išõzMo{P•Û§Ê +OAÇ"El´;9¶#_“‚>=<öÍÊ5ÌlY†ãù•%“½Ç[ >ÿêV2`a’Å‹r2-‰àê`bùt]«f0(nŠü¬ê/ú¼;P§‘Ÿ?>==¡Ý#ïMʪzD¥„ÿ׉‹oµ²WÙø*Yåå§§âüš_Üùy3ŒA&ƒÇͫτûaÑU‰°¸?Û¶…à*qû³³X,xE£ðbrø3DXÑÏ<’*M7ùvHÝûÎf³»»»»»»årÙ4 œô7ø]+ yã§§§öÑõðLýiÒ2’ãh1óQÒÁHR£2Hº Þ[„‹ 6•ÕbôCŽÛ‹˜ŠŒÃ\šãòZ-µo+Åü³þÏ6B€Á8¤Åª¾ÛßýÝØÒS l°»)~ÞòikÕxsïýüùóÇ oÛ¶pŽH›žŸŸUÏZ&ˆIÝ8J( ͪ¾^£êŽ­üLL“ÂV9Ï/N›8G]B›“µùét ÑošFãt+±†ý"/<_-ñmHö5´m•ƒJØ/Îf3¡+ÚŒ¼ùþx`§(îe^ªÜöl¿6› _çªQ†cU™N§÷÷÷÷÷÷|J? À Àï-ØèZ¥å¡Â«Ü’]³JP0Ƈ0v²S´³ŒÊ cS¯ûæÄš»ZÓJ87H‘§²nÂe-ܺ"sä0†rìMŠìkÚʤbå——Ú7<–f ¡†±{*žqkQ\:c7ŽÆL"ÚÃYò¾ÓOã\¶aïøjµzzzâøëÓé”Ë¡½%×"^Ao/œ¯Uõá”ZEoÕ°TŽVûPU¬Â•™ÌùTXe5)M]…+ú{Ú¸|åFÔ°ÍÜRxò¦åÔ=<<,—Ë»»»¦i‹E9ºŽG\¾bJ,ê$,úN§S> Þd<&|&”H ½!/€ßXÖ­èèåsÓ›/>*À÷´‚ß{P@ÄCÈ&ô=‰5½câT”Ù¨UöÛNìªÂm¦ÚÑcÖ}£žëcEÂ6)Z˜—Áæy­>Úë¿Pâ­Ê¼ÖÜå85r·Û5M#]ˆÛ‘eÀ±¼9†·Ÿwº¸¢ÓVœ-]UýUÒ¹Ú–_û^NçGVEu_é ÿ8èéos6% ”þ ²r/Z}]­%&¢ï{ú}ÝËB|—ËåýýýÝÝx¬ ë³­(«p\,QE 4["kÕ¿¿¿W=TáÇ™‰µŠ|ÇôoÄ]6æ#©åG [åWÖ)ùŽk}Åû?Š ÆfÊÃQÌâòôž“çÐwÉyM\&‰Ù-a2 ;aUý —;[8¶nz<Ö»8d©ÒšEËߨAtšŸ¨r×U)±œxÀ™\‚“ø¨eWÙ­5ÔÊ"æED¹Âa Ü] ©ù[…ø'#ÚϾ+É c(Áí®*ÅeÄÏ<É ¯—{Aû}€?‰«".Å6³.¤à¸ÿʯs*æÇþq½^G´¦ˆ‚7º!T óù\c¿7'Ö¢!Fl¹Q UµêÆŽþ.{…ÊRØGºjü‚çÍÍ>l§¡ ®)¯Ð_·V’i* jÝ^ÌJ ýÕÜCqxöá3ºœêêBI«öÝÝÌCð«.eEm_ì§:ý¶Óþ¥*ŠS Á‡Ü U+j5˜öÌÙGh~%NïàÉÒ] fà{]Þü±ãã/ü]©Èª“â+qîìãØæ‚”ü{ðh[óÀÜ‚,|*P"Ã8}B(YÙe·Â:™LHF¡$„ÜXœ{sb-ê°N£W£¸ÿ‘Ÿ•ÏìÀyýOëk/%²—æW)}Á´¶ Ñ–z¿`LÝ‘î‚Xý|ªþÍ+ ¨Å99šÒ':ežzP{r"¤~Ûï–ø(,/™BœÎ™ø¨¨š­_ùåïœöG¿Ž·$4U}YL šÆˆ²ÁÏ;¤×ÏÕ+¶n¬å¨ß¶3¢Ê¥%ú&À=j6¯iR.êqÐÄœÓp´ª ¬‹”Ú?Çôê}Þ–X«rÂU7NÅìÿªPwÙ°/¨>Q LÊI‡«9g ‰´'s>•ueõÏÚXV¨ðI ¬h4fŸk Ÿ¬×÷÷÷wwwð' À1‘x:æ½":Q~æ$æÊ4ªü:”ðónsÊ¿ëÏyÛë‡d2xÀ£ »ù •Æfï原wHÞ%îž³—rÛçÜ•ƒ‚®c¹¾Ëxꟓ7Cð“`ÿ vf‘…áh=ÿÇÑ ŠQéŠÁ ÍŠ+oäË&Ó®jn¥“K>E ¨8J™léf03ÄW±Uô܈ž*ž´Öƒ¹LŸÇ€Y©to+…¶®eÃåuFÂmg¹*wo¡Ø”ÿ-¼ófKþŠ›,ô°:—Pÿ;5úãÚê¢êÙàW9"ðým·½=’1DÅŠ]­ÈÜ`“H™„^pWO'aè$EqN„£*O~z},›×þ¦ìÌ(û£WQ.ˆWË€•ûÉ}Õ·[Î`?çz’|Fp]í°´+øT½"ÔËѯ®C•8qû^ªU±4âs´Í}ß?==½Lh¶½;Ú>`@Iž 0Á^Ü}®Ú~샲&í_çžÁ¤"sÂWH«ñõÜ<ãq æ'eS2€¿=—_%'å#¦#Ö¾8þ9þP¬Aævøw\9p¹%*džQV=Û´ÕÔ¢Ê9«ÈÔqUŒ…áJîþyc*ìÜî5'o6¬c*%а2.ˆ¾±ìøHu$”ó5*ÎŒŒà†Ë»«ªq›í4&¬bÝ7ðé ¬Fî€ÁÚu933s@\З¾£˜sæB€¬j¬îîîÈ â¯‚\.}Òu‰­SÑQÄ}â²ívÛ¶­Z¨Kˆõï$UÇà8ý7ÎVK¯]¹¥“¼d À¯Áð;Ù[œ*c#]9:7•`ø— øô*Ä©D–?MQ÷шJ*¸‹ò™uÐ&ˆ#‚¦XèÕQO±Uå0õ©»·£½6wjíõzí°Uì´ç5‹þºXÆg£¯`“1ÑA,"Œmyê2€? ?¬çé®EÎ#5ÂÍòt½bê• >i@ð*æœÝútšæ'ΰÖëôÆ/ó¶Uð¬Í8+µ^Ð]×9!n±Xà0lPÀÆ9„×\Ù¿þFs3A_K\Mî=úǸFy™2€??J0‘Ю½Oÿ7ð‹;˜j–mä;ƒRH±`­TZ‘s&íl7-S„+W„¯¼ÑTH³Æž»ˆw]ç¨.f{9G³=N7›MÓ40c·RáÍ7òû ¬Ô6'xÆþr»Y-âÆ©áÞ{¦pòÔe$î“Ñ7&Ÿ•JÆ&Â+IŠ^ÏÙ³´VûWh˜õÖˆÓ|ûf¹÷ ‰oÀñð”F¯V+¦Xö}?›Í¢Ó!“vôù Û°¾&{ŽÂçmÚJ˜×„#‡3Œ÷4ŸÏ®¨í—¬£Ÿ›JT`ƒVÜ*²]j™I‚ÿ}=ù°Yèèµ’þ Àà8ü®ëºŒ8@âØy’Ïg\×ÊQ¡æ UWºKjõ'}Tíòyãõýp fÓ†­bo±xÓ÷ý|>×z7sâ¨I{þI*=Ólþø,æêÉ{ç½÷6î‹9yLÞp*Mˆ®2yê2€?z£§¿£lx©øê58ŸÏcÿIž=שªLîûh%Íœß<>ò› â%þŒ¿è¸'©0iÿAÐÙlƘB­²l†EáÈd¹2‰h2Ûh§ÙÄ`o’Dį́ÚÿáuÔK”(£Ê+ºø vpV9&û¾ŸL&Ü0]×aâ¡,K,-q»®kÛ–»‹/²a"2.'Îxsž£‡y´‹³Gó¤e$Ögß–ÍY)æ5MÚ˜gÏsÛ-“»ºiód óWÕjÊz%Åíè¼HR‹gµ^³Á²ƒã¡á*Ö€I„ÆíLWéÍf#EãM×uX`ú#)ú+ôꕃn¡‚”´àÎHþÊ€aÄ‘´iŠ(1?„Œ%ÞSŸÔ|lÏ?íq?…ú1UûÊò¹ÎHþj®0˜DtLú$;s)ŒØPBÛ4gŒ\=…ó®ë4Yr^$»%TìÊÑ"*š*ë3%ÖÂ}y£e ei†: kŠ™×vêüàqž–[úd餘vlË€í”c ;ÑmX7sãý™§.#ø‹"öC>xõlÖŒ:ÛÌV•cŠOÿç8l•ÖÕ§§'V7:¸dEô%C‰où5=«»r¬Ç ½d)`6öÛÕýΤ­Õž$]ÊtSÿüW»I-¯>Ëþ¯N‰¾ À{PŸçtnÅ(ôÕ20ŸU©Cì‚5Ûë—»Ýn>ŸÇ5.ž½9ÃáÐNÍRJì/†}["ÅJü9.¾U/r¹J“£¨±r'QœSyyÆ9Ûî+Oo÷Ë.-n€ræwFðeHp î¾UG Ë}Eøò¼U(RŽ2¢!0×¶m=±«7Qð\޽:¼zâøa¡:6O&“ÙlÆ ÃÙlæÈ‡ëœu·j¼ã©¨US¬ûl"¡r2a·ç½á”¤ŒŒà/ )¯Å¡ k£“û×t”^ùÒVB:ÔõKª ùp8t]7›Í8«$ý°Zä½_©èo$."SUÊe|Щ§Ê|>ŸÍfËå’‘º¤7®V_ã'-A:îÀÚ<5Õmeü-v¨T¼…¨SXWJœ‘|8ÑC1*œ£¶(râÜ)GöæéŠI¼ív[JY¯×¬zJÇ_”_U¿³zUá8L)ÎS"áL˜Íf‹ÅÂAL²œë<‡æêùg£•_›ª“ÿ-Žª«Ø‰Ä Àù\g$_l§µ¸üS„9ä%¹S–túå±ÝnYìƒfÈqôÔý‰Og*”àkA©~0ðk©Ù# sꃾfŒ`úp#̉$»Ê¹KPrü)z&7Û‚ãÿ:Æ#ŸëŒàK.‚– å[%Ìݤs§\Â(eÁo6›Ù'-å_&Iß IDATußsNZÄãXŒWQ…Á$)e°âËaÄãa[Àw^-kŒrœFª<³*èrlXÊöü[TçgsÑqà`J˜ 8#ø’;åSŽë:¨QQœæ*h‡(“éuh£=¬"´Rd{[_©£Çz|)Å6beê‚îh4jš†Æ_m7@\Þÿ•ð*–ªŸú¼kínÏÏ ›$ vîgIxÛsí¼ê î¶“g$_˜Ç–|ÿÉ7Dœ;ådzçó9‰SH§ƒ ©å8óü–ÿ[°p ©5"ÍUienüÌËu:êìâ\Åσá $"!./™odú¯ÂíT ÉçÓ ŸHœ'6#ø2;åÓZQéã;å¸r‘ïÏçz=N§SXv€ T>EŽÓ6¤òkƒµÚfÿpW’õYç_šèt¦}f¬¿úw?©8Ö;ĉȀ§˜ ø ϵ©{µ£Ñh·Û‘W¨,?ó¹ÎH¾ ޵"“Ï>ä¯R-Y4­ 8]ä¢q‹ôdÆóöbòùÔˆZL‹?³ÊV£~U‘×ß.ÐÛp˜0\ŽF êPeö±—^íUi…÷Ú‡åæïoOl9Ê5¢iŒ”^ÍØÝç-#ø’ ¸*Õ+ÇDtIô¯0Lf8Ò‹ªq«¢¯[QƯW$Ø7qé|ç CG]­×ë¾ïÛ¶e´0Æ$Øg³Ùn·ÓÓãÃePÎŒrr”1iÒ7D4ò´Q;'*äc£WFFðWGÔhènè=‡ùdžê”—c‰—Õ )¡¡«ê«þã0†èŒZÓý¨µR_­ív»^¯Û¶}zzjÛ¶m[dPúétº\.—Ëåóó3ÿtðÎ{ ŽY܆ÐD/Q$öRçíwÎýéÞ…âEÓ4œ^*#ä6¼šyb3€/ÀYò"¶Y0Ž£Iü"+ÖÕÔÉå¬J>Ÿ9Ž0žáh¦ÿ±…ËÝ÷}ß÷OOOmÛ‚‚ô;ÝÝÝ9Qq»ÝÎçóh4íQ¹‡8ÝIDUœÞÈ&¯ëºÍfÃd§Íf#WGÕ>&o¼ó°­kL¥‡}ß#VÀ¯ÔMU2àŒà  o„[eéõȪ8”è[apÌ”ÆÊëÂ¥WqEŽ?ül{Ñq¸ÙlÚ¶}||\­V«ÕŠJðp8¤¤Í¸Ãív{¿Û횦Ùív§±öt·Q9hºÛÛl6ëõz½^¯V«¶m7›6XšY÷ †3Ρ¿*¦Óé|>Ê|:שî'g$_8œ>ë›Hæbí-Ô©†èr>Q‹øú»×OÝ{9p‰ÒoÛ¶ëõºë:¨TØ»b·Û- ‹Á1{é.Ä2mœhTÑn“Ï`¿¼Z­ ÁDb³u¦Ißv[‚¸¦ôÙ?‘°ƒ)öçœÇŒàË0àò«õ¿âØ8“'Ψ!?™§îLýŽY¥ÈŒŽ´•o@®Õ4Íb±° 9Î:Œ´>úFÃçrT]À]×µm+ýÝl6Îw!œ$‘,ím¹™8€M•ê]K5+M ÎH¾Ÿ¾JM®¦ õ–"J¬ AÎ~ªÑh´Z­@h`.¹©ÐÈ‚¼‚ðhº´ßïItÀüήëØùQýå7;Íéjí¬¯ƒm–ãz©Ãòòå¹ÍH¾ð6¹„ óq#GTÃ2¾ûE7?I°m[ì¬íHör‹Ä$?º®³ùJÀÖi$òÝJœe˜÷†p#½àÕë2ÓÉ÷IÑÞs¡µÝˆvÐî·®Ù-<#øŸ`Àqf;e¡rÌž!e)î6è/¥\Ü4ïîîHoŒF#RÁŽ”€ËRFÛí–Î!üZè/?nIà ÁXeuœ ,ŸF ;›Í Åtw^¸¿ K^G8jøóqÎH¾0r´Côø ¸çœÎÔËø¾ë2¯“Éd±XØo6çóùf³!K ë­h”o·Û-ˆëú-½¢ºÚçÅJ00¬Ì) {ww·\.çó9Sæ¼po{Æ«ÝsùuÌT>Ë À^ŽE_T6ÃᵤN„ˆ5ò\ÝÒº¬˜…tâŠED*Y,ßÏØc\;â†ìô}tU+G…éhgýê½%#_.—qªqâÄ;1øt“§4#øòè«ñ/¾ƒèh¶Û- û±ùÄ!µyÞn`Q溛:f5ŸÏ»®C!“(&W¬‡8ªAé)Ù=½Ib–ÔjK2œÉ0àÅb1ŸÏçóyîü>†32€¯€qRd•¤1”z0œ¸iš¿s—qý+2ØfÛæÈ£¢I$C&øŠŽiZ–Fp=u9í Ž›L‹bÒâl6óf›Íf‹ÅBú›ù猌à›Å`ʽ®PŸR ‰AVÆlØ¿ÉPßä€a,‚¸B/´˜÷V‚˱šJ˯ ÁqÜí€äh”ˆ»9ïS{•‘qÕ›ø˜ûÊx[D L§ ë£\$1øVoy-7ÊgÑöïj¡Eʹ@”×–`ÌåWþ¯È Ç…| Æãc¤ùFFFð¿‚Á0'³–ã\XéQ¢ï?r3(¼ŠSÁ`pw"*ªf ["Ë’‘€Ùç69FÄÝì;ÏÈHþ·–]ïkù›èûÞÞÑ:n×DßSCJû”" —ã„iösΰÙ7ï´ŒŒàtÍ-Gkèÿâ#É5ñß -$ÍQ;ö˜×8ñô>‰ÖТ²š1¥%SFFpF9=¥¹&f¼ÆüÓáƒñ~XÕ½d8nïò6ËÈHÎÈÈøó.-úV÷ÔºÚÆé®• ›‘‘œ‘‘‘‘‘‘ñ‘"ÉŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒàŒŒŒŒŒŒŒàŒŒŒŒŒŒ«Œqž‚Œ+‰çççÿm ‡¹5ÌÈÈHÎÈøèœ0|Uq8|=Á`àkFFFpÆ7ƒÞçççÝn÷¿ûr<}†/…¸n‰üç`08…a.ñ8Q9##8ãJÑWèõ•ýùùy8 ÉÁ_ºíápà•Øï÷ƒÁ€«s8€Uß ‡ÃÃá0|‡ƒÁ ^»Á``œ‘‘œqEèö}ß÷}×upÓ4Óé”÷¼Éø<Ük…^®‹!*ŸÒßAq—×âñh4Êsž‘‘œqaú»Ûív»]ß÷ëõºïûÍf³ßïY£·Ûít:],Óé4RáŒÄݘ„¨òûýž÷Ð_y°?H.ZîËVÉà"^A¿?ÙpFFpÆ…1Ü]­VëõzµZ ÀËår¹\²”Çßõ.|ñª GXn·Ûíþ}ßó†/ŠÍ1;?ŽfðUÄe'“É`0˜N§£Ñˆ×ñx¼ÛíÆã±\9„ŒàŒŒ¯¦¿¤Ÿžžþü¹Ùlº®+¥Ìf3`€õòô½H°ÕÓ z¯oÄÔívKb»Ý·¼éû~»Ý¾SÓU ÚÊî`0 ú âÊzÁãét: 1÷û½ü8ËÃq¯–§"8#ã+b¿ßw]×¶íÏŸ?ÿÏÿù?«ÕªïûRÊt:½¿¿ MÓl6›Ùlö½ö|´X1• ^|‘%½ìî¸ïÁch1ºt? ª+ôqÏÏϣш×ý~?ŸŸŸÇã1\Ž:vÊù“Éd45MÃ{Tæ7@…ÿAì‰5EææŒ€3¾Á®ùc×ô×Û=? ¶ÇX­VOOOðàÍfSJY.—‡Ãa<Ïçóù|Þ÷=•àëgÀ&ra™¬­,¦æf«¦/><Î<™ÿ¶mÙõǃӖ~AÜê6躎šî`0èû~8v]7 Ú¶uÃ1W«¸;=Æ|>ŸL&»ÝN0&5­Pëyx½CL3À$(¾$'g\#ô¾(Iý-yL6Æ4ã®d8­“…Þn·¥`>ŸCËXž¾¡7‹»Ûíø'š£áp(Ø” )ÁT²›Í†¢ûz½Þl6r_›ï$¼„[/GtPÙívQ™å>c2™l·Ûñx,÷}?™Læóùt:Ífûý¡;úæÁÆ|>©ˆ¨9w»FöÞD½:ó\ú€3®z]#L¾“]ñ;ÁÚ¤¦¦|PK®½.Ûí0  P‡Ã¡;†0výlÆCF0nÇãñt:mš¦Ûg¿^Ó}Ùñ¬V«¶m7› G^wjº¤¹âú޼ù˜eÞ«¬æ+ ²º®F³Ùl6›u]7›Í¶Û-Ì5q}“™*™®Ú7Ã]¯™³ôTÍ/˜8ÉHÎø=­D1qAÏâ®6ÇpE˜N§lÉɉ}ÈúÈö_¾nQ ]×õ};a®}Íë²¥°†:Çãñr¹ä³Ìçó¯¯ðIÍ9¶§§'2ÿìrø/ó&?U5—ÐeÄ'2GîZ-æë ._ÔÇãp8ô}?¸ÜHí,9ómMÓ|à=v d×'«»Þ¹ó9 ªÞ¸@ž|õ¦"âÞ7188ãÂË=¼Pë(@Œ|ÛâÎï3Ú¶eQ¨l1f³™›÷¯$*¡‰,ÁÔ Ë1imŒ%éjש ÌÒèû^* É»»»ó³|`:áÌ‹kË5‡gÊ¡mÛxQ¤_&BÍ‚ZÃf[¦1–Ä´ˆ€jN›k= ø'ÌX¶›ŽV–oýº#waºÀ-è[ez83“ɤi‡år9ø5r LθýõI¶_SˆÚï÷$¬Þ°¸³.ô}ß¶-[ –ƒ®ë‹uÙHµßóY$Ù± FªdU,ˆ+ wBmÛl?þ|zzÂZh¡ÞÉûÑhDF‘¥ö‹ ºì&ÍÀ†ìB@›¦±hMÚC*¬ßd9ªv«šEì!6Éá6 Þn·àw•¯ŽÞ–ŸºAù¼þl¶ž oò±äüÛ¶…À&œh›žÏç>Úî­£#×ÃàŒ ­ØA«@É´U %ºó1RlÛv½^ÿøñÁX~•Ί5lIÐUãU ¤p¯pÑaK“ÏOOO?þ$»k^Å/„\Âv»L&_PØö%[pßõz ú‚…´ê¢U^,lf³ÉÑ7’0^-…xs À xá›L&f€ÞÍf tÔ7PþðæïOR/*žˆÉçì?¢ÎœsîR|Ñê„|¾Zè(WNŸ+apÆÖzžsЗ疤»fwÐæ ÏYVXY) p?~ü #h·Û ‡C”26ÒL§S˜ñ›99I>‘¨ÌBSBï¬EÄk»"2<ÑWe ˜a÷À©›Íf°a¤I_³iã8á¾´qçpY¹Ä°®Åb1›Í@â¦iF¿F5_!J #üøÊME­w:¢Àl¸Æã1gf³còƒ;m2™|HÉü|õ¢ßé.öw[%s¥uïÑÌÄjÎiƒ5JCåolMʱþ‚ð"êÒO]P2€3¾šëŸÌ¢C%yC¥áüß ú¶mûøøøãÇŸ?€Y"Á?H’ìíÍYh[Má²~EIm¬g›ù¼67G1ÆÆs¼Ò²Í›Í²ã–‚ú§&¢¥eb€ÅÒ'äÀÚå1Ø%¨¿•øþNÍ¥4™«$QmÄg‡yCÇ)ÓC̯l¼-›¦ƒÍK¿! Ç:™%ŽèëÆB³‘øêx.^«., ½ +§šl¡—ÝŸTí$÷¼ŸºrLøc䘜qáŸ'œ§ L¨X4w»Ýù hºS`Àðp8\, oÛ¶www¬ïYäñrþP(#Ë.š¯ó÷_|-ÌÛ–,éIøb,ˆN§ÓÏ^U%”@Y ê‘|Lk±XÌçó‡‡0 0÷N§Û. 8§*Á_ù°ôþv]7™LÀàápÈ-Ç÷“°•kn·Ûûû{îs0øw,üü»Â½HÔ]óÛªßi½0\âèŒí>ƒs˃‰½‰{þbüU-Ïét:µãË­OÌ<瘜qaú«šTµ+ìd2™(ô8sˬŒ‹_HƒŠBYå6“É$v‹¾s?ѵ[^‚õUl€¹*Øl„í¿T|K)Q<ìêoQp»ÝÚäóyÇÉIVo 'ã@sÉŠ?<<,—ËÅbA•Z)ŸÄ÷wÂÙG±u8ª«p¼âwòºÙlȬ0öŠº$n¶•ä`I_S–¶NÈ?†}j(ðʾ„ßàE“²(¬ÒÑ‘úÇ;èk#8}h6©p¦¶í¼ > âåè|n©Âr‰ŠDâàŒË„O>³ºk½•$Sg€íÿ!‘G…þ²tn6›Åb½ÞI­ûB \ËXUYžÊÑ¿â:-â!6.Vû,°Ú¢‚fÁ•-IÉ^|ÒG‹vKUÏ` ÇŠ¥_Wü7¬õ13ŒzŽp"¬()RQïì^ùN\H›¦ÁºR^n‘¼ýöp âž•þ+v®žäß\8Òž¨ê¾ÌÉä°Í6»'³îkŸé°^Ð7ÎpD¬Çuá¢üËvÙ ÀW´îÛÝáªêzêê`™êõÇ5ö5Å%Ctg…'Üï¿3k*càïj¸!\±Ò‘‹öo]ö¤‘lä#«qE`,,ÉÃø2«Ä§Î[Œ©WdÉnÚ:÷Xñ¡¿U–Ž­ÃÖòÙ]ÙalÖ*©-Âü“.8ô0[L5ÿ¯ßñb!q@(g—}·l Jè}Ò\3Þóþ³ªãšÞà°+;ROÛ¼À´È–Ó{f8$¨ŽâÄ\ €3¾:ÿÓžVò°R!òâ¼3ó¨ëXåõ÷Tè('xÛ!é ”£+–énÓŒ‘;^UþY•/L°ÐKÙMEðÊ~B aÁû3ÑÑ4M¨ÐYÉ,(ØFéWÃÑMÞ˜„7ïÊÀ†è½%§¤ Ã,òÀðìj©¼+~'˪©«1T†æ”Ï@KW¢hþþ_~§ƒ=ÉözìC£è®¿MuÈÅáúï1ØÉHÎøHÖe]-úð ‘mœIbøURjQ½¨¼Í¥ÁœáÛvâ²1ù )\×8SpdAýÁ«Â`>…Sü¤;°7N¾5laü„õ{ûnYm?i'Ö^Ûè£âúîÿyUF«¶tU G•þ²cS85÷¨,Ê_¿7bõáÿý¿ÿáÿkï\ÛÒÜ–%:c¼puíÿÿ/w5Éù06ã”óE‚‚B\=?ø¨!òÞ˜ÕÕ]]Ìl°WtÕ_“ÝWÜò¿ ch›n:~ä]H,“aöMÁéü”ySœU\\ëdœ†vª.uo·†´ÿg5«ƒ¶*B…s Z7†ápý3ïh¤ ó< °Á~g¥‚î «*ŸUÕÒS ²ò²õzíMäõPç,ˆ~(_—rs%Xº|›þÐK'œtªjäo߾Ѳå\¬ïß¿¯×ë49תËcwAaWa±Uìîî¥!X®è‰öϲˆ_ÃÝÛñ:ôÒjÔZ;Ÿ#NîD-l8;UÛXg´Þ*m ›S“QókÿÅlþÓýÅ|WçY¿c_V@„&…žÈ6èÉ,™õËÜÊ÷\ê_¿ðv¼cVÚ ]vŽÝóL°…CjºŠªÒÆK§p"ç¥Xš_rC%©Ä€Ô‘é_Tævª+,Õƒ'œ_<'-úŽÌíïÓ ›^.Äv ±Z?Noooçóùd2!Mê¸Ú}S‡•<؃|­¡ ·Vð_™Ö R=”ŸsX$9¯ÔÙì©$Ò-¬”orÒ\ç„°ãàÅÕ§jPÉ‚¾NaSœ’€w&5`.»=H‹t,N`Á¨Ñç+«Ó¥9S G9͢ǵuƒÈá3sr‹¥LŒ«B@ •³³„·=Ÿöör ÷ˆäÿ÷ïßÇã1Üw>ŸÏf3.V?œ ¡Â Š“Ï!;š³Ì‚$…m/Sèû¸g*…ßóÁQð5Žeü—ÙæQÕtk}Nª» ¥`x·–U*m-Ç¢y†É1}¥µÒø‹©©s‹ÏwÉq9‚îëô…¶™Ÿ8™L:A÷Ö³bÀÃô{U¶š½YDìî_s¼£÷÷ˆÏŒ—?Ë=M×ÏìS:+³ÏÚá!íy™7ʇVWj¢¥®¨<œÀXøZ«øß ½©ÃÒMSéÊ>õ°_±r`‹ÓlˆDÿÂßwjzNµË‚â0IkÝ×ù†¦»NNa’éo¤øûC›:Þ3©e0¤©² 8ű.ä„uoÈ<ðqÕÇC*™Ó™ÒŠù¬Ì>B.E_#ÂtÎ’úSPÌUÝAµ €kmgÀn(9›eÏsqꨀSÇK§‡EDÐQüÎŽä4PÌWˆ/cÉYxVh÷AиÿeúÎí©O ØøƒsT:Þ6rt鯠«h.Ë®N Ø™Òq°÷¥…‰J ŸEÿõÜFNë"´—¦oæÙ5AúÖ*®µeQœ†ÌÃlíž@ž³êZL!¥$f+8Íw SŠ•{\gµë],6;aX!ñrÄ)qq±Z­Ò9á¬à¡CßT/§·”~ Ùó“êÙ¬XênÑ tt²®\¨ë¢Éßtéô¯ñ‘Iìcœ7‹L–9j«©U\ëÅJAržê‡»}s·ZÄÝVÊk9‡•ê¡ÈkÙæöÄÏFšCm4Z.—è­ì8’|H²…|öG™Ê0ÎÒ?’26g¤dʬÒ{ÁÆßÛKîðn…ûŽù c2Ùù—Äà¡‚Á¸zÃ,ÔžS«¸VKŽ›ÚpgѼ» ¦HgrhÈ(¸ÈðÚUmm2Pó!rÌíjµbÖÛb± çŒäÊ <¼—0Êúââ" ®srÀ9ÀCŽPÄQFBåõáväp_QЃ×Ô³E Øð舧™`? ˺iö_wñ+ßäÇÄAçTU«Vp­;Hf)e]m“u[ÉF‹ 8Ó¼9FÒïd2&[ûšdÀ¿^.8ñóó3µÞåryww·^¯‹Œ µ¹YmyGõ|öh“ÖŸŒ3”•gØ»¥=Ísºœ0Ü^¶Óä¸@ ÷è ¸½¬_´—¾Wy‘¿d ØOG¦‘òã`üúÉÝD9¢•ʺ¸Ö93ஈØQ®%ÒÝQ¼‚g÷#pñׯ_Ù½Ê7Ýôü V|Õp¥ÏÆb± mª\z úŽÇc0Ô™D÷ÕÕ•$¸Œ“Ã|YIÌ´°N.• XœKäóÇ úÃöb£cÀí¨ú¯sX9:°½œõ›ãò ÿÜ}úÚË¡ÂÃÀµÎnɪ¡4)Ôþ‰ÄTá&-wI>“ˆN2'Ûna/¬°@zMÏ:J=í‘8ÃÌv[sWŸüå áÓ„þ.eyÚ IDAT Î1g›[6“ŸÕD{på£ ±bäÑpÞîìNSÆ44þ Øá #?/]½à£óX„¤ÙÂׄ*6\\ëŒVG¡ÚFF›Ûh¦ÔvP(—”wÞÙŒ¡Ï­ŒA—NȸY‘©^.—øl@™5„ì½£Ñh:"õâ¯ýþý[ïûaØÑ>@üîK?ig[™9xŒÃhžNûɬ»ÿ:?Ç"úÖï£lÁé#¦ƒ¦w¼ f0|(üœz;O+ãË|Áû.~¦ñ‡ÿ”ã )X˜:Ç,G2×:GÜ1*?¢þ¸…2÷˜½¡ìj’ùÚ}´Ð°´‹-ƒü3Égx!a>ÈJ]y<;ró?Þ‚wª` 1ž¯ÅüƒO™Ç™"¼‚Öcæ%¿oë(¾ã2mЩ~2)š0é}tƱžl6Ë¦í ¹¾ª©»Ô¬>ÉÝGàM釡`0ÿÉçYÜÕÃU¶¤ÿkpÖ*®u^ X¯ŒŒ»%Ä»pnÄÙ˜Á¦ã$"8kºÏw‰J‡à:3˜YÂJó½}½_ÎÌf3ر‰yvÀóî¼—¡À'_vÈ _ u7ÝGªÆ¨j“TwR*×’b¦óT‹Ö&NÓüÄá'›·Ûš½·n”^âÓ×ûì$"z5RëÐökÙ£SŸ;U×З4?eŽŽƒÀ!úbz“,¼Hpp­3bÀ-쮲îØb@òÔÝ›rÚJûûLÇAO;ÃKbùÌ9¯×kÒÈü¨Ë•£È9;i•Yo,Õiö‰ÞR¼)ýKéwñi7'‹©ˆ]U‹Å‚ß(‡ßëæo"ZTêôˆÎ=:ws»«uš]ÈÅeƵ+=|½?¥û©>ëRîLCô5ß“_»¤Š|e{9Û4=l|﬎››?ÑYª­¯¸Ö¹Dñ™4îèQÚ îðLÎm=7zKbÒËH£u…4eGé×wI9´ŽõÀÒl6cÊÊgqÈÎà´ñDpKvób17ûGî‹ç—FÖwwwwwwšŠpÑØ=95¾À%Y7 Þóøøèˆ Mµ²»ú@œ·[„ íµ½þx`_ƒ‡ùž®•×'­iRÁžj Mµ2„µ­€à&}Ú¹Ôi\* æ™çù “RòðŠ×:£(¾*—/È-u·gr7z6½$ÓN=[ƒ‹ß/—KǦ²ËÈžQ]QôÏçdžù¥mÇ)=Í)Cš\âL¹\.¡ÑnLŸ°+Rè¦ùãÇ?~PýÅ,ó÷ïßDhÊf³î››»càn¾ ga䜥£œEçÅ=J’Í—T`ùhå7ù¯ùyñúÛ<ê„/|;ùjè)$ÓSg”cõ—ÌÂ=Þåû÷ï6‘?>>âüÊŸ-ú[\ëì°ñ{7+mÿ¹¹YwLþjjÎ>`X¬VõOOO0TJ•ì2ª’"Ãq<3áœïIÕvJŸ¡Œ- y×ÃÃ%äÕj5™LÖëµäòC¯¶ŽWÖ}QwÓè̞ˑ˜y¦¼í¤ŽJv«l‰vû¶ ÌÎNSÖQB·..];rÎΖ¤³zòÛ@cü&‘T¤øgý¾½48sþ¦É½Ð ™"èú³*Ü¢µÌ~ÿ;oê§L=|mtÀµÎó¡5¾öÓ¾çÜÜŽþæ$[2ÜŽ³Tf²4;_µžøöíÛh4‚ûR÷L&Br³}‡:Û4blQye»¼¹¹Yo–âj]G>”þjä< FJpÑ[½d× 2:ú›)h ~ª¦Û¦Ɉõôô<Îõóv›ùL S•}s̸¤aˆGÕyhì9 ØœJrß¼&ŽHâŽ<>>:#9õS©œÊ1”6wef¨Eu¿_–‚åÎ äkwc×*þ ¸k^DÀ¼6¹jnêLí±ÝËËK˜nLùµF‹zóúúúçÏŸÐAë¾³Ù X‚oE¦n–b¥8 Pó)þÔõõõÇ]j=7œ'£µÉC‡þj*2Ná÷¶‘ ذÆ)YöMÙè© À©-o›2þ˜ÀöiϤ°;øk¦âS»4tÊtuI ?’`£:³Ç—ëõš”zõ.o¯áh·*§˜‹¿é‹ø$X¯±:S®XÀµÎާ6Ò.[^À†Ò¯1à®æ÷ò³œ"ÐM’þm˜ÑEH3öÐw>ŸƒÄÀÒk=¦Ù~*ATÃ"–ë™õA,Á7MÏgÄÞ†ôÉòooo ;¼z¯ÝÄï›E8¢º-wv¥X‡Ÿfö¹r£½ÝCBÙ¢Qç¸t¶½”ƒu.Tê›ò±T ÀÇR_ZÄ즿þ 7åÊÀÞjµúùóçr¹ä±ïL3’C{lVpZ4p;X³»)¾k›Þq¼h4¥Q<_3! €k–%´Í8dÀI#v¨ ÝŒ @fàÀ“eJ“·†˳›Ðq„ê @}mCÉÔ·.î°]eDœL&¦ß?‚ˆ‚¼ÖWdÕR¡½Bx5ŸÏ‰9ðïܱoÊÛ8Ó››$lze³Å¯V«étzœ³#[´Ðt05YG¹ª™IîzÍMÏpSBï ,"Ëôù}¯7kŸé×-ê¸~µŒö˜³¯Ì¤±aŽß>ù^«Ž ·0å3k¯¼!²¶|F´‡3ƒRô·¸Ö™2`¾p^cÀYÄÕ“Ö½ž Eã{i1ˆ¨[žˆÂo°žÎf3¶•™çŽJ”\AV2ûíî̶øŠ¡Ì±Ó€äE¶]P–ª6gªòÙ­sÇ™ÊôªÌÛúŽÇãÔõ"ˆ•~ÙÃÖÑ_€0-3‚¾ç çFû2O»•¤›jÓLçrш~fÈk{Eеym4òyØŒA…Þ¡|Ú‚û"覇s×cmÁˆ#0|ÓðƒKô-ú[\묰i÷YjÀ»°û 5-Óqv=¦ 6ó¥d\ÉF#»mö%×ëŠmew湃¥ËËËñx Á¥Ö+iã\HÌ3“‘ƒåÁ^"Ñw>ŸÏçsB Œ½vI ³ j•Z;Uag<gÈD=#Þ¨«µ¦J{9«àX—ÑŒzj 9_Ÿ}Þ™7m.Œ‘e!óùÑ1æµKº1Ûö“óK4gÎ6t3öIpS±hb9l$úÜß.Š%ÖäãCüšU‰Ví¿ÀµÎœçÌ>¶ïÝ 87 k«:ËÌägÊt…^ØÊùxP°¦Óéíí­:¬ÎòiÇ©±=‘‘›Íf¨®ôÛÊt\N/8zÚt7¥_úŽÔ»’öO_¶Î̦ú& „-ü’ì(5! äð²ëëk€_:$ÔHnNY†½é3š¥ë¯gê§L$èý$õ]8qbA*ÒÎEC4Ž“¤ýt:õDijע‡Î‰SJš¡O‹Y]JYWWÈ1Á@ýÒM ´x×ä[8"šå÷þ3O•y.®u¾ ØT˜ª¨ÿÝ¿PAoeÀ2aFŽÅ~aôíBw+‹U³ÙŒ“Ê%{*¤ÛÛ[¸/tp·i˜‚æ]F£$x:rÀ4M?îàdV N‡Ò/)h: ôßV³ÊѲrÁ±KvEŒb H£gRC}TlC´ãHJþ`jî vm£Ú=ÖÖßÑ_ÜI‰¨l`ÓM%“½I|ÓaÊÃ[.—£ÑHɱ•‹­;i³j¬“pkÐ)væç.Ë]¶EÞÿâèI—á¯Vêç+³Û¾ ·Vð_À€3O˜rV ~wôwÔÄ­-#}ª¹jtÙ 0â@]ż¼¼D2Ê+UO§ÓDßýI››)å=x6Û·¹Ù,~Ð̾ççgÏŒ4¾»»»¿¿_­V°Uó‡~#`›àíºW´˜pÀ#®ày ŽÔŸN§æÛÛ#}Ó®(«ÚN¹g´´'ÞÛóCœÁ™B…Ó™ëââÝSÂp ùUÊÇÌ(ð¥XOOOŽížB—‚N”è›Þ)> ÆOÆy¼€ð4EÖY:/O?ŒùO~¬lgس™ªVp­S2`õ¢éL›Ò\Qùµ>`C5û¾*FÎ;i<»ibŽÁßG ô:gðò(6µ_¿~iîNƒ‘ ŽÈ€el€]¿÷÷÷ÿýïzÔ6Zb°Ptùöí¦(Œ~²¥U€æ×ëõb±ÀÜôÕ¨k-Ë]ÂöçÅ7ZI`Ù'“cŽõ¸fÆ…¤:$8õeVÒýѸÊÔB¦‘}Z, ’ð¡ÝšÕH7 ;‚xìy€©˜˜È÷jÑ)—e?#]ãP›x…;x®6ßZÀ%κobç nE˜“vøÞñ°§˜RËÝßÛ¶‹MÉb¥vúçÈçà%WWWNè˘CjxÄä³ùÒÅbíób± }ê~Jâ…´ùL8Õáß™\¾!­Í€+9¡(GèÌÜÍAJ#ˆ6˜ŸÝ±GŒ[ 0{$²RãŒÔëH‹ýzqq±\.onnV«±Úkoº¨½GÈï”L9dn† ÛÜÅàÔ'J;‹.SýÄÇ*´×*®õ©KR•ÉÌ̪¹ tà·u@n”¢o–µŒåùãö¹bz¥. ¤8ß½¹È3T6ñµ3'Êá6‡F΄’2òèþþžÁGXR[¥Ö.Í1Éïì¿Ú÷“{tIA[åÜ3éÊëß7*-¥Ò,bØ„“¥è£ä¢»¹O]ÞÞ3dÉŽ´xV—ËeÛLD0¡Z\ŽJ÷´k‘!ûij¢ƒ2Ý ¥ðÇÌBs„»Ézýãíe›uí`µ €ÿz æ#=ìèPÕ™À9dÀ™‹Ëj6Wluá‘‘ðO¿ØÏ×ÃÂfswk§ëƒØ3CJ€Ì0ÐûãÇ,8ØÙ9ÇÕju}}½\.¡¶x5*Ë%¾™'OÛaÁðHÙ‘},Vô™û”™’ŸYtC£ â ¢±ÌQ¿ w[øBy¡¼‰ü2£ŠŽbqî(>.‹¼8Ü£|Bºø£óÙÎÁ –T¬¿t øµëY«Vð¿ŽþºÝXúr‹L‡=‘CT!º²ÖŽ&2Ç%iÑ^VÔ’ƒv©ÝÇšÕÌ”fç™°Ûöa¸_§GóB›±wNe×\3…Ó‡ ïÿ>Ø™A.onnZ¸ŽXÒæpöæ`Ô¼U- n/{Ì:+òöä¢sDmlÔwAâõz­&À0ÔÌAvå¹Ìµ¨©gÊ'µ‡ærjéU\ë2¹s '(Xˆ5ýÚ&òûåÊýý­ Ï¢)êÎΓôdp&ë±T»¶çªEâM hìN¦eE~¶»¹›ôgÝ‚>««««år9,Ãg¦:™â[Ó :ucâ˜FJ²­¯ëJáöׯ_°aMÍötVi1˜K®ß™B«|6dÌ©3%yrÒÅ*Ɖ:™•ác2`MH2mbÜ(¡¶šZÀµ¶äŸmáH;! ›#ôî<ùr8yçÉwÂ8£Eå²›6“#Í3€80È!}éeV ]Àèx!{ ƒÔv",ÝKøš®ý ª[këõz</‹‹‹ ÈwÛ8šuŠ¡C¢<Ícò{}» ˜b^†ß5™Uû&öß^1#ª¼w©`ÈÙ”€"}çÎ*˜u|ÛáÖ™9ü.£ÐAþÉþZÀµÎ†%Á)‚Š)¡J€pžäÀªäÖâÏ_I_l±íØj¶—rÀ9úWb j2o‡6Fa6Â|§¡§+Õ³9K º‰ï¿ùñã‡=9f¡­ú§{â[0쓈ÁŠ)Øã4߈é“äá×ë5ΣPmÕöÓHK…·º”tú&KÃð1i´Ì˜—eIÂàl(~vÐBVåó ÿ˜Ï¨U«øß ½úwœ~ÙiŽÑa‚[+s)¼JßÿìÜxm¢Ë©òÏNFÒQÙi€º…rUÕQˆe> 1 & Œ9¦ Œ)³Í»¿kœâ±ÉÞÑh„­ñz½¾¼¼T¯K˜¯Ðåì~~+·MŸON_eñö"û­C¸°‡J ›°ÇÇGB"&sðyüÇa°**K¹[?#;8w{©*° pòjK­ZÀç»Ôã´Pš8Â( v(9»ºØø¥|W‡Ûý‡ø~æthøQ1Ä΋“¥YkÞ VÌ ¯“—dÛäŸñÄÀœ›››îúl¥Â[ÉñP¥¥}Šƒ}iîòjµâ—¤T¿ã”ÓV,%{×××øk…pý¡ÂÊ•IÎC…‰¨+C…)ćGßljÒÙ!fÏt^ØæNÐ3ŽRdž$»DXµ €kõ{3P3`••öøãky<'½+©uº‘úÒôÒ:Õ);—0+²l‘ð6'ÐeïÊ[Ñ—Ò/Æ“Îäí¸ãñX ô*¿ÚʱvÝj„N]Ìù¼µúöwƒ¼\¯È ïh4‚ “ˆ¦-ÊÄ¿ÿfôSZ?>>>N§SÕÑøi˜Ž>ÊhaZÐ n DZ ©]ȇaX†'øprsgQYè[«¸V¿SÛ#”ƒÕRÿén¾µ‰¨+¹õ°§®CÚé"í%ÓË©ÁMÙa!¦º¾u¯×Ì’Ì3´ïññ‘¯0`àÞ_7í7ÇÝSkv½YT|íŒ2?œãß±Œ0+f¾“*zK•£Zk2{;fFV8òðTЦo„GX„*”KïÕÌ*w)åî1Öa»óEkÛ©U\뀡q’F?þÒÄšcÚ¶N¶„&`“ÌL|8–´ò´g ÀäÀ8§À¦ëÈ;诩TЗ‰¿š‹!Ì!û-&4Ññ¸¡†òu Ÿ¡ K@¥Õ6ØvXîü¼{Î TàÆñœÇïšÂp×PKB˜¤4)zÄê$'Hg0·ŸïEÄŒHÆc@…[LñÊ • Ô 8ø¦óŽî¦EÕžS«¸ÖìH€dÀDô†ÿÝ&’Ä·›Ïêî¯Ü7´N{Ên£Œý9ÝÁ¿õPí;RUD޼“ 7MGÈž-p~D¨A„¡ç”°Ô1ƒOxN£ÊC¢Õ¹$›ça±XÀ;)Ew&¦ñ¸Z­&“ ª1†t ¨/tÛfµ‘ÐËmn~üø±Z­²5Ëb„fÎÝߨo5§…b‹SÈ9%'ìØ‘t7®Vp­0àE/›«×œ¡r×Óß ó”Mêž¶œìГMßàœ~øI°ùgß{a¹ ï|>}2êx¸8 ÅP,î‚]¹à12idïQއ ˆç3NŽÈ¥& “¦hUó™*G!E9Ùb9—Q~ï U²Ð•fÍ«Ng63㙹µA™ (u5à$úü˜–gÉ€þüy}}}†Ð«¸,Ç[ÕÞX\ë3 ”uéFÔéì“tyˆ¾NL"=ÈÊÆDþÐa¨ç3FÁQ’ø6Ⱥÿ^Öõb©æÍ„0¥ßT>wÛú‘?u——¨»ŸŸŸíGzxx0Û4ÂQDóâã–êí±6  úµ•rC´QWWWhÇ ëÈÔ¹’æ*|´¤ÝfŠ˜ƒ¨ˆäÄr¹¼¿¿§LÀïyÀxnÉ%r]øh½Æ~w 79]›·æ—g2’Á’Sv¶Íl+Ï®vÈàZû94o&NÈ ÍÄüÙäóQ?ö<µ,¾Z NW¬ÍÜéýt\´°ÌS¤,‹Ã#« 4:ÈA*ÜZƒ4“Ó† Ûa•cB2] Š˜ä}É??<<èPFˆiŠÂáTóùœ,4‡7 κ)9“J¼'¸±åšiWGGÜ|ö€ýg›2W ­@ÛÆr‡» ÿIɶ €k}FÚƒf>«ú2¶hIîËlCüß¶ºSyO¾œ6hâ—œ ·gvùÏ‘¤ex´ õÕ¦Û8 ‡ |èÁQŠœÅ ÔÙ­„q´Öâ –OOOËåRð¶áÍh:k$AiV ÖƒSñ?iî æÞme“ÆX>ÛÜSN ¢‰‚,Óû™æ=JØ— ÃF)î²ÀÌñ¬7ëáá!›à™OÕ%-j“,®õ± ØŒmÚ„³¢äõµ°?2§¶½´ƒ°V÷…¯dzM‹¾jtÙÜM>kö ÇÆÌ­ìÇ-þÌ…½jÈý:*œ‚yè/_• ÃWWWúXueàn´°­tp>±\} -O“ÉÄÙvÐm½,¤ñyÓn¤1U܈·‰*à—ï¾Î¢©šƒÔHÚW­ v0w=r÷÷÷< ¼’ÉTÿüóO¦ñ‹×úØ¥ŒÄéé3̤Öôµ]#§Ê°­ËªOnþüÉlÓ¬ßX ‡û‹½ íF£Ñ¯_¿æó97}2wYÿ¯Ï,Õóìi^‘š>xJ“”†ûª.NiÂ0œ“¸ÚF„eŽÿ¨Pœ‡|:R÷…kJúÚÊü³÷Ô‰Â!5fþ5MNÞ*5Oš ´;ÌÊ._“¶—ÐwNôU€vww‡-(OùÂî§§'kRÀÀµ>v”À ´æ3Äà4zëÈñ. ÿÚ‰Ñ×éOÀ› 5`[K?S ›Yhá‡ÑV ûf¶ãÓv^wyÛ²•VF£»»;ŽÊɆžBÒ;OÀsN­ ¿Ê™BX…ÞÌ{£N‡ø¦%øŽ8)ÍÒÕ¸¡ë<ÑTeƒ…6Síx º±˜ÙÚ”¢9‹ÍɃÛ&=Þ¢CZóÑÇÇGû¯ ¸9æËËËétŠ8îÜš¦jÁÅç“ ý…}¬:nôÚ<à6Pÿ«æ«ƒ•ýk«¤'É©&R8°ˆú‚Å~s°V¦5¤üÌ#ÌÙõ\ŸTW9Íi¹\&؈gÒh±Y:Q q\z„i¶¥=è»OÑDúËëG£‘†ºî‹‹‹»»;û¾¦Ó©ÁÝàK+ñÎS²·-§G€îøWÿÿ^¯×f›ÓŸ¤ml2 €^<Úø¿)ÈçgÓíÀµ>œ!± ÉzOZ–Û9vó¿á¶¨…ÛùÓΦž$L¯‰ôàÔ%ãXÞwx‡%ÔWíÉdâTG•Y  /ûÄ0Ôivæ@èÑ`Y#øcâÅ„øR_WH¼Z­t;É&¨ÛÛ[‡LTOž-gK›MÌêÈD_Û¦m:ÏA¢ž¾é›Ækg„tBËJ;×úlzägïêêJv«r?úã'óßüÑíjáCƒ­“B¶ÿ‡Ãë<8SÐ{ÂGQÁANòFúzÖYÓc2gÙ5'ñm­‘ Sdýwƒ°öL{è(ÎŒKGs’õµh­ôz2™Ð<aáCÖÈ…s¿ÓÜ ¶[-ÕðCê_ÎK¤ZæôJËž…BâàZ' GGŸ<þ"œµðLžC-< N °ÎÇñ´¡~¨ð˧Í?Zk)fNÄâ¼Ä˜Œ:°nI8 C$h%¢Œ8 €k}uã«> ‰•˜ÚƒÛy׃€ÏíùѬ Â)fNœjn¢ÝIDAT´z`¶ÎáIδâÞ´“ ÞÚˆ•Ò÷››͉[I:!Áè,1N»úN6I|ù‘Œ1`ÒÑYêΊx‹É›]‹0j"eÒäþùçŸù|N4çË7î×:#ðºunv½ïƒá¿9>óI8éòsíæˆU|o2¶µ†à™¯IU-xçzô&_o›*‡Ç;—].—–uÖë5õl$Ó¬n&·.¡ž²Ú¨´}Õ¯¥ªÜ®$è% A0˜†íãñøööV­~}ê €k †Ïy_þKa¸ògÒ±]í•¡™)ËÊɆêãRá%m=JyE—ÍüÌêž8B(,4”ß;*;!3EXmèÇnÍLÕ 6þ”™çLäØ(òÌÀ-…pGP(û¨rp­Ú—kÕúÿÄ~jÎarõû|˜³Ù)q÷ð§]hÏ·ËhÀ4²_sL¡¡€¾]†ž‹¹î|¯ô¿$‹œ+8ø³œ”"sGa’7ól+Z=rÀµjÕªµ%@"„•έ¾Öv" ]G’5XNd5Ûœëü±äîÎÍk 8PJê”é¼ çŠ¶p¬4Rá•¶ÉÇ"±ÿTÏXp­Zµj½aeW³ë4hûhV—,¶E,Q6=ž ðtS—ß[üN¿Ød®Ö³É$ ÏÙԔɳMê䫳­ž¢àZµjÕ:KþÌl*µU5ÉÉn™¬€Lè~ –ë‘bctþè¼KÃ2QŸÙø4fïz¾Kí\\«V­Z_deŸOÛÌèt3¦¨‘5¦îÊÛB#„Õ†]'[9–;É;5kš‚ué÷Ϋ²Vp­Zµj}þ b¼ŽN&< ‘C;ƒ¹So¥Z*3Æ‚®•Z«Â‰Šü»m²Ðù—Û O½Vp­Zµj}èêôÉ@'R¥is{™!×Äãâå’Kyye—@ÞjB¹õjý«Öÿkâ’Öß¶sIEND®B`‚mediascanner2-0.111+16.04.20160317/test/media/image2.jpg0000644000015600001650000011151212672421600022500 0ustar pbuserpbgroup00000000000000ÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿá@±ExifII* †Œ›£(2«i‡¿%ˆ#û#CanonCanon EOS 450DHH2013:01:04 09:52:27š‚9‚A"ˆ'ˆÈ0221I]‘’ q’y’ ’ ’ ’‰|’t‘†’ø!’03‘’03’’03 0100  °   Ý#¢#¢#¢¤¤¤¤È 2013:01:04 09:52:272013:01:04 09:52:27  /qy"ÅÔ  ì P˘  v€  &0ƒ“t•@¤–ä—ô˜ô™5ü Ðªì´Ðàø@Ë@° @¶ @ ¼ @üÜ @ Ø!^ÿÿÿÿ07€,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿo·"!DÀàô”ÜøŸøÿÿÿÿÿÿÿÿCanon EOS 450DFirmware Version 1.1.0ªªv?u@PŸ”’ŒŒå»»ÿÿÿ #µ+ª3œ:KÌÌÿP ðð(S07‘u’1.1.061Žddh dedûžï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þï¾­Þ[¦æPº%•VEF-S18-55mm f/3.5-5.6 IS Ÿp` ° ° sss¢È¢sss™™™iÇi™™™™û^ý^ý¢¢gA¿þ[¥ýA¿þµÿÿÿ0ÿÿÿÿŒÿÿEF-S18-55mm f/3.5-5.6 ISK0336838Ô 8,8ÿÿP ŽK"Ø<  Ï6 œ <x<·ïîù¿¼,&çå±ÿÿ÷µ Üêé$qtt8¥ ¢ 0òâ Û V Ïèè${[Y×xX U Œ t¯Œ t¯ ýu¼½P ÒXe ;pÈhhn € Ô À¼½E©  ¦-2.ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}ÿÿ}~þdä”*–þoÄ'Ïþˆyl íþ—RX'ÿ´pBÿÂïàdÿÕÊP•ÿó›\Ìÿhh@7Ø9f ¬ x™Þ€ £ÁÁ¸ Õó£ð Fvi` ô"#0(/*/&%'! 0-2C,+,,)%')&"%>EGF $)'+,$(/3 =7D:;4E:;01/I65H`nbkUUWGC=994~w~¦kfig_SRS?LDC¬»¹¯GJMPW`X__NTY:!=3<35-;3-%%&687GZeX`LII:6/.-&~v}¡c^]\SGGF4>4/­»¹®EGIJOTLPN??B29/4,+$.%%&20?JPFJ:87,'# qhj‚LFGD=30/")#•—‡-./048132'(&   W`51x?*7OG7\Z:/%W0 'WH»0îBˆ+A ÈMˆŠE‘O0-³€¨³ }Ò"õÿˆ.Ýÿ { (6¿3ߤÿÿ£âÚÔŽ~ñ  ñ  c’ÿ×;'€/Anu+H S‚#Eš#²#º# Ò#ãQÄ ™ ¡*Ô0qî202013:01:04R980100I$Q$(Y$PHHÿØÿÄ¢  }!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÛ„        ÿÀx !ÿÚ ?üÙѾx×ÄWVº7†5›«›M¾|)nÁâÝœnSÈÎ _±øñR³[«ëwÍœK¯ƒÏ±B W+ßðÏÿ@r|!¬€€³æ!ò€’yã†Cÿ_QV_ölø¡õ­”žñ^]+¼4yBcyUêÛw.qÓ"ŽGØWD:ìõñ#Åvæ x;YÕ" ¬Íi“ha•Üùr9浿á“>1žŸ%ÿÀSG# ˆfOŠ£¯€¼Iÿ€¦øfOŠßô!x“ÿMŒWý™~*È2žñ# ‘‘lO#­T“ö|øäV’x?Z[©X¢@bÙ€,@\ä?AG#ìE¿øf_Š¿ô!x“ÿM'ü37Å_ú¼Iÿ€¦ŽF25‚ž<Òf1j^×mä%¾Ù‡ïî/ÔçÔñYz‡Ã¿éZÜš>£¡jvúª­g$ %ÉÆÞ§9Ç\ÑÊÆZ¿øMã]*7Uð§ˆ,bﺲ’!\°{ô¬[êWS,Vös;³ˆÇn=zsR6š?U|uâý3ÀõÿèñGs«YDªÐ]#$åÞm¨dFÚûÊXíÀ"¸½GLÕ¼káOÅð¾K“Ä2¾°Ö—·ê!Ìl°˜ÎLÀ\+¹ºèFÒÕÖÝô!³çx¢mÄ÷Zež¥6³g\Û^jå w*´Kƒ×åªö"ØŽMzÄ_Ú%~.øfkËÔ}^Ñ¢´»°{\à\,ò¬‚3’È9bbÝÚÝs”TÞÉ¢N¯ögñ®³àßøS_Öu=5ô_&¨·Êm6´ŠÌIpͽ@Âï¾ã.>ÏðÄ[Oˆ>‹YÑà»·³™È‡íJF\¦IPs‘ž«†è”e­-dt“\yŠæ«ý®:z±\é¾[m+Y·6¢3Êãð¢ä²›Çp+…ø±â<'¢Û^jQê §$$×P2ˆ—)AÉ`ûˆcåÉ …ÈÝ„yWÁŸ[xöïÃšŽ¥<–70JÖúæYä•\Ï'Dæ%ž¥Ï\f¸ï^5[/‰w·‹¤ÚÙÜÛÙ5­ÅÝ…Ä_kû@*ð̪T9*Ñ“‘÷ylÞÃ^FÅßí3âM7GIá°Ó5{”‰¡[C"¨»'ÎP¶í©n®IåˆãŸoøâ+ÿµ˜ÖSØÞíî¢uvžáÞDp6œ_/ž:ÈH4¤ÛFÏ‹|eâ›AÞè'&+ˆøtÈ Œ÷SžW¡¯µ‡zß&ÖôËIn¬c¹†é¤µ0$Æì6ÜÄ€ð—8VmŒ!ùÛãmάÝ&µ¦êpÝëš…Ãå„ÛÉ l¸V‰~BÜïÆÁ› ÀÍùŽ?×õý~Øß´&‹©ø‹Bðý»øyµ+[]bÚòî+I‚xðä…+» äá~^XV/Ã?€úŒ–¡â[;­cÂ7²,–Ö¶RÇ6öí3ÈñÄÅåò·~l¡Ž7kW¹6¾ç„~Ò_áðN±iy£éz¬vÒ´’_Lî’|ÅÉ%B¢‚˜Ær*6sç>0¸Ó¢½¿‡ÁÓ]Ýé—“¬’ÝmòÕÆ,"0ˆi-ÇCµp/3$¤ÛYëz§‚ü7á·ñ-¶à‹xöM«Ëg&Å–G²–å[s¨Áp O5õ—À†±|/Óçÿ„gÄSx“AÕH»Yå1óJ€dR¤«+9#¹~6¥« Xöë<°QòõÆkL_¢6T0–h¥bý¨Ý¤sŒ´fɦ!î3E×…ôÝ{JŸNñ¬Ö7 ²[yÐ28Îyßê(bZ3…ñßÁ[+Ý>êoÛAq|öd#¹v4,¬ P A°!qÆAð¿‹-5y&¿_ê:uŬq‹+ÑåO 7‘±Î\d}ñ»-Aç™kK·1´ èzoˆ¼=¢øºãG’FÙ&}Lj‘Ä‘IºÃÆÀs"Þrµ á—äõ'D·Ñ|ñ¿ì‡â ›v½Ó¯Ñc§ê-å-×Û#U½˜otVH$mKìm»¸ÞØJ›-4~–ø“áΰ,4Ëev—QH€‘ÔƒÁȇ=är ²ðSè–1YhÖYYÄŽÞÚ1&NN$ŸÆºœLîdxŸÀx—M›Oñ6—õœªUã™3Æ1Áê'‘ȯ>-~ËÿîeÕüÿjÑî G$7–Ë3ZÏ$ƒ•ã€Ü'Ì¢+—ü!®MámÃBðU’xöúâÞc;èpÜZý÷MÃ|Ñ\I´}ÏÞçêß…ÿì¼á‹h4Ø¿M4³ù 3 ¹ €“÷B¨à| æ³ŽöÙùÎN1ï1êj¬+Œ:ƒzÔj‘ÛƒE‰¹jg”€8Ï9­;}"õˆ)qëHf¶‰vfT”ê@é[ÖÞ•ÀÏÍïLjâß|5ѾTl~òÉ È¯È?0#=»^yâÙƒDÖµ+‹íbÉ/'˜†‘£ä ¹E=2Cõã ËbîËIðT‚þñ/ˆtåíÍÂÞ¡>æád“ÁÇá\„þñÏ„¯ï ÚN‘âHg”°šÂO±L¸ã9 +g®C˜±ÀЏ¹K?Û‡I´{ŸøoÅÚ$Q‚ÎçMkÔP?ˆµ¡˜Ç98ÀëŠäüiðëáÅMkNÕº]ê±iò@ÂâÉ.%…žo3r¹xÊïR¤«+NÔ%‰Ý»Þì|¤CÀj€í\î¯CH´ÂãᕜÃ÷2±ë¸Ö=ï·ìÝq“È©æc?þmâ¶7F~™æ¬'ËwÕ½HšwDò³RÓÁËÛ2Èá[6º$Vêo¡9éøQ`/ˆ­aÀ`{ç¥]¶ŽØŒ£ö©4Reå»KŽ)%¸…‡îÙ3ßš”йÝÀdúç “ZÍÂ"ë֪ù™â?ZøkH–úòßQº†0O‘ag%ÌÁ8 “ÛéÓžkÎöƒÒnîµ8ì4«»ý+N¼6w®-™eÇÙ¢œþâP‡/#’váC3mU)$Rò>nñ'íïqá/ÝY^xlˆcÇfmÅB¬›¶ì£cc(ÚÀGÌGÔ^ñä:ðn‘®(·˜^Û¬¥âaÝ|¬À`‚1¸ãÉÆkjošM#žNÊìè¾ßSË@8$œÕÈõ¨¢}Éo°èA5³¢ßS/l—A·:éºB}GZË%Ãf9j£FÊÄJ¶·%KË´`EÑ v+ÿ׫_Ú÷O÷äSÇaŠ™aƱãUs y¥þ×Ü•°x©t öã¾Þ®CLYÛsÖ‘®ãrIŸLÑ욪™Rãc–òdt !ñv‘ö«È °ð¾™§G†ÌÆ´È7F0wg îPc%±Ã7ϦäÕK”ú?F[n~•`ëg¨|¥z{nq‘ |ïÆêëØë(ª Eñ çpÏÖ¤úùl¹úÒl,+xÖúTcÅîäOÌRcHWñD‰˜cL,}ܲâŽQ\lÞ/dSŒ“úUã™P þ9¥ËqÜ“þYž}ê+lûÈÄþ.(µs2ëâóå'Ó''®¿‹gàHþµJš*äRxòéùd/¾sY7þ"žñ÷ÜÊÌ{dôúU($Ìü¦‹^–Hã7-ĈžXAÇ 7ä†ÉÎ{ãpûWöXÕ.4ßN×/¨–\¬0²,1eŠÉê§%±“‚Iþ,Ÿ+Ûš:«%Ê{ý·‰¥1/ñçš³ÿ \ŠvaïÏó¯AËS‘Ä©?‰îr6Ú£ÅR™v»ÇŸî–­oØ‹ÓĨ§=Áþ•:k!ÇÍÃ}j죿¶”®7ƒïšÏ¹ñ0µ“WcÐ#dþ>ŸDª¢”Lø®]ù›„ƒtüjí¾µñæ öÎJÆ5¯{šN£'ñx*%*ý -Ò¨Mâl7É&à1Éïüë¢ ™6• øˆ?/ƺjÔl;]j"pû§#ÿ­HuHsó1$÷ )6ÆÎÙYËqÕâ( È®Œ¬BÇÆB£– È$1?.¯­|ãë-_ìû­Yõ½JçÊX,­@ oÓ·€Ÿ"†`z£'p¯>S®¢¾‡±XëË ¯‘& 㓌T—šÛy¿<¾dŒyØ?­tó^F¶E;¹ó™Ðc¹¢8¡# ]‰ã$óZ{v•…ìú²_²ÆK‘•‡pÀÔñßM§ ÿhwFyéÁæ³–&êͧÔÅÔ‚¶SL,8^¹pÃëœÆ¥i¹uÈ2©aØøì4Û@ßÙºÔÅ—)"pr ÃûònÿMü ðnà}+ûBþÞâ}Võ‘ä®8@§9#<öùùè¦zvG§>©g–kY' `âWEÏÐc¦sSÇ©8MѸ!N9üÈ®µ?æ0Ô°úúÁî,I=z|>#·æFpÄtnÕe-t3äñz™ÇÙæ™ˆ<&TJÇÔ¼g,r8 É'æ99Ïó¨o¦Å¨¥«3tMcûJý¡»’dBü/ þ?àzVÒèZ{³ «ë§oe·OL÷ëXT­*nÑD¿x©—gó4W3 vó@<{cëZVº½Ý¬`Ç;Ü•<(<ã=s×?7UËâD¸èjÃâNæÚY!Ô.š,€Ép€sŽ„ãƒÿÖªö¾-e1ÚN@SŽNwc°¨ëÚ.WubnΖÇ[MJF[ˆív‰c$Š}zàvâ¤wŠ\^ò÷äŠè.Àxû­Áü ©U\_/üq¾¦ͧM1ÝA õß7ÝÆ…$*¤¤$.öwÖòÊ’¡°}+ù}k³ëÚKúûÅÈŠvwÓ¦ËQæ¶AÆàp;qŠÉñν/‡È#[XùÛ­ïŽßµf\j‘¡+¢H¹ÊFJ“êIãµb¯'¸ÞÄöq\K)¹h!ŠVëå’ÀƒÑ°;ŸÖº 8Ï/Ù$ æ1wwáN03Ç1íŠç«V;†ÞHu1Ê¢ßh¼„ásô_LVö©uk,qÅt’7ÆBŒ²öÊž•­ §î½D1€? ñùUR÷£¡-/ã­&æÉ#´EDw ,ˆNÅÇ\ õãߥdÅ­ZG$¯ ä–å‰ÿVä‚LáFz÷©„*F÷Ômö*jwrÌ¿jŠìÜÃrø<}}yÅxÆOj›*âÜI§:«Éœàáˆ=;x9«s¼vµ)êõ<ÃÃÞ'¸ÓîRY.¦!â0O8è3ÙEz–ãy\Çu(Žx`¤ƒâ¢=Q¤×SÐ4¯C4&%·£)´” »}Ûš»&¿4ÑÇ+†1'Ë™vŽO¹ÁíÇ^•ͬevÅea'ñ3jÖÒɺc.ñ÷ôØ>ªö©\OnS)ŒŸ›s}qíþzÖu]¢î ­Åœl$Ž"ø81æãßίŪèáâKûJÄ ŽB¬xÏ!²{t¯*sªöa{î-þº"ÃYÁQ( âR¡½ O?C\ö±ãë]&ÑÐÛ©icÆÓÀëŽNÕT(:®ÉŠO±ÎG®BW‚)¸Ã ÷Áç9¢hoì¦ s1W99Ñàt÷¯eZé6: ?Ã׎šZÒê'ŒmÜ’cÔÏcÏ#¡ïF¡ö™æ¥Ç˜öí&ÂÍ"ºÇÍÇãŠÄFSp°XÉ}M£+nRYfVÜ¡[h=yŸÎ¸¿ˆº•õ¤·QßâO28¤Hã-ˆ9Ù‚ÙùyƒÐ“Ð×_:‹BQ»5¼âTÕ<7fóìu‰äR¤’ào'½pGaŽÞÕµOiºuÊÁä2^>pBþinqœÏüG”Õ^^¥r\¶5K‹0ÆÞppn=ÅpŸ´¹¼E§Ç>‰FN%•AÎò¯n•£´µ[Š*Ìñ{ ˆ­Ü=Ä 9ì ~c½t¶¾*‚28H ÂìúñúVJ|º›5s¤Ó|R\µ‘‘ר=Ç^G¦:Šê[ÄWW–è·¬æÝðŽ@n9ëþ*RwêCе¤M$Aä¹’-©†(Üþ=xy_`]Î]õ[ÛÙáKˉ FBþ¡–HÀÉ4³Œgo|ŽÜçô÷¯VK•Z›—.n%†1ö$coüQ—?0=y¾Õ®¾'x"Ž]9K*à‰$U‡¥a(s%™[§ñÍð+ey³R3Éîz}FÖ™¯xÞ++Iˆpb¿ÍË~aÆÜc¯N½Ee7iŽJˆ?løˆ,…µ´6÷(ï.oÆá÷Gr=ë#âö—q3ù[c¾I<–üˆÑlLœýñ&ïÃÅi+§˜âµº2ü ©ÏOóÆ–ÞaãhÃ.02G~Ÿ¯®k—×õ/³øŽ{¸ñt‹6õ {g%G¶îßýzÙG©G¡x›ÄWð‚Þ÷S…$ÔnÈC %eI$qÈ8Ñ»b¸×»×®|$·Z<—bÖÑÄnaPÊ»V58ïÉ$`¯Sšç¥>[ö½„µ<Ø`¸@H©3Zö–/×t*£$Æ@†{×SC5,®¢µd;rÙÃŒ–#Ó©÷çô®žMF8)æ…CŒ(ˆãäдՉ®‡Sfñ¬©ÀE%\g-Û=úV/‰ü]ýPéÓGç3>6º 3ï’?*ÂKšé“cˆo0Ôÿ'í[ß¿Ÿ»zþ¹5±ázkGQ5ÌR´™(1’ ä’z óWb™½y«mß=ÓáOÞrsôã¿j±¥ÍÔ~dS+G»œ£Ÿ¯ã[9(èdÕõ4ÒôKr#°šI¥ÊÄÁPŸ©8÷üª¾¯¬ ;—“"̀募SÐzÖ\Þò]JHž=hØi³^ÙL Ç”:œ¡Ž97S\Oˆ~&ßk"9„”Þ1ŒüÝ2F9ãÜV/Þ•ú£HÄæ4ëÆMV ¦”!‰ƒ+F{nž£½iø—ÄçÄ·bîuÙ1Œn*ôz1Œ~ž–WL»´MlhP½ÍÌBhÉUH˜1ÉÎqבÇÓ­s÷sGq©;ÀêEÃä;rÊ w_^3ɧvMº‰®k·7‹om$Ò¼6È#$'åÿ‡à*KOÞâ¾e,Ð@êË1pÛ91ŒšŠGŸ­õ¤Ò)鯦:ÅñÆnî8éóž+[‹¬_.Ý·wiÈÞ H|A©3ïkû²ÙÝŸ0ç4r 'kr}ýVý¸Ç37OΪO«ÞÝHÒ\]O#·Vg$š,‚árNMĹÿxÒ¦©yK™Õ‡BñEX•µÝE“k_]ô2uÏó§ÅâMV-¡xŒp xéÍB±j?ø‚Û³©"òp³°ë×½RmwQvf{ë¦f9bd'?ZJ ;¤1­¬ß:á®îô.j6Ô.[;§”çý£O•.¥t£ q0ï?´®ˆÁ¸›>ñ£•ì“ûfûþ~î:mûç§¥Eý¡sœùòç§Þ¢ÈW¯gv%¦“Ô–§hÝlÛö‰vôÆãŠvÿÙÿÀ@à"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?Æ¿ðf“=з¶y-¦+œ¹Zµá[»(ï,d›wÙ¥Ú‡zƒèz×Gk`°;ÌçÌžC–sü‡ «"Y@€; c“ŽŸÎ®ÀFT2•=¸Ï\¾•â‹Íòaw-OC×õÐê¾"°Ñï`µ¹-¾^r 3Œš¡¯é‚-BÛÄà1ƒjç“×õ¦ÄmArϨÝ[¶Ð"TeÈ óùŠÌmx_k#JÓÁb¹óç¸÷¤ñ;5®šu[VÄë•~ò¿øáΧý‹¤ÉglÇí÷?5ÄÀòƒû úúÐ#¨ñ‹ZÆâ=7Kq$Ⴜ­ó~ZŸQñlšn½mhÒG$*\®Ü'©¹/ i°2\jú›ì­Ô€ÝÚCÓã­`O9–íä2¼¤¶w¹ù›ÜÒ¸ìzv£â›?ÆpY_±6ÕeûÝó]ìj3“ÐWÜKo¬øŸJ1º*yq Xž›G9üzn“â3ÄâÚÞFÈVWcåéh¸t9Ï_ßYÞiÚ®)6Ñ‚<Ä|£ô>µ¡¡øÆ/Úϧ4kûÂÁsÊ1ÇjÏ´'Yðn«¤GM‰a¸È|x÷¯5²¾ŸL½ŠîݶËeM Õèsë1[ßéÖ—“ÃsbZE‰ySƒó ~µÛøÄ·šÊOk¨Bââ¸Ë³h#Ðû×Á©jòg‚æTžèáÙ‚ù= úÖï†üSyá[‹˜Þ0”1\AïøÒ¸#Ý)Àâ©XÞ¥õŒQ¤¨~5hÓ&iF)™âš4!˜E;4Ö4Q²Ô†šiZiZ”ŠM´‡m!Z±·ŠiZ€­4­XÛMÛ@m¤ÛS¦‘@í£6) D8¥ÅHV›ŠiÂŒ’÷¬½[[·Ó4¶¾Á•mzõ«z®šš¦-›» }åê s°i³Ç¢Ï¡_NÒ3#2MÕBƒŒ§+€ÑãøŸÛÚífQûÄ$Oÿ^ºáÍy%²Ýi·ÑLÒ<‚,a‡žzc]þ‰¯bòå#„,PœÝÔý)& V'‰nµ :Ù/¬˜4QœMÜñýáô­I.ãŽa?> ߥ߰n 2õ=1L5ÿ„—V׈µL,W!¢m¥³ÀÏ ®¯Ã×Áãk&H`û(Ùµ$Ý“Üçë\]Õ×üVpù—(±«á2íåyêo/Ùo#¹¶žÌ“Íåt|÷8ì}3Ú•ú…KÅ.+™ÓVôçÖõZ˜­¦ùzѳ˹RÚ0Äã=ë ŠÎ-éZÐç¹¶Q“?Ï‘Ÿ\ŸEÓΣ®Ûj?k…bÜ²Ë ±Xõz~•êJÉ,`© Œ8=A$ÂéœÆ“eá=n¥…£ˆÏ /̓ÐúÔz§†|1m †–PF“·s…$÷«Z§†κ†ŠÉa¨!Éd\$£Ñ…V‹P·Ö˜iZÜ iªDÁ“=öd=þ”Âíng/„,¥Ô^ÒÏ]¸Žê4ÜѪU}ÈÇ5Æx§Â×^¹]ÌeþäÀc'Ðû×Csg©h:ìÖ–¬c:“…K·f%HçSëZzÀ»×ôfÓ~Ôž}¼BK:"¿2õ þtƒ­™ç›M…Ú6Ž"Iš5çžÎjö¨ÿgÛ’c“™A‘‘‡Í9Luçô¨o­u SnÒIs*¹ Ä,‹Ôdwª6’Áóo•ð9Ò–Ácݬ5‹ BC3baÖ'\~´¶Õý ËQq,ˆÑÜ/Ýž#µ×ñªR.¿¥@òyöÚ…¼jXù Ç&Ñî8?•X7ñ¥ÃOâ«Ì¹eŒ„^zOÇ5è½MCÂQµÉó< ÑKžx¿†+Ë Xµ-YüçòVfÝÔ/¦}«ªðN¨šU®«Ã)É3) ‘•ÇãS}Ah½oH}_m•˜g6êWÈNpW>¸ªšÂüJ¨^îPrÉëí“X–ž*Ôîîmôëû4Á09w'¹nµësI™¥4“>ÔŠ>YŽyÅ=ÃesË@y8õÏz3Ì€§ëZÝö™k4“yk8F 7éŸJÏ•#m·g‘8ÁeÁ?‡5-Õ±¶Ø¬à».Y0ržÇÞΣÃÖ§c=±]F(ìšlK„c©"±ÕÐèó¾® .þü¥¼JG!>G{õ¨[{ÀzqµþÑyc_0Í„rrÅ;WeŠÁðž¥×–î˹ÛÀÇ9ÏêtxBHVޏíT•€ˆÒb¥)Q°Å0Êçšçui¸ÌÖæ92HxNÓŸ§OÒº23M+H.yF£á ÍežåžÚ3‘5³m˜ö‡N=…7Nñ^¥§¬iÐϨZ/#ÎLIÏ#Ž¢½[mcêÓ¯æó½µ×ðÏnÛÿ¯øÑªŒ£eãM&æ57½¤„|É:•ÁúÕ-gWðÖ± A<¦gNc–ÝK:PGJÌk½J+‰ì$k=^ÞÞP$Y0²6IÂ’xü«™×ÌÝÇ6as¥±b’‚ß = éCbFä~ ‚âc¡ê÷Bh‡6÷ë•t?øzÓ®ìõ[ɸ¤ŒùñÆ@zŒàö÷ü+‚oÞÜ‘=È9pLg>¦¦Šæê´wšXã©+´gœ›Œë¼i«Ûj¶–0ŵÊÅæ>Þ¨1ÐÏ#Ú¸ø̰ [(YŽ3éøÕ»›U…šãRd—z…R»''°æªÍe2y‚c‹ˆÉóžAÍ}cø®F‡Âúƒ¡Áò±ùš”èÒ¯0j·±žÙpãõ‡­Øë m%½æ¯´eÝ»ì™Î9!°jÄÎ{áÄv s{svPçåÏùâ¸wIŸK¸òf»Šr¼(BrÐתÜIq¦‰lµ}÷zT˵.Bå£ö|:ó/è÷ZuÎLÍsfß43ƒ¸ôÏ­fv‘o4ú”+±C l‰%8UÇzÑÕ4[ÄÔíWqå;÷ÊÛD™îCùÖn›{ö èçhĨ§çŒôaÜWS®êÚ-þŽ>Í)Ü>í´‘È}˜Pð}"4èå‚c.×’h[pÇeéYx%Iàu4ÚšX$š2rÊ3HŸÀ÷÷EÅÅê[¤°R_i9ÈõªÚçŠ5õ •†ãɆI íÜc“ÔñÅ\vR諟ªYÆ_™DÌÁÉôÏëY1è14诫iûsó4ŒýE>€õ2U./®B¢¼³9èI­¹<®ÛÚ}ªKòÀÜ@ °Nµé~Óü5lþv”ÖòN)u}ƺ<Ò³‡1à§±m6%°¹•dEÄðLrAõÇoºÐy¬Ù´È^d¸€-½Â$EƒÔÜUÈÁU±cÜšhEÔñÍD š™W**\Ô9£q6E80ªûéCÐÍ‚*2”›éwPc”M&€Ê…–§<ÓH  û3IåÔûE&(_e=¤+šr®(<Ón,bºˆÇ*²:U… SÃP3 ûîaiz#<à:B3ü±Y:ž‘w-²ì¢[˜dŠêØänS‘¹zà÷Æk²8ÍG(‘£a|pXd€8]JãLñ*ÚÚÈþMîðÍЩï·ýê=VÒX Š,djä“‘´i‘_Òµ5}6ïSVеÔàù¾aû©‡q‘ëùÕ¶Ôí5Hƒ#¬:…¾I·‘†O¯¸>¢€<ÊÓAœ7Š!D¼H‹¢e<`ù¬H´ËÔ“Ò+8} FÇœŽ¿…{jZÀ–ÐËD ­î74jyÚÞ«\V­miœú`AÉmßlŽAµ—<{ž1žâ¦Ã2ô¶xz[;¹Yf’Làás‚1ê:þ«WNÑí"k­2â6ÑrÁðæ»ù'>ߦªhz¹¡ÛiÑÜyv볂‹g'ž„sÇÃjún©c¬%Ì`;lŠ)·­Ó“ϧQL^‡@Þv”š•ô³Âèb‰˜á”qÜzWC©x~ÓQÒ)“3ìe#.dÇϧå[WºdÇI…-D-*-¸áì?¥GnÍö»X¦(—*Ï#FO@ÂãðcƒíNÝýO1¼°Õ¼-¨Zíº–;H×Êi£^NYIé~ºŽ÷@‚u¸óݲö•ävÁô©î-£¿µ¼›ËYa`ÉåsŽÿ\Ö'ƒ¬të[»ˆìVæawO!ÄhIûˆ;ûšK@vÜëjÕ²3Q•@clØÿj>šÓºPÆãv}*ñ…­xnÞçVƒV7&)¢`@*¸ z{ÕÝ^þ]>Ä]Fbh÷ ÅóÐý( j_Û\·ŒtûµÒî/­5+ˆ>Xo”ý02 ®žÚhî`I¢pñ¸ÈaÐÔ¬ªêU”2ž Šy'‡§³“L¹›TE†æ%ÝÃÆÄ°îN'ŸÒ¯ëºS]Co~‘,²±P ;’29ÈÇCíÜâ»› ÂæÜÛ´@BF 1Šri&(‘ º–0‹µ0€=:R°# ҧºœ ƒËýãnýÐ3‘íŸÖ¤Xn¬m÷ÜEºÊGš3•až‡ö¯K‡0<³É¼´²6á#LÄ)Î~è?‰ªš§µ8çž{3kp“Âc’f þ c>ù©°Ï4žÞ!¶H'È3òö9èE=–âÖXînc H¤¬ nS‘й&&Ž ë–·P!áLkœŸPz~“Y][–6w+}kY2F7b3Œý3E€÷êËñëiºa½ïò]K/ªç¶ U{ËHïm%¶˜f9© Œ×’xôøïthb–7Q!·û»Áçå=·s«™Lšd¶‘ÇÆé«zŠ«ákk½.;&ës¬˜$ÁÃ!çûZè) vhÝL4™ CŽ €AìkšÔ´•†B’pÑÌ¿Õz©÷Ñæ“4áºÅœ6²…Ž ‹y9ߣb§ƒ­eâ½ßQÒ,uDÅݺHÀaXŽEy~¿àûí&Gš(ÌÖ™Ètä¨÷€ÇIâ¹·].Ï-HŽHÐr}ÔT ²B»Ìnø Ž´BòZ\,å9×#ò5zç^¿»ŒE4ŠÐƒ•ˆ Ú¿J@[‚kRÖßèw èYá\3}W8?\×jÂ~ Ã,k Ðëå98ýk’ðç‰eÒ®ÒK€òÛ¤f5Gž?íí¯ü+âD²­¿ÚOg_ÿ¯E‚ç'«øRÓ7ÝiÒùð¯#a"@>ÿ ÐðŒ^Õ¿³u‰â9ŸøO£{{ÖÝö—o§²Ëmâ)4ø›þY¼¡—ðÍI§xIÕâ{¹®ŸSfÊyÎ6ãé€(³@u*CT‚B)ê9ªÚnž4ûU¶YãO¹»¨•t  $^8u¨\Ñš‹q¥Í8šS3Fq@æÕêMÔ„LZ“uC¾½M¾“}CºÔ6ê3P†¥Ý@î¥ *Ò‚hÀaNÍ@œ€&Íê iàÐø#dU[+OºB“YÀÀ÷ØüêÒ©4õCE‡sž‹Â¢Ò1†«lƒ¢,”{`Нwá­båHþÝV#î´–ªHüAÖˆø§É .qóhZüö«o>¡§\ªŒ%¡{ä7ZÉ—ÁýÌrÛÜkp5³©P­ r¹ôÉãó¯GòMWº†ìko/#ª¾pÔÏ9¶ðmæ–»nu-EGËum1u_fB2Ó4û«MtD‚-CNÕàR4êQÐÿ¼½+·K˘-õŒÊÿÞ ªï‘‘øŠ§yö+•ó7}Òy`~xþ”Èät­]ðý¬jšÓ[ù­ûëgÀžyçô¦øsÆzu®¡wmu#[C<¡¢Y#)´‘Î{κ{o ÝÛ¬€ë7;d;Š*®èO4\x>Îô¿¸¸ºA–B;{šÒ†öÚäf ˆ¤ì05)Í\|<Ñ™ƒZ}¢Íÿ½¤f¡ÿ„gÄšzãMñ‘îÇwî>´Àèî­£º¡™C# ç'°Ô£˜ZË<3Z»’Y×SÓϽ7ûCÆZsµé÷ñޝjø?‘ÿ ­qãM2fX¯ ¸Óob;Õ.¢ lÑ jjéº\ð9îe@Ï¿È\mUÆzzZø¨ì/-µ;8îí%Ybd2ŸÒ¬ùf‹XW¹)ÀSÄg4ᦦNϻȈ”gã>µ`!©Æ2Ê@ã"Ï"øƒ¯ËbÖ¶öŽ,¼Ì™ñÃãÚä-´»›­>Bº|ç/ÿ p`1Ð×­ê-.çÝEs™Y²Ùç°ÏZ¿oá•´P¯2 ³=1Î@©³:cmíQ5±ÏJÚòE!€±†ØúS ¹ô­Ö€TfÛž”ˆmÏ¥0ÀÞ•»ö_jO³Jv‚aaÚšc"·šÔc¥BÖ€ö¥`1JkFJ°=ë]­=ª3iíE€óøHÉÝ[:£RÆ);G}§®=«”ŽÂËí¢ ›;ê³áÆ3‘žÝëÛžÌ2•eÊ‘‚q\'ˆ´(,mãŠwÙ7ú<Ýõ\÷Çò¤Ð\›B»ðäv~DI¿9)s€Iõæ¹/êš]΢FŸe * ´ØÀcìó¬ÝJ CN¼–Ýä¿V~|ÅìA­=/û-f´–Xlš8‘e•·¯¹©ôCÒ×UÔã‚kˆá‹«¼ŽÐg©¯nÓ¬­ì,b¶µP!EÂ㿽r÷Zï„%òí~ÏÆÑ…EÓØ+±±·†;HÒDQí Å4‡} ¤"¦Øi dv¦",RbžVšE&hÍ!”»©¥¨"›@Í&i)1@‡fŠJNô´¸  z­ ´¡y©•2)é(ÄŒ”*E\™S<ÐO ö§­»Õ}#Z” ¤wÙÈŠ\Ö—–­*À¸Æ)V(¸©Öš™bU©æ€+ù)ë ô«# Å!•’jy…}*ÚF )Ž€)»TrÚŒf´68¤)šÇ6Ä“ÅD``zVàP;S J{S";RÇ‘V>À1Z 4â´Œ¦±¬OAÓïÜ}®Î)È×8ÕÕ* -ÃâÏ0¹ø~Ö·-6ƒªO¦å¢_™3ô5ÐÑ2ɲOáÿÖ©¢ø‹¦Bû5M?QÓŸÒx?Ïá^—˜š|¶1H›$]}dQ¨Y5¿ü+q€5‹u'´™_æ+vÊóN½Á´¾¶¸Sÿ<¥VþF«ëðî²ñµÖ›´mœÂ¢2ÞÇEgÍð—³RÎ[rXfaüóEÇo3¦6{¹Š‚çMó##‘Œ‘\×ü+ ítŸjÖ˜è†Bëùfœ|=ñÍѼIgxAqæ(¸YŽisÅ3”j‰4SÄE–ƒ œq@ ¤V^â‘bfíO6íØÑ  %Hûµ(ô«)^XŠtŠxÆhVQ(=*3µZ1ŸJnÚ»"eS=ªž¥£YjÖmky’6üÁõ†µJûRcÚ“@™çW¾¸´†xോT¶1Ÿ(Ü7ïb8觸ö®Fðà“YmT¶x.å Ñ%{ò?^ýµNóN±Ô&‚kˆ¦ÃÇ(2{J‡ÓG=¤øKÑîMÍ´ Òã¤mÛ~•ÑÇdTŽ+Ed\u§pNE+XeT°ÉéšG°ÈàUÕrZvK:ÐAÓ›®*6±>•¾£Ä­š.9³fǵ0Ø·jéÌw5 ÛíbÈ V9×µqÚ¡1Úº7¶=5ØU#ô ,sþ_µ/—[­§&x“û>>€±ˆ"Í/}+kì>‚œ-8Æ(ŒA L–æµ Ÿ ¥•=(=mÛÒ§H9lDËÎ)ëŸJ¨-óÒ¤H*ÐÒœ‘éLdK*ÂÙ©v²ÔŠÜsÖqR,kOÞ;ÔlØ"Ž]NöÒé ˆÃ&ذùg_R;U“s¬Y®Wò¥i§Nkuhf”$ æ…ÜÐŒ)½3EÖcÕ­MÈ¢C+GóËÆ<þT¬‡sE¦SJ·?‹?ZRÉÐóF"'¦!Lìù›æïOòã?Ö£¨è(þ~G ¤I[,2)b™(©ËÇþÍFnž#©|µsžÔnŒóÀ4¾jÔR7´VéPý”¯¥XóÔ¼R4Ê{Ñvpi|Ì R¾yªì¯ž9ªµÉLœIR¤ Udþí&÷¡¥aÜÕóTŠ_5{ÉódîšQ9iXw5Ö\÷§–²Vç2Ý‚0iX.^.)¾`ªB|ž´¦_J,.Ò0ÚÙíUÒOZyrôì&W©ŽÂPf+Kö† Ò“ˆ\²B砥؇°ª>s±ÀïÞâæ‹ËL *®iªXõj]€ž´Ò ‹ +9\üÔŒ§?)â™Mgã4l$ô¡S ƒÈ¦”oÍCÝzTedhÆLP&5]Á˜d#µËë?½Kç É2)¿haE‡s\Ê=i¦|w¬ÁrI憟4X.i‘M7#Ö³ ÇÒ˜e4X.jý¯è7`÷¬ƒ)õ¦™ ›_iFnñÞ²<öõ¦™Ï­ šÿl÷¤ûZúÖ?šÇ¦hÞç±§`¹´.ÓÖ¥qúÖÙvç€KèiXw7¾Õ:Òˆpy±¬]²ÿ].%ô¢Ár]GHÑuH½> P¶ãòí9õÈæ¹­wÁp^Ém“¥ŒAÕæ‘“Ì'oÝIÅtÈ;Rn~ô¹ù˜élÚxâÜÑÁ–¹©ïµ{M6=÷³¬+Ð8×'âêpÝÚ®™je´•Ôyð¾^C×hÇAÛ446r"šêâÉõ·IwcÈÌBíÎF2­uâ-RÑmdw‹Í’Üî‘Pe•‰ŸÂâ NÏX³…tûi"[pCDäQÛñ÷÷¬–Yì¡•¤ŒCŠƒï&>µ,Hè,l“!ÐFÛC7FàÏ…Mºµ±É·ŸZ7T[….êv ’Kº£Ü(ÝE…r]Ôn¨·Qº‹É7Rn¨÷Qº‹ÉwRf™ºÔXWš3LÝFêv ÍzS7Ræ‹Ç`z 0=7u©X.;Ò—›ºÔXw$©wQî¥ÝJÁqå³Ö“4ÍÔn¢ÁqàÓ·f¢ÝKº‹ÉCR嘆Qº‹É7šÞ£ÝFê,;’ù­ë@rZ‹u¨°\”ÈM7qõ¦n¤Í ÏzkÞ”™¤ÝJÁq<°}))ÜNÍ.h°\g’ž‚ƒ ÔìÒf‹âyiŒb—ɇ©£4f‹Ä0Á¹P´vZ˜šniX.AäÆ?‚šcÜ©óHM0 ´ð”dá©· BE".Ý…&æïRd{Rn_Q@0î*C"c¥F£"•‡qKFzѺ 0)‡oµ!ØN1E‚æ'Š|7eâm<Á3¼R(&7SÀ=²;Šç´ÝUÐté´ë¨VöÕØ¸‰Š²'Ðdƒôã޺닕'jtýj¸•Œ…>N¡Ìx¼ûÈS¶ XŒïÏLTÚ€‚¢kvˆ²b&1Ž0wg¹$žž•òÜiàÙÍnå _^ã×" µîòv‹€"F ÷5Æ+Op‘¶TIàqíœW­Ùx§FÒ¼#´š]ª KÇϻԌñÍp6¥ŒW³Ï©[‰·nò~\ªxúŽÕMncº3ĪGžÀ¤[IÁŒc©çš| õV=_ÂZÜÉ ÓË;Ý+ÈŽW”Æ>ñ÷æ»?2¸øq¬tÕ¸¿AçIµ£FZ,güzWn$÷¬omH•®Xó)ÞeU3ÔÐ'OZ²KBCJdªâPzJd_ZŸÌ£ÌªûÇ@hÞ(1”Ñæš®d›Å0- /˜j°ozvÿz@OæZ7ûÔ³Þ“½XÞ}hÞ}j ôž`Î2)ˆ³æZMÞõãFê@NúÒï>µî(ÝŠ.2ÀúÒù†«y‚ËŠµæC-TósüTÒÔlÏŠõSqõ¥ßŽÔoÎ÷¤óÇ­Tó) Ðß´ZC?½RÞ(Þ=hïÚ=é|ÿz£æ{ÒùœQ`/yÇ֓ͪ>n;ÒÉï@LÀu4Ï´/÷ªƒIêi†Oz,˜¹_ïÐ.Æqº²üÁëG˜3Ö‹­ç“Þ›æŸZÍ‘ÐÒù¬{šC4„¤w§yƲüÓ¼i<÷PphLÌi<ñY_i“¡jgÚdÅùÑ`5ŒàwÅGöN2k,ܱëQ´Ì{Ñaš¯r‘õ9>‚¢ûlgû³7Òo§`4Åäg¹Z>Óþ!YEé7Ñaæuî7íiýúÊßïFúv š zC|¼Š†K–“©âªo£u)zBüT[©¥¨Å¢½ "M~|ÕD=¸È#ŸÊªËùe¼¶³‘­wãæ z‘Å: ZK{Fˆ*ï=e Œ`/=ªœ3Ï+Â<çýk–愧̹!ÀùW€£¢Ž¸ºÏÙÛ E¯næHRØä  ǯJÀcHšÙeÃJã€3ÛõÆi¶ñ5íØ¶„½ò$óéFÌg¹Xߛ؞P¥#ÞBs¼õp9°434:T0ÜÂ"’1·ŽŸ…i‰}ë¥;£‹Ã-Î:pÖ©£ç¥Iç`}ãEÀ¶¿/qOµDL\úþ4ñ!wM,£½UóHíMó¸äâ·ŠiqëUÀï-D×B˜üÐ;R™r:Vp¸'¦?:<ö  þi£ÍoZ¢·=?Îh¹=é7Uq2Ál}iÂEÏÞâ‹cÎaÜÑæ±êMWóï@}Ý(s%i=ñPŠ ÇZ°$'ø¨-ƒÍWÝÍ;pÇ!~“Ì#½BZ›ææ€, H<1©Ç¿5Ozÿ|~yž†Ë¦uíQ™—Þ©4ê½ò}*5¹Éà \É)¡ç-7Ï9àUmÙí@cœ w ¾ÑíJ'¯Y™G黯rsEÂÅÃ(##ši•OµAœŽÀPß/EÉíJábbê{Ó ¡ª²9Cý( rH§q #½&ª™Ž84yÇšwè8x¥-èj—ÇÞ4¢oCHe½Ô…¸ëU¼Òi ¹í@É4Æ85 —ŒÒ†3@‰rM!õXÜ`Ò›¡F£'Á¦1Á¨¼ðzœQæî8Ýšb[Þ“uDZ“u0& Kº ÝK“@îÍ!p>µhÁÍ?}&êB¦“o½+Œðmå |¢;(cÆœïQ#†Á%{ŽÕnL‘°w'žNMr$jC¹ƒm>Ý+¡Ðµº’»‚FàpùïX0*ý |»£ÜMzáË9-"¸¸‚?0õ r¤~uQ‹oA7c¬¶¹Žâ’)¡09ÍXZ£gµ¼^M¨EPrBúûÕšè^fl¶®Øõ§ UCõ09Æ)0%Žÿ­(~¿1ž° ‚yõ¦M¡'žÕ*IŽÂy®¿ÄqKæõ¨@ïÎ)séT!ÄÓ qÒ”¶ÑœÎ¡k:i¦"L‘JWûW<(üéåØap)ˆ¶2{Óò{Ö`–@~ù©¾ÔÞ”0.Kó©ã5T^àc ßUÏãF \,ÝéÊÿTˆ;5K磀|ÀG¡©d1õ4»ªº’Ã#&Ÿƒ´’Ø¥Ì%ßFþzÕ]á{þ´»ð:ÑÌ,–Ü1Å4£0áxªÆE‚ÄÔO3ã‡#ô¥Ì>RybÉfǵBÓ»Œ'Jƒœ““ïO =i9¢;•“Ÿz]ù¦g'$æ“57‰ã›gšfVþ5˜ÎAÈ9+œä6©2Y®qÜ} 4É$šÏK¤‡POµH/qÊÆ ŽôÉ-yã<äSZlô&©<ìä’Gà)¾{zÕØW/ù™³øSd¹,¡p8öª>aÇSIæZ,-y™¤Ü=j¶ÿzMÕVÖð)D‚©î4o"•€¼%㨦™óU#)Æ)<ÃE‡r×›ïM2 ­ºÆ€”ÉšBõê P»©r{¯ºôcÌaÆM/œEUÞ}hßE€º· Ü›| >VÃzšÌÝïFïzVÍÁá³ô¦î#œóT„„t4¾{zÑaüÖ XõLÌOZ çh°ÏÀA ägŠ¿ ¬Q^æUH;ˆƲ×Ì“ 1Çá[6ÊÊåÊv+· îôëÒ¹Q©kO–+Ý⹈•ŸduêÞõµu}u¨Â–épDO´¹T/¯#··ZÊðýÖ›¼Ñꃷàqõ5ÖØÇe 7qÛ€ŽÔaœc¸ïÍT[d²ö…f¶–Jw1fç.›[­veÄæ¦è˸u¤Ë¬@ö¨¥™Kõü3Îlª@XÂúdûš…üÃÈPµ(vÇb~”Iåš„ì!ØÄô5*ÄÞ SÀ Ó¯½ÎzSsbå‰Aç,h0sœœS³èiW$à}ÍO3ˆÌ>†—Éã†Áô5)F^vàzÒ Èp9Íâ±_k‚¿•Xô¬ä¨ÛÓ…¸ì~´ý ùF'˜¤m|~5`ÌÌ¿9úŠ„‚W#wòrF3RåpJÄæAØýjºU?zª¼Ä’ }ªc$íFGè]7£¥5¯Aè ª, Ósާí]š u¼ãi©Æ8ç²EÂÆ¼šŠK·“¾*_Ñ´O â¡yU[““YQLË*°<ÕÖaÜÒ½ž£,,ëÎOôÓ:;uªFESƒÖž‘,­…‘AúÕhMË›ÔwÈô©’uRr£ª9€ ŽçÒ—ìò Îåâ•×q–C¨ÙXtÁªë)RwsÚ¥YrG#Óh,˜¹4¿7çAqϧJoÊ“ô«ç…9© T¨C ¥åHȃ¡Ühç î4n5 1¶A4ò‘(ù²?|â+äæ—ô§†‰QÀèj9ÐÂŽa8ïIº˜ÇÁÍéUqÝI¾‘‘€Î 0‘z.1Ũ-Q’=OãI»Þ˜M)ÓŠ„HGÒïbõ¤÷Rð{ÔJp~u4íèÜϾO\ 6ûÑŽ:ÔjÊÍœŸ¡©Àà M€c§<Ò4ÜdJđҎ`†ŒúÔÜH,3@ÏzW|ŽßΗxèr*4sšVl9ö¥qç?xâŸ/‘UËÙMBLÌ0~_z`jFŽç”8õ'¨Þä)(©­TF€Lã¥DîÉ9g<žj}Gf\{“³i;³Qoã9ü1U~Ñ·Ð ‚[¦|óEûÅ–¸XÉÛËÒ˜fÞÝóTLŒæˆçÜ{ŸjV –ö÷4ÇdqþÐ¥1Í*ebüÏZ¬ÑÊçˆÈÇZi‰°-Ž?­&à*U²£fN=M;û:^;{|È’ø=EN—$w¦' îµ/ØóŒš9õ,¬ð¹!—ƒÞ¯ã}»qÇ¥e 9”„zô¡epáTàqÏQIù1EݱÏB3SÛ¾ì‰6äuÇøVDsÈ.§$• o5‹$Œ8늋·åEŒ‚qȦK§BÃtdÆÞÜʰË\¼8 œûз—IÃ;Â…ѪñáÜ2ŽIÆ øä·+ÂŽ:‚9¬¯µM1È8~^.NpG>ÕV}@ÕD·-Hühš(Èó“ô–gÈsÏ^*H®ž'#åeÆx4YŒ¶Qãlª©'ñ¡Ý™Æõv§G¨G*áÓžàÓ£½·Ýå‚Ày"ŽgÕ Ä;íÃÐÇ÷M:CbÇe>ÔùâŠTó’@¯cTDk)æMžøÍRi‰¢ÇÙPä, ž¼ŒÔ3Bñò6ê§j-=$Üd‘Õi­§m9Y”ŽÙâšš]BÅ??j€z÷j# $åI÷­Ë£1 …j å8aö*ÔX„ì#;K €÷4í&w»€?ئàtYý>`°¢™çÒ—ÈQ×òÍ1¼Å`‡o¯¥#<‡å®=O¹˜XF¤r¬¹¥‘°ç·5T;‰Iè95:É2/ú¼û†¡¶„,§9+ìE€2Jˆ\>0ÈÜœúÔ‚ñUpcä¤Qv; ;‡z©}~–VòM4Q{Õ†½YEŒëYz©Ôle·c‚ËÇ×µvHå!×g¾b¸KraâP@öâ«I{i$?fQ›d²¶p=*¬ºuÔ™Ø#“‘‚~•’ò‡—¦jÔ –Su&æföäu®ÇC³µŠq"ïv;‘‡Ó¯Ö¹ÕÓ'S—I22Pp1éšèS[·±Ĥ¯8ì,Sµ· ö7…Û©ù”{U”Ôßå²Gâ¨A¨[\ƳG!ŽF1R4Ç;WcƒÐçšz>„ܼú‹¼8['¦9¤d¨G·³DƒÀúšTžE?)'Ú»x±ß†Ÿó÷Gü«<ÝÊF üÅ;íLÍüéêâS±çÐÒ?ˆtéQÇt¬›]A÷"œåã Qp&Ir>^”<¤)''ØUv—(â›»w×ÜÒO9AË)ØóOŽäœN?Úªå1Ép}©¬ã¥+Ü¢.A8 ¸ž 5®6Ÿ˜qŽ0j€aü=HæšÌN}© ´×¼`qïÞ£7 zwªE¦fÂFXûRí˜1 Áô§ îY9#®*6bI;°9PìÜÇé“UÞB͵Hçµ+Œ „ ŒõŽqÉâ™(tlŽxê68å‘°Êrzfú“qÀ7TðÉ‚¸ ñ‘Þ š"¬AM£×µ,2ù÷Î9¡»­t·»”nP§·+Λ +ž3Íe ÎÔÛƒëëN{‡T, ‡Ò°åÔf”w0+S·­ì›#Ec€†!ºãÐÔÒFÛ~P:Qd~wœáB5J;[§j¦#“~¹'¥²ð$î3WèÙ³̼óÈö¨|Æa0:‘Py›²œÓÔ1Á-íÖ€—.ƒÇSÞ«–$Œ{ƒš¬rsô4ðU—%3õ§{•/¢š&®Ã'z–7.¹+¸œàÕD‰7h>ê矄Uvj–×@#’/‘}ª ._ilúZI§ä©Á횦$Ý Ö‘¿Q2ôd³ùÛê)Ãò†I>‡Ò©î!ÍŠ³ÞvÊ(ïcšmwí6óc|xl}áÔÔ‚8ˆ`ŽIµRQ9‰”Œv¦Cr‘ɳa`yÃb§ÐfÔtvË‘ÀþŠ|‰ ò.ÁÓ#ŠÊûS¼#;\Œ€õö”Èÿ;Æ ž•<²a¡rKIF×·f•Ó¨§$óÀÃí¾Gj§³2áSfsÀ×S´ÅÕ6ä`®EV»1Ì–Ì…Õ$cž™Æ*4%páÆOvè*’4ì^÷©–)ZFÝÙäæ’¤mìÔík‰£$}ÓŠ¬Rݬw_(êHçü*„¯$Œ “ÆØ÷ªÍ8A Žä_p¹¬Ëo·âWõŠ©sºcË•œ÷Í: ›BŸx£{µ…K.OB¬8úÓNÁbºÄN “ùSÊ>9sJD‘®Ñÿhjß‹W*q'®Fió>a«ç#ïvÈ©vÉ ÙÔÿ»R.¯R¬§ž›E+݉FÖY@úb¥Ê]‡d1l!lgyÅkúΡ§Þ4PÝC·BWë]l·‹±VÚ¸äs^g­êO¨Ý¹!B)!H^—QÄÔ–+™ls0‚ÛóÖ²Jì·ó>c#N8¯]_@ùŠE!`R: Ô²$’ˆv—*€có A,XhþUõÇÒ¬$á%»2e¤ð=éúd^\ï°mWårÎkhAÂ9ÀÅ4³(êW1°Vû¥NyÏÿZµlf¾7K,«û’¼xÏÒ˜š}©þ úgœ}*Úf £å v˾yùŽÍ ”ƒÐàõÈ8&œ œ’3ŸÊ˜‹”€wg>´¹i˜ˆ ê­œsK4’A^Ô®«0äž »™ùôŸœôÏÖ¥GQêO½6ƒ#3Î)†b9¨^V9P)Vlg;¯¶U•¤`-íÚŸ‚>ö ¤XÕ2qœSC©o-Iÿ<Ô¹ 21Ê ïRF›‡ï0:Ö¤†1–mõ5·g™²:Ôs_a“§—f±¨å8ïU..^iUÎz§$0Ó2†÷< 6Ü IšL|„'ûF›å%»#qÏÔ²LÊ>B¿jî }æÏÒ’»ßÚ!$õëéQ™ã.P·=zÕÒvFsÖ›†‚¨\Ž£5\¨EÙ%‚UÈx¦,QÁµš^µWŒ«ÜVü‘€r1I´º€ós@2à±=3Ö›rK9vR¯Ö•QpýjÈ ¹Ž3ŒšŽd¶wÉw8èn 'h“ê)o]ŽÄŸÆž¶“*›ÏsJêÚe/„Ѷì¯l‘U¦…%Ã#ݽj)rª ŸwŠHÙ³†_£ kMP" -Ú9'=‡¢áÑ€l(ïWU¡UÜÒRj­|Õób*êy«S]@¯,ÜnC·ÔŠb^Ê£î÷¦ºl})·=Zµa¾Ø[†b8çµ^8=‰æ©™ùI÷§ ç%F:cÖ‹ ,ùÛî ŸåP€r<úSþM€ª†#¶iÌëÓœ“ü4\,F†ëϵ[dd$¦NxæšÞqõ«Aò6XòAïCXh—k®ÓÓR?(9ÓŠšã%FaV¸<Õ X(?»‘sÓœŠIÜ Öä®@HƒéL”4Î΀‘žxª†BÈr= >+‚¿, Ô`ÕíªnX6ÝÌ9Ú{V”Z¼ y±"°öⱤN6íÉ'¨ê)ÊJ¤Î§@O8¥$žãØè@BŸ»HÀíH‚‚rLÖ*ßý™6£ïϽ2ëQ3&ÕfSê eÉ+…ËþLrå˜ÉœÕY¢•dÚHõÅ6Ù­ÒÕŒo‰©=ªS,®·ÍÉâ­]1˜%î#úTl’¯1«zš˜Êzôæ…¸ð|¤ö4ù˜yÒDÅ‹”~Ù¾ö,Ç“É8毇aWp骼1œ„nHÈÔÀdr ažûj+˳»JîåWžx¥‘Ù[Vf©9û)V]Êä#zg½Uú"­jnÌ„îS†SXþAK)''ž„úÔ/‘áÑÃ:Ž ªÛ“rþ8¬Ü›-#cý [e‚°‚yÉ÷õ¥·µóP`F_¸úV*±-…9ô©Òi#Üœ18¢ác®¶’/,"c ÆELOÉÜVŒ¦ m£''=êнp1‘T‰hÒC‘ò“NõúÕ¯X8$Á­ˆ®íçUa)zNV Ž5‘³ŽG^qSÇ,“o <ŠöØ&„uÀã5,wÎø1øj\ØXcååWRqS½»¸v*zcž*)Í«Õ[¶66–5>\¿/¡¥Í}‚Ã^?.M»¿:M’¬H#µ#Í39f#èM9'Q´† ޏ5\Ì,^KHcA!Ë¼ÓªÓ Œtªï3–2%›øzÔCraöÇ“»Ê¦ýÆHeß3 áIëšž'†'\)ÁÎO­g·œ¬J…BÞ¾žÔDZg)&æU÷Æh{­.k»íáV«Èß'!AÀúTH@“ƒ€0*_¾Ã zÎöˆ× ¤ÿ9¨~ÊÓI'Ôµqmò[?Z•åüÊIö¥í±ŠEà‚G¡¦}öü‹€+Z!oõÀ~õ klùoõ SíZè·bFáøP¶®•sÇJè~ÍÁÌ&fê(:h_šCϨäQíÂÆTPÊ‹…QŸRzÔéjIÝ#~F¯n¶Ù±¸ã­T’ RLÅ"ȸû¹Á¨çl,Gö4E'Ï=*Zx_xĈN6•ÁiK€7Œ7p SO™õ‹»È]C#÷p*;«‘j›¼ÂÞÄTÛÕ‡`ã¶zŠ1ßšš9Z3÷ª¢ËxàÓ•³ßB5Öá$P =AàSþÓåG_¥d«–èE[‚å7(|z‡4üæTÜÌIî´Ô»`?ºEDrÌ N7Þ>•?Ê n;KqÈÀ©º™a†U,9ÏJis22žªÌ’yP-ÁÈãéY¦òF,ŒFÖ9À¡j"CvÎà’F=;V„·b;L¹ß+F{V;8ÁS’;sÍYI£D@cŒ¨4ÚH’PßÚ»«GZjJ$2íaùTÐ"†(nÀXÌe²õ«ºÈn¾´ÐÀ )†D/ÃúV7¸‹Fê/ŠvõnVCЬ‰Œk“ëR|»pPb¡¤­1Q†Ã Y>a‘ô5T÷Ǧi‚CG Ë®pø=‰© ¸’4 ævètòÍ Dr¼÷E|"EÈ,8Î)ò6€Õ—2¶|À>•^Hʰœ†^„ …obr¨'jdÚŒPŒŸ¡¡)tvø/pIóŠGi“c ºçœrk2KèçÉ(ä½M7è†=ëNIurëSxœ zæ©=áž` nȵEq<— ÷ƒb ‹†V_^ⵌ@_šéðSôäõ¤Yl[$õô5_pöç¦ML¶Û·g©¨Ñu¿|¶°'x5 –³DA!ŽFOÏÒ˜’}•Ô,›ûݪúÞ-Ä[N“Õ{Ô]­‡¹I<¼(ÆðAéB¯šû[ G5bkx™øc¸Ž„qN[2«ýãîió!cÊÀ/˜ËÔgò«6ð­äÏVÎ'“YʳÛeƒ.Ob8ÅhÇq¦ð»×“ƒÐÖRºØ“K*dýìÔv7 #±L³H«‚çÉV#ô­w$‘²mnČЧ+Ü ¹Ǘ廞Ý#úÓmv6ó‚:|ØÅiM4eUç qÇ­fÈ›¾ÊšÖ2º ª£~åÁ#–¨Zy:îÇ=ù©.Wlä IûýR.3ÉúÖ‘ÔD„¿˜A,Äú ´¶höì·;Æã4èî|¨ãã¾G5åñHñÅ!êJ—&Çc 9¿²¦¸YQ›zŒÏÆ~µœ¡æl«ÅþRxš¹mzB&!ضÐXs·­SšP×RªŸ•¹ûR(ÐÐä rå†ÐWå5бw3°«‹‡Í Ò6íy÷ÓC.ûxœ ©ŠÅ·m„ˆä8>ýj™YÉ,MI#…çùU)õ»w g¡;‹áUL‚UäôF^In8 æ7ÈÀt¬ &i'#œÅmi ²Mƒ ’x.>ð?áEÆm| õ ­FÉó``f¦k ´Ìê’*³<±Þ!…5+ì Ûž™8§$$YÚ…ø‚2"ùe8<úTºt²\Z‡“Œž3Þ«˜VúÍ/YÇï1€ÕÏ]Ùy(l„'ŸjêŸ+÷—úÕy– £`ØÇz4gœúUˆ¢W ¹ÀÇZ®0§šx“kmSP3R8a,<¬ŒXw§¦ÓרïY)#FýXcS¼äHHlýiÜF Ã`gžÔ+1ãÍeÅpCe˜Š’;ÆW9ó‘G0ìjvÉÎ}¨FÁéTþѹÕ× 8`=êa2“ÀS¸¬_†èÄ[kEX7ƒbì'8ÁSYHãž8Š™K9(ÏqE“êÜn“æ$«@íM")ØIªÄ´gb¥ŽPÃaÆ;qÖ–À'™ÆzÔ±H™Ë©8èEB‘19+òÕÛ`‹{uéCvB'ŽD=Iû¤Tªè䩪ª»d$Ùèiû•Cß©5›C,îš@@8àÓãu/€7}ja‰—sIç¥ZOåY¶„d<£}*1<ɀɿ×(Ç4Ó Ž¿JI ¸\zP×1…ùÐ~â«Èê ƒQùà‚zP¢4H×°¡9SŸÊ«ÜÝ[–;r\Ž*½ÛîPJƒïÐÖ{1àôô­£¸®Y3>Ý¡ˆ΢Þ[¡üé˜b:çÓ…{1ÅjÑ6~V©8è¹Ï¥IlÃn3S¤ÈG9ÇnÕ.@C ð9ôÍXŠØ—™y¨–açEÛ«P×JdŽ[8ÅK»SUbår;¯5Nx-êJv÷íÀ¨¤„»g gœ ˜ÀÑÊãxìM^òÕTaWÁ8¬6]† ÖŒyŠž} )® X3yJJ² ÷íREx²üÊ~ïUµEÂàã+Ï9ïUC½¼¸SÉ£•4ì—oªdÏéT滆eu Å'\â›å¾k9 œœ*bÊëÊ¡÷ëR¬€«Ò‹µHO¡íZŠ']²GÎqÁ¬øíAù߃ÁZzË,;ÖC•=§+=†‹ÑÜÁw¤§g×µT†$i´à*ž=jäŠ(%Ü仌¨8>æ²Ã¼Ò*ÿ ÁªŒ{65kƒö·%‹D¼(ÏP=*…½ìP Œ(áz©ª\K-’ÆŠ¯°Ø0¹ødg”rrN3ÞžÊÀz L€@b{ç4—wðÙù+6Iœà`pV²¶Ùé& 2¹íUõ'†æÊfpA‚ÂëžN?JÉ­tTÕô±jL )FR=sëøV4ÐáPñ´ò+kRÕ¤¹³’JŒsج Ï&Õ¸À&ª7ê4H×- ˜Fݸ¡Óµº³«Àʱå]ò†P{Þµ§n¨ÊªÈI8'>ÕvVÄ+²Maë"S8p?u£®H N ÌÔÙX!gm£¨ZV6G_*7ݹÔàŽœv­•‚7˜/<œV[¡a…êN>¢¢f’1ŒsŒS©ÐõæËºBÏ#ägž;WJÌç`sšä¼,¦–9SóÒ·u›Púd‹ Çpù‚îÆì•Œí{ ©Êk±ù¬ÀàîÁ\v©£ýžÖ8äÈfçŒç¥dÜ\IstÒI—~˜5n8ü‰ÜpáAþUªÑ šûU]íþékV_/•€H¨ÏZÛÔæþåmþʱ lïÇåÍbÊÒåáuWd<úf„î2ºóÁ§0O,z€1׿"“ž•B ǵ ¹¤< =)êr½áHÀ§·UfRÍŒð3ϽF«€ÜóSE`OÓŠ%˜ïòÐ?ˆ qOÎÁXí'øj8ã|ÀdñëV@‘D¹`F÷  ƒàÏ95zÊÎI¸Æj³G¿È8Tå•P(±¦ÓI\E22Cd`f¢2¡^œûR+gŠvu$w+€:`õ©£—ËŒ—¦EGmå¹Ç¥/,@UÈîEC³ÐÝÜ«¯AØÒn2ÊëR.ݘÚ~”ŠêŒ@'Ö‹?›åFnœÔâ\»‚}ë.öAöf'#sŸz©©j;âXâÈ# Ô8PÖ¾Ì|´äŸâÖy׿,¥€ ޵ l¯g¸¾]8=êŒG÷¹Ú0x"š‚ަ+ñqQlgo¥PåæeeÁ㨙½¹XØnqéT£¢mùÉ#ši$# YAÜ÷­*£Œ8ëíXÖ›ä!—Œ~UynBü£§µVû+Æò„ú`Ô$åþlg×Ö’GcÀÆ=E3 ( †ôÅR.öŒü ‘G–ãÚ¢bØÈ9Öš[×­;²]›î‘šp“xÃ)ÈêÕo$j]ê£çÒ“ØBê}?ÎŒžXdôÍUÈhóÉŒä•Ï6C4óIV³Þ¡’Ýå{Ôqù¡ÀyG'¨®ª^LŸj[0'i3Ì|üTÓ:”?($~•U¥ÜÇi-ÚŒ“¯¦i?˜Ì¸,BTˆÒ0ýÐÎxÆqU0¬BœãëS˜d‹Ž ^pi6†i[Ç<ªIùqÆ3HmoA8d#9Ö|z”Á€<y÷¨âÖd[“¸üÄT}zÔ>kè7›²Mò•ääöõ¬ØË]9c’ qºŸªêâá‘Bñõ=›ŸåP_ß-Ë#¤b7 -W3ê; q©M$=lBAû²•‚H¬Iãž*RÜq‚{š€%@ÎsÞš¬Ð.c¼×És†'øF8ýM¦[f“Í(ŠÄÛ÷…aØÎöSòÁÏZÖ¹¿{«Qˆ®­€ö9Æ,Öm;Ü {‰vÄŠrÌ9=VùÒ@CáHÊâ¬L¨ÌUd€#ÚªÇ4ª½úý+T2ĬÀH¥±Ô÷­(.D…±Œ÷*+"7À'8ôõ«Ú[çÌ,B:Ð&]U’V* šÉ¼œ,®™Ü1ƒéšÜvG¶o²±ýå<šÁ»Ú_s¦ôëš\×WVÜÚt€,ŒÃ ;zHÚ#ù€²Ž^”y»“ÈPH/¥0:ªX«3[ž¹ÅE«jpíló Ÿc,Ÿg‰RWõªO+y÷¨åÖà1V²å3ïSÚ2=Ôk!Ï#5SpÉt«z|A®rÀp3ÍXªOfO”vdóÖ¹½nÞÒ2ÂI¶ÓÞªÏt•ÁW €Ã­R¹žIܧŽ*Tlî2o/£e±ŠŒœ‘KÁÒ¯#>*ÄK€sÚ€Ìe óÏйÿxóW¢"Úœó@|¶óXH^Ø©ß÷h¦5#lŒõ§Ú¯úNðŽM¸XÒYhby¬dFs nÙ¦Ç*´ÉǨÄÀg ÍNâ, +’ã { cƬCçÕf”¾Üõª·RIoõ'²(³uK•uÆpAæ³%‘™ÔçQQÜLfrý ëŠj±'ny”‹·Ƥc·`m'¿jPøÆ sQ(i$ð;PIÈóL ãˆÌß) óu'¥^†HšÞEQž5ebó>PyëŒS% #mUØÝ R¸ˆc£Qµˆ ç¯ööG‘˜ôÈ wªÞK‚8ê2)•VÁ#=©€ŠÄÐÔðU‘†7pV› ö©`ÆÄ­ëÚ˜’à”TÀàç5YÎâOsO•òØÀÀô¨I'“@9Ç^icvNAÅ0óéOXÙù< `)ë¸sN8g~HéOHÕEG;tQÐPÿÙmediascanner2-0.111+16.04.20160317/test/media/baddate.mp30000644000015600001650000002070312672421600022640 0ustar pbuserpbgroup00000000000000ÿû0ÀInfo(!C $$$**00066===CCIIIPPVVV\\bbbiiooouu|||‚‚ˆˆˆŽŽ•••››¡¡¡¨¨®®®´´ºººÁÁÇÇÇÍÍÔÔÔÚÚàààææíííóóùùùÿÿÿûPÄÀ¤ 4€LAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ]ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÿûRÄ¡ƒÀ¤ 4€UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTAGTrackArtistAlbum-1ÿmediascanner2-0.111+16.04.20160317/test/media/krillin.jpg0000644000015600001650000013465612672421600023016 0ustar pbuserpbgroup00000000000000ÿØÿàJFIFHHÿá‹àExifII*  â "*(1 22R !"#i‡fâ bq Aquaris E4.5 HHbq software2016:01:22 19:28:42š‚t‚|"ˆ'ˆW0220„˜‘’ ¬’’ÿ ’ ’´ 0100     Ä¤¤¤¼¤ †@B 2016:01:22 18:28:422016:01:22 18:28:42 ^drdR980100HP(X€ˆHHÿØÿàJFIFÿÛC  $ $ , $((((( ,0,(0$(((ÿÛC (((((((((((((((((((((((((((((((((((((((((((((((((((ÿÀ !ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?Äv,i›Nkç=‡¨»3HÀ&{œv­!ihLŸ*¹ìñüÇ<ôâ«]°™õÍz”©ò#Ì«79ÑãXŠž¹ïItÞlBQÕ~ð­‘“‘¥áûå–@y+oÄ0»¿ÓŌҪ8•LW8én?Ò7/\Ö‚ßLñ©žRÀ.ÜžÔÞ„7#Ë]F7n?9÷©­Ý£Ã¯F95BLцU~8÷$ÑFX2¶TŽÞ´  ¢¹çž µ$E”Ì6«'æhBœö»XO÷ v©àˆËñ‡\0'¥P=HÖ7‘ü¶éü/—.VÃ/zRØÙJ8Kû±Æ~njŤ‡0¶ÃÁÒ°^ìÓ´¸Ž8ö€¥ºdŽ1Dâ7£êÇç8äRR `$‰È{¦iñ˜Šþñ2ØêOJcp‘±à Á>´È"—ÌmÜ7qBÜ¿o ¨i•þ/Z¥~#äñÀ¥pebÁ³Š ŠdIrŸ¹rw­’ÀàˆœNyÍE¶%ŽFSvœÒa±wfhÙé_=³ßd77ÇZ¬­ —ÍbpkÒÃRåWgŸˆ«wd[)¾-¡ww\Õ@ªÎcqƒê{ì9w)Ï$9àzÒ@¿Ïœ7>•]LÞ¤°@ÖÓ$gŽi×o4çb),{ « dAm`Ñ?Ú'_ø «.±·EàŽqëL’hÀ˜³È?E òÛË—$™¡ /E:ìS‘ÔTÖòmAp»²8>”÷PŽ$¢m»óÔŠŠXü¶b_#¸÷ ]Î>aѺÓâ„Ç8ܤ†è=Á½ JLß±÷M0GÄaÜŸ0`÷¤ UÊ<`ŸzàxÉdPê3ÖŽ¡Ð’Úò@<¼Ýâ¤R&rY°G\š@÷^Ly³ñÍ$ªêù Î9þªH<¯•ÝAÏQïëCDd(Æ8R 4NÄ=³N6Ûœ‰H]Ür(h:ͦ»Hw'Ìœ«€}ª ]¦pÀýÐ( ŠÓ†<wâÇOje¬’Hæ cêy$ô¤Äõ5¤Úƒ$Ô"t ÜŽq^6“›»=šõ9bGª ¸U¹ˆu0«AºDò‰úf½H«#Ì”®ËhÁ!1‘ó)àPÍä.?ÚdÜk¬w ƒ‚@äÕâ(Ù¥4K-iï…!Žr «NÑÿ¯U©ã«¨¯ Éx,‹€ÝOZˆ[]®2¤ðǵ;jMõ`­Ÿö‡5,ÖHÀˆä8­0Ü-¶ùd±ù—³SÕÚ@W$°èsBØ—hÚ'~||ÞõÌ ÊÝØóE½b†YÊ9ò*ß‘$x¶Î[)h' Ñ¼)†ûÄö©%fE4Ãó6ÚÇMÆ.cêÃÇJK•YØ4Kòç¿­,v‘ÏZÁ.qqžô‡}$>f\1é‘Òžªså3ǹXc‡ú)ÜGÌzóS¤q°ó ñŒÓbkuûÃïgªõ"£_ße;½OjLd“F‘’†éÁàÔ°|þj¸.1œ½I ¹Bô¼‰ò'©ÅVŽ&yG̓žO­²ÅíÓHá#“ž´‘2¤‹( ƒÔÕÉF‘±×VneËl#˜@dîzUMFÕ­fqÏ#ŠÝ±š¯Içÿ×VG•4g/Ž*‰)n+ 8à}î(žÞ9ìžGQT– Æ(H;OL±"©ÇñQÔ›€R²y~gÊyÇ­Lm’T1,€9â¨V"û+…ÚO R+Ë †e$Z:†Áv’C‰ãQŽŒµ\´ñ‘"6á×4n#B Lçy$@}©$€;ù‘¦=»SØoT#€­ó¾ûµbk€ ²Ÿ•³È DÆÌÞÆó¡Q$cæPy>õ²3å‰ä'ŠvpªŠ<·8ÏÌJrDbýë6å8'“ܱŒÈRxr ’§Þ©ß»Û|êHÁùMHÞÃl rfFÎû¦§ŽØ5È”1-œãÖ˜ÞÅØôÐb"V9aÈ4ßÝZIöv ¦~V£pBOrM‘ǓٽûTsDÅË¡!‰ËŒ~´¬íU—nòˆúUy¥W‰˜œUTr~´0¹B&óÈ€nþà¦ý˜Hw®xûãÒ‹ ã.ÆôqÇ¥%œŒä$€z}kš¶hiçvRN£¡4·h·1««êpEP'¡M²æn3ùS%œ¿È­À=é’Fûæ1žj{EóSïüËÆÓÔJÑ!\lÖåÈTl’x§2Œàá¡î!ÒáØ1‚ SÙƒ4žd’¤áqÔÐ!÷ ¸‰c<€*?+11‘¸ô=iØ.,kDZÆ{`óš‡oÙÉÆQ¾é#¡ [„$Å&ÆB=HïZÊ|–‰äqÍ1ÜŠkF`¨yÏ=&Ÿû¯ÝÉ8<ÐБ²m^Ñ–öÌŸ—ïïU+ë”}®9k˜ÐѸlF–ѤMæ@zSb2E@ÿsªVÔ›™ãp |ÃIíP»§ÏyÁ=­+ ,T’OåWí!ùpÌz{QÐ:—Ú¦…åóFvãöÇZÏ’ Ã{%{zS@Ij²Kº N7s¸ö)_²2”9`î=}h`W¹À̈˜ôþïÖ©˜šWów¸å±ÒuŸ›g´ 0?6OZK‹a ¢á[‡#Þžì,f&J ŒE"™# žNkËpÈW _é“SÆR)—÷Ÿ{§@VÔ`ò˜–<ÁªYÇ·ÀŸQ`'C÷©í­L©l¸ckA_ˆs˜PóÜžþ•”÷ndf=sÓÚžäᙣP§æƒžÕ$i(}Ù8þ,Sõ/ÛÅå~öh‰Fõ⥷µ[ˆŒ^W,p¤ž”ÂÄ/fè †ÆÓ×=}¨žÍ'Lã¯ñc¡ D¶ÐÅ#yw®Fžh–$ŽQœ©è‹†ÌTŒÝbÜ6“†'×Ö–{Øe‰±·±4¥¥ÜÇon‘>I##wAíUæx£‘–91§¦3Š=†Gm$dÜ¡'­C<.#òã±þÀˆ‡ r{÷5fÑu0˜0ÀsŸ­!=ÃPµ´²¸$è*%™ž@Yˆ°¹íI”‹öa]w*î#±ïTnxn B·ÞúQpnĨÑÍxÇÜ'×Ò’$i°%˜e#ЄYò-¡ù¿Öáöõ"¨ÍÕf‰2§îäÒ[í¤ÉÚGÊsÒ¥¹Ÿ÷‚BÆ8¢ÃêdËÆûŽrzš¯,-Ø;ò9¬‘Ob[E‰w ›?/¥Z³xäV”ƒŽ3L]ùO©#,f!ÈÝéY“.ÂB®9çž•v¸‰ ˆÄ7–à÷ÏéW¥›v؃äÃ7§¥P\®³î}˜ïÀ>µVêØ±k†yä g&#<¯Û[·˜CGÁ^ í@º–d–d>MÐʨÈÁíN‚ââFù[k``“ÛÖŸQ‰=Û¢>}Í:v|¸w‰Ýþ˜ ž£ï,î ›Ï~pHe*«-˩ܽHç¥÷%·Ÿ{‰Öl>y£-È24g•ëïL °M¼}X#p U«[DÜ!¹9VÇÍïIÔ¯}uyŽNàv.÷Âd¡ëŽI¦;ŽdVŒ a”äõæ—{…ó-œ ;±-šWk‘’ßÄxÚj¦"ûQFc³±§½)3N¹CG ãó¦\—™D‚2K.7ú@%­…Ab±#­-Å‘U ü|ÃÚ˜¬IÆÖ¢X³÷¶ŽE,C=¼å’èOz‘•.‡ÊâxpÏ“ózv5HNàcŽ3Å7v©Yc1¶âOGeÈ$:òVH²½ãVúþ•4R—ÚíËc­RqDpmš,帓ê¶³hË/›³†h’ |º²£‘ïR$ÂTÁ0éŠv,LÜ/CÃb¬Qq1} Ž:Òê+êUXÍ»å×uçùÕÈ®#‘AŒà·£¨1e1G÷‡Þ¥YžP¥Ur£ž:޽)ŒG„™~Ð\”$n4°¹ŽfùJ©?/µ1ùÍp6c¸÷õªZ«A$;‘ÎUý)%±Ž(Èð>•¥ÜeNòpÀ‚iˆžÞá®ìÒ°Ú3o¥@nÛp‰@9ï@6F.>f”n“Ž) ¨hˆ£?­·¨²8•ÍÁqÐ f¢Ì†_6%ù_ƒ‘Ò‘ê¡”2ƒ»=i˜UÀi^>fy¡‚$ƒU¶†Y#¬ …^ýi.Ì’xãyï@.Jo»ŽÞ´«dÈ»¾LüüÓK2—ˆoÊçµXŽh >p‡ÌV©lQÐåH¶˜%`2ß!öéQ)š åã=½éƾÚÈWw~Ýélo¬¾s&ÿcI”liúªJ@þì)ïR]ÜIæy¶Ò¸Îÿ:·k{ÁÞŠ;€>ÜÕSŠ$ß¡n”è­Þ+©R2=jÄö‘jQ‘Ÿ0/ÊÏÆ}¨êR3n<=Ã!k:çÂLèSíÅ|½,K‹>†¥$DaÖì†Þ]GfãëU¤ÔnD¹ž'è8¯ZŽ&2<ÊØg¡¥eÔ[ä¹ÜWŽ:ÖŒ+ÄFæ<ä¸úWj’häqh‹X‚)n£¾ñ½V…–hLl¼¯RZz ¡Ë"ªí#8çš½›Ô7q¥@ܪD–­£Y`1ó»g\t>•ªÂ¡ÀV'ÐúP êAÀGäd7 õ« i™ s”ЛhšêßkK³g'>µ"BÑ&ç-€pp:{P)º‘OP98íSA;G—¾Æ9Eô4Ø–„W—A9¸u;wsþµNæå-æóá‚@í@=K0Í€\Fä9^y©-į́÷[Ù¸åIçŠ-¢ùxCI=jÌÊ¿À û¹lÓQùc¸’6Q‚0 íDrÉ¿g‘N}1Ö6RK ŒŒ2äÜRy)u ‚GAÁî) ˹ŒM‰Ôo¿<ûU™Ô„•sŒŒbô-i³¬,ƒAÉ´¢•˜-À¡Øàwõ¤>…«{v±•ZL°æ GãV..¡–&<6äøÍEkƒùhãRÀ¬jYu…ü«˜Ï–ÝlsGR¯¡®m‡u§-š°éšø{³ë>˜ÉŒ~UNëÃV³g1õö­!ZQ"PR3n¼Ñ QUN‰®iÁ–ÞbÊÃ75éÑÆµ¹ÃW žÂÚß=²4:–žäŒ§ ~ [I.D¸ç…'ëR¯£Î«BQ-Þ …|øÈ`ǰéVôÛØ¬`RpÁÎp?„×Jg;DW·³Z\2¤‡kò=êµôsûÅP2sÀÍQ6)"ùNwžÞzÙV{rÑœ‚z‘Hd›‹bR8ÏÍŽÕ¥q,rB&Žª@Ègýªb)ÜÛÍ<­*ÆJ“ò¨9Å$bTE–F*¹Ú¸<äSfÜ}¡^;…Ëc“ɬï./šÙ“‚N× Dvû¬e+(ÜðÖœdä1*ÿã€dªè€…º”$qôªí<¬cgã>”÷'© · !ÉÎKdñÖ¥¸–E‘H£åæ€#µ¿žYJNç!x4’yöìžV\õ~8ÉíH¡÷Vï:y 0«Œ{V|¶ÜÈnG$Ÿ4Äô#û,P§ï\z¢¬éòÂçìÓ Uý8¤2ÔWRJ‚,%ç=ªºÞ<2˜‘sýíÆôóˆúôÀ¢T–ì²JTíÁÏéALëšœ|¦š-ÙzWÄ5së¥0:~4,dœ‘šÉèQ:Ú$E#iÑ·i);‰¢¼Þ·˜` ?Z¡{àKiÉ" =ÅuÓÄÊ Êt£#"óÀ·Ðm.îžEg¶—­Ø·úE±‘}S­zø||dìÏ:¶ª. ž1ØhÜ”¸è*Ä[fÕ$Y¸'Ö½XTŒÑçΜ õ+j|­ûÑ’3Œã½6ÔÉ £+ÊŽ˜ëZ™—•À—Î1(ŠNÅY†Qo7‘$9V(cÐmIŬ“ vddà|ÝGÒiÞwË(`@8ôÏ­d“Z‘,Šwc={v¤ÔlôícRFwíLEHž'Iƒ€{ÔÖs À´qž~P:ÐÄó"áòwràyJ¯v'’U0BUÕàéÆ3MÑVp䆨g îjW°bÉÁ#¿b{P.¥{°Û„ñ·ÌŸx ·§Þ P®Ó³=}L‰ÊÆ„G»œ÷¨ä´{YüèÔŒ¸#+HLƒVÓ‚Ä­m†G-jØn¼ ÀPx$Ð2õœHû2d‚N3Å6kQq3;.Ÿ˜(é@E PºîŽqVÑ ÈÂpXœ©ÆN(nÛW‘OÎjõ¾®§·ç_(´}Zw/Awop>lU„…|Œ+6®Š&Kw"ž gæR*%'ŽãŒ—ì˜^•“LMØO±ÆÃ™¦I£ZJò‡ÓJR‹%ÈËÔ¼cqð/>Õƒ{ðä£ù¶²çס†ÇJ3*”!U/t~ر–/8{qYîÏâê ÇBV½ÊÈTG—[ (= º}Íœ‹²Vʸ<ѱ֭5´R£ù²áãnyÍw)&q¸´O ý¢ÉUYLˆÙÞNúTÌn ²´qfC‡cÚ¬†gÜ;íó‰ÜÄüþ”–ø†o-[÷r ý)“Ô­ªiïf ̾'œš­m)p ƒÜÐ ¾%W_µÛÜùù—Ö”–¶“À/m·+c çq¦ICR³{IHãr[Œ`Õº-!Y$Ÿ½é@ºŒ;wî+Œ69Í6 uŠ}›ØÇ!çm1Ü׎8æ€+%xÈ¥hþG³üßcÏJ@Bm¾Í'•q»aëÏZ«}o+NÛÎîP•ÆG­0¾¤g–Øy<2>O½^µ¸O,Í€ŒHíþ5#¨KÈé•ÉÎ:àÕE¾+uºY ñËcô .ZÈ5:ɑȯ’q>© žDå\ý*í¶«p‡æ²q.敦¶†8>õ~ J)ƒê*uL{£BØÚÊ2®*ȱòÓöq’ÐÆM¦&R¤Ž A$~5”©4fä1íùÊœÓ×'`âÓ)LôØßædªW¾³¸\In§>Õ¤*JBù”·0µ/‡¶NKÀ†6ÿf³n¼-®Z·™ ¦Aßu{8l{ZH毅Œ•ÐØgó,¶F,’yÚ­Ý;\ÆÐÀŸ&s”è ¯n•EQ]=Znœ¬È£‹MÓø¿ˆÈ²©1€q´ûý*ŒÀ+¾óÆ3ÇzÜœث_ÛÈgŒ°QÈ&«Cd–óyWÊRG#šdô¬z{±Œn N§©Èíå¸ù†yôÍ=ÉkSå‹h‚°#q·^ÀV]í”¶’mqŒóœqŠmKvVËsˆáw­þ5~ÒßN¶Ýo0ó (ÚßÝ4„ŽÜXH7§Vê{ •íÖáB«|êÙVÝÇÒ€èE<-{l°;5{â÷ª“ÙM}ÛÎû^>'¯µb4²³¶‚HÂÀ‚ pOµ@ÑlD”ƒžV“k ÊÀ¡ÙŒgÞ¨]•¶°Á_J,3F7ÆC~UaE|ÍJR‰ôШ¥±4|jdõ°±ª&‰Û£Uˆn^#ÃÎJì«—­µ'RzÒµ×1–5–±bk™–ÚâÈ0à0«¶×Z}ÆÉSï]©i#–¥9GbÓiqºïµ˜7¶j7²»ŒeÐûñNtWF §q¦ß訧µ#&¸%rã=Jïm¸å…DöŠx+Wît)‹a¢i²ßªß[#Æùx®gUÒ Ó5k*Òè¬%³ǧô¯/©ucÏÆ%'r­£³”Æ0³ÃåÉÔduéV"ò¥¶2d+/ 㭸ǖçS’D˜Àì1íIÄvá`$%ºP·ŒìŸi¶?4g2õ¨^o;÷ˆ™Ô§LPÁèýe…¦ÆòÀ <Ìqì+&ù÷\³"mÉçšZ‰Œ“ω˜²p-6’H>Ë2ËedaÍ ¤6+àœŠ»fÆã勦x9®Z”#4uS¯(2XîeBVEéïV ¸G]ý9Ç5åWÂJ.èôèâ£=f9ŒŠk‚Qi±’c²¶sVb•מ ÍÆåÜž;¦SÆ®[ꌸ³õ¬š`õ4¬õÏ/HWß5­eâ9¸aïZÒ­(=NJÔÕ—P³˜üÃëJÖÖÒœ¤Àþ5ºäªqÚPb6ìõ§ÛèÖì7I“[QÃ)ÎÁ*­";ël×ÏVû¼­qîÛ­Iåಠþ5Ñ„‡³Ä´*žÊÎ$T{¦ù@l=Á¢'xmÅñUVc„\ôˆúW´9”ïá’,’«+g̽ÿýTËy˜Eå´ IûÞ†¨‚ˆ¤a!¼*w?ßÁïT®·¼ß&9ä•=ª„Ë1yw-ÔoµÐå—Ú®P.ЕڛYHàžônMʺ” "$‘!ϯm¿…6ÍuÐ è Å0êMö)ฎÞH÷7F søT:ªE/›’AÉÇLÿ…0 GiÔ rUXbBÇz5¤r3$EËaU±ÆZL.6î&óRâá$t8ü*¾¡§#ÂÚ‰@ªÍ…U9Á÷¥pE¹)›gPÊAëڈȖ/²¼9|ü¯»é×4‡¹Z€ ó¼ñW,¤U‹tpÊr[=EIeë›ð‹ÍÏó•#­V`Ssµ½M&“*- µ{„›o™·xâµ]î!‡Í%X/S»’=k†¾3Øí£Š”^¤Ö7båT€~nŸZµV?#ŒýkÉ­‡&z”ëF¢%019Å*¦Þk•üF÷ÐUšHΚ³o~éüT¥Ü»¯"õcùÕ¨u®sæïš”œYœ©©ÚüÅv‰³õ«1k2ÄÖu÷®ºuås’t_WÖâ ‘Èçë\UõÉPÊǹ®¼$ùë\аå£aæîÞø¨Ã#Œ¨=>µfâ3 Â2& Ç85ížK(¤ðË;ZÉj®ÛH. é@†ê0Rx¶ù,<µ<’O@=j¬C%{dÕ­?B­‰€ôÏ\V³kk—Ón÷91í«D½ˆVÖm6è3‘´ r+Lª@ÔG Ž7§¯µ¢6¶$I#•œ0±Ó&™t°Ãpp0c W4ÆIöÿô…bqÆ7Š#ºó["xÊ÷¤u)KØ\•°ÇæÇJ·´¬Ø€„üŠZb¯ùddª.{Õ!pmä1ÊÅ¢'𖆶)Üm‘Ùb]«ŽýEU’ày^XÔRcCâ“kËžqÆjh²’© TnÁ éQÔ£vÚ Ú5´ªÝr8=*’YÊÖÌÊJdÓ(«4·“6ßÎÀu§Ø¼ó)ŽY[åé“͉ôï&&bYvž†›wö›5Ì289泩N3Vf°©(= ôÿÞ© ràV«m¬Ãy8€G†#zó+à5¼OB–3£-Eöy˜…o˜u!µ=A¯2tç ÏB5#% žS/­Húgó¨±w,À’7 ‘S‡X.IÍW.—3{’K¼ysjçux£’ëd´3]XâøŸá²HÑå6¥Ûpü\Õ»icÕí’DˆÇ€…ýr>júx„?ÙÀ+ùàý£8s¸ÿõ¢æÖFAyrH`~TÅY V»’ÎXïü³":æDÿ>ô·–Ðß©uÚ»Á1°ÁÚ:íúÕ"L¨­fÔ-ñŸÝðäõÇ®)°Kö`Ö’³1$ìcëô H[e ¯™²RÀ.Oo§­BðÏ0‘ð—vÉfš}AêC»“å4›¶Žµ5¨ó#(v©ÁÏ<Ð&·³K‡1]­³÷D÷¤·Ö‘²½Fš\/ÐÌæY™T’OëžµŒ—–ìUFÑÓ#'8ëHe…°Ö8̬p1Þ²^"“ì, ‚Þ”šm_Ù[ý¡þÉ ’˜¦[ý<Æo»×žµ™¡¥asq%®ï61å‘ò±Á?J[ͦån•ð_ﲟÏ šê8tÛf0Lr:{Õ8[yD° 0öí@žÊõÔ\œóùU›«T’5¼s¶7N…÷ÿJchÊxäŠBzüµvÞ2¨·1 ®¤uläúÐMÙ®“¡PŒ&%®ê±ª^\[*ÝÅ ´³XUêÔÚž"pclµ£)),v2»GZ»i{euÔí9ï^]l¡±êRÅF{›Zu‚H¹QœÔú†˜Ö¬„Ž¢²&©š*‰Ì«w‘Ò¹-râ¹òó¸‘×ÐÕà`ý©–.ISâ ˜ÐÇ"(ÜIÆyÀÅ=Dv÷ µ<°¶ì‘^êÒDK€fŒ˜óóV²ÛÏ–»z¯î‡CŸóš…±QsÙäÚ‹;˜ŽIíOº† #Yâ ðÈ:ýh>—ö‚C*©S–|r=>´g¬7kfïæ£sF:ô š†’Ò3,Ж’>\¦údS--måŸÌ¸c .IvÖÖ/ipçä'ìjå…»ÜÃ=–¥òàþµUÉh¡¨ZÿeÏäô(8p~ð§YÚ-Ó‰pTëëCµ‡ÜômÊìØ˜¨ÜÞÅZ¼°†ô)#ÅsΜd¬tƤ¢îrÞ1† 0ÅrK÷sÍqw¶;yª¹By>õtpñ¦®Œ«W•GbÖ—LpÈ ‘/†AïPÝù&òH|Ðñ–;yÀúV¦-“é6áçkS&w)P3Æ ^™mtöû#@…ãDØÈqïï@º®¤E¼k~`§„ç~OÖ« t›Èxq"’Txö«%/ý‹*§ïç8È?γì-æ¶»ó¾c|§i Ò>|ò/Ì7pÝpi‹=Ä2òÁY×n æ˜ÜÙ´%e‰@Lª™ã=ð+2ú ¡”…pÀç;Aãœu¤&ˆí‹Å"Ås`ÇŽ{ÓÌ-Þ# ©ïLo­¡…ã¾…÷+·ï¸>•¡mÔí1¸1/’0~^zÊ€%Xš+—P‹L§æ#¨öòª:¾›\–ö矾=jJOBÞš~ÑgýžŽUÃ}ô_YËnÏÌí<ûV&Ãb‚/-ŒË–û§§½]·S(a‹>hôÏS@Ñ+Âmeûm³G60jt´S"\îVR¸%øÆ}hm­’Ed Júžki<àñ«nÊØSÄ7·î’ã*H y8õ^I„-öy-‚°c–=qô E€Ñá^ü¼{V¼sYÃ¥aÀóÆr>ñô4‡uqw¯_}¡âsÀQ€nèšY]±ü42ÔNÎÒìÛLJùU'Ò²õ¯[y;,®]XÿZ•¸3ŒÔîæ’å˜¹'’yÍC5ÏŸºÖ8r\|˜^A­ºÝHm –kiY0’ÌÍÛÐzš†ÌDU¢¸ÎÖRcñŸsJà÷&2´‘/’ª’ ûàõ©e’k¦rÀí_—éŸjrÔvÿ ½‚~|¥>àúöªš…¸"îܾ[•-ƪBd&Úâ7 í€Ý…-î‹#²dPN|ÜóŸJ}IEkx£A$Z”çr`"íëõ¢+¨`•£˜­÷íéL: -õЉí[k|ÙÁQšI!Kû)~䯹ˆ8êû¾ýi1Y%š_)Ûç^„žÕ`ÎaÊnß0Ûj¬GÇomlcy×̆oõ©ÜcùTÝ\èw/ ¤„+6Tõ·õ¥¸2ãÅìFõvòrB?ÝozmŒ÷Fæ;wºì} ¤Ô³qi.x“˜‚‡9Q»<{ÕíJÚE+s´8 gÜb¹ú%(­S{À/lž¢¬fÓÛÊåQ ÁsÆåôúSæ…½œRZ=»:óÈ9Î[Ò„ÞÚ=ƒ|yä {žô€D´SkçK#sÏô©ìù€,ÛWïò?J`I ’»î1oèGæ›uc%Ħi—<³côªZ“qöškÍ2´È n…´ÿá’é<½¤!9Àî}iØI¢ý—‚<¨V=ÁFr}krÃKµ°@"\‘üF¥½ .îKwžÝâXW3{¦]®,Ú<®IéI+¡7c&}yÒ,»O\T2XÌ$’ó¼/ÊW¡«fF-äÒntdù²CÒš6KmL@q¸ÿõÓ$œÅl±‡†\2ŒH u>Ôôheù Ü²~õ1QÌñî Ì‘«õeüãL’9ÒU´ãxÉ”lcŽGãHfuåñ¶VY·ynJ¢öÆj=0]G)K™Jä^zÆ«¡mjÙ¢UÔ¥ó “) ž}sP^¸…n-‰? ÎW¡ôÎ*‘Ä·“1TŒ™æ:M}=É{‰|µ'åþð”ÀŠòÎÂXÍÄLævÀUëß5Z4/ÎÀ`á·zÐ ’ïIÉ•ßÀ9äšÓû.âÞ«ü’Ï=¾”–àEe~¶¹±tA±‰,ßÅíWdµ’òq{¤ÎAõuB[YoÑ­WË.ƒw™¼dNzÔö+ö‹V²“#o%‚eøW1Ò·+\Ù¬#ØwŸåAxî‘¥–V ˜Â稠cÞ9­Ýf+'#r銶¯5¦ /Û+  TõÅ "šÔ+AŽ_•™×§·5RæáâÔ ¡Wé lšåì¨ý§q²0zÔ^N¸ºYHçæðMRd²å¾¤âu´Œ»r+oIñT«µd–'O¥iL¤tºv¯ ò€~Wô«•Œ•™´]ÐÉî"·C$Ï€;ÕFÕ´Éc3ÈЍÅîLåÐͺºÓ#}ò°Ã’1žõ—z4äIPN[tmÜûUµs4ʆÎ̳G[‹üÒ7P}}ëRµÃ-‡Fã·ãK¨™ ó£åmŒpåzÕû7¶Eû2ÆK¶v1o»š`Ioyg)6Ãæml)ŸsÜS,>ÍwrZi%ùIä ý #Ô?³å…Ëȯ+uÁSííTÒ$Qä+• rš!š1ïÔí^$“Ë…Ô¬†Nr#ŸZ©Çöu»i’Äþ\£ÄqÏQMMfhç;2ÈÜäŽÀv© ¸Ã-ì.z2“T*ÙZËmã %›9À>•ZóG‘•¯mäós–àŸo¨ ž$x1:wêwU¨ç[˜JD¿ÀÞ¢Ž¢yl×h.¢€ù‘àH9>¿¥[Òµ¸·Ú@a ×,O­&4t:µ¬v—1ÍP’`0pPj®`´ÔœFê8PyÊŸZä[c5oµÚÝ–#xVœðER·‹eàLo Õ ÇZhOqÓ_Þ®-|ܪ»O±­­eÔì<¨%åAe¦ïJb*fbî¤(!~pxü©ÅY‰• ´g –Îãôì1T¶I¾xíËl†ãiœÔ~t6·¯ °†Y » ÷¦MÇGk9|³…R?­Oaq$MæÇ÷Ç ÈÅRd³kJÔÞ)$ƒÔWÿá2{TÚìµVæBRådwÞ9³žÞDÕƒU´¯iÏz³Moû”Bv¨Î­JM+'©BÿW¶Ôïçž(È?v§µC ML.q2gËãÖ›v3ê$w<™¤t|1íYZ½Á¼srI,I à:Rê\NÏ!ŒœxÉèkfÆ9gÒžDŒ¬°7ÎsÕHïTÚÆ‚ßí1Jƒsc Å6þö{§SŠ«`¨ã'¾1@¼ }† ÎA ‘’s×9¨om%²1Iµ¼Õ]Òô<ТhnR4óáÚ!fýâ°éíK{l5K0-‚“.‡wð÷ÉFmÁ7îñ9Vš5<«pþuŸhá.|©ÊzóT‰4á†;ãò,‘ȘGlà{Š™’8ÃÛdàïÖ YR<¶‘O€³$ïï-·dßñɦO–EntùJ¤«ó0íê(C; wJ»ß¹.Ë1çnîýø>õŸž{Pî« `Ø×":Þå­>wÔaˆ×r°Ý¸ä“ëTçYe¾&H6±;B Å5 =‰µ-N´L­ãI#sÛb’Þy%òÜ\0q­P‡ß ‚þÙT‰¸ÎÖ?_zjùÚ|Ï Üxó¢ÚHÆGâeI­%#iÝg'¨õ⥺ÓÚhÅÛ\yŽFí±Œãü*‰e»XÀ·Š…ÝFNOééQÜE{ ÊN°ó:1š fœw(!û1ÚŽ $ìÉ-žƒ ͺ]Ç._çŒgƒÖ®.ÄI\e®€/!•|ì¸Lªç‘Æyü*…®Û9C$¥q•r£$Öî%žÚ r—÷;†Ð_¥Z‚p"ŽH üÍ‚6äç®j^£"Ôm¥‘P‰‰øeÏݪí\Z³[:‡#¯ªúûR¥Ý-m®$ŠS‚=ý*½ƒ˜<ÇrÓv3VOSNk6¶˜]µ¿îd?"ÿv™’ÎõáxIÉ;Xž­e¨,¯,´ÌâQÐï o/56ãcs±¿ˆç=¨@ȯ'™Œ"–>jƒÆj}"sh$±•~@wÈóÚŸB:”õ¨â‚ñu(cB²H6¨5WWÓá©dãÉ~€°Ü>´ÁŽ·jv»þú5ùwc§µ]´»qy2Ûy³Õì\÷ }µk›vd@‰Ébr¿Lõë{K˜ØI"‘*òs×´ îKi,‘³³ò².[¸«‹N«hÄXpüu£¨=N¶—WÒa~LŠ2›”c íLvI!8÷®XŒ·§´ëÂ-UÎ f_nÔÛø§Žì\üæNZF$¹öíO¨ºcŒK'—vå"߆.H÷«³éÖ§@{váÙúã®)ˆŸLÛ¨™´´,7Öí·=3מ3QÉçJ¾]ÀRñŒdŽIíL?™m|«<ëp6¸^¹Ç\TzGÚVõ-­1ÜHËÑph%Ž·ž{ òä)‰÷(m¸ žµcS¼†x<•f܃ä$`þ‚˜O*Ìñ³Ç¿|ÕÛmE!¸KèâdE8a×µ—<ÕÓn|ûv+ÇÝæ=ª-rÒ(g}BÕƒÇ&¼i¦b1kuhm'¼Àß!UûàÔcÝÛ]K:¢±É^£ŠdšI XN¯ºS”°õ¬ÙDš]Ù_'îqIn †uºžFˆ*HfMÃg}ª·{b.ä7Î1TKÜÔ²½ItÆ™e.SÀÞ”ÎÆ´¼Œ´# ëë@\u‘–øæYe<@zßò©šÖ(¦µ¡/R1þ¬v>ôJú+Ï) Û[2#.6ú~b‹ËI.aÚ¢E•XykŒå{Õ!H´¶6—6~Yw1:ÛŽ2z×?,WVïýŸ>à’cjžôæšÕÓî´ÝGl‡iVã Öµl3Ÿl‡”d7Ý4ÀѺšÆêÀKEŠ®éGQÓŸ§¥dÞÆ÷A]dv)?0à/ þ´Ù!!”„ äûÕèlÁL›‰É+‘C74 ¹Rá`…¶–#$Žqè*MJÆMqH¤±xË/U'ük—©ØµE-’ù!Öb0FFzâ´îš–œ.R5 çã<r¥Pî†lÄ¡˜®CÃÞ«ÍjfÍÄIò—èi’Mcs-»­Ì#£€ê hjSýšâBÎ-¡þgÜ£ùìx£¨†2ý¼Ã½[!³œ÷•‘Éc“w?)SŸÂ˜™§%¥Öœ TÈÜßw‘T¢¼I¬A¹‹{ÀØGUõÍ (^ív7(›C·ËÏ+Kc2J¾Dò6úµQœšdõ5ôÒ—06™b¿ó©N Vß|Ú-À#€úQalkX[KkåjVóóÏë[š£[êÖÑ\"’#PfÈÚX{b¹^¬í0ï-¿|Ö¡¶†o”“œ—I»›N•ì®'xƒt;yÏj¢z’êV²iò)ˆŽD+ó. úûÔvö¶)§‚ò‘;¹Ê·Ý” •,§òï¶Ê§Ê/’½?Ñ{S$O¦;Ê]Žè€ä~?…0èZÓl¼Ë/³L¡Š’w•8n:T3Ác‚­²þø9†ûÞâ‚X¶%›8º± 9Áô¦nm3PûLѺ«¹Y:}EÐn§cÖ®ÂIP6‡èG¶?γ¤0Z²M`ÒÎÝ=é’Y{²¦­`gŸSW¦¸[;ľ…ˆYÈíǽ%Öt˜¯îaÔ-†Ä‘aèÕ˜±MmvÖ2Dã€wp 4&†Û$–wœ¢£$ óWÖDY¾Ïu>èd¼§ãGQt(X,6wå.7”@q²§—JÇqj„µÃÑœžØü)õ›¤¹—ìBÙC†%qßñ¦éæK ð÷:>OJ¡×ö*×x)—#اڛ±Šökç8V28 @3I.ìöí´¶ä·Èàg§_ 5m‰$€¦aò…^ÙéøRŸqbö74xŸ;CóÊ¥ŽâfDÒçr‡rqøÕ™³+X{”¹7±® „ïóøÖmñkèÚý†AMºs[Ý@mäm“)È}ïj·e¨æ#ev¼’ ¥º(0ØšK+­jÒO2<Ëæ~óŒcŽ=ñY]âØiÒFAŽC ¹Ø:3YËo¯•â@$ŽÙ〽]ðÝÄ«¦5ÃÀœ’:g…rJº–•< 5ËÊd @.Çœöý+&êRfI™KtÎsT‰f”ÞF¡§ý£$È=ÉØv"Ë)«àœ`g¸¦&JÒ›™þÒÄço˻۵[ÀžÓí¶à…¹ (xÙ,çŠáeÛÈAmýO­M/—op“AÉ&UÆFy÷=(^xfµ’Xb`Æ éž½i²æþ߃@sœ·:b*Û^ßÌw=Ìd@»JIýÓǵ^þÞ;9Ùî) ýh>Ž-®­^Ùq0ÌL͌ڭهu›N%LŠ ‡pÝøPÏÞ4öØ]6ÒË1äžÀU F)l¯–ÝçÓqךhOb=BS<¢æÆcæ$y‘“õÍ8ÇtŒ—×?t½E0&_$ÂbµŒÈä|Ù>ô¶–᯾ Ç àçkÒ2-V;‡„Ü*¨xËl¹­eKšuœL0ÝryÀªD2ý•Á¸³}:(³"KŠt6·Z™T3þõGÈ ì(NÑQâTŽÿÊ’6êX`Ô—xQ|Þë¸89Ëñ¤QEÞ{ä-$;\Ê|·m>‡5V—ŠäÌï"lÈIÉ"¨†Ou©¥ìm5‰ùÀ&U(3·ßµs{ Òïž ’d‡Èè¼n#ðÎ)¡‡ÚôÝHÜ.ä*s†àâ¬ÜÞÌŠÀùGš`hiææúÝd–W'øóÛëïMÕ œ–%môí²ùÜ@¡n Ù²¹—yŽkf‘`Ë;mè½Çÿ^¬[ÜÅi©E4váw‘»9ô<ŠåGeÍ]ZÊk°cAò2Œ2‚٬ⷅ䷽‘òŠDj9&€béòË ri2# ÎãûÒ^[ÄÛHaø8Ï­Q$qÚ4ð°óXØ û µ§\ZÝì±–}„G9õÍX[˜.·Ú\_-~EU#§Qõ©-¾Í5ŒÊ»¶¡È hнޞØ|-³àóŽ?J¤/–#öKw‘¶à¸ûÌ{c½2Xš«A×Ú|­ªç r(½dònÚcÌ.½µÌ¸~HÝÚ` WÔûVªµÜϧfÛ˜ ïÜSؽ¨_ÜFð_A Ç&GAü^õcX¶ƒTµMU‚d'%sì?¹ .–_,´Lß>üâ¬\Û}†Q¹—'çUCœ~5D‹+¬w1½£™@JV·{WÙ„”‡ OçH=”¶ó³ÜÉí±•ØÇ€sÞ±î-Ò9Z&ÎÀr‡z©ÆÁxËu¾Þ2Œ§={ Ö[¿´õ 5 Ùù¶Ž„*berþUé]è\Œ'½jÜìžÝ¯"Ø0IãßéIn¥Ùr&šb!òñ½O$voéYRÜZEt·[4Š2n_®sM @óÉkpšœp ‚v #ñ©5˼Ó6ÂXˆ²ð•èO§Ö˜Œk{)µˆäšyÍL´Í#v¨"m%kY(ÝG¡õª$ÓÑà1_˜ã‘™rQñÅjÏa$°´š{¯Ì›_xûÀŸçS°÷$ÔѤßD~IrIQúSc€yLÂWÜ"·\z×2ØëfÖœEÖ––’Êà“‡ 3Ééý+:kAÅ¢^;Ž 3ÐP2KûkÛËUÖ.$¤üŒ=})·ÎfÓa1  ç%ò úþD”Åã5ÒßOneò†Ò§¢ÒNfž_µZ ŸLl˜‰Ðm­ Ã,‘Þl–lõÇåRIääiC¤Rýæ·µ8^"›ÌŽ| Ã<ú¸ÍU¾³–Êýíc˜ÊøÇ=ñ@1/4éu+D[ §ë l ÞäÕ$­|§ ˜þ\fš%žP%,æ äSè³Å¸¸·i¡¬{ÐÓ´2²"_»'Žyü+CAºYâ}TbÀ•ÁøûÐ4aêÖ·EÛÙMÎXبï.d¹U’å•]Wž€vªºn¢®²¤±¨„goO¥O¥K&³k&˜ç/·÷A†qŒÒb*éóýžp/S*‡dŠzý~µoVhVÙmÍÂ;/Ì»€ÈéLFÕÓFD»>|þB¯è·%6rO¶)†TÆîÕD“A{¯Ú­' ª‚€OåMÒnˆ¹‰l4E¸zMǨšúw·½Øyõè1Ö›j¹‰¦dÜwýáNá¹RVðóº4nÛ£y•?_¥Mm+Ú§9 岄öô4Âæ5õåµÃ\oESœüàgÞŸ Ö±_GÕÓæ³“Nä´XÑ/m‹5½Ú‘òØÿ=«JÚú{,Ù´«³`±?v‡¨^[$O¤É)ÛÞ¸8î*”}” ˜€QÕk•Œ“EÕRõsÊ» –ÚGJÚÖ-à),ÑD6ÉÌŒz0úw¡‚ØÀ–,m6òñäÇ´ÿJ³á襒Ííår§'¨ä r•Q"jvÆÑÄ›>\ÍŸõƒ·åTôÙ^ˆFÚÙêã…ô4ÄXÓšÞææi®ŠçË mîÞ¿Z’/`}4¾æ‰³b}©,vÒM[˜Õ—'ïÚšÍ-å«\ÛÛaáP)q‚ßO§zb3^âgP‘M·Ì9Oô5Ý…õ——u,¾d3˜ƒœ}}ûÓDîMåíDve(8)Ü®xÍT1Ë錻fÁ>¾”Ášsl°F„äA€c>þõrî]ÐÛêÖ€ù›±rTòO½ êXÔ4÷Õ´÷Ô 4ctlIÎ3ÈþµÏI*›´y8oœ“ÖšXÖ´é¬ä[ØyRÊéIk©Ék$Vãd›Hg~÷Ñ’ë:i 5˜ä—=G§½Œð8»·ÔnŽrÔRŽ£gûÕ»(ÑÆÇ€½åT`¼– PHt9F[’ѧwë*5Kq³$$€v8§ÇcqogöåÛ„ $zÑp±cHHo˪ÇoˬÒ}µX–ñ®ï vxG–2Â8ûŸáçÿÕK¨ÖÆ%ã-ÕÛÏM¼9!G Þ­GrúÆd1ysF7;ÔŽ*Èê[¼´UÓ⸛®»dØb|W-9—N¹6ñ’ÈwLR@ÉþÊ#œL¨¶Ã‘Ö´!{=N9;ƒ¼#ÛÛÚ™&¾EŽ¡ÍÃ;£ŸÛ`j=n>ÊäÂ7‘þgü¾¢¹NÖÃk,~e­ÄhŠØIñíšÝÓµîô£ Á,ê~`*}E6ÊžH­µ“,ÎÛfyzóÎ*šÜIg©;¬Ù•r­ÁÿëS ÒÔäYíÀGÌr ‚ÇîÿŸJÊ’y!Ã2ÊÂ;ãÖ€#‰6Ýœ²B;»ow=¬Kwo<ë2È#Ö˜‹š”¬f[ˆCrÇ¿½R{h-$Þ²Jð–Àl@2-CO3:2~`3Ê÷ÇåM»3^AäX¡(¸ù‰Ï$zž”ÅbM.à&“-¹ÍÞ¸3»5£ö› .§‹ku%Wúb€dpÏ {n®!Þ2r»±Î:ÖŽ‡}Èö®XÃqÔÐúÓbF¥›&‘pRS#! ‘÷½«/]Ò­ì. Š6) Ï=ÿ®’Ül†Æ)¯¬e°“"X†åÜzŠ£ i[;*œ qT˜·6lVÛçÒï® ß²<ʪÞéR[JóÅœBC.Gj]BÄ:檷6лó(ïšÁ}¹n8Š¥±-êièz¡ÓCL±ïIΙïý*ÓO¨[Ÿ´9W[¤èNF}èê"(®)~É$…76ìÇÙ» tó‹Éü‹w]®ª%Ço^hêÜèj>Ðϲ?ºñç$œ*­,gZÚVÈ^3ééM;‰–#ºšÃmݺæ ™éëQk[ÏnÎÛˆ0ƒìyN´ r…¥Õ¼¶FݾYÀ*>÷ך¯d—:lžjÈS'»Š¡\Ë5暢vVxfÂß6;Pê‘ÜjlwM'úËô½sŒ‚ÞÊk›Xî]Ø|ûqþsZº?™§jfÙ¹~»O~ôÞÂÄzP†øÌÓFE ž£8ª¢ÚÖK"Í*yª2YéŽé~’^ê²Ëç Ïý|ÕkË?³<Ö’8r­” ñÇZbèRùëå ü¼‘žMIk=³9†Fh×q …Ü~•B7tèÖãKm>iG˜Ä4yj«khªdI. ”"(ÎìÒ@ÆÏ&Ÿufù.•ÿxÇ?0ª±N!‰¢LdîTŒûÓK›Q K‹MÂ">vLjäTúŒó¤1CÊÉÏòä©é¥2§ˆÅu‰rIÉ=£)·Ê«’ùõ  ‰ ½Óæ-qaŽ;zš±mö•›ÙÝlyA&6ŒçqôÍ 2­‰²ÔV[¬~êL0np=ê–« oq-ê)1³á2j–âèMcukk»0”chaò­\Ô¤ºÕ´õÔazœL‘Žƒ±¡îKjÑIfè6ȹY6䩬ëý:3ÕáÓ9u´Ñ-\©m$ž[Ì¿\šÜ‚î=CF‹Hv "Iò¾zûSB4È˵»‚& €¾§ÖŸ`ÒÁþom˜sæÍ $ Î3Cܵ,2]Å)ži«ź²ô{ÕHâ}R"Š›^ÝOÌOÞäu÷ lE·–ÒŽô‘Üü§‘Í:ÞÕ£k € ¹€cïÕ±›©Cm§jËa¼Ê‡¦­Þ[.¨¦âÕÄ®Ãæ?€÷8ô EÝ*ìÛ\Oüu­ ‘é—ÆYÁ>9É#=»W1ÙСºÍy%›]ù žäÔk/‘"0f,Œ‘Óóª·5ŸÛôy"¶7v `¶H=N?Õ”¶Ó¤ÞI6ømÃE$ §À´¼¦,»Š±è«ú½š)ŽäܦàØTAÑûÇ×5Bèf(kiþѱ·ƒ»J&`³ù¥€%÷¨a’A÷¦Iµo6éà¾7±þñÇßnsŽãÒŸ©c¨­å»8ŽBq"ó‘ÜÒ¶«kkñOnæUŒ¯OÃ5·1ÈÇÉË+å9î) ,Kö­E|ØDJ®w<`ŒuãµV…Úí ŒhK“Ô/j`A} F’W;•ʃê1ÅO¤ÜÚ]ØIe$jŽá)Çì('©oI¾Š ÓºMй*~\gÞ­Z*x\xgˆ‚@ˆäóÐsHe/ÛOó\¬QÀó@9ÇÖªÚ îéo(òðÍFrqM Ú•-öÃ>Ë™w!l0>‚µ´ÙÚXV8%È1‡ÜYsCÔE[ë7ÓnÜ÷‹DsíL¿’i,†©jD'“2qójiŒÌ–Î ¤°Ü¹AÒ¥°¼XW%·É@EQɯ&»#\}¡Œ¸ÙrîjÑŒ(MA&BÀ“³=G½&1VMBïP}DJpB;éR_A½«5ŒžZ³?\ÿwë@2&°¼ÕíÚ8² ÷ˆî{t¤Šyt¾j´GÂã¡_Zd²ÕêÚMêVöÑ³Ë YS¹ç“X7ïÚ†v”Š˜q‚SéMj&\ib,ÄWW©ükBòí >;·y±œHò6ôÅsh¯¨Eqw³WXc}à|w©'Ë6±³.bØr~¦˜‡èr;H±E½\…$ç¹Å%îŸ{ÅÄE±Ž3Áè¥z†kTye(Ú6 ãޮشZ–šIæÆv¶óžz`eßÁ$†pª8ÅH­Ü"ÒÉ~6ä|ÅqŸ¥2^ã¬e²ˆ4r)b~áÏÝ5¥*Íy¤Ëj²ñ¸t9ê§Š]DEonníŸ|¹t@6ïUäW#œ Ä>]‹÷^iõ{ ‹Hƒ}­ÙTà*äƒõíÍ%æË+T´—øŠŽ9€¹_T\J%X²,Àu4Ûímïc7ÐòãŠ:ÔÔ»M¸G›N-…›=>`1À=ªÄKý©ºû;K,Hw(8lqÏÖ‘D¶qÛÞÙµ˜B“ÊÇz?c\åâ]C+FÊRHŽU}ª˜’4s[¬ê ’ ™xÀ¦Û´’º›xØÊ¤r§  ”i]Ç6­i±Fe‰· žHÅUÓ.¢Ž@ŽAŠVÛ*ž™¡l>¢j6ãwØJ'–„ˆ]3õ'­b•x®<äƒÍZbkRüQÂcDO"^ 0û¤÷«?eû«ná$×Âæ†I~+Ø4¢öÅÒX‰à§¥Y‡Ë¹†F–@«:ˆãî°?׊’ˆeiä®ÝK•Î: Ô¥­Å¼©}r þ¥—¿ÇëM=Dö#ŽôJ ¡m‘NrÊ£„튋SÑŒ÷góN¹YYøþxªÙ‘¹6¯û»ÒbŒ1^]þTšxûO›3áƒI™8¾ï^+œën—¤ÚVü30mî1ÓòªÖ×7H‰fI!& ¡†p;ñT"ÄW­eªíYy%p£OcÍkßYµîŸ ]Ü‘òï'iìOLçëÖ—Pè`2Å$f¶É챫:jC¥_”–UÙ Û”9Á¦!òX¹DóÆIfbKg xæ©L²[ÜùA•Œ]Nr(&»‚q¡ !Ãç#éÚ¯Ã$1j¾m’³À£k¹sÈ#ùÐ!)¤ê¬Ä‰G, ;b[ÆÚœ/y–’Äõ‖[©^¾WPÇ·§çJÖ+6™!Ka;“óúÓ=6Òak6“u ‰ÐåL€ U]CM‘ÕnQ•û1ŒqôúÐ'±^+™£vxn9á¹ÆG¥kAr,µt—ÎÝœ -‘Û¿z ¥æ‘vÚ¢OòK)pÍÉ\ÿVñ=ª²B)Ä„‘æ:Ž·-Áìgi× ûO)í0XñŠŒÝI¤ßì[åQÓñ¦"ÓOê"xîq¼e㊋SÓÍ–É¡ba¸äqÐдGŽëPµKS$y‹,®x8ÅQÔ­¤Ú—6Ñ6ÜáÎ:7½4Áì@amæ s»§5³gšŽœmÖØ¼°ÆpËØZ(ŒC õªÇon±Ë‘³Žž¼Ôöz ˜-¼±*|Áe•s‘Fã4mš7–}êÓ.¤uP8#Ó·½CfèaÍâ¹;¶ÈŒÙààþ•e\Ù}‡P™”ç)ß×ëWôÀúŽ,Œ¬²/Ì…¹;i½Hê7\¹–åV[kO.=€9nn­æY £Ö1:Y¡vV]—pÛʼîÜÀ ÿOz‹WµŠÛPŠàHï$ÆÃ8ôïÍ0cZÒi æ'MÊC«Ø×Gáéío4ñk.· ýý?úÔ0F~µÌ­%ÒDŠD»EÀ ;ƒøUY uòõ ”ÄYùeQéëõ¦!×ZƒÞX™m‚ª–xQÏ9ÿëÕ+Ø‘¢ÿE$$¹#¨8 À¶³ÏhXÍ ÃóÐv«zmÆ`679c¸l@:þ9¦I~H£’Ñ×'ž}?JC4–‘¥¼GÎN0¼ôî})$A®í–yî[;ÂÊ«õãüûR,vvW¯³˜Ùòl`þµo¦ Ÿz<ù³ŸBi¿!²ŽïÏ ’äȳú `f±Ln¾Ë” W=ºu«–÷ÖÍi“.X†Èñ¤#e^GÒä³TäTà¹ÎçôªÚô±¨ÚA)*ȹ–=¿6rèF=kÊ=ÂM2ïx‰ap ížô Ñ.é,¥‹å”ùa_בRê0-”(ƒiÈaO¨žÄvûÕÝžŒ}~•‹™· ¤Ùéšd›÷KK¨a·f*9ÏsÜ d–ÒÙ^nåK!ƒüŠ@Oþοkk‘²)pÁÚÅS¸†A,a6—&&fÍÉ ¤ZŠ$0¿Êଛ˜Û~•JÍ!ž; ۸☠{u%Þš<†ªÙ’˜ŸZ“Aò|Äóí‘ßËì:çéSWOšÍåwûQdLí}áôý*¥Ä~“ Š6rÓÉ¿€°þtˆÇÛßÛD2çÂò qϯֳäŒB‘ÏŒWhÎ8©¡06qÇÚÞç †´¡ŸÎÓc¾Ž•â;e@¤’=I¡_R¶’îßûN° ĉžÔº]Í¡‰l&‘Ö)1EnsÒ€&ÖÄvq‹Kb²I~õJpÃ×5‹äÚa2à2`^§>½±T“éOÖÓ®ePJæ=ô¢Þ$¼ dü̱€FzçúÓ¹%­"ÒÜ\[èщùQŸ‘×®+Yï&µ»Ù• ùh‹cG®8瞬e l-ã’E½]Þko†"O?sÖ±õD6—¢æ¹d̈­ÂJ¨²dŽÕü5½÷ö•¶¨Kœ†zæ¹ÏxRÓ&óî#Ìlx‘Er'©ÔÕÑ_H•>ÙNr$Ê6FqïV–(tíEƒ¸ ùGw^Ùú÷­T¿ŠX.ÞÞ7ÀaÎzâžcxÜ¢çï Î1L–tvÀÝi~Tx36.sùþµ¬µÂ_­ò! fç§l~t e}rÌ#ý¹W`•CœçÔûUö†[ë[+»oœ”Úê{ã¯éLL¡pÆÞãÊï!ÊŸoJO!®`Xܰ“?t½éŠ .h–†6’)ãÉŒ<¯¯µrú/b‘¡¸s$QŒ!þtu°Ä5-5‘Ø QrØ_O_zŠü]_éñ_ºD¬™]êØ,¨ú ŽÉ¶> ÃG­» ØŽÕ ì‘®§çtÛ[åÎò  [÷’BT%_-nø«*á.tëö “ÐC@Ö—bÌEn“FŽçwšÝztÍ]‚ûûY<¹ìã’xØ”Fsï@Y¥¤ROa¨2Á{ý=ùª7ö.’g]²…UÊãZ. Ršk!¢²)@ä#ûÕh&h?ÑRáÕ‹áN)‰šzMЄ:iÖC2àí= dÕmç‚÷쳡$Æh^Ëû9íAþðys¹8Â÷$µÍ_ZEos$qÎÍO—·¡æš#¸¿‚áÞ+{QÊ7déš¶·I}VUBçžJv'©!YoÁ»¶ –.Ù$÷«A£Ô¼„…‚K¿Ë<ž{Ûé@u.µ’Cj$¸•Þ@Ùã;ºzwúŸZ-moMwŒ:…œ6ÓëŸé@™Ý-A#>`ÊNÞâ«Yê>d‹,ñ©ÕX°¹#§njО掙xÐÝ„ó#ˆ¿üµq–öUýR.Qí ‰Y‡ÌXt'n1íNh²{ ’õÛÍV2 äóëV<=u"+Û`¤ îÇ?¥0#½˜Ç/›%¡$ ¯ðäÔ p#u›,ÆvŒ·n‚XÛinmï|ðû1˜ü ÿZ½ ÂÏRP.a"òã£Cf+GÓ®7e<ïõ`dçŸZpµ…594çmÂQò°L÷°4Ÿ{m4SÉnÅP«îØxÈ”üÇ+É$ö»GˆÊ’@lu¦ô)"ÓYÊȼ£ ç9÷ªÚÌ{ÓÈu”ÜG–‘ä’iuQˆ‰áòä¼mäœãÒ¶–Ù´Òš¤bBƒnÖr}è‡ß¬†î ›TY#”Œì¯^j÷ˆVkë!r°¢ìåßø1@Î~Ýå‡PK•Eonõõ Óu»i¾Ô5n"YðTg¶:þ4ú‰‹o,PŸ2(|Ù÷{ðµ\¼¹—Z±yøW‹<ãSHº·¼œ$ªC‚xÏO©éa%þÌŽ@YFõpÙàŒþ8ÀÏ–ÞÆÒÒ9DLÒ y—9ÈôÛKe%Ö¨€ö…ã•A’7^ÇÓßÄOhð[]‡È”gÕhÈ–w‡å 2¬dgùQ¹%¯µO1ó¢r<”_8ˆÉ“Èo^ÔÛë[»ì›pqóHÃ'ú æ„3¼£ëX ’4™ r(*Ãæ5 -°Z‚²Ü1Ñ×Ò…¸úÚy‚ê;­.d9oÿëU)|‹r µápC‰¿„ÿN•Kp'½Š³MN ‹ lUéþy=ëfݦ“EK‘â Á¹#úÿ*lž¦B–IšäÀÂÞYz`mÎ85Vø—_—³1'9ê;S5µ(n¯t¥–2žS ËpØÇQÜþ>•‹gk+ÛÉ´„DÿXÇøÅÔåŒ,ŽÑ†ëèj=®wù·&9f0zri‹©­s=Åî™Ò»4–ãåFx51v©©Ù)×;‰äÒ;À÷vñëMhŸ{k®î üjd¼&*ëÊ>÷AZ­­8·Ô ¿€9Áýã¼uZK„>|³JwmfRs¼Ðt† kÉ ÌÌàÇËŽ};Ôò_]GvWQþíÁd%¾êóÅ4t[¦!ôÑ.×NcaÛ×ô«:\¶÷7n—O#*# CßüúzÐ37ÄV‘i–í Û“$ÏÄŒÙÚ>½éÚMÌš¾žúeÉŠ§î·}ìûzÿJÇ·•loH˜àã'š¾[ì7é$ó#¬€üª8Pj˜ƒWÒŸOºdß½$Æ0çÓéÒ‹)n®*ª#!;Ÿ¡aŒ~?J¯­ØZ[À·1±%Á*AôíXƒP»š_*æfbÇ‚NNi­DÍK;õ“J{¢»ãs‚W'_AÖŸcgkulÞhe“a°8ïŠ{ vixrö6ß§jr…Œ€Tޤޙõ«·Ò\yíoldàÄÒF2Oñ{Í.¡Ðì(¬°g5Ÿâf¿n¶\É$z¯qI;”Œá5Kp±s³wP;}išÕ”D58:M.v•5 2YîWP·6±EDßUUÈ#ÓÖ¬hw±ZؘîÈ ,kÔýhb#Ô4çĉq3*GÂ…èAèÏ­CmeæšæÝöÝ)ÚÁ˜ ÃÐzš÷,hú›½ƒiQÀ«,h|ØÆ;õïҨ˽øoÃFÇk8éLE›´k[¥xu1,„BœqZËŸMò®ÉKòzŒP#oGò­Ý,å|,ÊwÙëÚ¡?kÒîfŽâ70È0Fìô d7ÚY]F.ÚCE¼Æ‡vÖ?Ê¢¼»ó_ÍEXö(1GŽÝ cõgwm–w²Ë.ÍÒ+uÝU,伸Í­ó,GåGãôîiˆ¹s§k_fþÒºœŽÞFr:ôª·‘̧íplðÌÙÀ¤3Vtò-FÊdÀnU8íSjMo±e3¢KòÉ·¶;þtfëJ[ý:3Hê7n~£=kŸ¶»6—Få#ÊGò¹Çj4&¼öÚ¤¿m°‰SÊž¬=p:SôÓm}Gw…(üìÝïT†xº…ŸözƬÈĦW?^¿QóÃ2¬Lc1Ê㜞ô™+Ãe„q]NþLÀ’Ä “ŽÃëÜÖ¶a™¡û6ÉWHªBbÙm¶‰š70oÞÆâ´.-â†5rÉ!ä/E>žô‰/b–)"Õ-µ’F1£|Ê~:UÛmX²_L&`ÛãlœŸÔÓbHï(®sa +u¡”0*à ŽA©JùÊkú]¾œò,¤ªHÙ‹h뚣h‚÷B’)[w䌿þºÑܨ·Ö1&b&?“aÀËÜÔÖÒ¡3 Ù"…V˜`¨ìj„jê¦>'x<È¢“æ7t¬k«)`¹Ži› +‚7Lò9úHOqÐÜÃk®LyaŠœ6F=\Õ4ìÇ=Å«g?;vŸqô÷¦+0Êí!”໯.Ü`Jµ"Í2ØPÌyäÓƒZ‡Eyq{f‘AhX–r@"¯Ü5Íî”·’Ý.øøenwc¥¹^èK5²Å$‰;”`žôÙ-¢ž+{­Ê²nÛŒ ð98¨è.ì-­%>vdó8X”är}êêÿgê‘Þ4®åðìî:žô ’Kpî1o°Jß™ =½*ŒRÜÙ\4s¨Úãæs¶€Ü½¡í•dŽ;U,ïò<ȃ·ãW-`‡2hóRA¾,6p}(cI¿»·¿63D¬£ƒ¾íŸçYzþ›ö=Eâ”…ðxô=) ¤ܾžò~êB6ÊØŸQíT/c67ÞNÜ)=Hç„IrÞ(tКœÅÊ6_¶ éW5o³‹xµˆb#züùän¿*c)ÙyîW1³n@¯aOÔìÕš;,‡Vƒ ¦#™×-™¦ûB£ phÓ¥ ky˜åNT“ÒôSDÉ¡b©knÆH—20=9ÆZ³aê6oö»ÇGÈf(¿úÔç¥Ðyë\ÒWF¢b‚9µ’E\Yô=ÂæGÍz¿dY¶£|ËŒòGµ^³‰­,–EÊ}’±o½Í CüCl¬<ËX°"#ü¾ÿZËŠÑç¹Ú„€äì?ß¡ îXÕ,âÓìQ$¸Ws'0¦xüit»¨®۽đ!Oš(øç?­0êhjö—rÛǨÛöUž¤ŽŸäUY-×ì¢í¥É$(=`ýh j6æ95 HõGÉýfxäu¨íºE.<¬—Œã?Q@u#Öæ];RMFÊVÉyíŸlu«zÝõÅåŠÞÜ´RFXãFöäš ²\é—Im*˜:‡^y)c61\ {€Îí•Üh  ¶3>Ÿ©µ¢°òäR®Oö«1Ýh>d¯kºpLŽTôÿõP4K¬é‘\ Ö- (Hà ç½cée>Ø!˜â9XóŽ‡Ö˜žäZä6šl²[A÷s”“®jkE›R+ª3¡[uPȃæ*;àv÷§ÐE­zæ-ZÕl ÆÎ21€½eiêD¨— K'B3Í©kXÓÏ—%„>P‚ ±ÆrO¨ûÕͿڡÞ"Œ®:ñïMj„É-/¾ÌËs„3åe_nõ±0’ÞafŒ6>`wm=¹éŸjbG¥Ñ\Íš… 98©ê1Mqz’ÏrÜ(FV“aV‚õ­"«¤¼²ùúDxE•K¦îävªJçOa½r–65P˜Øš%e´Ö2øäã¿5ÔAki{aö5Q„³wÝŽ(b0¾Çqöo´,X!ŽÏ›”Ç_çM½uxÖ•ØÇœu¦–“yos¤žVù™Ï\ñ€:þ>µ9»·ÊbÊоøÔΗPÝÛÜ\ÜG#<™‘rÛÖ›hž}уíAž$-€=iÑišû6æO”©+­ëPoµû[[AhÁÝ0…†Fîæ’^ˆ­®¥Ò¯Sl,Ä4‡ôª+zöòyPã(Å¢Ï\zPK'’ÚÊþ8g¼½eGc¸íÈS×óö¨-.É‹û= ²Å7¯ñS\Ú•¶V¸ß½N\“ŸÃ×5 ´y’|¡;Øôö v.î‹Ë63Ldä³sÓ·ëZQ¹Ö´…‹mØí µ£Mq{i“SùÇoóÍs·\[ÜKj«ÏPÄwõ ee[íviŒñîh¡çÇ£ÓÞK8ÚÌþI¦Ö¨’õÛi—?c¸ZT|…ã>£¥V½Ž[}E­"Y$Ì~ƒž? žÊ)n¿{«º=ÆUÝÞÕGQ‚Y$ûTðù !á žžÔЊ1'Ù¥g]¥ Ç"¯Ù\BK+™&e#0ªž7v☭IÞ¹¨¤fŒO—P¸W'®Æÿd¸1mýÜä¶z‘š¤Í’ÖX£Škpç.A<ö?áRëÈ"Ôb’XvÁ · úçך°eYí’±"NY¾û—$dAô«^½¸ŽâKîoù”¢äœýzS$žóN…ZYæ,7qÏ9õæ«ÁteIˆÔûì£#™éš@Èô«¸e¼k(Ø*ËÀˆ¿ †ê û\ÞN‹´9Ú{0Î @Í‘¬›š<r¹î O7Ùš×ìñÜ!hÎC÷  ÖŠÿd‰ÒàîjdŒƒÔœzU½Y´:»e±óÔ‘×ühê1u[Oí}<Ü[Í¿3¦³î-¢Há¾µ|°^˜HëÇaͲ¼$É:#åDˆ]¼õÅ^¸Ó`°T¹I6OŸ7pÜ£Ž;~>´À5e¸DÔfÛ$¡ÊÍŒn^Äþ?ʪ^ Ý&F‰Ó;Àgû¤ó@÷$Òî-¡Mï–É'Fbd÷ãÒ´ô™K¹<‚ÎJœ£ùf¢i#[-L(‘Š9 ò/ðŽ†§ñ.ž·B{7'æ Û'<šìr:ŠÉæ W(OïR’!¸2ÜËç# 3¡þ,J¢ ¡³Žþ ª¤\$€9ÜN*Ä‘¶¯§"ÁžØœ’~ðÿ@ÊzV£ö;„”¡R¯‰qÏãVæƒÏŽi¶2£䬭Ôg õ<Ó{IŸ|>Q!;¨þu´á-±) ¡;=é’{&isšäÝ› Eh ®?W’à½Ý¼j™rFæ=8¡_M·1Iž²<(¡N@îE6ÁüÈÝ&E’K|²«[?­Pt(¼ši·’k…e™‹<¹5nÊÖu–ëxüµò ¬GM¹éLF‚Ý5ÓµÔÖ'u±x#ÓóéÅbj»™Ã vŠ)àŒrØõ¤ ¯”Ñ–¹ µ¢!ŽOAZ·¯o½…1 Õ|9&•nZÞäJ ç~’3Àö®~kYÎ.ƒ¦i¦ ÈNÏ5ÅlÅ¢µèHW#­óîctbä€òMTFQÓe¹°ÔËn²Ÿ-=Lç¦j{€ºf®8²œ±-»+êiõ†N¸²Y^¼’Z««ýÑíØýjH.f†Î+YŠ]TuúûqTIÐhWvÆ ,CüÿÄ7aŽ=OaPêE”Ö‰j؈îgï'ü*zŒÄÚͦ¹d>ñ½”}ïoj~“æ^A&œüåK(=°?˜ˆæš[Xc–y7ò²Ÿ“ßߊ§­½Ûdf3FîÆ²Æ›“q$ˆÙ÷ëϨÂeŽg¹>r‘ׂ¸ÇåÿÖ¤Á «›Wu8®¨!ˆ+Ë7z­¨]´ó‰ ª¬ª™.Á¿Z[›{»‹1¨HÀËË!A÷TqÏ¥gA!·–H”q“ÿ름Çɧ¢Fod}®Í…ŒýàGsè*Ô.îK•lÞÈ r{ý)Œ§¯iï§Ý­ÂL`®J·+Ÿóš-/õ µdŽçËVc»¿Æ"í•ÛØ_,Âà?+²~Uµ&›§Gr¿e•„“1‘è8ß41‹sßiþi”+®CƒÉSý+½·{Y|ÙXÆóüT!5¨jÒiÑuk»/ùIÏÔSt=JÚ&k{ðRݲ˻'ØzÕ[Bz—ôÍBÁjÎw+ûýk/Róc¿¾HùéÚ„ ¹os+[É~¶È»“cä’}=+=­V;æïò]I©Zõ =)ÞõÃV¥­n ÉßY\]^ÏtŒ =ÉU„wúU§`±‰vnlÞK6eâMÏŸQVõ[¶Ö£ûR[”T(vÄLÕn ŠX¢Õ´|d`Ð ’p1Ò³è" ÈÅ‘ˆlôÛÚ¨F–¨˜5%»UâSŒ¹î;×E¬Gª.‹³.ÒœnÈ<{óR÷0.!¾ÓnÖÞ1µ¤”Ò ÔPZ\$ñݬ’(¼±€1Û=èi ÒæŽk—ÞVua¸SÜäÖ[ňÒ^@AIB£ŒSMžFù Ñ­Žà÷¤²Ô¾Ìò›•,‘ãéøÐ»jÖWÖRéþhÉÃÆd縇ò¤Òd€1²–A·$+sèOzG¥ÚÝ=Ôº,®À2–ëÁ#§þžõ µÂæUY¡W#œu¤K/i“Á[¹JùW3|Ä“Ü/~•ÚIÌ–Óm‹xÃc±¦>‚Ýiˆ¦²‡Ûw4¬Üí²ö÷¬KXLS®€#hÍ0±~Ëv§`oÝFZY óŽÜv­;#uu¦›xn?}hÛ‘ÁÏà=ÿÂôk‹­8}ŽåX çÝÉǰ÷¬Z ›MI¥;Z"xŽr(CdV¶M,O+˜þá'ŒTÌMóìP¤•-ÓÜS¹,•g’I£¾’Ñ€ c½€§×!QÛ«' œHcŠ:ˆ­ò¤ñ³)œŽ@ãÐS"iõgšY]€1Î1Û4öÒüÅsòœÓº àOSVúšA nÍUÂÂO/‘jó÷Pœ×)qr«äéÌ~r†GÿyŽkU¸t1u]A x^Ñ‹ýý¸$zUŸ2Êf²šê2ˆ‚]Äp=¹éZ-„Ùœò&§&™#*ä1@ìyéõ¬ù§6/,~ñ$È`­Ž{~µKqtj¬ö"f”3‡ýÜJ9P:šëô›‹]gOòшrrÇø‡AÏ_ ©`ŒÝnÚe·û|ÖÞT›¶²©ÈÀÀª²lžÜI-¤{%]9ÖÒèÎì"<¼°I¡‰S½å§ÚmB™ÎHl`ozÎÕ,« ]Ç8ã#¨À~´‘} –¤m ;óÀ#u)³‚çM¸9ˆn©oaéL†SÑcžÞpeeÚà† z}júiãíŸÙÐLÎŽ™'=G­>¢FEų½kk¬ì,vœô5©g%´:fë‰ÊɃW8=?*àŽæ&ÙÁ{Ԫ䓑^zfíjk©-Ó½1v xúUB—е+}>À-ĘWa»éÞ¹K™œë ªF¬Ëò¶@èµÑÔ’Xíº7ʤ¤Ÿ0cÇRk=%F˜Nòp¬Ž™iƒ5õˆ#¶xµ+#Q¸2InzsÞ«ø–ËíQ¦£fFÉ;‡9Ï=8¦˜˜Ë[¸´øŒ±Ú°í\`7â¯xrHm5Ÿ.fÛ“”à}è«®ÂÑEöˆ”O±ÀÏ®?•bO)´¸›OÜ#Y´‚wqŸ­![OZšJ¨Ñ¯ ’{ÖŽ¥£±yÎ6ÅÑrü–=1ëIî#&ò‡Û†?,ª­œþUoK[i-¤µt&O¹=F}»žÔ÷lLúmÙÓçC"²è§¯ï¹Õ-~ÆëåH‚‘òã­!”c²»±’Ëò+±è3Œý*8‚ݵ¤Ã2ñgŽ¿¯©ÜEYÚ[[ƒoå‘">w{V´¶áãŽî$ûSÜäcÐð8ÿ`&ví©Oh-ö‚*ÎQžÕJå.¤¶¸·Cæ1'©'Ž)¡m>Ák²ÞW”JÎV@qµEZ¶ØúµÓ]¶€pÉoZÇCauj‹2m‘Ôï'¨>§ü*Ì7)aª%¼÷kûäÝ*°è¥.£3æ´þSCÑ3‚ù#<÷¦ORÍé‡PÓ~Ónãty%³YqÉ3ÄUÁÉ`{P3Ó4ë–¹>†¬€sŽ¼×žŽ‰-DžCÄ}ÍBå³Ov.‡)ã¸îuiC*1ŸNMfè—±K¤¼M#Œüà ’µ×bgö‰/ž _JO³»%Ê(vˆê? ÇÁ‡Ú˜ì*@ G^Ô!õ64I-oìM„±|ÑÄIeêÜõ$ú§ÓIJiÓG1GXÏî`î9ç>ÔúGMºPŸd¸Ž?•šDó<~gŽ)·š…ÅìI&A"¾PƒÜòiÑhÚŒz–ž –ÎÐQwg-Ïnçôªº¾š.­ãº[EC"¢ãžùôíIî9¿³£/õözÆ×mÚÊýíÞ"¥N1œóIn0Ò/DzŒo)Àcµù#—5« k=^7óÉ©sœÁª¸5í.çM¿,ø$r6ô>• ÅÞ¢‘ýªb‡íî„?¥ ÜF¯†®RÇQŽ9 ª2ç,zds[ZŒ3yÆ{{s'ÚW¡àãË?Ê“‹g4v¯qc}´‡Š2¾¿=*­–³>Ÿ*Á?äU=}(sV±q·JòMç}ÒFõ¬±p’³y7Ï$P;Žô-A–4ùã–33ÊæiXF2:{æ¶ŒVPZy3·võÈëÏlÐÆAeZƒZíÙ°™ëÔŠ¯k}…Ìðd01`¹ z h]F[˜Þ7³Ls6UF:õ&³!k‹)YÝ\6è²;Šhr;–¹„ª. ®×™×9럧áP<¦Ïìíí2YŽ)¡u4VÎ=BÙ¢Ê N6Eó8õÏ õ¬«!•&—óÔ‚¡ºîMÇéRÝßIý”.•Wyf2ôÈþµ[±[ïÒîDŽ$ãi^sžÏNh_ÐÍΙJ' $”¿x¨ïU|a¤]€.cBwczr9¤ìs¶ï<3`s™Î})·¶òÛºÅ(¾YGlÕ’µ'Óu+«’4óbî+÷‡Ò´4æ–ù%µ¹;³“áÎGj= gKHÚy$ (®^‹_[/¸í${WÇTWS¦·LFr:ñK3âEP}iGr^çšø„†Ô¤¼“ƒœ¡?…Àßß1´#åùÉÝÆxÎ+¹hŒwl¿ ë_¤lf< ’»çèGÒ™¬À5`·°6ýŒUäolõÇaÒ¡èËèdH²E#ÆÊÃd¸5»gyuäÅ®ÞDŒQLq(H}sOp-\ËþŽo®Œ¼‰ ›8àý+-CíCM[wwWÌM¸á=N;Ð„Í BOÝÃr¸ 1½‘‰úçÞµõ;³ªi‘Åe!gE3¯@;Ÿé@Z¤Vé¥q2È̼•<¯±ªš­¥´q]GsóIÖ6Ž3Ÿ¥°5~Þu.2׉p99û9¬gHíD ÁÁ&2§¨ô i³$Wi=ÚGçË©­3un!‘á&2ÿu$'=…ËIæ´‘Œ!Çœ°oá¥×­6Ìš½¶Ó€°ðGLSê2­è¸„%Í­«F#`ä–ÉŸ¨Í6¤±kKnå Ô±xþ´Í7Q†ÚV³\˜¤åCrG©×­C¨h÷éï„ )|¯9oÇöèÑŠýLVÆ+f’P lPBã®95Bú9tÛÏí9¢óˆV_ºê?¥õ+Dnt‹•»4Y;£?ŃޭÞêP]K–²!I޹ï“Üÿ1.u±¾Ñd¨äL/=*Ü®§§% ¸!¶… õ^s¸Ò§?®ì´»[xm|§Uá˜òOr~µ^áfÕ-å®Ä¯«Ý@ªèHÏ ½Ì×~tPÝ&ælãu­!;½Òê>ZB™À ÀúÒ«ñN¦Q~Íφ¬ž$7¯Îüþç·¡ÛkDÜ€äàǽC#âã9àsøSŽæo©ç·pÛÉ©°¾/å–$(^XJ³eEÉô©ü/7™/Ùs–Î×;ð1ëùSèI5K.ng„À¨ ;#ço×°¬è­´ÍB6ŽæfY•p ®ó&’á¤v¸šÕy!~@W<ävõ«–ZeÖµª£Ë" Gmòì㽩°2µX¥²½’Û#—Êæ®\Ë}wj&2 H…SË Èú})ˆlL@k‰.Is 3Ûš³§ËöögõG+òúõçµ-X-•ÑKw•ŠôÛ¼Ý=k'íÒéw7‘³,l<Hî?ýT-À¦<•rÑóå®áÆ:ž•·i{qy§;Ý\D‘JHpä;éÒ˜‘…g=¤î±\ºOJ»ytga¢E¾f‘”#zr1õ¦##T7Mrñß\–x¾LØqŠ·§êV6r[­’É$¸Ì®yQè(èÕºc6 L!Lô?Ö‹pú6¢¡¯ Éæÿ«*zvoz›Åšk\Æ×ÑFÁÕÿx]'¸5Ì/™d<ÂHI>øô¦Éh>×oÁ–ÒF1±ÃcŒŠ×f‹RÓÃÇ8U݆Bxt¡èì¯l"¹›|©ß×­]µƒÉNOjóNç±b%Y`#¨ªl勲ŸºŸÊª?—FpÝ–›íPF$`Ø0‘‘ƤËdÿnˆÃ’²F¤Ç|ÕÚõF1V‚x/ìdf‹Ž§½tI,i¤I«#Ä®À:7ñg=½*,ÏÖ,­^h¯„À£¨fôÏ_­ejW à’ÝÆ#oÝÈ;ã½ q3[P½v²†õg.d Ï!ù»ŒúûTºíÌÒØE$‚7@Å•séž´úŒ¯#_jvÿj»»Ó8À¨#šM/SF£ ÝqÐÐ# ÖGÚ4qo3îP ãpïŸZç|Û½6å/c1è0=2;P…"+¿µé— |·&F|u<•Ï­lÜÞIu£ÛÍ›AùæAý#åWíbiÝ™³Þ¦¯#\ù©¥=àA#VŒ‘έ©Zæ¥Þ+è‹Æ²üùã5­c4c¼ö¨^Þcµ—€¿JÃDZì3Y\¤Ör‹ƒ½O¯z«©E²(µ(ˆeFÈb¸Ozk`!Ö.ìo'þÐÓÄŒW‰L=é4ýB;b-î“tRÈb˜ºŽ¹Ó`ˆ¼³]"¯—˜ò¿=1MÒãÝ©!I™H?3cn;ý(è]ÌG%²Q»/øÓ¸¬SŠŒÏi DŠ\üÎ8µ ÂÃ9·œLrS¸§¹'©‹v-¾NMJ±™>LãÞ¼ÝÎÆÉ‚”ˆ©ð¬Ûˆ9%%¾VCÏ¥Røˆ[3€°¸¹ût–ÐF<°ä™ÁÀ«–ó]NòꀪÛ]¤lg½v½Œc¸Ù㶺F¶µåŽ< ß<OÓÎÑæ´¹“_0ëÞ¢ú º¾Ñw v…öìÏDçõªwz’ÿf¥‡‘ÚÄ»„ù‰úе%—´›ÄÔt¿ì·þ=Ô°=IçéOÓš9ŒÚL’`ýô¦GoʘÈd{ˆ­¼˜QŠ$äNÄŽ”뛩5}:4<ÕVÉPs€x¢â{›~¹H–ÓHY…¹ŒEœsþ{Õ+ƒMl»¤˜ç(>êß4¯f2§Ù ¿·M†yÌ ü­òŽÜûÓìƒÚÞI¤OrȸRÍòñïTµ«[ˆZ)œ³„Ú¤…p9ü¹¬Ù¼Åu™«e=E A2iäë9u'ô>µkB¾ycm;ÊVS–%#±=*·5o.íõKck³Ë1 PäÖm”¿m€é’òFîç¦=© Kx®ÛJiY!U@;›z -JÈösÊCdð;Ó¸žÅÛk$º-ÇÌXâË1lcñª·öséòGr’oY,Äò;b–šö9"ŠÂÞãd7Þ\ñÇR}kgj„B7 ~b)‰½M}SYÔu)ÖÒÕ:0Ìqó¸Še•Óéó›K„”‘&yŹjè ö#k$Ž®»·•ä Ôž#†ÖæšÁLˆ«¸9<ž9ñ¤&Î^öK™ˆVȲTt"ÙM5‰¸„nãs}{U\•©ìRCŽE$JU¹ÀãiÞèsäsúUKÅ Œ}E.£‰å··š&«"£r®q‘šÕ²¼’ᢕbÊÍIœnç­vIègÆÙC}m;ØÀKyÄp}{f£[Yt­f8u,Ë0+g³¹e¶DÑõ`±œÅ8!xÏc8ª>!ÒäÓe…l°ÉÝŠ}DTÒ.ä²¾Î:©ä¨­².t©TÎáˆö<óM‡Aºšº2jvŽ@“€çн¡ÜYE¦}¤@Ü·NÍÉýj: ŽÚí4ÍhÏ ‹å3‘¸δ¯³d˪­?]zÛÓ41r@ö×ÂòÝfî6œw>Õí¤pÄ·¸ƒœŒü¼ô÷¦˜™©s§C«]E8»òUã;‹Ž29¬›¸€•¢X·*¦cAí@X¢<×ÊV_™Xõ$mÛ÷qœñ Q64mä‚ÖèM4O‡ ¹õéÍ7T·JÕEÚÆ ;nUíƒIn2mRT„.­c!VbHzƒÜè*½õ…ÕÕ¬”ax\¾¼õ÷4ÁscªÛ”š8¿Ö `¼q¨\4lf¹³ 0CœM1lT·´žøy‘E»h;4ý^8M”7’r Ï+ìj®M‰4ûô¶µÛ'Í^L‚œìnÚYZÜ"¨rsŠC{6ó OEi 4(qŸâö¨ô-DOg%”ÎÛ[ „wOz·35­"ãNQpÅLr¯ÊAª»Z°‰X’NYsÁ\[×PÄÆ9š9dÉ<¨ö¬d•îh¶Ä`±< ʽÔ÷Laòˆš¹þѤÑÂxÇMݪJê@ ƒüY銋E¼¸k)tá##©Þ€r?•u^ñ"Ú›-3Æ|æ‹c<mßíÜú÷¬û‹d’Ä\ä×–çò¨)—-¤³—EY.S|êvÛàò Iâ/6îÖÔKÕ)ó°Nr84úˆÂò-üõh³ƒË7 ­quawgFbd‘Ï›¼ôÇCùUtÅ»X=“s&@*9úš¥it÷0>— ³lŠ[ym/l1Ã$móbKž¹ÇjÛÑ/¢“Ht•.@Ú:œLÐÆW¼K‰¬<¹ä/åd©ÚFÒ}j…ÜÑ\i"W»#aÚ±Î{Ÿj‰tÛ×»´{K†.ûG”Oaéÿ×öª÷Ìöè©„0äî?¦o"4d˜Êrª@he‘Œñòœä®{И„{¹ŒñÅ…FÉÎ+Z¥ÕídŽO-šÀ$|Ì=½…1 ²‚Öêá`A‰9N¤Žæ™¥"Im¤!ÕŽ! ì;šÁ®¦°\Ž^Fnb~ zV|7€O4æ%%º)\¨=3M •žõ¬æó!›ppV\ `T"kkhÞÊêaÌrãJ{™¶}ž3†Åž¢®xngžI4ýß¼˜c%sIì‹;¹lµ-ÆM±+8êÞ¡moª\Y[í*H•‡ñg‘EĈõÙ#ÕaX‘˜ÉJÏÊ=…s×2_+i¡ÀÀëLMÛLš ådþ$<ÊJè¤õŸ.:Õ;­*=†P2ÝMs8=Ía+3ø‡§¼~V¢„‚ÒG¯jÆðÍܺ€¹š•³ò©îk¢:ÀOâ7fktžO¶‰ n„ÎOj®Ðé¿h1#âWLm#OsSÔdZ*âyíep ƒå1u³ZZÜê6óÙÜÉ‘gßõëõª$À¾¢•`a¾Eêχ¤Ig6Ínž2«ÏF뜚}ÕÓã…®<ë™’2„ŽƒžÕ‘â/ì7òrAo“9¤›¸iæÎÚi ¯œÑmË÷O¯¹«më麪¤ûv®HVçmVàlÝ][-À‘­ËC3d9-Ÿñ5‰$_b¿µ¶Ðï¼#sŒTˆKÉâƒQkíÃkQsÈüªÖ¥£­ÅÚI4žt—nxçéU}Éò¥‚ÀexÎCž(°•¶¼RoóƒoÓÜ:–ô÷{½WÌò@‘†8àš·am¦¸±K2ˆ;Ùúgu ã’Kma¤‚F).pq÷—?ýj_náÕ,‘P]"褮¢{ â8¤”Cw 03ÜÖCî‚M²œ“ó*ƒÚÐ=Âý¡¹Ó£û4dJîT^¢™%õ¥Ö–Ríæ#ßýj: ©Z{ÞÍnS8΀â`¨‘5À¸etû¬´®ÅÔ»h–ñijÍ+9gË)Öµ\–ê ¬-Öaéñ¡+°E]3RžÍÚÊVÝ1ñ–>«x–Ñl¦óeYNIÆ2i¡ë”Tˆ@IëC ©Õ-{£[œçŒíĺD„¦J`€kж’3r—–ê©å}ýªiü&s^ÆêIá7*Fûyw‚ç9Ù¢ˆãU¼;xdx-“œûQÔcn­ïSQ…BBFé'Ž}úÖι ³¿³ó@òÊðqžþø FˆìÂÊ·qƒ±òUÇO¥6ÏK¸kE¾„:/E'ø½iÜ]MmjÆîöÒÚò8ÔAõ5W]ŠS¦ÇpŒlo|t"’`U‚Ð^Ú‡E‘ï üªr;“Võ¦Óã»K›5ÉHǘ¥p3ßÞ©54ÛdÔt—ydDcƒ[ŽøíTõ( 3^F^@£ !ã§NÔ€§{ _ÚÇvª¸…Béß·­\е¦IáG›xŒô4÷¥ô‚Yšêçd!Wzb¨™ÔIýâ¶ÏÞZkPd·w‚+TšÞU-¿î*Ÿ—ñ¤µ½YYËyŽy%ª€³wo*Èåe·&Ó‚Tõª–·ÒؤÈч ›1'@qï@®K¤BnVM6có·1¾sÛ ‘4S †¾hXàg–“Ô—ª.Ár-ôó{dÛ% £çž¿Ê²âŽâ&óÃñ‚}{ÓO@h¹¦@× ;›¶H1ÉgžõJê·;P‘ƒÁ=èi®,E‚D†Q6ïÞ±?.*D¼‡íBæÎ}Ì0ŒñM ryRæ;èõ]7[há}¹«7—øŠ'ƒìùÚ¡REÉ9÷úÒ «E æWOL}'°ú˜^.%t»‚£¤gÀ8‚Þgb¸ùñØŽµ=74ô˜-Ë¥¨º`$By‡§µh®— ôqùØ^6<?ˆšÈ5_²¶ˆmÖLÉí IÉÓÐUM2DÔle…‹3Ä7·“O u,_ÂÓèër‚Qò±÷çÿÕU,g’fHgºà|¨ž”3cMÔV|éwÛbòAhÙŽ}ý}ª¥”Ȳµ•Ãe'?+c<ö¦#>ðÏ¡êBXØ1Ã0Î})’Ý=ÕÓ^?ï7³ŽM=„]ðî®4ƒ"Ï8PFLžœúUù§Þ® lÂ` ÆNÑ‘ßè9  D¾¹°’Cãæ)ñùÑ¥;[j Ã;d\eÇSUÐ ]M"šÎ;i?wå®â’¥‰éXM²ndÃy?xÔR@3scÌHñ6åïŒv©^;WXõÏ2 FOÞçž{SEŸìZÒµÃ`#âE#W[ ]í£bbè=sLV\Ýi‚)À ¤„¨¨µâ7"âÝpn;{{P­ÂDæ9X”—¡i\%œúL·FÝË£*‡SòŽ?pÜ¢5†ÑmØ.ýßx}à*ääê»c¶1 Ç»zdÚ•ñºQ˜<´ØJŒf ÒÄI’<Àq‘ëGA-Í},™¬¤²/»Ì»#±©ô¸î4YMŽ¬ì±¶ ª·åJú–ÿÙÿÛC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;ÿÛC  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÿÂ,©!ÿÄÿÄÿÚ È<^•—§Îº5ˆ=–"Š*[¥î:BrYxö¯9VÀl,+‡‚@ sr çS]CÖ9U¸¢cB !AÍxÕácÞ¸‚ƒ ¬ÊÝ’[JÂÕΣ*p–è,€ž?WWÓçJï=¬‘}tJÌ-S/SHK%Ôö;ó%VŠ4KÁ;@’§;Ù¡£ÖÞ,”ÂΘô&¢X®P=“…_{¹i<½#¦‘Ph&‡)bAêy‘ï^ßs×y–©”Í xœ¥4Ò˜¦j×ОzMªÁ'Æ•Y¢ Jr¥“Ï_QܵÞ>‡=nÌ*¡.6åI=#'¹ÜvžVâ.—rrƒb}]$ØëŒX÷O aËÒGEKeG¢Wë¦eŽk,±ë׎üÈuÏP[ˆ¤´b½BÉL÷ú8ôòá׳Kt«gG™PE©x¤h{ÇŽü„é%æØl"R)Jy> s׎¶ñIR†¢ S¢7§GLK-ŸZ3pùóOC.¤…©xƒS!Nr‘>¥LÞFnìŠhHYDFN ‚kuÿÄ&!1"2AB #3ÿÚñôüK•ôІ†/'ƒf‰gÆ<6HEe4!•篚ðÐü}£EÙ´„QB(ŠÌ‘E‹kÅ êQBÂ41gïQ™(y VhjÍ}<45R(׊,ëMH¿2;u#;Ë+±GèfˆÌ¤Ð•“X4¶¬ù ^$±ö³âÏØu(ýIŸã§’'´ÏQ@бª—}9ÜRª–Þu/o‰*dF‘ÒBBˆ²ªÏ§r8ÿ©%ë>6É.¾ôÝ9íå X½Š‘TäY¢ó§4áˉsF¥Êw9`ñxyT•4ñ(Ú>û¢ivVãŠÚ¢Š¸öµÑ²¬jT}1Ü•¤ã’pÊ:Ä^ÐXrYWomgMjjž‰DX¶âšÔœhø—gÆ]ÈbSŽwŸðße44lD‘vFêF ,©h²^ð§tlßµÓZäV­MvŠtô9#$•À…WUãæwx”t¶Ý!+Œ^N¾éõmusQ{¥‡7„ÎEBõ(ÉÕ<¥êKvÖe´‰o’]ã!;MZÊmf%2¬ê~¹\r™,£qfÉj6âIt’õ%¹F…,ljÓÁ?eÿB^²b‘É(Ñõ)4䩯ɫ_KÜ¢K«þc†iö‰(‘ªqö„°šñ´m¤<7d¨f$»xMTÑØ— d>'Æä± $æ©Æ¼FT†Rõ”>KdŸIJ/²³´üJ=“^ÉY¾->Eï$,Å—jû ŠXäõrĪÐû'·ã>y =ñ¼½¬Ã,Œ‡a;©Käµ]¢–cvÉEuŒ‹;¿2Ì¡ÕG|‘©< ¹DJËN+®HØÖ4²Iu”]×úîÅ£ò~1—eð–Ö¢ûÃâNº­+,H’±5Q—·*é9U£',¨Ž¿%ûÇ&N;&½¡.²’ ñ¡¿Yd«²Ž¾$d»|âCãûN·ÉõÎÿè¾“ÅKsÌ/3®µë’¶a7òq¸+-“‹ÔÁCXæÇ6©àRìg´ #qi§NMŽ"£·Q7¬?¯ê†=~R²õ%é 7Bd•³=‘gÓ–.†ÈÈ^ÑåÉŸ?“ããET“ï’SY^ÐÓíCfÒ2?Wß)Ÿ®^#ÿ8î‘Ëç~¼˜i¸¾Mqü¥¿O é7×úJáÝŸÿÄ!0 !1QP€ÿÚ?Pº·§*³±bê/”OOäàû™ðCÃI:ÒÄ~H˜¬QÛÕOâIà[SÁ+“î?Ú‹¨9-†IänN¨Ÿr2ë=6F$ðv­ø0G±äí0.ª|“äðNåé+Z\ÛrÄ¡ñ_”&w¨à°Ò¤èÉ#{¯Iààít’(Ͻ’IÁC”Nô³ÁäjK—ÉÝøÁaN+Câ ,ü“70`¾â’5µýÑ'’Kàkaü#CmÏÑÆÔûin9Bù;ŒÉ'ƒ}†‹²>WM…Õ£ ‡Ih‡”—µ{…Õ¹Ó”\ð=‰-©-d—ùFØ;^§(•©(g+Sî«Z^’6X’vØÉMå#Tw£¼{<¥Ú0½6ÁÛEÅ?cÝ„ppJÐkN¢w½p½{¢iœÓbv/äídô"n°Iàv!á–võNŒƒÁrM.àåI'(yDAŸš±¾GÒ'¾EÎhÖæ÷%\ˆñI›£þé:¡¢6ôÁæÇ¥­.[Cr(¿åÒy<’F6;·É$¢=Œ²éÒFø;…i‚™8h¿Àúk;díéY;X¿‰:2Q ’ëÊ<àLÄîBêTc$%ù079#c2w©“¹gS»I¥sù?Ôá‹«Ü„>†yÚœÞã^ÅÇ­&ëQô<—3Té/òcBqöž K$é­o†[=' •G$êŽí¡‘&|Ë<£Gì‰ÀÖåò©w¸ÒÓÏzãÐéXgø©Wf`Ö¡¯dô 2Ó*–ÙTœÇ  ªÑt&¦ŒÇDo¸æñP?Üpâx8—V€è˜{¬Gw!+Ónû¥“µ¾HwÏ3v³‚ â`ÇþăàRbòâ~„Ã(íîU—¢]áú…šj*ÿ2ʧrÆjåÃè¯QÛi^"…+Þ% _6KkÄÔ˜ê`¼¿Ä¾A=Å¿¯2Þ"~™”l1ž§6„^y"ÛPt|øŸä‹Ã˜rEz¿æqB¥¬ØžWÚÏ1Z`f 0g„ p‡Ò2hÇÞ¦SËø%Ëq¦&'˜4¤æ{ƒó %^ŸÄi\õK 2Ÿå™´QüÃä“Ô}lj78#0W°¨ÞµþT¡½ûKŠ`¸`°°»~!+»–x–ìߎ¥“é—â½Jav&9–½buXsó(¹ˆ®F¼\î=¡(AQ£˜AîQ–r÷÷ÔK¸q DÉßdܘráùžsÆI}’ëH›=M—÷ jZX^jz¾ólÉ2Qˆqë©j&^ážÔ¬ê oK†XXÕ·ó6ªó.[•½û”Úºf.cƒP ãŽjk|@4é×¹L†üb?l|B¥˜”)Ó²£x&É—[Ärð¹&ñPc¦sRñÔR”ð€X¨–®8–2Ñ[…f³’¸—QÿÈ”g7XÔòœ˜aoLùy'‰ûBÔs˜(5‰LØÉ÷µî6(FóÔºe­U @ú%ÀË·” `WI¨~ D(¡¯ˆëk¼žfovÕ†DZˆE½%ðSÒ‘ÄÞu/®~„kÂË¢Ãx•º5©E·Éë_£5¸­À*É¥w•æ§¼²â{ ƒ¦¿-SNš…Xq-¶mnÞ‹fXˆCY1¹œHñ :œÅÊþÐÛ\f†e:b­X+ÊT©AÝW žÍC4üžã‰Z Ù_ÖQ.íœÉe­·@;‡¸5uÁ¸}\1Áä|LV–éš_eÀ{ñ ŠWR¼“›oÔÓ•>èÛpÒ¥·ËêPr½Cˆ\,,E/"ŽÈ¾M²¿UÍqn XÏLBŸ€Õ¼¾ÉE¬[9ü¥k¼þ#)Û©I{%qô“ª¢¼¶{×Ð\Óºée‡±W™\œ œu «ØX¹7ö3<'©úi]6Þ%Fuû£MWô,jàrª\)¸^§ò ¡z]À°›¬LåïO$Wz­ÄbÝE÷]™`oå—ûÌ”l »}ñCØÆå”_Ù(äö@ÀU™ñ9†`Ïþ¢#òægGuÂ|¤&ÝlýC –\ÉsÊÎoˆ³A˜Ø _Ķ*<©ª*ÌÇDQcÓ*Œ»ä†Ì#ƒ² „áÔ@ð¬Ìø†jõ¬Êoàûƒh¿ºbûƒVDZ~¥+«i½wy=K¨±×˜qHp¦¹€KÚ²î\g£ö™bÔ°P½‘V7ù_Ä r"€ÈÞež’ç0q©cø?™†s.,¡™óöýjüþ‘ev.z`žÁÔwêŒÅE♦,ÅÜ?‰ÿ› (˜Æ{‹+g]Mð‹t/Þ'ᙆ³Á¡ª8Wp¾eø›1ºÆtGe°˜á[ÒHÅY‹‡d%­Õ›´)@ßñ+=x•IÏw/!Ö¥¤¤©}bPà¹QvºIr3©áÎ7O%*`wr€îÐ0òL(›É¦q‡ý‚¾md°Í¡q{ÂÐwÛ1 ·> P#xGŠWV¥jùn1¡®;žoÛèˆBÜÇÓ88ÈÎV0KZÄo‡¶£ÔWAaÎÂ# J§Ì%œXE7*gôìL–ìfω‰¶WÄØÇ¦wC=~µV/;¨ÍÅwä¦&Pá!Å«y¨(òye)ýˆ¨§¥‡DÛz̶Š‹)\²™CŸÚ5»ÂJˆÑœË}Ìan÷uoaÙ?Ü>¦ƒÔºl`¨eŒó .œncC?¤AQá^!"B±Îð”˜pF%¸ä¸7™e9Ñ\6|Nc<žfmÈn.[!§‰ø£Ìÿqý}\‰.)Æet¦˜ÅfjÌGcWܲͪ²^̽G±Zû¢`1¢(°³5üAl+šB-pÐÒéw$(lîDÑíÇ´3Gâh~åø•¡J@Ô¸³ÄB¦+ÊV«s2U¦Ìœq̽3Gí09˶ªS¨ ~ˆÝCö¥ÈŽe^¨S:7ùE™ÃHCò¡Ÿ!ž?þ. _%L4ãýÌûG)6[ÂR-ð¶ßA½Lù-¸*k9á4]çs1(¸¦Ò dê0ÖÿÕ@ˆ1<¯9Üì²ZØþ¾˜1€XÕâšL½F’o7†ôPˆRb:‡5§}ÔbýÒ"ðÕÊeÖÞ&ÛÚe´+QÚ£'>&Þ§tz‹—Zs.G–ú‰Q¹]KÛâ`±É=ij^æØÄZº;”§K{ƒ§yEGd¥/~à^ aĬ_;\É穇:±øqwRX÷aƒ¬PÒÖÙüÌ;ÅŸÔ#V·hàá¦áÉQµ¡”BÅG¦)$ –ŒËYŸÝë¦*,ÚŠqîæ –3ˆ ûÎ<Ê]VI…ºàeÏlU¶9P€2ÔU¸˜sIÿ¨â ›ó1Uá˜G’UÕwÞ¥:KlºšS°­E–ê!w*V—ˆö¯JŠÀPhˆ$»£æb‹iVf˜.ÉŸ‰™0üYQ®s÷0R´¢MþÏä…àµ!£)8ÎH••æ™þ)˜M÷Ñ Yi‡ -¯â4W%ÚKI¶Jñ^'#cÈV1|@²1êXœvKÝ[óUñùœ<&…©×¨ÓaÄekdUÑS$Öß2“qÕªæO‡QV¡_̼[x´4† ÷d·Ô«µå®W[%IÔ0õUˆ¹9y•tEÈrÔ§’ß´®þˆŒ,›e|iì„…ÏCUܸ{ŸQð3^3c‚`Å›BŸpêŒÊpîãG üÄÑì#ôê" ¨¢7îW†·qr6±‚XÊÓ;áZ9‚*Ëé©K=‚õÙ*¤¼Ç?XÛ?rQ((;.7ù˜[¸«ü ZÃ’^ æh~bs½æbý\8l‡îL&î,‘ùYŒ½äâOˆ}~’ÿÚ Êû½šîÙ®æ¯õ×ÄÊû+gBÜk_Ñ*û@qoRÊNø?áÆ¸½Á^÷‹@SZk@¨rÃ¥ì>æQÓfƒ©^³ã³]>ôu)¹ïýåÁƒæI’Ù¡Fiñ2¢á>*Ü-¶ïu W8ÕeïŽ2-œrµà mçs§3…÷{I÷ Þçý6¹þ®c„¹rã ŒpŸ—v°ŽÅöçUÔŒãÉ%ª‹€K÷Fä¶ =Òxô© nŠ>R*£,¤:'ï´)j£]ͤ·éS@ ÿÄ 0!A1@qÿÚ?@¶2¹|¥ù_ñº<¯ö{—‡îF="&]F\cý,c=€y‡ý‚0ZCMGUVB|€%¡@Ÿ¶¤ ~N‹¸äÔòT¸«ûØ8<xÞ‡7–$¬»1ØÑÑ„w4c«¢spÊË«„àâµtpáî÷‘ìåç^óû>ðqò}äϹt¨ágÖ¹1_‘ŽøÈh[¸p½A­bE¨²OΙ /1W”÷»NH„ÉA#Hs…Š +1“¹.|ÿÌœHœ'f$ oÉÄ•¤àyÏè1ÐÎñVòàc¾?1+Ù—8Ð_GxÀÌ3•O»,®)÷Àž!h·‚¸+¢'gï"ÐHİFD#€ù” !kŸ›À:E]§X!eáýaÊëyzæÒL5Ž š‘ž VÁ6Ÿð³6b‡EéÜpzÖ);œ"—³œÌc¶¸ß|ë×YÅ8Œ¶‰æe(‘ÜNÎŒDõ­ãG‘Ã7n}ò¡”\ÎÌUL=Ê?­áˆ(2n;ämu!¡„HˆÑØÔ{ûâ&ˆmåNnr3‘ÿG%ÊõWxc†g§ÝŠÒGRp¾¢~˜8 O«åtxÉ8…@8ÆCr³Vbä8È©!ËÛ hÆãêÉš ¥Ï­}ð°AD¡ö›ÃZ…IÓ~ø%XáT—Óõ ƒ 3\a"Å@Î)wFoŽDt*ã!7™®}säœrÀh|üaºj`†‡¨(Îו‰0ô€MS À#¾˜’‘êP?:br)gíÎIä—nñäÑ£檛YëÔp„ ‚§ýÆ%E{þð!DÄL¡³ $‰zWàÁ–R-=rD‡bb/iãÛ$äBáìHgïœåTçç+Á$¬]ññÎÖ0Ird‰©aÎ $†½ÏË“zBÜà'@Á‘ûb÷–ò·Cv~WàF2†¦[/×#B®ïÇ&AÜ’ˆöc×%# l<øÅbiB¥qZáúã6bB¾$‡³«}ãÛ$‹µoY$Á2|>˜5t¸¯“’1YËÓ*°­šs´{þ˜¯4’l•Åñûʉ'€¿³…=tC5•^Ï0⊪ð;}°¨ª@Ol%s\dlÚ©ç 6§c&)ëKï &B"žç×"â‡ûŠqFXl¼V;¯oÆÌ‹=^†ˆ‰BRæ~tÇ’J‰@?ÎÙÍþr`‚½(ü>ØÒ(¢™aF)¿ÁÏæðqCHÿ¦BªíªŒLÐ:8#—%l’GŽ1" Š…¹>ÿ $üá’·TêNEIÌ$ÿ¸ŽÐ,®Ïle€†Ž‘ëˆ9aC‘bÖ¹-L}_®Ö¦GA»)c“Îvƒ sƒ‘ADD,߇ …_RñÇ&9(¦:˜h꘤ž>¸øCäצ­‘0—óûXÒ‰Zt;çÂÀ,_3õÊ}E\`Q3/¯½`rˆnKˆp’‚S¨"õ2½€Iýc­l¸ÅÔ–¢­Œvã G~O¦ 3`(ŸL¦óX×F½²(Pf%ûúãbêWwX1f©ëùXÌQRw1¾Ïïä’ä…5‰¢ ¯žqÈÁ'¡MúäÄͰÏlkŠ jzmËf6Ñ]O×ïŽÉdÙÉõúç4¨ÿ™,Pty0GCB–'zâ±&Rc@Wï™ýØ,•4À©$ŠN¸Òcb“‡²FŠÀt<˜Mh‚ÒÔºs‹ 0¨é?Ì ä‹Ew_Žþù/ƒˆ’%Sé{×­b€Å '8:ÃÓLâA@ ïÚqàD¡ÇÓö¸a @‰¿Î ŽÅG“éôïr2D »+½`#)êôOYÇÛ‡…|V§ï…®ÌÜÅÌÙ©éöÅ(D*’†½òHY#vû⤕Rùb°"3s¦œ7p‰ÍMÏnýða"¤ã<»ßœ$]OV6GR PcžÝ)ÈJOúÿ3ù¸€=Ca‚¾‘Ž‚A£ÇÛÎ øÞEëÏ®J]/Ž›Â¦†d!›“Ö'`\LÑ>ó^Ù1ƒ"O›Ë ô0¿¿|B°ŠpµÓÛ-‚#ü^!áÏu„\ƒ¨ó# \z—HŒ„ÌDqÓgÛ34O]sÇ$½½~ÎOMVݧÓéo<™‡¯\l½‘Øñ—³÷€ ˨nýÈîe8¥Ñ2D_Øó–¾6Êa{tÅ* P9ý÷ýåŠ0„±~ø0Ô‹Vâ®DƒnÞ~1Jn8‡“é¼$€H2£óï”Hd"Ë£™^¶*m]W¬ãåe?éy564<â¦õü8RÁÏIùX7cŽÈLV»aEA75nœ×Zîuz޹»’À38dï8çÃtõ­çòqÌð¤&D¶=1„P „c/B¹|$“pÄtÄr6ÜûlúaRxù8`’ ©±ôS £“ÕÌKK×~p´‘x–ýŒzÁp‹b<ýÌ™D9 …ðâ £g¸Ð×L”ÏVJ/ϽuÄw6ìõÄgáœi Ž d¤Úbiúà$ÁÏG«Ô“}p3»,£¿QùÎ GD†G3çYD†á¸éë8 <1õûÙØ©ñ퇊A•˜3Z÷^î^…±¾Móóy<'‚ÆøcÉ€F"-‰ç·â²kÝ‚Y ¿| =[RêÎ÷Š’hµg| 5@t6×ËË‹Ç"ÔL¹:"Þ¬%¤fUÔÉÖ†$6Ƨ£öË‘î<ãZ ÊE8¡T—RŸhûà,e, òëL{`SØí莦GúÈ‚’HØ÷:a r“ETÍx¯lL2›_5ƒ5)C2¢xªó¢ôC©ñùÅÖ©÷|÷É€!B5Wõúd¸Æ zǯç î W©× Pr†·Ù±” $=²@€Ñ?Œ‘¡ëúþb>ºM &w±Ò¼b¢°D…a¤ŒdTA"U¾·‘<‚D*`^¼d˜µl Gp² Óð}fæŸÜ¨ÏáÎ÷ÝÄ©y\=zdµ$>Åù×ÈØ 8ýd£B‹,ð×=p¹A1” Ró_|Ñ™¢,‡ÎŒ‡ •2qüŒh\S Dðøã¿L@4’ GnŽ•¶ N÷þbç3M˜„Ô=°„·Eyë…¾R²Ýï´Ï×Z^æÝ·Ò1c2(±;Lµ@ †¨"HÂ&œrcis­¥5:D%Y;Zä‹››:û>Ž(o @|<ÏNcKÔ~ÿáVfÉëˆÀ¹¨}k ‡€ç®ÿ¾ØbDôúþㄈXû8’òq&À•v¿Î ,ÐbÙ×,x%ßgιqG¡¿lÞšC«¿‰û¸€a5^ØÀà0v›ç 0ÀuÃ^l ‹ΓÄkÎ0">#möë„Ü’ÉØ®w…08n:`R¢¾ˆí€N–>„GŒˆÐ’)3îzä|Ïü‹ÈU;œ8èe<ÇèàkRF»á¶îÞŽµÓÓ7WTÄssߦžÆ¾5ë³6[Dued5&žùJ*Ü•Vncp‚’œ=cžùšDûk% ¥Tq˜d Êž®Zšè êé<Åúâu«\‡ÎÙÂa 1(Š.MBd<†žÆˆ›ðgwòïÿ/® XV`±[¸~™RyDÏÒp‰H`s"פkÛ$)š‘ç­ùË`ƒ¤A&ñZl¥MÙ’`ÛS¶zÏß&Ð\âYýyÀlH­I¯kÈ¢$*P‘Lþ¯$˜4“ŽmUPÙß-pA5óÎ@+ ryvà Pó·óE‘Ô`˜Â@šz¹8n QUéŸÀÿ¡î„dÖXy‡›ÄvU`,0±÷ÈVŽË¯jÂH§“ôæ14€ï‚O2ˆ²FÜF½¾Ø ;E¨µ_9Ç(<nC —EÖïèúc7z™1wß ¡*P¸ ˜(0•lõùÉ€Uj çéôÄU=^QO¶(^#e.›À”iKç ÓKrž{ôú rlOHÏ‘þÿâKfUçÎ;É¡ uâ½°¨À|íŽ/ ƒI´:xÝ뜔° t'º{欶MË×÷•&榟xýc¢K ;8¾b>Ñ“’-›Ž;ã€á¡;î^:b1C›azí¬‰DÙ&>xÈ2ˆÜ_ß‚Sµ}þ˜Êɪ…GYó±jŽñ=ógŽBQáñÛŸl0#$ÿ9Ü}póÿpØ ØšxÝáš 8Z‘0`gcz }|™9y/ON…ÿ1`Ò’ûuÆS"݇M÷®»ÆR­£‡V}¾¸ Q"H¨9A"D1@˜ˆ¹ s­u^&~½ÚØ¡ÂL"Íûă,KƒôÁúKTS¬aDëß#ÅܯS…DÆÊÆv_rB]âb£×¨m:šÅ€–°ÜÔêy¼VFu)¿®2 Z7º29M¥¹‘‘%8™øa¥…8"Jùã‡;…£XÕÌc £™ÉÆ$ê1r`þpàŽõ¡LûO1é ÕW‚øüeû㸾lwöÈ#ÂebCÛ+¶ÈI•Í|Þ3da8dÀ «‰½¤|bÙîgg' ÷d‘7ŽZ™ê/õõÁpUxvî§×Ñ`·Ã*ÄŠ}p"V-ÐÞ«ÆLY%)}ò‰b ‡iúâ Æ¬(1óŒ°älÄ>ó=ðŒ„”î’{Þ#&8ü| ’ÎHFöV%^üôÆ…Á Ã5EåFë¤Èâü[a0Ö-bÈîýý°%a“ó›ô@æQ–§#ýX„ÝÛ Å³xJÞ4u—üÉÈÚ‘ £ŸÎk­ D£Æ>A1=«©ì`w"ZÛää Ò‚´#Ÿlk.øp4¦¶|q°û¨öPúwĉEHŽÞØL°¢$ûqýÈÚáûbK-ÎÆ§Õƒß!°ô>2KrE7Óó‘,•|ˆçˆôÄ`©qØ(YI›lMN™ÐëïŸÂpi©§LY\f‚@RMœÄ5'造¡ƒVùç}7– 41½XK õÔóß'Œ“V¦â1¹#ݾxÇS† ™“_Ÿe†Í½W§û’9Ý!ß~p†4Ô=1¬ƒ£G³0ñM½Gæðˆ3PÄWwm bèñxX“1 MH{ä1­ ¢gÉÉ8RÁÉ~RBøðäõü°›»€¾8"+‰–ñ´ *vŸO®:oy¾Ãëï+$ ¿Ü˜ƒ)´¹ ’ ‘ G¦ð’ ¥Ì̲W¶)P’±+η±™dÌGŸÞX”ÉíÀÔÈÄRÔ–8JÂb)K]ëÛ¢¥´Î%Ø¡$‘ìá+ê ;ô‘x[Ù÷ÅsÀÂÉÀ|ç´†ºƒ>xÏ@·ÆJ‰ (SM¾Ó‰§Jù¨œ\¦U‡"ËM2çL * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include #include #include #include "test_config.h" #include #include #include #include #include #include #include using namespace std; using namespace mediascanner; class ScanTest : public ::testing::Test { protected: ScanTest() { } virtual ~ScanTest() { } virtual void SetUp() override{ test_dbus_.reset(g_test_dbus_new(G_TEST_DBUS_NONE)); g_test_dbus_add_service_dir(test_dbus_.get(), TEST_DIR "/services"); } virtual void TearDown() override { session_bus_.reset(); test_dbus_.reset(); } GDBusConnection *session_bus() { if (!bus_started_) { g_test_dbus_up(test_dbus_.get()); GError *error = nullptr; char *address = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, nullptr, &error); if (!address) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to determine session bus address: ") + errortxt); } session_bus_.reset(g_dbus_connection_new_for_address_sync( address, static_cast(G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), nullptr, nullptr, &error)); g_free(address); if (!session_bus_) { std::string errortxt(error->message); g_error_free(error); throw std::runtime_error( std::string("Failed to connect to session bus: ") + errortxt); } bus_started_ = true; } return session_bus_.get(); } private: unique_ptr test_dbus_ {nullptr, g_object_unref}; unique_ptr session_bus_ {nullptr, g_object_unref}; bool bus_started_ = false; }; TEST_F(ScanTest, init) { MediaStore store(":memory:", MS_READ_WRITE); MetadataExtractor extractor(session_bus()); InvalidationSender invalidator; SubtreeWatcher watcher(store, extractor, invalidator); } void clear_dir(const string &subdir) { string cmd = "rm -rf " + subdir; ASSERT_EQ(system(cmd.c_str()), 0); // Because I like to live dangerously, that's why. } void copy_file(const string &src, const string &dst) { FILE* f = fopen(src.c_str(), "r"); ASSERT_TRUE(f); fseek(f, 0, SEEK_END); size_t size = ftell(f); char* buf = new char[size]; fseek(f, 0, SEEK_SET); ASSERT_EQ(fread(buf, 1, size, f), size); fclose(f); f = fopen(dst.c_str(), "w"); ASSERT_TRUE(f); ASSERT_EQ(fwrite(buf, 1, size, f), size); fclose(f); delete[] buf; } void iterate_main_loop() { while (g_main_context_iteration(nullptr, FALSE)) { } } TEST_F(ScanTest, index) { string subdir = TEST_DIR "/testdir"; string testfile = SOURCE_DIR "/media/testfile.ogg"; string outfile = subdir + "/testfile.ogg"; clear_dir(subdir); ASSERT_GE(mkdir(subdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); MediaStore store(":memory:", MS_READ_WRITE); MetadataExtractor extractor(session_bus()); InvalidationSender invalidator; SubtreeWatcher watcher(store, extractor, invalidator); watcher.addDir(subdir); ASSERT_EQ(store.size(), 0); copy_file(testfile, outfile); iterate_main_loop(); ASSERT_EQ(store.size(), 1); ASSERT_EQ(unlink(outfile.c_str()), 0); iterate_main_loop(); ASSERT_EQ(store.size(), 0); } TEST_F(ScanTest, subdir) { string testdir = TEST_DIR "/testdir"; string subdir = testdir + "/subdir"; string testfile = SOURCE_DIR "/media/testfile.ogg"; string outfile = subdir + "/testfile.ogg"; clear_dir(testdir); ASSERT_GE(mkdir(testdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); MediaStore store(":memory:", MS_READ_WRITE); MetadataExtractor extractor(session_bus()); InvalidationSender invalidator; SubtreeWatcher watcher(store, extractor, invalidator); ASSERT_EQ(watcher.directoryCount(), 0); watcher.addDir(testdir); ASSERT_EQ(watcher.directoryCount(), 1); ASSERT_EQ(store.size(), 0); ASSERT_GE(mkdir(subdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); iterate_main_loop(); ASSERT_EQ(watcher.directoryCount(), 2); copy_file(testfile, outfile); iterate_main_loop(); ASSERT_EQ(store.size(), 1); ASSERT_EQ(unlink(outfile.c_str()), 0); iterate_main_loop(); ASSERT_EQ(store.size(), 0); ASSERT_EQ(rmdir(subdir.c_str()), 0); iterate_main_loop(); ASSERT_EQ(watcher.directoryCount(), 1); } // FIXME move this somewhere in the implementation. void scanFiles(GDBusConnection *session_bus, MediaStore &store, const string &subdir, const MediaType type) { MetadataExtractor extractor(session_bus); Scanner s(&extractor, subdir, type); try { while(true) { auto d = s.next(); // If the file is unchanged or known bad, do fallback. if (store.is_broken_file(d.filename, d.etag)) { fprintf(stderr, "Using fallback data for unscannable file %s.\n", d.filename.c_str()); store.insert(extractor.fallback_extract(d)); continue; } if(d.etag == store.getETag(d.filename)) { continue; } store.insert_broken_file(d.filename, d.etag); store.insert(extractor.extract(d)); // If the above line crashes, then brokenness of this file // persists. } } catch(const StopIteration &e) { } } TEST_F(ScanTest, scan) { string dbname("scan-mediastore.db"); string testdir = TEST_DIR "/testdir"; string testfile = SOURCE_DIR "/media/testfile.ogg"; string outfile = testdir + "/testfile.ogg"; unlink(dbname.c_str()); clear_dir(testdir); ASSERT_GE(mkdir(testdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); copy_file(testfile, outfile); MediaStore *store = new MediaStore(dbname, MS_READ_WRITE); scanFiles(session_bus(), *store, testdir, AudioMedia); ASSERT_EQ(store->size(), 1); delete store; unlink(outfile.c_str()); store = new MediaStore(dbname, MS_READ_WRITE); store->pruneDeleted(); ASSERT_EQ(store->size(), 0); delete store; } TEST_F(ScanTest, scan_skips_unchanged_files) { string testdir = TEST_DIR "/testdir"; string testfile = SOURCE_DIR "/media/testfile.ogg"; string outfile = testdir + "/testfile.ogg"; clear_dir(testdir); ASSERT_GE(mkdir(testdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); copy_file(testfile, outfile); MediaStore store(":memory:", MS_READ_WRITE); scanFiles(session_bus(), store, testdir, AudioMedia); ASSERT_EQ(store.size(), 1); /* Modify the metadata for this file in the database */ MediaFile media = store.lookup(outfile); EXPECT_NE(media.getETag(), ""); EXPECT_EQ(media.getTitle(), "track1"); MediaFileBuilder mfb(media); mfb.setTitle("something else"); store.insert(mfb.build()); /* Scan again, and note that the data hasn't been updated */ scanFiles(session_bus(), store, testdir, AudioMedia); media = store.lookup(outfile); EXPECT_EQ(media.getTitle(), "something else"); /* Now change the stored etag, to trigger an update */ MediaFileBuilder mfb2(media); mfb2.setETag("old-etag"); store.insert(mfb2.build()); scanFiles(session_bus(), store, testdir, AudioMedia); media = store.lookup(outfile); EXPECT_EQ(media.getTitle(), "track1"); } TEST_F(ScanTest, root_skip) { MetadataExtractor e(session_bus()); string root(SOURCE_DIR "/media"); Scanner s(&e, root, AudioMedia); while (true) { try { auto d = s.next(); EXPECT_EQ(std::string::npos, d.filename.find("fake_root")) << d.filename; } catch (const StopIteration &e) { break; } } } TEST_F(ScanTest, scan_files_found_in_new_dir) { string testdir = TEST_DIR "/testdir"; string subdir = testdir + "/subdir"; string testfile = SOURCE_DIR "/media/testfile.ogg"; string outfile = subdir + "/testfile.ogg"; clear_dir(testdir); ASSERT_GE(mkdir(testdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); MediaStore store(":memory:", MS_READ_WRITE); MetadataExtractor extractor(session_bus()); InvalidationSender invalidator; SubtreeWatcher watcher(store, extractor, invalidator); watcher.addDir(testdir); ASSERT_EQ(watcher.directoryCount(), 1); ASSERT_EQ(store.size(), 0); // Create a new directory and a file inside that directory before // the watcher has a chance to set up an inotify watch. ASSERT_GE(mkdir(subdir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR), 0); copy_file(testfile, outfile); iterate_main_loop(); ASSERT_EQ(watcher.directoryCount(), 2); ASSERT_EQ(store.size(), 1); } TEST_F(ScanTest, watch_move_dir) { string testdir = TEST_DIR "/testdir"; string oldsubdir = testdir + "/old"; string newsubdir = testdir + "/new"; string testfile = SOURCE_DIR "/media/testfile.ogg"; clear_dir(testdir); ASSERT_EQ(0, mkdir(testdir.c_str(), S_IRWXU)); ASSERT_EQ(0, mkdir(oldsubdir.c_str(), S_IRWXU)); ASSERT_EQ(0, mkdir((oldsubdir + "/subdir").c_str(), S_IRWXU)); copy_file(testfile, oldsubdir + "/subdir/testfile.ogg"); MediaStore store(":memory:", MS_READ_WRITE); MetadataExtractor extractor(session_bus()); InvalidationSender invalidator; SubtreeWatcher watcher(store, extractor, invalidator); watcher.addDir(testdir); EXPECT_EQ(3, watcher.directoryCount()); EXPECT_EQ(1, store.size()); MediaFile file = store.lookup(oldsubdir + "/subdir/testfile.ogg"); EXPECT_EQ("track1", file.getTitle()); ASSERT_EQ(0, rename(oldsubdir.c_str(), newsubdir.c_str())); iterate_main_loop(); EXPECT_EQ(3, watcher.directoryCount()); EXPECT_EQ(1, store.size()); file = store.lookup(newsubdir + "/subdir/testfile.ogg"); EXPECT_EQ("track1", file.getTitle()); try { file = store.lookup(oldsubdir + "/subdir/testfile.ogg"); FAIL(); } catch (const std::runtime_error &e) { string msg = e.what(); EXPECT_NE(std::string::npos, msg.find("Could not find media")) << msg; } ASSERT_EQ(0, rename(newsubdir.c_str(), oldsubdir.c_str())); iterate_main_loop(); EXPECT_EQ(3, watcher.directoryCount()); file = store.lookup(oldsubdir + "/subdir/testfile.ogg"); EXPECT_EQ("track1", file.getTitle()); try { file = store.lookup(newsubdir + "/subdir/testfile.ogg"); FAIL(); } catch (const std::runtime_error &e) { string msg = e.what(); EXPECT_NE(std::string::npos, msg.find("Could not find media")) << msg; } } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_dbus.cc0000644000015600001650000000616412672421600022064 0ustar pbuserpbgroup00000000000000#include #include #include #include #include #include #include #include #include class MediaStoreDBusTests : public ::testing::Test { protected: virtual void SetUp() override { ::testing::Test::SetUp(); message = core::dbus::Message::make_method_call( "org.example.Name", core::dbus::types::ObjectPath("/org/example/Path"), "org.example.Interface", "Method"); } core::dbus::Message::Ptr message; }; TEST_F(MediaStoreDBusTests, mediafile_codec) { mediascanner::MediaFile media = mediascanner::MediaFileBuilder("a") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setAuthor("c") .setAlbum("d") .setAlbumArtist("e") .setGenre("f") .setDiscNumber(0) .setTrackNumber(1) .setDuration(5) .setWidth(640) .setHeight(480) .setLatitude(20.42) .setLongitude(-30.67) .setModificationTime(4200) .setType(mediascanner::AudioMedia); message->writer() << media; EXPECT_EQ("(sssssssssiiiiiddbti)", message->signature()); EXPECT_EQ(core::dbus::helper::TypeMapper::signature(), message->signature()); mediascanner::MediaFile media2; message->reader() >> media2; EXPECT_EQ(media, media2); } TEST_F(MediaStoreDBusTests, album_codec) { mediascanner::Album album("title", "artist", "date", "genre", "art_file", true); message->writer() << album; EXPECT_EQ("(sssssb)", message->signature()); EXPECT_EQ(core::dbus::helper::TypeMapper::signature(), message->signature()); mediascanner::Album album2; message->reader() >> album2; EXPECT_EQ("title", album2.getTitle()); EXPECT_EQ("artist", album2.getArtist()); EXPECT_EQ("date", album2.getDate()); EXPECT_EQ("genre", album2.getGenre()); EXPECT_EQ("art_file", album2.getArtFile()); EXPECT_EQ(true, album2.getHasThumbnail()); EXPECT_EQ(album, album2); } TEST_F(MediaStoreDBusTests, filter_codec) { mediascanner::Filter filter; filter.setArtist("Artist1"); filter.setAlbum("Album1"); filter.setAlbumArtist("AlbumArtist1"); filter.setGenre("Genre"); filter.setOffset(42); filter.setLimit(100); message->writer() << filter; EXPECT_EQ("a{sv}", message->signature()); EXPECT_EQ(core::dbus::helper::TypeMapper::signature(), message->signature()); mediascanner::Filter other; message->reader() >> other; EXPECT_EQ(filter, other); } TEST_F(MediaStoreDBusTests, filter_codec_empty) { mediascanner::Filter empty; message->writer() << empty; EXPECT_EQ("a{sv}", message->signature()); mediascanner::Filter other; message->reader() >> other; EXPECT_EQ(empty, other); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/test/test_qml_nodb.qml0000644000015600001650000000224012672421600023115 0ustar pbuserpbgroup00000000000000import QtQuick 2.0 import QtTest 1.0 import Ubuntu.MediaScanner 0.1 TestCase { id: root name: "NoDatabaseTests" function test_mediastore() { ignoreWarning("Could not initialise media store: unable to open database file"); var store = Qt.createQmlObject( "import Ubuntu.MediaScanner 0.1;" + "MediaStore {}", root); if (store === null) { fail("Could not create MediaStore component"); } ignoreWarning("query() called on invalid MediaStore"); compare(store.query("foo", MediaStore.AllMedia), []); ignoreWarning("lookup() called on invalid MediaStore"); compare(store.lookup("/some/file"), null); } function test_songsmodel() { ignoreWarning("Could not initialise media store: unable to open database file"); var model = Qt.createQmlObject( "import Ubuntu.MediaScanner 0.1;" + "SongsModel {" + " store: MediaStore {}" + "}", root); if (model === null) { fail("Could not create SongsModel component"); } // Model is empty compare(model.count, 0); } } mediascanner2-0.111+16.04.20160317/test/test_qml.cc0000644000015600001650000001400712672421600021713 0ustar pbuserpbgroup00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "test_config.h" using namespace mediascanner; class MediaStoreData { public: MediaStoreData() : test_dbus(nullptr, g_object_unref) { db_path = "./mediascanner-cache.XXXXXX"; if (mkdtemp(const_cast(db_path.c_str())) == nullptr) { throw std::runtime_error("Could not create temporary directory"); } setenv("MEDIASCANNER_CACHEDIR", db_path.c_str(), true); populate(); test_dbus.reset(g_test_dbus_new(G_TEST_DBUS_NONE)); g_test_dbus_add_service_dir(test_dbus.get(), TEST_DIR "/services"); g_test_dbus_up(test_dbus.get()); daemon.setProgram(TEST_DIR "/../src/ms-dbus/mediascanner-dbus-2.0"); daemon.setProcessChannelMode(QProcess::ForwardedChannels); daemon.start(); daemon.closeWriteChannel(); if (!daemon.waitForStarted()) { throw std::runtime_error("Failed to start mediascanner-dbus-2.0"); } } ~MediaStoreData() { daemon.kill(); if (!daemon.waitForFinished()) { fprintf(stderr, "Failed to stop mediascanner-dbus-2.0\n"); } g_test_dbus_down(test_dbus.get()); if (system("rm -rf \"$MEDIASCANNER_CACHEDIR\"") == -1) { throw std::runtime_error("rm -rf failed"); } } void populate() { MediaStore store(MS_READ_WRITE); store.insert(MediaFileBuilder("/path/foo1.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("Straight Through The Sun") .setAuthor("Spiderbait") .setAlbum("Spiderbait") .setAlbumArtist("Spiderbait") .setDate("2013-11-15") .setGenre("rock") .setDiscNumber(1) .setTrackNumber(1) .setDuration(235)); store.insert(MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("It's Beautiful") .setAuthor("Spiderbait") .setAlbum("Spiderbait") .setAlbumArtist("Spiderbait") .setDate("2013-11-15") .setGenre("rock") .setDiscNumber(1) .setTrackNumber(2) .setDuration(220)); store.insert(MediaFileBuilder("/path/foo3.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("Buy Me a Pony") .setAuthor("Spiderbait") .setAlbum("Ivy and the Big Apples") .setAlbumArtist("Spiderbait") .setDate("1996-10-04") .setGenre("rock") .setDiscNumber(1) .setTrackNumber(3) .setDuration(104)); store.insert(MediaFileBuilder("/path/foo4.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("Peaches & Cream") .setAuthor("The John Butler Trio") .setAlbum("Sunrise Over Sea") .setAlbumArtist("The John Butler Trio") .setDate("2004-03-08") .setGenre("roots") .setDiscNumber(1) .setTrackNumber(2) .setDuration(407)); store.insert(MediaFileBuilder("/path/foo5.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("Zebra") .setAuthor("The John Butler Trio") .setAlbum("Sunrise Over Sea") .setAlbumArtist("The John Butler Trio") .setDate("2004-03-08") .setGenre("roots") .setDiscNumber(1) .setTrackNumber(10) .setDuration(237)); store.insert(MediaFileBuilder("/path/foo6.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("Revolution") .setAuthor("The John Butler Trio") .setAlbum("April Uprising") .setAlbumArtist("The John Butler Trio") .setDate("2010-01-01") .setGenre("roots") .setDiscNumber(1) .setTrackNumber(1) .setDuration(305)); store.insert(MediaFileBuilder("/path/foo7.ogg") .setType(AudioMedia) .setContentType("audio/ogg") .setETag("etag") .setTitle("One Way Road") .setAuthor("The John Butler Trio") .setAlbum("April Uprising") .setAlbumArtist("The John Butler Trio") .setDate("2010-01-01") .setGenre("roots") .setDiscNumber(1) .setTrackNumber(2) .setDuration(185)); } private: std::string db_path; std::unique_ptr test_dbus; QProcess daemon; }; int main(int argc, char** argv) { QGuiApplication(argc, argv); MediaStoreData data; return quick_test_main(argc, argv, "Mediascanner", SOURCE_DIR "/qml"); } mediascanner2-0.111+16.04.20160317/test/CMakeLists.txt0000644000015600001650000000740412672421600022317 0ustar pbuserpbgroup00000000000000if (NOT DEFINED GTEST_ROOT) set(GTEST_ROOT /usr/src/gtest) endif() set(GTEST_SRC_DIR "${GTEST_ROOT}/src") set(GTEST_INCLUDE_DIR ${GTEST_ROOT}) add_library(gtest STATIC ${GTEST_SRC_DIR}/gtest-all.cc ) set_target_properties(gtest PROPERTIES INCLUDE_DIRECTORIES ${GTEST_INCLUDE_DIR}) target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT}) # Clang complains about unused private field 'pretty_' in gtest-internal-inl.h. if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set_target_properties(gtest PROPERTIES COMPILE_FLAGS "-Wno-unused-private-field") endif() add_definitions(${MEDIASCANNER_DEPS_CFLAGS} ${GST_CFLAGS} ${DBUSCPP_CFLAGS} -DQT_NO_KEYWORDS) include_directories(../src) configure_file(test_config.h.in test_config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) configure_file( services/com.canonical.MediaScanner2.service.in services/com.canonical.MediaScanner2.service) configure_file( services/com.canonical.MediaScanner2.Extractor.service.in services/com.canonical.MediaScanner2.Extractor.service) add_executable(basic basic.cc) target_link_libraries(basic mediascanner scannerstuff gtest) add_test(basic basic) # The gvfs modules interfere with the private D-Bus test fixtures set_tests_properties(basic PROPERTIES ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules") add_executable(test_mediastore test_mediastore.cc ../src/mediascanner/utils.cc) target_link_libraries(test_mediastore mediascanner gtest) add_test(test_mediastore test_mediastore) add_executable(test_extractorbackend test_extractorbackend.cc) target_link_libraries(test_extractorbackend extractor-backend gtest) add_test(test_extractorbackend test_extractorbackend) add_executable(test_metadataextractor test_metadataextractor.cc) target_link_libraries(test_metadataextractor extractor-client gtest) add_test(test_metadataextractor test_metadataextractor) # The gvfs modules interfere with the private D-Bus test fixtures set_tests_properties(test_metadataextractor PROPERTIES ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules") add_executable(test_subtreewatcher test_subtreewatcher.cc) target_link_libraries(test_subtreewatcher mediascanner scannerstuff gtest) add_test(test_subtreewatcher test_subtreewatcher) # The gvfs modules interfere with the private D-Bus test fixtures set_tests_properties(test_subtreewatcher PROPERTIES ENVIRONMENT "GIO_MODULE_DIR=${CMAKE_CURRENT_BINARY_DIR}/modules") add_executable(test_sqliteutils test_sqliteutils.cc) target_link_libraries(test_sqliteutils gtest ${MEDIASCANNER_DEPS_LDFLAGS}) add_test(test_sqliteutils test_sqliteutils) add_executable(test_mfbuilder test_mfbuilder.cc) target_link_libraries(test_mfbuilder gtest mediascanner) add_test(test_mfbuilder test_mfbuilder) add_executable(test_dbus test_dbus.cc) target_link_libraries(test_dbus gtest mediascanner ms-dbus) add_test(test_dbus test_dbus) add_executable(test_qml test_qml.cc) qt5_use_modules(test_qml QuickTest) target_link_libraries(test_qml mediascanner) add_test(test_qml test_qml -import ${CMAKE_BINARY_DIR}/src/qml) set_tests_properties(test_qml PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal" TIMEOUT 600) add_test(test_qml_dbus test_qml -import ${CMAKE_BINARY_DIR}/src/qml) set_tests_properties(test_qml_dbus PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal;MEDIASCANNER_USE_DBUS=1" TIMEOUT 600) add_test(test_qml_nodb qmltestrunner -import ${CMAKE_BINARY_DIR}/src/qml -input ${CMAKE_CURRENT_SOURCE_DIR}/test_qml_nodb.qml) set_tests_properties(test_qml_nodb PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal;MEDIASCANNER_CACHEDIR=${CMAKE_CURRENT_BINARY_DIR}/qml-nodb-cachedir;DBUS_SESSION_BUS_ADDRESS=" TIMEOUT 600) add_executable(test_util test_util.cc ../src/mediascanner/utils.cc) target_link_libraries(test_util gtest ${GLIB_LDFLAGS}) add_test(test_util test_util) mediascanner2-0.111+16.04.20160317/test/noscan/0000755000015600001650000000000012672422030021031 5ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/noscan/.nomedia0000644000015600001650000000000012672421600022436 0ustar pbuserpbgroup00000000000000mediascanner2-0.111+16.04.20160317/test/test_mediastore.cc0000644000015600001650000012631212672421600023261 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical, Ltd. * * Authors: * Jussi Pakkanen * * This library is free software; you can redistribute it and/or modify it under * the terms of version 3 of the GNU General Public License as published * by the Free Software Foundation. * * This library 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, see . */ #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace mediascanner; class MediaStoreTest : public ::testing::Test { protected: MediaStoreTest() { } virtual ~MediaStoreTest() { } virtual void SetUp() { } virtual void TearDown() { } }; TEST_F(MediaStoreTest, init) { MediaStore store(":memory:", MS_READ_WRITE); } TEST_F(MediaStoreTest, mediafile_uri) { MediaFile media = MediaFileBuilder("/path/to/file.ogg"); EXPECT_EQ(media.getUri(), "file:///path/to/file.ogg"); } TEST_F(MediaStoreTest, equality) { MediaFile audio1 = MediaFileBuilder("a") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setAuthor("c") .setAlbum("d") .setAlbumArtist("e") .setGenre("f") .setDiscNumber(0) .setTrackNumber(1) .setDuration(5) .setType(AudioMedia); MediaFile audio2 = MediaFileBuilder("aa") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setAuthor("c") .setAlbum("d") .setAlbumArtist("e") .setGenre("f") .setDiscNumber(0) .setTrackNumber(1) .setDuration(5) .setType(AudioMedia); MediaFile video1 = MediaFileBuilder("a") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setDuration(5) .setWidth(41) .setHeight(42) .setType(VideoMedia); MediaFile video2 = MediaFileBuilder("aa") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setDuration(5) .setWidth(43) .setHeight(44) .setType(VideoMedia); MediaFile image1 = MediaFileBuilder("a") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setWidth(640) .setHeight(480) .setLatitude(20.0) .setLongitude(30.0) .setType(ImageMedia); MediaFile image2 = MediaFileBuilder("aa") .setContentType("type") .setETag("etag") .setDate("1900") .setTitle("b") .setWidth(480) .setHeight(640) .setLatitude(-20.0) .setLongitude(-30.0) .setType(ImageMedia); EXPECT_EQ(audio1, audio1); EXPECT_EQ(video1, video1); EXPECT_EQ(image1, image1); EXPECT_NE(audio1, audio2); EXPECT_NE(video1, video2); EXPECT_NE(image1, image2); EXPECT_NE(audio1, video1); EXPECT_NE(audio1, image1); EXPECT_NE(video1, image1); } TEST_F(MediaStoreTest, lookup) { MediaFile audio = MediaFileBuilder("/aaa") .setContentType("type") .setETag("etag") .setDate("1900-01-01") .setTitle("bbb bbb") .setAuthor("ccc") .setAlbum("ddd") .setAlbumArtist("eee") .setGenre("fff") .setDiscNumber(0) .setTrackNumber(3) .setDuration(5) .setType(AudioMedia); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio); EXPECT_EQ(store.lookup("/aaa"), audio); EXPECT_THROW(store.lookup("not found"), std::runtime_error); } TEST_F(MediaStoreTest, roundtrip) { MediaFile audio = MediaFileBuilder("/aaa") .setContentType("type") .setETag("etag") .setDate("1900-01-01") .setTitle("bbb bbb") .setAuthor("ccc") .setAlbum("ddd") .setAlbumArtist("eee") .setGenre("fff") .setDiscNumber(0) .setTrackNumber(3) .setDuration(5) .setHasThumbnail(true) .setModificationTime(4200) .setType(AudioMedia); MediaFile video = MediaFileBuilder("/aaa2") .setContentType("type") .setETag("etag") .setDate("2012-01-01") .setTitle("bbb bbb") .setDuration(5) .setWidth(1280) .setHeight(720) .setType(VideoMedia); MediaFile image = MediaFileBuilder("/aaa3") .setContentType("type") .setETag("etag") .setDate("2012-01-01") .setTitle("bbb bbb") .setWidth(480) .setHeight(640) .setLatitude(20.0) .setLongitude(-30.0) .setType(ImageMedia); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio); store.insert(video); store.insert(image); Filter filter; vector result = store.query("bbb", AudioMedia, filter); ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0], audio); result = store.query("bbb", VideoMedia, filter); ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0], video); result = store.query("bbb", ImageMedia, filter); ASSERT_EQ(1, result.size()); EXPECT_EQ(image, result[0]); } TEST_F(MediaStoreTest, query_by_album) { MediaFile audio = MediaFileBuilder("/path/foo.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio); Filter filter; vector result = store.query("album", AudioMedia, filter); ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0], audio); } TEST_F(MediaStoreTest, query_by_artist) { MediaFile audio = MediaFileBuilder("/path/foo.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio); Filter filter; vector result = store.query("artist", AudioMedia, filter); ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0], audio); } TEST_F(MediaStoreTest, query_ranking) { MediaFile audio1 = MediaFileBuilder("/path/foo1.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio3 = MediaFileBuilder("/path/foo3.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist aaa") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio4 = MediaFileBuilder("/path/foo4.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaFile audio5 = MediaFileBuilder("/path/foo5.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist aaa") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); store.insert(audio5); Filter filter; vector result = store.query("aaa", AudioMedia, filter); ASSERT_EQ(result.size(), 4); EXPECT_EQ(result[0], audio5); // Term appears in title, artist and album EXPECT_EQ(result[1], audio2); // title has highest weighting EXPECT_EQ(result[2], audio4); // then album EXPECT_EQ(result[3], audio3); // then artist } TEST_F(MediaStoreTest, query_limit) { MediaFile audio1 = MediaFileBuilder("/path/foo5.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist aaa") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio3 = MediaFileBuilder("/path/foo4.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); Filter filter; filter.setLimit(2); vector result = store.query("aaa", AudioMedia, filter); ASSERT_EQ(result.size(), 2); EXPECT_EQ(result[0], audio1); // Term appears in title, artist and album EXPECT_EQ(result[1], audio2); // title has highest weighting } TEST_F(MediaStoreTest, query_short) { MediaFile audio1 = MediaFileBuilder("/path/foo5.ogg") .setType(AudioMedia) .setTitle("title xyz") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title xzy") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); Filter filter; vector result = store.query("x", AudioMedia, filter); EXPECT_EQ(result.size(), 2); result = store.query("xy", AudioMedia, filter); EXPECT_EQ(result.size(), 1); } TEST_F(MediaStoreTest, query_empty) { MediaFile audio1 = MediaFileBuilder("/path/foo5.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist aaa") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title aaa") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("albumartist"); MediaFile audio3 = MediaFileBuilder("/path/foo4.ogg") .setType(AudioMedia) .setTitle("title") .setAuthor("artist") .setAlbum("album aaa") .setAlbumArtist("albumartist"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); // An empty query should return some results Filter filter; filter.setLimit(2); vector result = store.query("", AudioMedia, filter); ASSERT_EQ(result.size(), 2); } TEST_F(MediaStoreTest, query_order) { MediaFile audio1 = MediaFileBuilder("/path/foo1.ogg") .setType(AudioMedia) .setTitle("foo") .setDate("2010-01-01") .setAuthor("artist") .setAlbum("album") .setModificationTime(2); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("foo foo") .setDate("2010-01-03") .setAuthor("artist") .setAlbum("album") .setModificationTime(1); MediaFile audio3 = MediaFileBuilder("/path/foo3.ogg") .setType(AudioMedia) .setTitle("foo foo foo") .setDate("2010-01-02") .setAuthor("artist") .setAlbum("album") .setModificationTime(3); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); Filter filter; // Default sort order is by rank vector result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo3.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[2].getFileName()); // Sorting by rank (same as default) filter.setOrder(MediaOrder::Rank); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo3.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[2].getFileName()); // Sorting by rank, reversed filter.setReverse(true); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo1.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo3.ogg", result[2].getFileName()); // Sorting by title filter.setReverse(false); filter.setOrder(MediaOrder::Title); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo1.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo3.ogg", result[2].getFileName()); // Sorting by title, reversed filter.setReverse(true); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo3.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[2].getFileName()); // Sorting by date filter.setReverse(false); filter.setOrder(MediaOrder::Date); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo1.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo3.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[2].getFileName()); // Sorting by date, reversed filter.setReverse(true); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo2.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo3.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[2].getFileName()); // Sorting by modification date filter.setReverse(false); filter.setOrder(MediaOrder::Modified); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo2.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo3.ogg", result[2].getFileName()); // Sorting by modification date, reversed filter.setReverse(true); result = store.query("foo", AudioMedia, filter); ASSERT_EQ(3, result.size()); EXPECT_EQ("/path/foo3.ogg", result[0].getFileName()); EXPECT_EQ("/path/foo1.ogg", result[1].getFileName()); EXPECT_EQ("/path/foo2.ogg", result[2].getFileName()); } TEST_F(MediaStoreTest, unmount) { MediaFile audio1 = MediaFileBuilder("/media/username/dir/fname.ogg") .setType(AudioMedia) .setETag("etag") .setContentType("audio/ogg") .setTitle("bbb bbb") .setDate("2015-01-01") .setAuthor("artist") .setAlbum("album") .setAlbumArtist("album_artist") .setGenre("genre") .setDiscNumber(5) .setTrackNumber(10) .setDuration(42) .setWidth(640) .setHeight(480) .setLatitude(8.0) .setLongitude(4.0) .setHasThumbnail(true) .setModificationTime(10000); MediaFile audio2 = MediaFileBuilder("/home/username/Music/fname.ogg") .setType(AudioMedia) .setTitle("bbb bbb"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); Filter filter; vector result = store.query("bbb", AudioMedia, filter); ASSERT_EQ(result.size(), 2); store.archiveItems("/media/username"); result = store.query("bbb", AudioMedia, filter); ASSERT_EQ(result.size(), 1); EXPECT_EQ(result[0], audio2); store.restoreItems("/media/username"); result = store.query("bbb", AudioMedia, filter); ASSERT_EQ(result.size(), 2); std::sort(result.begin(), result.end(), [](const MediaFile &m1, const MediaFile &m2) -> bool { return m1.getFileName() < m2.getFileName(); }); EXPECT_EQ(result[0], audio2); EXPECT_EQ(result[1], audio1); } TEST_F(MediaStoreTest, utils) { string source("_a.b(c)[d]{e}f.mp3"); string correct = {" a b c d e f"}; string result = filenameToTitle(source); EXPECT_EQ(correct, result); string unquoted(R"(It's a living.)"); string quoted(R"('It''s a living.')"); EXPECT_EQ(sqlQuote(unquoted), quoted); } TEST_F(MediaStoreTest, queryAlbums) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDate("2000-01-01") .setDiscNumber(1) .setTrackNumber(1) .setGenre("GenreOne") .setModificationTime(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDate("2000-01-01") .setDiscNumber(1) .setTrackNumber(2) .setGenre("GenreOne") .setModificationTime(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistThree") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDate("2000-01-01") .setDiscNumber(2) .setTrackNumber(1) .setGenre("GenreOne") .setModificationTime(3); MediaFile audio4 = MediaFileBuilder("/home/username/Music/fname.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistFour") .setAlbum("AlbumTwo") .setAlbumArtist("ArtistFour") .setDate("2014-06-01") .setTrackNumber(1) .setGenre("GenreTwo") .setHasThumbnail(true) .setModificationTime(4); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); // Query a track title Filter filter; vector albums = store.queryAlbums("TitleOne", filter); ASSERT_EQ(albums.size(), 1); EXPECT_EQ(albums[0].getTitle(), "AlbumOne"); EXPECT_EQ(albums[0].getArtist(), "Various Artists"); EXPECT_EQ(albums[0].getDate(), "2000-01-01"); EXPECT_EQ(albums[0].getGenre(), "GenreOne"); EXPECT_EQ(albums[0].getArtUri(), "image://albumart/artist=Various%20Artists&album=AlbumOne"); // Query an album name albums = store.queryAlbums("AlbumTwo", filter); ASSERT_EQ(albums.size(), 1); EXPECT_EQ(albums[0].getTitle(), "AlbumTwo"); EXPECT_EQ(albums[0].getArtist(), "ArtistFour"); EXPECT_EQ(albums[0].getDate(), "2014-06-01"); EXPECT_EQ(albums[0].getGenre(), "GenreTwo"); EXPECT_EQ(albums[0].getArtUri(), "image://thumbnailer/file:///home/username/Music/fname.ogg"); // Query an artist name albums = store.queryAlbums("ArtistTwo", filter); ASSERT_EQ(albums.size(), 1); EXPECT_EQ(albums[0].getTitle(), "AlbumOne"); EXPECT_EQ(albums[0].getArtist(), "Various Artists"); // Sort results by modification time filter.setOrder(MediaOrder::Modified); filter.setReverse(true); albums = store.queryAlbums("", filter); ASSERT_EQ(2, albums.size()); EXPECT_EQ("AlbumTwo", albums[0].getTitle()); EXPECT_EQ("AlbumOne", albums[1].getTitle()); } TEST_F(MediaStoreTest, queryAlbums_limit) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistThree") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(2) .setTrackNumber(1); MediaFile audio4 = MediaFileBuilder("/home/username/Music/fname.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistFour") .setAlbum("AlbumTwo") .setAlbumArtist("ArtistFour") .setTrackNumber(1); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); Filter filter; vector albums = store.queryAlbums("Artist", filter); EXPECT_EQ(2, albums.size()); filter.setLimit(1); albums = store.queryAlbums("Artist", filter); EXPECT_EQ(1, albums.size()); } TEST_F(MediaStoreTest, queryAlbums_empty) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistThree") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(2) .setTrackNumber(1); MediaFile audio4 = MediaFileBuilder("/home/username/Music/fname.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistFour") .setAlbum("AlbumTwo") .setAlbumArtist("ArtistFour") .setTrackNumber(1); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); Filter filter; vector albums = store.queryAlbums("", filter); EXPECT_EQ(2, albums.size()); filter.setLimit(1); albums = store.queryAlbums("", filter); EXPECT_EQ(1, albums.size()); } TEST_F(MediaStoreTest, queryAlbums_order) { MediaFile audio1 = MediaFileBuilder("/path/foo1.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-01") .setAuthor("artist") .setAlbum("foo"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-03") .setAuthor("artist") .setAlbum("foo foo"); MediaFile audio3 = MediaFileBuilder("/path/foo3.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-02") .setAuthor("artist") .setAlbum("foo foo foo"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); // Default sort Filter filter; vector albums = store.queryAlbums("foo", filter); ASSERT_EQ(3, albums.size()); EXPECT_EQ("foo", albums[0].getTitle()); EXPECT_EQ("foo foo", albums[1].getTitle()); EXPECT_EQ("foo foo foo", albums[2].getTitle()); // Sort by title (same as default) filter.setOrder(MediaOrder::Title); albums = store.queryAlbums("foo", filter); ASSERT_EQ(3, albums.size()); EXPECT_EQ("foo", albums[0].getTitle()); EXPECT_EQ("foo foo", albums[1].getTitle()); EXPECT_EQ("foo foo foo", albums[2].getTitle()); // Sort by title, reversed filter.setReverse(true); albums = store.queryAlbums("foo", filter); ASSERT_EQ(3, albums.size()); EXPECT_EQ("foo foo foo", albums[0].getTitle()); EXPECT_EQ("foo foo", albums[1].getTitle()); EXPECT_EQ("foo", albums[2].getTitle()); // Other orders are not supported filter.setOrder(MediaOrder::Rank); EXPECT_THROW(store.queryAlbums("foo", filter), std::runtime_error); filter.setOrder(MediaOrder::Date); EXPECT_THROW(store.queryAlbums("foo", filter), std::runtime_error); } TEST_F(MediaStoreTest, queryArtists) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistThree") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(2) .setTrackNumber(1); MediaFile audio4 = MediaFileBuilder("/home/username/Music/fname.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistFour") .setAlbum("AlbumTwo") .setAlbumArtist("ArtistFour") .setTrackNumber(1); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); // Query a track title Filter filter; vector artists = store.queryArtists("TitleOne", filter); ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists[0], "ArtistOne"); // Query an album name artists = store.queryArtists("AlbumTwo", filter); ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists[0], "ArtistFour"); // Query an artist name artists = store.queryArtists("ArtistTwo", filter); ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists[0], "ArtistTwo"); } TEST_F(MediaStoreTest, queryArtists_limit) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); Filter filter; vector artists = store.queryArtists("Artist", filter); EXPECT_EQ(2, artists.size()); filter.setLimit(1); artists = store.queryArtists("Artist", filter); EXPECT_EQ(1, artists.size()); } TEST_F(MediaStoreTest, queryArtists_empty) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); Filter filter; vector artists = store.queryArtists("", filter); EXPECT_EQ(2, artists.size()); filter.setLimit(1); artists = store.queryArtists("", filter); EXPECT_EQ(1, artists.size()); } TEST_F(MediaStoreTest, queryArtists_order) { MediaFile audio1 = MediaFileBuilder("/path/foo1.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-01") .setAuthor("foo") .setAlbum("album"); MediaFile audio2 = MediaFileBuilder("/path/foo2.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-03") .setAuthor("foo foo") .setAlbum("album"); MediaFile audio3 = MediaFileBuilder("/path/foo3.ogg") .setType(AudioMedia) .setTitle("title") .setDate("2010-01-02") .setAuthor("foo foo foo") .setAlbum("album"); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); // Default sort Filter filter; vector artists = store.queryArtists("foo", filter); ASSERT_EQ(3, artists.size()); EXPECT_EQ("foo", artists[0]); EXPECT_EQ("foo foo", artists[1]); EXPECT_EQ("foo foo foo", artists[2]); // Sort by title (same as default) filter.setOrder(MediaOrder::Title); artists = store.queryArtists("foo", filter); ASSERT_EQ(3, artists.size()); EXPECT_EQ("foo", artists[0]); EXPECT_EQ("foo foo", artists[1]); EXPECT_EQ("foo foo foo", artists[2]); // Sort by title, reversed filter.setReverse(true); artists = store.queryArtists("foo", filter); ASSERT_EQ(3, artists.size()); EXPECT_EQ("foo foo foo", artists[0]); EXPECT_EQ("foo foo", artists[1]); EXPECT_EQ("foo", artists[2]); // Other orders are not supported filter.setOrder(MediaOrder::Rank); EXPECT_THROW(store.queryArtists("foo", filter), std::runtime_error); filter.setOrder(MediaOrder::Date); EXPECT_THROW(store.queryArtists("foo", filter), std::runtime_error); } TEST_F(MediaStoreTest, getAlbumSongs) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistTwo") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(1) .setTrackNumber(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistThree") .setAlbum("AlbumOne") .setAlbumArtist("Various Artists") .setDiscNumber(2) .setTrackNumber(1); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); vector tracks = store.getAlbumSongs( Album("AlbumOne", "Various Artists")); ASSERT_EQ(tracks.size(), 3); EXPECT_EQ(tracks[0].getTitle(), "TitleOne"); EXPECT_EQ(tracks[1].getTitle(), "TitleTwo"); EXPECT_EQ(tracks[2].getTitle(), "TitleThree"); } TEST_F(MediaStoreTest, getETag) { MediaFile file = MediaFileBuilder("/path/file.ogg") .setETag("etag") .setType(AudioMedia); MediaStore store(":memory:", MS_READ_WRITE); store.insert(file); EXPECT_EQ(store.getETag("/path/file.ogg"), "etag"); EXPECT_EQ(store.getETag("/something-else.mp3"), ""); } TEST_F(MediaStoreTest, constraints) { MediaFile file = MediaFileBuilder("no_slash_at_beginning.ogg") .setETag("etag") .setType(AudioMedia); MediaFile file2 = MediaFileBuilder("/invalid_type.ogg") .setETag("etag"); MediaStore store(":memory:", MS_READ_WRITE); ASSERT_THROW(store.insert(file), std::runtime_error); ASSERT_THROW(store.insert(file2), std::runtime_error); } TEST_F(MediaStoreTest, listSongs) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track2.ogg") .setType(AudioMedia) .setTitle("TitleTwo") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setTrackNumber(2); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistOne") .setAlbum("AlbumTwo"); MediaFile audio4 = MediaFileBuilder("/home/username/Music/track4.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistTwo") .setAlbum("AlbumThree"); MediaFile audio5 = MediaFileBuilder("/home/username/Music/track5.ogg") .setType(AudioMedia) .setTitle("TitleFive") .setAuthor("ArtistOne") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(1); MediaFile audio6 = MediaFileBuilder("/home/username/Music/track6.ogg") .setType(AudioMedia) .setTitle("TitleSix") .setAuthor("ArtistTwo") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(2); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); store.insert(audio5); store.insert(audio6); Filter filter; vector tracks = store.listSongs(filter); ASSERT_EQ(6, tracks.size()); EXPECT_EQ("TitleOne", tracks[0].getTitle()); // Apply a limit filter.setLimit(4); tracks = store.listSongs(filter); EXPECT_EQ(4, tracks.size()); filter.setLimit(-1); // List songs by artist filter.setArtist("ArtistOne"); tracks = store.listSongs(filter); EXPECT_EQ(4, tracks.size()); // List songs by album filter.clear(); filter.setAlbum("AlbumOne"); tracks = store.listSongs(filter); EXPECT_EQ(2, tracks.size()); // List songs by album artist filter.clear(); filter.setAlbumArtist("Various Artists"); tracks = store.listSongs(filter); EXPECT_EQ(2, tracks.size()); // Combinations filter.clear(); filter.setArtist("ArtistOne"); filter.setAlbum("AlbumOne"); tracks = store.listSongs(filter); EXPECT_EQ(2, tracks.size()); filter.clear(); filter.setAlbum("AlbumOne"); filter.setAlbumArtist("ArtistOne"); tracks = store.listSongs(filter); EXPECT_EQ(2, tracks.size()); filter.clear(); filter.setArtist("ArtistOne"); filter.setAlbum("AlbumOne"); filter.setAlbumArtist("ArtistOne"); tracks = store.listSongs(filter); EXPECT_EQ(2, tracks.size()); filter.clear(); filter.setArtist("ArtistOne"); filter.setAlbumArtist("ArtistOne"); tracks = store.listSongs(filter); EXPECT_EQ(3, tracks.size()); } TEST_F(MediaStoreTest, listAlbums) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistOne") .setAlbum("AlbumTwo"); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track4.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistTwo") .setAlbum("AlbumThree"); MediaFile audio4 = MediaFileBuilder("/home/username/Music/track5.ogg") .setType(AudioMedia) .setTitle("TitleFive") .setAuthor("ArtistOne") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(1); MediaFile audio5 = MediaFileBuilder("/home/username/Music/track6.ogg") .setType(AudioMedia) .setTitle("TitleSix") .setAuthor("ArtistTwo") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(2); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); store.insert(audio5); Filter filter; vector albums = store.listAlbums(filter); ASSERT_EQ(4, albums.size()); EXPECT_EQ("AlbumOne", albums[0].getTitle()); // test limit filter.setLimit(2); albums = store.listAlbums(filter); EXPECT_EQ(2, albums.size()); filter.setLimit(-1); // Songs by artist filter.setArtist("ArtistOne"); albums = store.listAlbums(filter); EXPECT_EQ(3, albums.size()); // Songs by album artist filter.clear(); filter.setAlbumArtist("ArtistOne"); albums = store.listAlbums(filter); EXPECT_EQ(2, albums.size()); // Combination filter.clear(); filter.setArtist("ArtistOne"); filter.setAlbumArtist("Various Artists"); albums = store.listAlbums(filter); EXPECT_EQ(1, albums.size()); } TEST_F(MediaStoreTest, listArtists) { MediaFile audio1 = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("TitleOne") .setAuthor("ArtistOne") .setAlbum("AlbumOne") .setTrackNumber(1); MediaFile audio2 = MediaFileBuilder("/home/username/Music/track3.ogg") .setType(AudioMedia) .setTitle("TitleThree") .setAuthor("ArtistOne") .setAlbum("AlbumTwo"); MediaFile audio3 = MediaFileBuilder("/home/username/Music/track4.ogg") .setType(AudioMedia) .setTitle("TitleFour") .setAuthor("ArtistTwo") .setAlbum("AlbumThree"); MediaFile audio4 = MediaFileBuilder("/home/username/Music/track5.ogg") .setType(AudioMedia) .setTitle("TitleFive") .setAuthor("ArtistOne") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(1); MediaFile audio5 = MediaFileBuilder("/home/username/Music/track6.ogg") .setType(AudioMedia) .setTitle("TitleSix") .setAuthor("ArtistTwo") .setAlbum("AlbumFour") .setAlbumArtist("Various Artists") .setTrackNumber(2); MediaStore store(":memory:", MS_READ_WRITE); store.insert(audio1); store.insert(audio2); store.insert(audio3); store.insert(audio4); store.insert(audio5); Filter filter; vector artists = store.listArtists(filter); ASSERT_EQ(2, artists.size()); EXPECT_EQ("ArtistOne", artists[0]); EXPECT_EQ("ArtistTwo", artists[1]); // Test limit clause filter.setLimit(1); artists = store.listArtists(filter); EXPECT_EQ(1, artists.size()); filter.setLimit(-1); // List "album artists" artists = store.listAlbumArtists(filter); ASSERT_EQ(3, artists.size()); EXPECT_EQ("ArtistOne", artists[0]); EXPECT_EQ("ArtistTwo", artists[1]); EXPECT_EQ("Various Artists", artists[2]); } TEST_F(MediaStoreTest, hasMedia) { MediaStore store(":memory:", MS_READ_WRITE); EXPECT_FALSE(store.hasMedia(AudioMedia)); EXPECT_FALSE(store.hasMedia(VideoMedia)); EXPECT_FALSE(store.hasMedia(ImageMedia)); EXPECT_FALSE(store.hasMedia(AllMedia)); MediaFile audio = MediaFileBuilder("/home/username/Music/track1.ogg") .setType(AudioMedia) .setTitle("Title") .setAuthor("Artist") .setAlbum("Album"); store.insert(audio); EXPECT_TRUE(store.hasMedia(AudioMedia)); EXPECT_FALSE(store.hasMedia(VideoMedia)); EXPECT_FALSE(store.hasMedia(ImageMedia)); EXPECT_TRUE(store.hasMedia(AllMedia)); } TEST_F(MediaStoreTest, brokenFiles) { MediaStore store(":memory:", MS_READ_WRITE); std::string file = "/foo/bar/baz.mp3"; std::string other_file = "/foo/bar/abc.mp3"; std::string broken_etag = "123"; std::string ok_etag = "124"; ASSERT_FALSE(store.is_broken_file(file, broken_etag)); ASSERT_FALSE(store.is_broken_file(file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, broken_etag)); store.insert_broken_file(file, broken_etag); ASSERT_TRUE(store.is_broken_file(file, broken_etag)); ASSERT_FALSE(store.is_broken_file(file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, broken_etag)); store.remove_broken_file(file); ASSERT_FALSE(store.is_broken_file(file, broken_etag)); ASSERT_FALSE(store.is_broken_file(file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, ok_etag)); ASSERT_FALSE(store.is_broken_file(other_file, broken_etag)); } TEST_F(MediaStoreTest, removeSubtree) { MediaStore store(":memory:", MS_READ_WRITE); // Include some SQL like expression meta characters in the path store.insert(MediaFileBuilder("/hello_%/world.mp3").setType(AudioMedia)); store.insert(MediaFileBuilder("/hello_%/a/b/c/world.mp3").setType(AudioMedia)); store.insert(MediaFileBuilder("/hello_%sibling.mp3").setType(AudioMedia)); store.insert(MediaFileBuilder("/helloxyz.mp3").setType(AudioMedia)); EXPECT_EQ(4, store.size()); store.removeSubtree("/hello_%"); EXPECT_EQ(2, store.size()); store.lookup("/hello_%sibling.mp3"); store.lookup("/helloxyz.mp3"); try { store.lookup("/hello_%/world.mp3"); FAIL(); } catch (const std::runtime_error &e) { string msg = e.what(); EXPECT_NE(string::npos, msg.find("Could not find media")); } try { store.lookup("/hello_%/a/b/c/world.mp3"); FAIL(); } catch (const std::runtime_error &e) { string msg = e.what(); EXPECT_NE(string::npos, msg.find("Could not find media")); } } TEST_F(MediaStoreTest, transaction) { MediaStore store(":memory:", MS_READ_WRITE); // Run a transaction without committing: file not added. { MediaStoreTransaction txn = store.beginTransaction(); store.insert(MediaFileBuilder("/one.mp3").setType(AudioMedia)); } EXPECT_EQ(0, store.size()); EXPECT_THROW(store.lookup("/one.mp3"), std::runtime_error); // Commit two files in a transaction, then one file in a second // transaction, and leave the last uncommitted. { MediaStoreTransaction txn = store.beginTransaction(); store.insert(MediaFileBuilder("/one.mp3").setType(AudioMedia)); store.insert(MediaFileBuilder("/two.mp3").setType(AudioMedia)); txn.commit(); store.insert(MediaFileBuilder("/three.mp3").setType(AudioMedia)); txn.commit(); store.insert(MediaFileBuilder("/four.mp3").setType(AudioMedia)); } EXPECT_EQ(3, store.size()); store.lookup("/one.mp3"); store.lookup("/two.mp3"); store.lookup("/three.mp3"); EXPECT_THROW(store.lookup("/four.mp3"), std::runtime_error); } int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } mediascanner2-0.111+16.04.20160317/HACKING0000644000015600001650000000115112672421600017560 0ustar pbuserpbgroup00000000000000Building the code ----------------- The list of packages required to build mediascanner can be found in the Build-Depends stanza of the debian/control.in file. In addition to those packages, you should install "qt5-default" to ensure that the Qt 5.x versions of build tools are used in preference to the Qt 4.x versions. The software can then be built with the following commands: $ mkdir build $ cd build $ cmake .. $ make The tests can then be run using: $ make test Note that "make test" will not trigger a rebuild, so it is necessary to rerun "make" first if any code has been changed. mediascanner2-0.111+16.04.20160317/get-soversion.sh0000755000015600001650000000021512672421600021734 0ustar pbuserpbgroup00000000000000#!/bin/sh set -e distro=$(lsb_release -c -s) case "$distro" in vivid) echo 3 ;; *) echo 4 ;; esac mediascanner2-0.111+16.04.20160317/COPYING.GPL0000644000015600001650000010451312672421600020253 0ustar pbuserpbgroup00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . mediascanner2-0.111+16.04.20160317/mediascanner-2.0.pc.in0000644000015600001650000000040512672421600022451 0ustar pbuserpbgroup00000000000000Name: mediascanner-2.0 Description: Access library for the media scanner's index Version: @MEDIASCANNER_VERSION@ Requires.private: gio-2.0 sqlite3 Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -lmediascanner-2.0 Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/mediascanner-2.0 mediascanner2-0.111+16.04.20160317/mediascanner-2.0.conf.in0000644000015600001650000000026112672421600022774 0ustar pbuserpbgroup00000000000000description "Media Scanner" author "James Henstridge " start on started dbus respawn exec @CMAKE_INSTALL_FULL_BINDIR@/mediascanner-service-2.0 mediascanner2-0.111+16.04.20160317/COPYING.LGPL0000644000015600001650000001674312672421600020376 0ustar pbuserpbgroup00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. mediascanner2-0.111+16.04.20160317/CMakeLists.txt0000644000015600001650000000542312672421600021337 0ustar pbuserpbgroup00000000000000project(mediascanner2 CXX C) cmake_minimum_required(VERSION 2.8.9) set(MEDIASCANNER_VERSION "0.111") execute_process( COMMAND /bin/sh ${CMAKE_CURRENT_SOURCE_DIR}/get-soversion.sh OUTPUT_VARIABLE MEDIASCANNER_SOVERSION OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE result) if(NOT result EQUAL 0) message(FATAL_ERROR "Error running get-soversion.sh script") endif() set(MEDIASCANNER_LIBVERSION "${MEDIASCANNER_SOVERSION}.${MEDIASCANNER_VERSION}") if(PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR) message(FATAL_ERROR "In-tree build attempt detected, aborting. Set your build dir outside your source dir, delete CMakeCache.txt from source root and try again.") endif() option(full_warnings "All possible compiler warnings." OFF) include(FindPkgConfig) pkg_check_modules(MEDIASCANNER_DEPS REQUIRED gio-2.0 gio-unix-2.0 sqlite3>=3.8.5 ) pkg_check_modules(GST gstreamer-1.0 gstreamer-pbutils-1.0 REQUIRED) pkg_check_modules(GLIB glib-2.0 REQUIRED) pkg_check_modules(PIXBUF gdk-pixbuf-2.0 REQUIRED) pkg_check_modules(EXIF libexif REQUIRED) pkg_check_modules(TAGLIB taglib REQUIRED) pkg_check_modules(DBUSCPP dbus-cpp REQUIRED) pkg_check_modules(APPARMOR libapparmor REQUIRED) pkg_check_modules(UDISKS udisks2 REQUIRED) find_package(Threads REQUIRED) find_package(Qt5Core REQUIRED) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Wextra -std=c99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra -std=c++11") if(${full_warnings}) # C does not have any more warning flags. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") endif() include(EnableCoverageReport) include(GNUInstallDirs) set(LIBDIR $CMAKE_INSTALL_LIBDIR) enable_testing() add_subdirectory(src/mediascanner) add_subdirectory(src/extractor) add_subdirectory(src/daemon) add_subdirectory(src/ms-dbus) add_subdirectory(src/qml/Ubuntu/MediaScanner.0.1) add_subdirectory(src/utils) add_subdirectory(test) # Install pkg-config file configure_file(mediascanner-2.0.pc.in mediascanner-2.0.pc) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mediascanner-2.0.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) # Install Upstart user session job configure_file(mediascanner-2.0.conf.in mediascanner-2.0.conf) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/mediascanner-2.0.conf DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions ) enable_coverage_report( TARGETS mediascanner extractor-client extractor-backend mediascanner-extractor scannerstuff scannerdaemon ms-dbus mediascanner-dbus mediascanner-qml query mountwatcher FILTER ${CMAKE_SOURCE_DIR}/tests/* ${CMAKE_BINARY_DIR}/* TESTS basic test_mediastore test_metadataextractor test_extractorbackend test_sqliteutils test_mfbuilder test_subtreewatcher test_dbus test_qml test_util )