mediascanner-0.3.93+14.04.20131024.1/0000755000015700001700000000000012232220310016711 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/.gitignore0000644000015700001700000000130412232220161020703 0ustar pbuserpbgroup00000000000000.deps .dirstamp .libs *.user .*sw? *~ *.o *.moc *.la *.lo *.cbp Makefile CMakeFiles CTestTestfile.cmake cmake_install.cmake install_manifest.txt /CMakeCache.txt /Doxyfile /Testing /build* /checkstyle-stamp /debian/*.debhelper /debian/*.log /debian/*.substvars /debian/files /debian/grilo-plugins-0.2-mediascanner/ /debian/libmediascanner-dev/ /debian/libmediascanner-doc/ /debian/libmediascanner0/ /debian/mediascanner/ /debian/tmp/ /doxygen.log /docs/html /obj-*/ /src/grlmediascanner/libgrlmediascanner.so /src/mediaindex/libmediaindex.so /src/mediascanner/mediascanner /tests/auto/mediaindextest /tests/auto/propertytest /tests/auto/propertyvaluetest /tests/auto/tst_properties /tests/gtest /tests/run mediascanner-0.3.93+14.04.20131024.1/mediascanner.gschema.xml.in0000644000015700001700000000621312232220161024105 0ustar pbuserpbgroup00000000000000 List of mandatory container types A list of the required multimedia container types. Each element is a pair of two strings, where the first element is the container format's name, and the second element is the GStreamer capabilities describing the container type. [ ("PCM Audio", "audio/x-wav"), ("MPEG-TS", "video/mpegts"), ("MP4 File", "video/quicktime, variant=iso"), ("OGG File", "application/ogg; audio/ogg; video/ogg"), ("AVI File", "video/x-msvideo"), ("Matroska File", "audio/x-matroska; video/x-matroska; audio/webm; video/webm") ] List of mandatory media decoder types A list of the required multimedia formats. Each element is a pair of two strings, where the first element is the format's name, and the second element is the GStreamer capabilities describing the format. [ ("A/52, AC3 (Dolby Digital) Audio", "audio/x-ac3; audio/ac3"), ("DTS Audio", "audio/x-dts"), ("MP3 Audio", "audio/mpeg, mpegversion=(int)1, layer=(int)3"), ("AAC Audio", "audio/mpeg, mpegversion=(int)2, stream-format=adts; audio/mpeg, mpegversion=(int)4, stream-format=adts"), ("MPEG-4 Audio", "audio/mpeg,mpegversion=(int)4"), ("Vorbis Audio", "audio/x-vorbis"), ("Flac Audio", "audio/x-flac"), ("Windows Bitmap", "image/bmp"), ("GIF Image", "image/gif"), ("JPEG Image", "image/jpeg"), ("PNG Image", "image/png"), ("SVG Image", "image/svg; image/svg+xml"), ("TIFF Image", "image/tiff"), ("Targa Image", "image/x-tga"), ("H.262/MPEG-2 Part 2 Video", "video/mpeg, mpegversion=(int)2"), ("H.264/MPEG-4 Part 2 Video", "video/mpeg, mpegversion=(int)4"), ("H.264/MPEG-4 AVC (MPEG-4 Part 10) Video", "video/x-h264"), ("Theora Video", "video/x-theora"), ("VC-1 (Windows Media) Video", "video/x-wmv"), ("VP8 Video", "video/x-vp8") ] List folders to monitor ["user:download", "user:music", "user:pictures", "user:videos"] List of metadata sources to consult A List of metadata sources to consult. The first element is the Grilo source ID. The second element is the plugin ID. The third element is a dictionary of strings describing configuration parameters like API keys. [@TMDB_PLUGIN_CONFIG@] mediascanner-0.3.93+14.04.20131024.1/tests/0000755000015700001700000000000012232220310020053 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/testlib/0000755000015700001700000000000012232220310021521 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/testlib/loggingsink.cpp0000644000015700001700000001015012232220161024541 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "testlib/loggingsink.h" // Boost C++ #include #include #include // C++ Standard Library #include #include #include #include #include namespace mediascanner { typedef boost::function MessageAcceptor; class CapturingMessageSink : public logging::DefaultMessageSink { public: CapturingMessageSink(logging::MessageSinkPtr parent, MessageAcceptor acceptor, std::ostream *stream) : DefaultMessageSink(stream) , parent_(parent) , acceptor_(acceptor) { } void Report(const std::string &domain_name, const std::string &message) { if (acceptor_(domain_name, message)) DefaultMessageSink::Report(domain_name, message); if (parent_) parent_->Report(domain_name, message); } private: logging::MessageSinkPtr parent_; MessageAcceptor acceptor_; }; class LoggingSink::Private { public: typedef std::map DomainSettingsMap; typedef std::vector > IgnorePatterns; explicit Private(logging::Domain *level) : initial_settings_(collect_domain_settings(level)) { for (logging::Domain *ld = level; ld; ld = ld->parent()) { const logging::MessageSinkPtr capturing_message_sink (make_message_sink(ld->message_sink())); ld->set_message_sink(capturing_message_sink); } } ~Private() { for (const auto &p: initial_settings_) { p.first->set_message_sink(p.second.message_sink()); p.first->set_enabled(p.second.enabled()); } } CapturingMessageSink* make_message_sink(logging::MessageSinkPtr parent) { return new CapturingMessageSink (parent, boost::bind(&Private::accept_message, this, _1, _2), &stream_); } bool accept_message(const std::string &domain_name, const std::string &message) { for (const auto &p: ignore_patterns_) { if (p.first == domain_name && boost::regex_match(message, p.second)) return false; } return true; } static DomainSettingsMap collect_domain_settings(logging::Domain *level) { DomainSettingsMap settings; for (logging::Domain *domain = level; domain; domain = domain->parent()) settings.insert(std::make_pair(domain, *domain)); return settings; } const DomainSettingsMap initial_settings_; std::stringstream stream_; IgnorePatterns ignore_patterns_; }; LoggingSink::LoggingSink(logging::Domain *level) : d(new Private(level)) { } LoggingSink::~LoggingSink() { delete d; } void LoggingSink::reset() { d->stream_.str(std::string()); } std::string LoggingSink::get() const { return d->stream_.str(); } std::string LoggingSink::take() { const std::string str = get(); return reset(), str; } void LoggingSink::ignore(const std::string &domain, const std::string &pattern) { d->ignore_patterns_.push_back (std::make_pair(domain, boost::regex(pattern))); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/tests/testlib/testutils.h0000644000015700001700000000362412232220161023743 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_TESTUTILS_H #define MEDIASCANNER_TESTUTILS_H // Boost C++ #include #include // C++ Standard Library #include // Media Scanner Library #include "mediascanner/property.h" #include "mediascanner/utilities.h" namespace boost { namespace filesystem { inline void PrintTo(const path &p, std::ostream *os) { *os << p.string(); } } // namespace filesystem } // namespace boost namespace mediascanner { void InitTests(int *argc, char **argv); inline void PrintTo(const Property *property, std::ostream *os) { *os << property->field_name() << " property"; } inline void PrintTo(const Property &property, std::ostream *os) { *os << property.field_name() << " property"; } inline void PrintTo(const Property::Value &value, std::ostream *os) { *os << value; } inline void PrintTo(const nullptr_t &, std::ostream *os) { *os << "NULL"; } std::string make_base_url(); inline std::string full_url(const std::string &url) { return make_base_url() + url; } } // namespace mediascanner #endif // MEDIASCANNER_TESTUTILS_H mediascanner-0.3.93+14.04.20131024.1/tests/testlib/environments.cpp0000644000015700001700000004072712232220161024772 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "testlib/environments.h" // Boost C++ #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/dbusservice.h" #include "mediascanner/logging.h" #include "mediascanner/glibutils.h" #include "mediascanner/mediaroot.h" #include "mediascanner/writablemediaindex.h" #include "mediascanner/utilities.h" // Media Scanner Plugin for Grilo #include "grlmediascanner/mediasource.h" namespace mediascanner { static const logging::Domain kInfo("info", logging::info()); //////////////////////////////////////////////////////////////////////////////// DBusEnvironment::DBusEnvironment(const FileSystemPath &data_path, const FileSystemPath &index_path) : data_path_(data_path) , index_path_(index_path) , dbus_daemon_pid_(0) , mediascanner_pid_(0) { } void DBusEnvironment::SetUp() { const char *const dbus_daemon_argv[] = { "dbus-daemon", "--session", "--fork", "--print-address=1", "--print-pid=2", nullptr }; Wrapper standard_output; Wrapper standard_error; Wrapper error; int exit_status = 0; const bool dbus_started = g_spawn_sync(data_path_.string().c_str(), const_cast(dbus_daemon_argv), nullptr, G_SPAWN_SEARCH_PATH, nullptr, this, standard_output.out_param(), standard_error.out_param(), &exit_status, error.out_param()); ASSERT_FALSE(error) << to_string(error); ASSERT_TRUE(WIFEXITED(exit_status)) << "exit status: " << exit_status << std::endl << "standard output: " << standard_output.get() << std::endl << "standard error: " << standard_error.get(); ASSERT_EQ(0, WEXITSTATUS(exit_status)) << "exit status: " << exit_status << std::endl << "standard output: " << standard_output.get() << std::endl << "standard error: " << standard_error.get(); ASSERT_TRUE(dbus_started); const size_t n = strcspn(standard_output.get(), "\r\n"); standard_output.get()[n] = '\0'; const std::string session_address = standard_output.get(); ASSERT_FALSE(session_address.empty()); std::istringstream(standard_error.get()) >> dbus_daemon_pid_; ASSERT_NE(0, dbus_daemon_pid_); g_setenv("DBUS_SESSION_BUS_ADDRESS", session_address.c_str(), true); kInfo("Running D-Bus daemon with pid={1} at=<{2}>") % dbus_daemon_pid_ % session_address; const std::string media_index_arg = "--media-index-path=" + index_path_.string(); const char *const mediascanner_argv[] = { MEDIASCANNER_SERVICE, media_index_arg.c_str(), "--disable-scanning", "--disable-volume-monitor", MEDIA_DIR, nullptr }; const bool scanner_started = g_spawn_async(data_path_.string().c_str(), const_cast(mediascanner_argv), nullptr, G_SPAWN_DO_NOT_REAP_CHILD, nullptr, this, &mediascanner_pid_, error.out_param()); ASSERT_FALSE(error) << to_string(error); ASSERT_TRUE(scanner_started); // Start media scanner service. kInfo("Running mediascanner with pid={1}") % mediascanner_pid_; const Wrapper session_bus = take(g_dbus_connection_new_for_address_sync (session_address.c_str(), G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, nullptr, error.out_param())); EXPECT_FALSE(error) << to_string(error) << std::endl << session_address; ASSERT_TRUE(session_bus); dbus::MediaScannerProxy service; const bool connected = service.ConnectAndWait(session_bus, Wrapper(), error.out_param()); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(connected); // Wait up to one minute for the media scanner becoming available. Wrapper media_scanner_address; for (int i = 0; i < 60; ++i) { if (media_scanner_address = take(g_dbus_proxy_get_name_owner(service.handle().get()))) break; const std::string connection_name = dbus::ConnectionName(service.connection()); kInfo("Waiting for mediascanner to become available at <{1}>...") % connection_name; Wrapper loop = take(g_main_loop_new(nullptr, true)); Timeout::AddOnce(boost::posix_time::milliseconds(1000), std::bind(&g_main_loop_quit, loop.get())); g_main_loop_run(loop.get()); } ASSERT_TRUE(media_scanner_address); // Check if the media scanner we found operates on the same index. std::string index_path; const bool has_index_path_property = service.index_path.ReadValue(&service, &index_path); ASSERT_TRUE(has_index_path_property); ASSERT_EQ(index_path_, index_path); } static void terminate_child(pid_t child) { int status = 0; switch (waitpid(child, &status, WNOHANG)) { case 0: std::cout << "Terminating process " << child << std::endl; kill(child, SIGTERM); break; case -1: std::cout << "Failed to wait for process " << child << ": " << g_strerror(errno) << std::endl; break; default: if (WIFEXITED(status)) { std::cout << "Process " << child << " has exited already with an " "exit status of " << WEXITSTATUS(status) << std::endl; } else if (WIFSIGNALED(status)) { std::cout << "Process " << child << " was terminated already " "with signal " << WTERMSIG(status) << std::endl; } else { std::cout << "Process " << child << " has unknown status." << std::endl; } break; } } void DBusEnvironment::TearDown() { if (mediascanner_pid_) { terminate_child(mediascanner_pid_); g_spawn_close_pid(mediascanner_pid_); mediascanner_pid_ = 0; } if (dbus_daemon_pid_) { kill(dbus_daemon_pid_, SIGTERM); dbus_daemon_pid_ = 0; } } //////////////////////////////////////////////////////////////////////////////// LuceneEnvironment::LuceneEnvironment(const FileSystemPath &index_path) : index_path_(index_path) { } void LuceneEnvironment::SetUp() { boost::filesystem::remove_all(index_path_); boost::filesystem::create_directories(index_path_.parent_path()); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->AddManualRoot(MEDIA_DIR); WritableMediaIndex writer(root_manager); const bool writer_opened = writer.Open(index_path_); EXPECT_EQ(std::string(), writer.error_message()); EXPECT_EQ(index_path_, writer.path()); ASSERT_TRUE(writer_opened); FillMediaIndex(&writer); } void LuceneEnvironment::FillMediaIndex(WritableMediaIndex */*writer*/) { } //////////////////////////////////////////////////////////////////////////////// GriloPluginEnvironment::GriloPluginEnvironment(const std::string &plugin_id, const std::string &source_id, const ConfigMap &config) : plugin_id_(plugin_id) , source_id_(source_id) , config_params_(config) , destroy_count_(0) , object_(nullptr) { } void GriloPluginEnvironment::SetUp() { Wrapper registry = wrap(grl_registry_get_default()); Wrapper error; Wrapper config = take(grl_config_new(plugin_id_.c_str(), source_id_.c_str())); for (const auto &p: config_params_) { grl_config_set_string(config.get(), p.first.c_str(), p.second.c_str()); } const bool config_added = grl_registry_add_config (registry.get(), config.get(), error.out_param()); EXPECT_FALSE(error); ASSERT_TRUE(config_added); const bool plugin_loaded = grl_registry_load_plugin_by_id (registry.get(), plugin_id_.c_str(), error.out_param()); EXPECT_FALSE(error); ASSERT_TRUE(plugin_loaded); const Wrapper plugin = wrap(grl_registry_lookup_plugin(registry.get(), plugin_id_.c_str())); ASSERT_TRUE(plugin); const FileSystemPath plugin_path = grl_plugin_get_filename(plugin.get()); ASSERT_EQ(FileSystemPath(GRILO_PLUGIN_DIR), plugin_path.parent_path()); const Wrapper source = wrap(grl_registry_lookup_source(registry.get(), plugin_id_.c_str())); ASSERT_TRUE(source); object_ = source.get(); g_object_weak_ref(object_, &GriloPluginEnvironment::OnSourceDestroy, this); } void GriloPluginEnvironment::TearDown() { Wrapper registry = wrap(grl_registry_get_default()); Wrapper error; EXPECT_EQ(0, destroy_count_); const bool plugin_unloaded = grl_registry_unload_plugin (registry.get(), plugin_id_.c_str(), error.out_param()); EXPECT_FALSE(error); ASSERT_TRUE(plugin_unloaded); EXPECT_EQ(1, destroy_count_) << " Source: " << source_id_ << std::endl << " #refs: " << (object_ ? object_->ref_count : 0); } void GriloPluginEnvironment::OnSourceDestroy(gpointer data, GObject *) { GriloPluginEnvironment *const env = static_cast(data); env->object_ = nullptr; ++env->destroy_count_; } //////////////////////////////////////////////////////////////////////////////// MediaScannerEnvironment::MediaScannerEnvironment (const FileSystemPath &index_path) : GriloPluginEnvironment(GRL_MEDIA_SCANNER_PLUGIN_ID, GRL_MEDIA_SCANNER_PLUGIN_ID, MakeConfig(index_path)) { } MediaScannerEnvironment::ConfigMap MediaScannerEnvironment::MakeConfig (const FileSystemPath &index_path) { ConfigMap config; config.insert(ConfigParam(GRL_MEDIA_SCANNER_CONFIG_INDEX_PATH, index_path.string())); return config; } //////////////////////////////////////////////////////////////////////////////// TheMovieDbEnvironment::TheMovieDbEnvironment(const std::string &mock_file) : GriloPluginEnvironment("grl-tmdb", "grl-tmdb", MakeConfig()) , mock_file_(mock_file) { } void TheMovieDbEnvironment::SetUp() { if (not mock_file_.empty()) g_setenv("GRL_NET_MOCKED", mock_file_.c_str(), true); GriloPluginEnvironment::SetUp(); } TheMovieDbEnvironment::ConfigMap TheMovieDbEnvironment::MakeConfig() { ConfigMap config; config.insert(ConfigParam(GRL_CONFIG_KEY_APIKEY, "fakekey")); return config; } //////////////////////////////////////////////////////////////////////////////// LastFmEnvironment::LastFmEnvironment(const std::string &mock_file) : GriloPluginEnvironment("grl-lastfm-albumart", "grl-lastfm-albumart", ConfigMap()) , mock_file_(mock_file) { } void LastFmEnvironment::SetUp() { if (not mock_file_.empty()) g_setenv("GRL_NET_MOCKED", mock_file_.c_str(), true); GriloPluginEnvironment::SetUp(); } //////////////////////////////////////////////////////////////////////////////// #if GUDEV_VERSION >= 175 void FakeUDevEnvironment::SetUp() { using boost::filesystem::current_path; using boost::filesystem::create_directories; using boost::filesystem::create_symlink; boost::system::error_code ec; struct stat media_dir_info; ASSERT_EQ(0, stat(MEDIA_DIR, &media_dir_info)) << g_strerror(errno); const std::string device_name = "dm-1"; const std::string device_uuid = "25baa005-8898-4255-a173-b47a72720f7a"; const dev_t device_number = media_dir_info.st_dev; std::ostringstream device_node; device_node << major(device_number) << ':' << minor(device_number); const FileSystemPath rundir = current_path() / "run"; const FileSystemPath sysfsdir = rundir / "sysfs"; const FileSystemPath udevdir = rundir / "udev"; // Write mock version of the udev configuration file const FileSystemPath udev_conf_path = current_path() / "udev.conf"; std::ofstream udev_conf_stream(udev_conf_path.string().c_str()); udev_conf_stream << "udev_run = " << udevdir.string() << std::endl; ASSERT_TRUE(udev_conf_stream); // Create mock version of the sysfs filesystem boost::filesystem::remove_all(sysfsdir); const FileSystemPath device_node_dir = sysfsdir / "dev" / "block" / device_node.str(); const FileSystemPath block_device_dir = sysfsdir / "block" / device_name; const std::string real_block_device_axis = "devices/virtual/block/" + device_name; const FileSystemPath real_block_device_dir = sysfsdir / real_block_device_axis; const std::string subsystem_axis = "class/block"; const FileSystemPath subsystem_dir = sysfsdir / subsystem_axis; ASSERT_TRUE(create_directories(real_block_device_dir)); ASSERT_TRUE(create_directories(device_node_dir.parent_path())); ASSERT_TRUE(create_directories(block_device_dir.parent_path())); ASSERT_TRUE(create_directories(subsystem_dir)); // Must use relative path as udev-175's symlink resolving is broken. create_symlink("../" + real_block_device_axis, block_device_dir, ec); create_symlink("../../" + real_block_device_axis, device_node_dir, ec); create_symlink("../../../../" + subsystem_axis, block_device_dir / "subsystem", ec); create_symlink("../../" + real_block_device_axis, subsystem_dir / device_name, ec); const FileSystemPath uevent_filename = real_block_device_dir / "uevent"; std::ofstream uevent_stream(uevent_filename.string().c_str()); uevent_stream << "MAJOR=" << major(device_number) << std::endl << "MINOR=" << minor(device_number) << std::endl << "DEVNAME=" << device_name << std::endl << "DEVTYPE=disk" << std::endl; ASSERT_TRUE(uevent_stream); // Create mock version of the udev data directory boost::filesystem::remove_all(udevdir); const FileSystemPath udev_data_dir = udevdir / "data"; ASSERT_TRUE(create_directories(udev_data_dir)); std::ostringstream entry_name; const FileSystemPath entry_path = udev_data_dir / ("b" + device_node.str()); std::ofstream entry_stream(entry_path.string().c_str()); entry_stream << "N:" << device_name << std::endl << "S:disk/by-id/dm-name-ubuntu-root" << std::endl << "S:disk/by-uuid/" << device_uuid << std::endl << "S:mapper/ubuntu-root" << std::endl << "S:ubuntu/root" << std::endl << "E:ID_FS_TYPE=ext4" << std::endl << "E:ID_FS_USAGE=filesystem" << std::endl << "E:ID_FS_UUID=" << device_uuid << std::endl << "E:ID_FS_UUID_ENC=" << device_uuid << std::endl << "E:ID_FS_VERSION=1.0" << std::endl; ASSERT_TRUE(entry_stream); // Patch environment variables g_setenv("UDEV_CONFIG_FILE", udev_conf_path.string().c_str(), true); g_setenv("SYSFS_PATH", sysfsdir.string().c_str(), true); } #else // GUDEV_VERSION #error Unsupported UDev version #endif // GUDEV_VERSION //////////////////////////////////////////////////////////////////////////////// } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/tests/testlib/loggingsink.h0000644000015700001700000000256512232220161024221 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_LOGGINGSINK_H #define MEDIASCANNER_LOGGINGSINK_H // C++ Standard Library #include // Media Scanner Library #include "mediascanner/logging.h" namespace mediascanner { class LoggingSink { class Private; public: explicit LoggingSink(logging::Domain *level = logging::trace()); ~LoggingSink(); void reset(); std::string get() const; std::string take(); void ignore(const std::string &domain, const std::string &pattern); private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_LOGGINGSINK_H mediascanner-0.3.93+14.04.20131024.1/tests/testlib/environments.h0000644000015700001700000000610412232220161024426 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_ENVIRONMENTS_H #define MEDIASCANNER_ENVIRONMENTS_H // Google Tests #include // Boost C++ #include // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/glibutils.h" namespace mediascanner { class WritableMediaIndex; class DBusEnvironment : public testing::Environment { public: DBusEnvironment(const FileSystemPath &data_path, const FileSystemPath &index_path); void SetUp(); void TearDown(); private: FileSystemPath data_path_; FileSystemPath index_path_; pid_t dbus_daemon_pid_; GPid mediascanner_pid_; }; class LuceneEnvironment : public testing::Environment { public: explicit LuceneEnvironment(const FileSystemPath &index_path); void SetUp(); protected: virtual void FillMediaIndex(WritableMediaIndex *writer); private: FileSystemPath index_path_; }; class GriloPluginEnvironment : public testing::Environment { public: typedef std::map ConfigMap; typedef ConfigMap::value_type ConfigParam; GriloPluginEnvironment(const std::string &plugin_id, const std::string &source_id, const ConfigMap &config); void SetUp(); void TearDown(); private: static void OnSourceDestroy(gpointer data, GObject *); std::string plugin_id_; std::string source_id_; ConfigMap config_params_; unsigned destroy_count_; GObject *object_; }; class MediaScannerEnvironment : public GriloPluginEnvironment { public: explicit MediaScannerEnvironment(const FileSystemPath &index_path); protected: static ConfigMap MakeConfig(const FileSystemPath &index_path); }; class TheMovieDbEnvironment : public GriloPluginEnvironment { public: explicit TheMovieDbEnvironment(const std::string &mock_file); void SetUp(); protected: static ConfigMap MakeConfig(); private: std::string mock_file_; }; class LastFmEnvironment : public GriloPluginEnvironment { public: explicit LastFmEnvironment(const std::string &mock_file); void SetUp(); private: std::string mock_file_; }; class FakeUDevEnvironment : public testing::Environment { public: void SetUp(); }; } // namespace mediascanner #endif // MEDIASCANNER_ENVIRONMENTS_H mediascanner-0.3.93+14.04.20131024.1/tests/testlib/testutils.cpp0000644000015700001700000000367712232220161024306 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "testlib/testutils.h" // GLib related libraries #include // Google Tests #include // C++ Standard Library #include // Media Scanner #include "mediascanner/locale.h" #include "mediascanner/logging.h" // Test Suite #include "testlib/environments.h" namespace mediascanner { void InitTests(int *argc, char **argv) { std::set_terminate(mediascanner::abort_with_backtrace); // Override some essential environment variables g_setenv("GIO_USE_VFS", "local", true); g_setenv(GRL_PLUGIN_PATH_VAR, GRILO_PLUGIN_DIR, true); g_setenv("XDG_DATA_DIRS", USER_DATA_DIR ":/usr/share", true); g_setenv("MEDIASCANNER_UNDER_TEST", "1", true); // Redirect, so that we check for operating without errors logging::capture_glib_messages(); // Install boost locale facets. SetupLocale(); ::testing::InitGoogleTest(argc, argv); ::testing::AddGlobalTestEnvironment(new FakeUDevEnvironment); } std::string make_base_url() { std::string base_url = "file://" MEDIA_DIR; if (base_url.at(base_url.length() - 1) != '/') base_url.append(1, '/'); return base_url; } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/tests/auto/0000755000015700001700000000000012232220310021023 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/auto/glibutilstest.cpp0000644000015700001700000000330412232220161024431 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Google Tests #include // Standard Library #include // Media Scanner #include "mediascanner/glibutils.h" #include "mediascanner/utilities.h" // Test Suite #include "testlib/testutils.h" namespace mediascanner { TEST(GlibUtilsTest, ErrorString) { const GQuark error_domain = g_quark_from_static_string("test-error"); const std::string error_message("This is a test"); const int error_code = 42; Wrapper error; g_set_error_literal(error.out_param(), error_domain, error_code, error_message.c_str()); EXPECT_EQ(error_domain, error->domain); EXPECT_EQ(error_code, error->code); EXPECT_EQ(error_message, safe_string(error->message)); EXPECT_EQ("test-error(42): This is a test", to_string(error)); } } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/mediaartcachetest.cpp0000644000015700001700000001044712232220161025213 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Jussi Pakkanen */ #include #include #include #include #include #include #include"testlib/testutils.h" #include "mediascanner/mediaartcache.h" using namespace std; namespace mediascanner { TEST(MediaArtCacheTest, BasicFunctionality) { MediaArtCache mac; mac.clear(); string artist = "Some Guy"; string album = "Muzak"; const int datasize = 3; char data[datasize] = {'a', 'b', 'c'}; char indata[datasize]; FILE *f; ASSERT_FALSE(mac.has_art(artist, album)); mac.add_art(artist, album, data, datasize); ASSERT_TRUE(mac.has_art(artist, album)); f = fopen(mac.get_art_file(artist, album).c_str(), "r"); ASSERT_TRUE(f); ASSERT_EQ(fread(indata, 1, datasize, f), datasize); fclose(f); ASSERT_EQ(memcmp(indata, data, datasize), 0); mac.clear(); assert(!mac.has_art(artist, album)); } TEST(MediaArtCacheTest, Swapped) { string artist1("foo"); string album1("bar"); string artist2(album1); string album2(artist1); const int datasize = 4; char data1[datasize] = {'a', 'b', 'c', 'd'}; char data2[datasize] = {'d', 'c', 'b', 'e'}; char indata[datasize]; MediaArtCache mac; FILE *f; mac.clear(); ASSERT_FALSE(mac.has_art(artist1, album1)); ASSERT_FALSE(mac.has_art(artist2, album2)); mac.add_art(artist1, album1, data1, datasize); ASSERT_TRUE(mac.has_art(artist1, album1)); ASSERT_FALSE(mac.has_art(artist2, album2)); mac.clear(); mac.add_art(artist2, album2, data2, datasize); ASSERT_FALSE(mac.has_art(artist1, album1)); ASSERT_TRUE(mac.has_art(artist2, album2)); mac.add_art(artist1, album1, data1, datasize); f = fopen(mac.get_art_file(artist1, album1).c_str(), "r"); ASSERT_TRUE(f); ASSERT_EQ(fread(indata, 1, datasize, f), datasize); fclose(f); ASSERT_EQ(memcmp(indata, data1, datasize), 0); f = fopen(mac.get_art_file(artist2, album2).c_str(), "r"); ASSERT_TRUE(f); ASSERT_EQ(fread(indata, 1, datasize, f), datasize); fclose(f); ASSERT_EQ(memcmp(indata, data2, datasize), 0); } static int count_files(const string &dir) { DIR *d = opendir(dir.c_str()); int count = 0; if(!d) { string s = "Something went wrong."; throw s; } struct dirent *entry, *de; entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX); while(readdir_r(d, entry, &de) == 0 && de) { string basename = entry->d_name; if (basename == "." || basename == "..") continue; count++; } closedir(d); free(entry); return count; } TEST(MediaArtCacheTest, Prune) { MediaArtCache mac; mac.clear(); const int max_files = MediaArtCache::MAX_SIZE; string cache_dir = mac.get_cache_dir(); char arr[] = {'a', 'b', 'c'}; ASSERT_EQ(count_files(cache_dir), 0); for(int i=0; i. * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Google Tests #include // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/logging.h" #include "mediascanner/taskmanager.h" // Test Suite #include "testlib/testutils.h" namespace mediascanner { // Standard Library using std::string; // Context specific logging domains static const logging::Domain kDebug("debug/tests", logging::debug()); class WordPlay { public: explicit WordPlay(const string &expected_words) : expected_words_(expected_words) { g_mutex_init(&mutex_); g_cond_init(&cond_); } string expected_words() const { return expected_words_; } string actual_words() const { return actual_words_; } void append_word(const string &word) { lock("append word"); kDebug("Appending \"{1}\".") % word; if (actual_words_.length()) actual_words_ += " "; actual_words_ += word; if (actual_words_.size() >= expected_words_.size()) notify_unlocked("completion of word play"); unlock(); } void lock(const string &goal) { kDebug("Waiting for lock to {1}.") % goal; g_mutex_lock(&mutex_); } void unlock() { g_mutex_unlock(&mutex_); } void notify(const string &event) { lock("notify about " + event); notify_unlocked(event); unlock(); } void notify_unlocked(const string &event) { kDebug("Sending notification about {1}.") % event; g_cond_signal(&cond_); } bool wait(const string &goal, double seconds) { lock("wait for " + goal); const bool signal_received = wait_unlocked(goal, seconds); unlock(); return signal_received; } bool wait_unlocked(const string &goal, double seconds) { bool signal_received = true; if (seconds > 0) { const int64_t end_time = g_get_monotonic_time() + seconds * G_TIME_SPAN_SECOND; kDebug("Waiting up to {2,p3} s for {1}.") % goal % seconds; signal_received = g_cond_wait_until(&cond_, &mutex_, end_time); } else { kDebug("Waiting infinitely for {1}.") % goal; g_cond_wait(&cond_, &mutex_); } EXPECT_TRUE(signal_received) << ("...while waiting for " + goal); return signal_received; } bool notify_and_wait(const string &event, const string &goal, double seconds) { lock("notify about " + event + ", and then wait for " + goal); notify_unlocked(event); const bool signal_received = wait_unlocked(goal, seconds); unlock(); return signal_received; } TaskManager::TaskFunction bind_append_word(const string &word) { return std::bind(&WordPlay::append_word, this, word); } TaskManager::TaskFunction bind_wait(const string &goal, double seconds) { return std::bind(&WordPlay::wait, this, goal, seconds); } TaskManager::TaskFunction bind_notify_and_wait(const string &event, const string &goal, double seconds) { return std::bind(&WordPlay::notify_and_wait, this, event, goal, seconds); } private: const string expected_words_; string actual_words_; GMutex mutex_; GCond cond_; }; TEST(TaskManagerTest, AppendTask) { WordPlay test("this are some famous last words"); TaskManager manager(test_info_->name()); test.lock("append tasks"); manager.AppendTask(test.bind_notify_and_wait("task manager running", "all tasks being queued", 5)); test.wait_unlocked("task manager running", 1); manager.AppendTask(test.bind_append_word("famous"), 4); manager.AppendTask(test.bind_append_word("some"), 3); manager.AppendTask(test.bind_append_word("this"), 1); manager.AppendTask(test.bind_append_word("are"), 2); manager.AppendTask(test.bind_append_word("last")); manager.AppendTask(test.bind_append_word("words")); test.notify_unlocked("all tasks being queued"); test.wait_unlocked("all tasks being run", 1); test.unlock(); for (int i = 1; i < 5; ++i) manager.CancelByGroupId(i); ASSERT_EQ(test.expected_words(), test.actual_words()); } TEST(TaskManagerTest, PrependTask) { WordPlay test("this are some famous last words"); TaskManager manager(test_info_->name()); test.lock("append tasks"); manager.PrependTask(test.bind_notify_and_wait("task manager running", "all tasks being queued", 5)); test.wait_unlocked("task manager running", 1); manager.PrependTask(test.bind_append_word("famous"), 2); manager.PrependTask(test.bind_append_word("some"), 1); manager.PrependTask(test.bind_append_word("last"), 3); manager.PrependTask(test.bind_append_word("words"), 4); manager.PrependTask(test.bind_append_word("are")); manager.PrependTask(test.bind_append_word("this")); test.notify_unlocked("all tasks being queued"); test.wait_unlocked("all tasks being run", 1); test.unlock(); for (int i = 1; i < 5; ++i) manager.CancelByGroupId(i); ASSERT_EQ(test.expected_words(), test.actual_words()); } TEST(TaskManagerTest, CancelTask) { WordPlay test("this is good"); TaskManager manager(test_info_->name()); test.lock("append tasks"); manager.AppendTask(test.bind_notify_and_wait("task manager running", "all tasks being queued", 5)); test.wait_unlocked("task manager running", 1); manager.AppendTask(test.bind_append_word("this"), 1); manager.AppendTask(test.bind_append_word("is"), 2); manager.AppendTask(test.bind_append_word("not"), 3); manager.AppendTask(test.bind_append_word("good"), 4); manager.CancelByGroupId(3); test.notify_unlocked("all tasks being queued"); test.wait_unlocked("all tasks being run", 1); test.unlock(); for (int i = 1; i < 5; ++i) manager.CancelByGroupId(i); ASSERT_EQ(test.expected_words(), test.actual_words()); } } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/localetest.cpp0000644000015700001700000000355212232220161023677 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Google Tests #include // Standard Library #include // Media Scanner #include "mediascanner/locale.h" #include "mediascanner/logging.h" // Test Suite #include "testlib/loggingsink.h" #include "testlib/testutils.h" namespace mediascanner { TEST(LocaleTest, FromUnicodeWithoutSetup) { LoggingSink sink; std::locale::global(std::locale::classic()); ASSERT_TRUE(logging::warning()->enabled()); EXPECT_TRUE(sink.get().empty()); ASSERT_EQ(std::string("Wide String"), FromUnicode(L"Wide String")); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); } TEST(LocaleTest, ToUnicodeWithoutSetup) { LoggingSink sink; std::locale::global(std::locale::classic()); ASSERT_TRUE(logging::warning()->enabled()); EXPECT_TRUE(sink.get().empty()); ASSERT_EQ(std::wstring(L"String"), ToUnicode("String")); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); } } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/mediaindextest.cpp0000644000015700001700000001364012232220161024546 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Google Tests #include // Boost C++ #include #include // Standard Library #include // Media Scanner #include "mediascanner/locale.h" #include "mediascanner/mediaroot.h" #include "mediascanner/propertyschema.h" #include "mediascanner/writablemediaindex.h" // Test Suite #include "testlib/testutils.h" namespace mediascanner { static FileSystemPath PrepareIndexPath() { const FileSystemPath index_path = FileSystemPath("run/mediaindextest") / testing::UnitTest::GetInstance()->current_test_info()->name(); boost::filesystem::remove_all(index_path); boost::filesystem::create_directories(index_path); return index_path; } TEST(MediaIndexTest, Opening) { const MediaRootManagerPtr root_manager(new MediaRootManager); const FileSystemPath index_path = PrepareIndexPath(); // Create a media index: // Only existing media indices can be opened for reading. WritableMediaIndex writer(root_manager); EXPECT_TRUE(writer.error_message().empty()); ASSERT_TRUE(writer.path().empty()); const bool writer_opened = writer.Open(index_path); EXPECT_EQ(index_path.string(), writer.path()); EXPECT_EQ(std::string(), writer.error_message()); ASSERT_TRUE(writer_opened); // Try to open a media index. MediaIndex reader(root_manager); EXPECT_TRUE(reader.error_message().empty()); ASSERT_TRUE(reader.path().empty()); const bool reader_opened = reader.Open(index_path); EXPECT_EQ(index_path.string(), reader.path()); EXPECT_EQ(std::string(), reader.error_message()); ASSERT_TRUE(reader_opened); // Try to re-open the media index. This should fail. const bool reader_reopened = reader.Open(index_path); EXPECT_EQ(index_path.string(), reader.path()); EXPECT_NE(std::string(), reader.error_message()); ASSERT_FALSE(reader_reopened); } TEST(MediaIndexTest, Lookup) { const FileSystemPath index_path = PrepareIndexPath(); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->AddManualRoot(MEDIA_DIR); WritableMediaIndex writer(root_manager); ASSERT_TRUE(writer.Open(index_path)); ASSERT_EQ(std::string(), writer.error_message()); const std::wstring test_url = ToUnicode(full_url("lookup")); const std::wstring test_tag = L"4711/0815"; MediaInfo inserted; inserted.add_single(schema::kETag.bind_value(test_tag)); writer.Insert(test_url, inserted); ASSERT_EQ(std::string(), writer.error_message()); MediaIndex reader(root_manager); ASSERT_TRUE(reader.Open(index_path)); const MediaInfo resolved = reader.Lookup(test_url.c_str()); ASSERT_EQ(1, resolved.count(schema::kETag)); ASSERT_EQ(1, resolved.count(schema::kUrl)); EXPECT_EQ(test_tag, resolved.first(schema::kETag)); EXPECT_EQ(test_url, resolved.first(schema::kUrl)); } TEST(MediaIndexTest, LookupEmpty) { const FileSystemPath index_path = PrepareIndexPath(); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->AddManualRoot(MEDIA_DIR); WritableMediaIndex writer(root_manager); ASSERT_TRUE(writer.Open(index_path)); MediaIndex reader(root_manager); ASSERT_TRUE(reader.Open(index_path)); const std::wstring test_url = ToUnicode(full_url("lookup")); ASSERT_EQ(std::wstring(), reader.Lookup(test_url, schema::kETag)); } TEST(MediaIndexTest, RoundTripMultiValueProperty) { const FileSystemPath index_path = PrepareIndexPath(); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->AddManualRoot(MEDIA_DIR); WritableMediaIndex writer(root_manager); ASSERT_TRUE(writer.Open(index_path)); ASSERT_EQ(std::string(), writer.error_message()); const std::wstring test_url = ToUnicode(full_url("lookup")); // Make sure the property we're dealing with is multi-valued. ASSERT_EQ(schema::kAuthor.merge_strategy(), Property::MergeAppend); MediaInfo inserted; inserted.add_single(schema::kAuthor.bind_value(L"First Author")); inserted.add_single(schema::kAuthor.bind_value(L"Second Author")); inserted.add_single(schema::kAuthor.bind_value(L"Third Author")); ASSERT_EQ(3, inserted.count(schema::kAuthor)); writer.Insert(test_url, inserted); ASSERT_EQ(std::string(), writer.error_message()); MediaIndex reader(root_manager); ASSERT_TRUE(reader.Open(index_path)); const MediaInfo resolved = reader.Lookup(test_url.c_str()); ASSERT_EQ(3, resolved.count(schema::kAuthor)); // Verify that both values have been retrieved in the right order. std::vector authors; for (const auto &properties: resolved) { const Property::ValueMap::const_iterator it = properties.find(schema::kAuthor); if (it != properties.end()) authors.push_back(boost::get(it->second)); } ASSERT_EQ(3, authors.size()); ASSERT_EQ(L"First Author", authors[0]); ASSERT_EQ(L"Second Author", authors[1]); ASSERT_EQ(L"Third Author", authors[2]); } } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/filesystemscannertest.cpp0000644000015700001700000003142512232220161026176 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Grilo #include // Google Tests #include // Boost C++ #include #include #include // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/filesystemscanner.h" #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaroot.h" #include "mediascanner/metadataresolver.h" #include "mediascanner/propertyschema.h" #include "mediascanner/taskfacades.h" #include "mediascanner/taskmanager.h" #include "mediascanner/utilities.h" #include "mediascanner/writablemediaindex.h" // Test Suite #include "testlib/environments.h" #include "testlib/loggingsink.h" #include "testlib/testutils.h" namespace mediascanner { namespace filesystemscannertest { static const FileSystemPath kDataPath = boost::filesystem::current_path() / "run/filesystemscannertest"; static const FileSystemPath kMediaIndexPath = kDataPath / "index"; class FileSystemScannerTest : public testing::Test , public FileSystemScanner::Listener { public: void OnScanningFinished(const std::string &error_message) { EXPECT_EQ(std::string(), error_message); if (loop_) { g_main_loop_quit(loop_.get()); loop_.reset(); } } void on_timeout() { if (loop_) { g_main_loop_quit(loop_.get()); timeout_reached_ = true; loop_.reset(); } } void WaitForFinished(Timeout::Duration duration) { loop_ = take(g_main_loop_new(nullptr, false)); timeout_reached_ = false; Timeout::AddOnce(duration, std::bind(&FileSystemScannerTest::on_timeout, this)); g_main_loop_run(loop_.get()); ASSERT_FALSE(timeout_reached_); } protected: Wrapper loop_; bool timeout_reached_; }; static void verify_scanned_media(const MediaInfo &info, int32_t remaining, unsigned *fileCount) { EXPECT_LE(remaining, 16); using boost::algorithm::starts_with; using boost::algorithm::ends_with; const std::wstring url = info.first(schema::kUrl); const std::wstring::size_type n = url.find_last_of('/'); ASSERT_NE(std::wstring::npos, n) << url; const std::wstring name = url.substr(n + 1); if (starts_with(name, L"tears_of_steel_")) { EXPECT_EQ(L"Tears of Steel", info.first(schema::kTitle)) << name; EXPECT_EQ(L"Ian Hubert", info.first(schema::kDirector)) << name; EXPECT_EQ(L"http://www.tearsofsteel.org/", info.first(schema::kHomepageUrl)) << name; EXPECT_EQ(L"133701", info.first(schema::kTmdbId)) << name; EXPECT_EQ(1, boost::get(info.first(schema::kDuration))) << name; } else if (boost::algorithm::starts_with(name, L"big_buck_bunny_") || boost::algorithm::starts_with(name, L"BigBuckBunny_")) { EXPECT_EQ(L"Big Buck Bunny", info.first(schema::kTitle)) << name; EXPECT_EQ(L"Sacha Goedegebure", info.first(schema::kDirector)) << name; EXPECT_EQ(L"http://www.bigbuckbunny.org", info.first(schema::kHomepageUrl)) << name; EXPECT_EQ(L"10378", info.first(schema::kTmdbId)) << name; } else if (boost::algorithm::starts_with(name, L"Sintel.")) { EXPECT_EQ(L"Sintel", info.first(schema::kTitle)) << name; EXPECT_EQ(L"Colin Levy", info.first(schema::kDirector)) << name; EXPECT_EQ(L"http://durian.blender.org/", info.first(schema::kHomepageUrl)) << name; EXPECT_EQ(L"45745", info.first(schema::kTmdbId)) << name; } else { FAIL() << "Unknown media file: " << name; } if (boost::algorithm::ends_with(name, L".mov")) { EXPECT_EQ(L"Quicktime", info.first(schema::kContainerFormat)); } else if (boost::algorithm::ends_with(name, L".ogg") || boost::algorithm::ends_with(name, L".ogv")) { EXPECT_EQ(L"Ogg", info.first(schema::kContainerFormat)); } else if (boost::algorithm::ends_with(name, L".avi")) { EXPECT_EQ(L"AVI", info.first(schema::kContainerFormat)); } else if (boost::algorithm::ends_with(name, L".mkv")) { EXPECT_EQ(L"Matroska", info.first(schema::kContainerFormat)); } else if (not boost::algorithm::ends_with(name, L".mp4")) { FAIL() << "Unknown file extension: " << name; } EXPECT_FALSE(info.first(schema::kBackdrop).empty()); EXPECT_FALSE(info.first(schema::kDescription).empty()); ++(*fileCount); } TEST_F(FileSystemScannerTest, Test) { typedef FileSystemScanner::TaskFacade TaskFacade; typedef FileSystemScanner::TaskFacadePtr TaskFacadePtr; LoggingSink sink(logging::warning()); sink.ignore("warning/roots", "Cannot retrieve information about.*"); sink.ignore("warning/roots", "Cannot identify mount point.*"); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->set_enabled(false); EXPECT_EQ(1, root_manager.use_count()); boost::filesystem::remove_all(kMediaIndexPath); { const MetadataResolverPtr resolver(new MetadataResolver); ASSERT_TRUE(resolver->SetupSources(std::vector() << std::string("grl-tmdb"))); const TaskFacadePtr task_facade (new TaskFacade(root_manager, kMediaIndexPath)); const TaskManagerPtr task_manager (new TaskManager(test_info_->test_case_name())); EXPECT_EQ(1, resolver.use_count()); EXPECT_EQ(1, task_manager.use_count()); EXPECT_EQ(1, task_facade.use_count()); { FileSystemScanner scanner(resolver, task_manager, task_facade); scanner.add_directory(MEDIA_DIR); scanner.add_listener(this); scanner.start_scanning(); WaitForFinished(boost::posix_time::seconds(15)); EXPECT_TRUE(resolver->is_idle()); ASSERT_TRUE(scanner.is_idle()); } EXPECT_EQ(1, resolver.use_count()); EXPECT_EQ(1, task_manager.use_count()); EXPECT_EQ(1, task_facade.use_count()); } EXPECT_EQ(1, root_manager.use_count()); { // Verify scanned media MediaIndex media_index(root_manager); const bool index_opened = media_index.Open(kMediaIndexPath); ASSERT_TRUE(index_opened) << media_index.error_message(); unsigned file_count = 0; media_index.VisitAll(std::bind(verify_scanned_media, std::placeholders::_1, std::placeholders::_2, &file_count)); ASSERT_EQ(8, file_count); } EXPECT_EQ(1, root_manager.use_count()); ASSERT_TRUE(sink.get().empty()) << sink.take(); } static void verify_symlink_media(const MediaInfo &info, int32_t remaining, const std::wstring &baseUrl, unsigned *fileCount) { EXPECT_LE(remaining, 2); const std::wstring file_url = info.first(schema::kUrl); EXPECT_TRUE(boost::algorithm::starts_with(file_url, baseUrl)) << file_url; const std::wstring file_path = file_url.substr(baseUrl.length()); EXPECT_TRUE(file_path == L"/mocked.jpg" || file_path == L"/subdir/linked.jpg") << file_path; std::wcout << file_url << std::endl; ++(*fileCount); } TEST_F(FileSystemScannerTest, Symlinks) { const FileSystemPath kMockMediaPath = kDataPath / "media/symlinks"; const FileSystemPath kSubdirPath = kMockMediaPath / "subdir"; const FileSystemPath kMockImagePath = kMockMediaPath / "mocked.jpg"; const FileSystemPath kLinkedImagePath = kSubdirPath / "linked.jpg"; boost::filesystem::remove_all(kMediaIndexPath); boost::filesystem::remove_all(kMockMediaPath); boost::filesystem::create_directories(kSubdirPath); static const unsigned char kMinimalImage[] = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9 }; std::ofstream(kMockImagePath.string().c_str()). write(reinterpret_cast(kMinimalImage), sizeof(kMinimalImage)); boost::filesystem::create_symlink("../mocked.jpg", kLinkedImagePath); boost::filesystem::create_symlink("../..", kSubdirPath / "trap"); typedef FileSystemScanner::TaskFacade TaskFacade; typedef FileSystemScanner::TaskFacadePtr TaskFacadePtr; LoggingSink sink(logging::warning()); sink.ignore("warning/roots", "Cannot retrieve information about.*"); sink.ignore("warning/roots", "Cannot identify mount point.*"); const MediaRootManagerPtr root_manager(new MediaRootManager); root_manager->set_enabled(false); EXPECT_EQ(1, root_manager.use_count()); { const TaskFacadePtr task_facade (new TaskFacade(root_manager, kMediaIndexPath)); const TaskManagerPtr task_manager (new TaskManager(test_info_->test_case_name())); EXPECT_EQ(1, task_manager.use_count()); EXPECT_EQ(1, task_facade.use_count()); { FileSystemScanner scanner(MetadataResolverPtr(), task_manager, task_facade); scanner.add_directory(kMockMediaPath.string()); scanner.add_listener(this); scanner.start_scanning(); WaitForFinished(boost::posix_time::seconds(15)); ASSERT_TRUE(scanner.is_idle()); } EXPECT_EQ(1, task_manager.use_count()); EXPECT_EQ(1, task_facade.use_count()); } EXPECT_EQ(1, root_manager.use_count()); { // Verify scanned media MediaIndex media_index(root_manager); const bool index_opened = media_index.Open(kMediaIndexPath); ASSERT_TRUE(index_opened) << media_index.error_message(); Wrapper error; const std::wstring base_url = safe_wstring (g_filename_to_uri(kMockMediaPath.string().c_str(), nullptr, error.out_param())); ASSERT_FALSE(base_url.empty()) << error->message; unsigned file_count = 0; media_index.VisitAll(std::bind(verify_symlink_media, std::placeholders::_1, std::placeholders::_2, base_url, &file_count)); ASSERT_EQ(2, file_count); } EXPECT_EQ(1, root_manager.use_count()); ASSERT_TRUE(sink.get().empty()) << sink.take(); } void SetupTestEnvironments() { using testing::AddGlobalTestEnvironment; AddGlobalTestEnvironment(new LuceneEnvironment(kMediaIndexPath)); AddGlobalTestEnvironment(new TheMovieDbEnvironment(REQUEST_FILE)); AddGlobalTestEnvironment(new LastFmEnvironment(REQUEST_FILE)); } } // namespace filesystemscannertest } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); grl_init(&argc, &argv); gst_init(&argc, &argv); // Initialise GNetworkMonitor early so it doesn't get initialised // in a thread other than than the main loop. GNetworkMonitor *network_monitor = g_network_monitor_get_default(); g_assert(network_monitor != NULL); mediascanner::filesystemscannertest::SetupTestEnvironments(); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/metadataresolvertest.cpp0000644000015700001700000001020612232220161025774 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: James Henstridge */ #include #include #include #include #include #include "mediascanner/mediautils.h" #include "mediascanner/metadataresolver.h" #include "mediascanner/propertyschema.h" #include "testlib/environments.h" #include "testlib/testutils.h" namespace mediascanner { namespace metadataresolvertest { class MetadataResolverTest : public testing::Test { public: void StoreMetadata(const std::wstring &url, const MediaInfo &metadata) { resolved_metadata = metadata; if (loop_) { g_main_loop_quit(loop_.get()); loop_.reset(); } } void on_timeout() { if (loop_) { g_main_loop_quit(loop_.get()); timeout_reached_ = true; loop_.reset(); } } void WaitForFinished(Timeout::Duration duration) { loop_ = take(g_main_loop_new(nullptr, false)); timeout_reached_ = false; Timeout::AddOnce(duration, boost::bind(&MetadataResolverTest::on_timeout, this)); g_main_loop_run(loop_.get()); ASSERT_FALSE(timeout_reached_); } protected: Wrapper loop_; bool timeout_reached_; MediaInfo resolved_metadata; }; TEST_F(MetadataResolverTest, ResolveVideo) { MetadataResolver resolver; ASSERT_TRUE(resolver.SetupSources(std::vector() << std::string("grl-tmdb") << std::string("grl-lastfm-albumart"))); MediaInfo metadata; metadata.add_single(schema::kMimeType.bind_value(L"video/x-awesome")); metadata.add_single(schema::kTitle.bind_value(L"BigBuckBunny")); resolver.push("http://blah", metadata, boost::bind(&MetadataResolverTest::StoreMetadata, this, _1, _2)); WaitForFinished(boost::posix_time::seconds(15)); EXPECT_EQ(L"Big Buck Bunny", resolved_metadata.first(schema::kTitle)); EXPECT_EQ(L"10378", resolved_metadata.first(schema::kTmdbId)); } TEST_F(MetadataResolverTest, ResolveAudio) { MetadataResolver resolver; ASSERT_TRUE(resolver.SetupSources(std::vector() << std::string("grl-tmdb") << std::string("grl-lastfm-albumart"))); MediaInfo metadata; metadata.add_single(schema::kMimeType.bind_value(L"audio/x-awesome")); metadata.add_single(schema::kTitle.bind_value(L"Yo Mama")); metadata.add_single(schema::kArtist.bind_value(L"Butterfingers")); metadata.add_single(schema::kAlbum.bind_value(L"Breakfast at Fatboys")); resolver.push("http://blah", metadata, boost::bind(&MetadataResolverTest::StoreMetadata, this, _1, _2)); WaitForFinished(boost::posix_time::seconds(15)); EXPECT_EQ(L"http://userserve-ak.last.fm/serve/500/18717421.jpg", resolved_metadata.first(schema::kCover)); } void SetupTestEnvironments() { using testing::AddGlobalTestEnvironment; AddGlobalTestEnvironment(new TheMovieDbEnvironment(REQUEST_FILE)); AddGlobalTestEnvironment(new LastFmEnvironment(REQUEST_FILE)); } } } int main(int argc, char **argv) { mediascanner::InitTests(&argc, argv); grl_init(&argc, &argv); mediascanner::metadataresolvertest::SetupTestEnvironments(); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/propertytest.cpp0000644000015700001700000004045012232220161024322 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Grilo #include // GStreamer #include // Google Tests #include // Boost C++ #include // Lucene++ #include #include // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" // Test Suite #include "testlib/environments.h" #include "testlib/loggingsink.h" #include "testlib/testutils.h" using boost::gregorian::date; using boost::posix_time::time_duration; namespace mediascanner { namespace propertytest { class PropertyTest : public testing::TestWithParam { }; INSTANTIATE_TEST_CASE_P(GenericProperties, PropertyTest, testing::Values(&schema::kUrl, &schema::kETag, &schema::kExternalUrl, &schema::kAuthor, &schema::kLicense, &schema::kDescription, &schema::kRating, &schema::kTitle, &schema::kCertification, &schema::kComment, &schema::kKeyword, &schema::kLanguage, &schema::kNetworkAvailable)); INSTANTIATE_TEST_CASE_P(FileProperties, PropertyTest, testing::Values(&schema::kMimeType, &schema::kLastModified, &schema::kFileSize, &schema::kLastAccessed)); INSTANTIATE_TEST_CASE_P(MediaProperties, PropertyTest, testing::Values(&schema::kDuration, &schema::kLastPlayed, &schema::kPlayCount, &schema::kLastPosition, &schema::kProducer, &schema::kPerformer, &schema::kCover, &schema::kPoster)); INSTANTIATE_TEST_CASE_P(AudioProperties, PropertyTest, testing::Values(&schema::kAudioBitRate, &schema::kAudioCodec, &schema::kSampleRate, &schema::kChannelCount, &schema::kAlbumArtist, &schema::kComposer, &schema::kTrackCount, &schema::kDiscNumber)); INSTANTIATE_TEST_CASE_P(MusicProperties, PropertyTest, testing::Values(&schema::kArtist, &schema::kAlbum, &schema::kGenre, &schema::kLyrics, &schema::kTrackNumber)); INSTANTIATE_TEST_CASE_P(ImageProperties, PropertyTest, testing::Values(&schema::kWidth, &schema::kHeight, &schema::kImageOrientation)); INSTANTIATE_TEST_CASE_P(PhotoProperties, PropertyTest, testing::Values(&schema::kDateTaken, &schema::kDeviceModel, &schema::kIsoSpeed, &schema::kExposureTime, &schema::kFlashUsed, &schema::kDeviceManufacturer, &schema::kFocalRatio, &schema::kFocalLength, &schema::kMeteringMode, &schema::kWhiteBalance)); INSTANTIATE_TEST_CASE_P(VideoProperties, PropertyTest, testing::Values(&schema::kWidth, &schema::kHeight, &schema::kFrameRate, &schema::kShowName, &schema::kEpisode, &schema::kSeason, &schema::kBackdrop, &schema::kVideoCodec, &schema::kVideoBitRate, &schema::kDirector)); TEST(PropertyTest, Copy) { Property p = schema::kUrl; EXPECT_EQ(p, schema::kUrl); EXPECT_NE(p, schema::kETag); Property q = schema::kETag; EXPECT_EQ(q, schema::kETag); EXPECT_NE(q, schema::kUrl); } TEST(PropertyTest, Equal) { EXPECT_EQ(schema::kUrl, schema::kUrl); EXPECT_NE(schema::kUrl, schema::kETag); } TEST_P(PropertyTest, Definitions) { EXPECT_FALSE(GetParam()->field_name().empty()); EXPECT_NE(Property::MetadataKey::kInvalid, GetParam()->metadata_key().id()); EXPECT_NE(Property::MetadataKey::kPending, GetParam()->metadata_key().id()); EXPECT_EQ(*GetParam(), Property::FromFieldName(GetParam()->field_name())); } TEST_P(PropertyTest, GriloGType) { LoggingSink sink(logging::warning()); const Property *const p = GetParam(); GValue input = G_VALUE_INIT; g_value_init(&input, p->metadata_key().gtype()); if (G_VALUE_TYPE(&input) == G_TYPE_DATE_TIME) { GDateTime *const dt = g_date_time_new_utc(2012, 10, 15, 13, 32, 42); g_value_take_boxed(&input, dt); } else if (p->metadata_key().id() == GRL_METADATA_KEY_LAST_PLAYED) { g_value_set_string(&input, "2012-10-15T19:54:12Z"); } Property::Value pv; const bool grilo_value_transformed = p->TransformGriloValue(&input, &pv); EXPECT_TRUE(grilo_value_transformed); Wrapper output = take(p->MakeGriloValue(pv)); ASSERT_TRUE(output); EXPECT_EQ(safe_string(g_type_name(p->metadata_key().gtype())), safe_string(g_type_name(G_VALUE_TYPE(output.get())))); if (G_VALUE_TYPE(&input) == G_TYPE_DATE_TIME) { EXPECT_TRUE(g_date_time_equal(g_value_get_boxed(&input), g_value_get_boxed(output))); } else { GValue input_text = G_VALUE_INIT; g_value_init(&input_text, G_TYPE_STRING); EXPECT_TRUE(g_value_transform(&input, &input_text)); GValue output_text = G_VALUE_INIT; g_value_init(&output_text, G_TYPE_STRING); EXPECT_TRUE(g_value_transform(output, &output_text)); EXPECT_EQ(safe_string(g_value_get_string(&input_text)), safe_string(g_value_get_string(&output_text))); } // Check that no warnings got printed. EXPECT_TRUE(sink.get().empty()) << sink.get(); } TEST(PropertyTest, GetFieldName) { EXPECT_EQ(L"url", schema::kUrl.field_name()); } TEST(PropertyTest, FromFieldName) { EXPECT_TRUE(Property::FromFieldName(L"url")); EXPECT_TRUE(Property::FromFieldName(std::wstring(L"url"))); EXPECT_FALSE(Property::FromFieldName(L"nonsense")); EXPECT_EQ(schema::kUrl, Property::FromFieldName(L"url")); } TEST(PropertyTest, GetMetadataKey) { EXPECT_EQ(GRL_METADATA_KEY_URL, schema::kUrl.metadata_key().id()); EXPECT_TRUE(schema::kETag.metadata_key().is_custom()); EXPECT_TRUE(schema::kETag.metadata_key().id()); } TEST(PropertyTest, FromMetadataKey) { EXPECT_TRUE(Property::FromMetadataKey(GRL_METADATA_KEY_URL)); EXPECT_FALSE(Property::FromMetadataKey(GRL_METADATA_KEY_INVALID)); EXPECT_EQ(schema::kUrl, Property::FromMetadataKey(GRL_METADATA_KEY_URL)); } static bool is_supported_key(Wrapper source, GrlKeyID key_id) { const GList *const keys = grl_source_supported_keys(source.get()); return g_list_find(const_cast(keys), GRLKEYID_TO_POINTER(key_id)); } TEST(PropertyTest, MetadataSupport) { const Wrapper registry = wrap(grl_registry_get_default()); const Wrapper media_scanner = wrap( grl_registry_lookup_source(registry.get(), "grl-mediascanner")); const Wrapper tmdb = wrap(grl_registry_lookup_source(registry.get(), "grl-tmdb")); ASSERT_TRUE(media_scanner); ASSERT_TRUE(tmdb); for (GrlKeyID key_id = 1;; ++key_id) { if (key_id == GRL_METADATA_KEY_CHILDCOUNT || key_id == GRL_METADATA_KEY_EXTERNAL_PLAYER || key_id == GRL_METADATA_KEY_ID || key_id == GRL_METADATA_KEY_SOURCE || key_id == GRL_METADATA_KEY_START_TIME || key_id == GRL_METADATA_KEY_THUMBNAIL || key_id == GRL_METADATA_KEY_THUMBNAIL_BINARY) continue; const char *const key_name = grl_registry_lookup_metadata_key_name(registry.get(), key_id); const char *const key_desc = grl_registry_lookup_metadata_key_desc(registry.get(), key_id); const GType key_type = grl_registry_lookup_metadata_key_type(registry.get(), key_id); if (not key_name) break; EXPECT_TRUE(Property::FromMetadataKey(key_id)) << "Metadata: " << key_name << "(" << g_type_name(key_type) << "): " << key_desc << std::endl << " Sources:" << (is_supported_key(media_scanner, key_id) ? " mediascanner" : "") << (is_supported_key(tmdb, key_id) ? " tmdb" : ""); } } //////////////////////////////////////////////////////////////////////////////// class PropertyOrientation : public testing::TestWithParam > { }; INSTANTIATE_TEST_CASE_P(, PropertyOrientation, testing::Values(std::make_pair(L"rotate-0", 0), std::make_pair(L"rotate-179", 179), std::make_pair(L"rotate-360", 0), std::make_pair(L"rotate-361", 1))); TEST_P(PropertyOrientation, Test) { LoggingSink sink(logging::warning()); const std::wstring text = GetParam().first; const int rotation = GetParam().second; std::wostringstream normal_text; normal_text << "rotate-" << rotation; Wrapper value; // check simple parsing value = schema::kImageOrientation.MakeGriloValue(text); ASSERT_TRUE(value); EXPECT_EQ(rotation, g_value_get_int(value)); // check parsing with flipping value = schema::kImageOrientation.MakeGriloValue(L"flip-" + text); ASSERT_TRUE(value); EXPECT_EQ(rotation, g_value_get_int(value)); // check simple string conversion Property::Value pv; ASSERT_TRUE(schema::kImageOrientation.TransformGriloValue(value, &pv)); EXPECT_EQ(normal_text.str(), boost::get(pv)); // check string conversion with bad value g_value_set_int(value.get(), g_value_get_int(value) + 360); ASSERT_TRUE(schema::kImageOrientation.TransformGriloValue(value, &pv)); EXPECT_EQ(normal_text.str(), boost::get(pv)); // Check that no warnings got printed. EXPECT_TRUE(sink.get().empty()) << sink.get(); } //////////////////////////////////////////////////////////////////////////////// class PropertyValueTest : public testing::TestWithParam > { }; INSTANTIATE_TEST_CASE_P (All, PropertyValueTest, testing::Values(std::make_pair(schema::kLastModified, boost::posix_time::second_clock:: local_time()), std::make_pair(schema::kExposureTime, Fraction(22, 7)), std::make_pair(schema::kFlashUsed, true), std::make_pair(schema::kFlashUsed, false), std::make_pair(schema::kFocalLength, 3.1416), std::make_pair(schema::kFileSize, static_cast(0x123456789)), std::make_pair(schema::kRating, static_cast(5)), std::make_pair(schema::kDuration, static_cast(6360)), std::make_pair(schema::kUrl, std::wstring(L"http://www.example.com/")), std::make_pair(schema::kTitle, std::wstring(L"Test Title")))); TEST_P(PropertyValueTest, LuceneFieldTrip) { const Property property = GetParam().first; const Property::Value value = GetParam().second; const Property::LuceneFields fields = property.MakeFields(value); ASSERT_FALSE(fields.empty()); for (const Lucene::FieldablePtr &f: fields) ASSERT_TRUE(f != nullptr); EXPECT_EQ(value, property.TransformFields(fields)); } //////////////////////////////////////////////////////////////////////////////// TEST(TransformTagValueTest, GstDateTime) { const Property property = schema::kDateTaken; GValue gvalue = G_VALUE_INIT; Property::Value value; g_value_init(&gvalue, GST_TYPE_DATE_TIME); g_value_take_boxed(&gvalue, gst_date_time_new_y(2013)); ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 1, 1), time_duration(0, 0, 0))); g_value_take_boxed(&gvalue, gst_date_time_new_ym(2013, 8)); ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 8, 1), time_duration(0, 0, 0))); g_value_take_boxed(&gvalue, gst_date_time_new_ymd(2013, 8, 19)); ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 8, 19), time_duration(0, 0, 0))); g_value_take_boxed(&gvalue, gst_date_time_new(0, 2013, 8, 19, 12, 42, -1)); ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 8, 19), time_duration(12, 42, 0))); g_value_take_boxed(&gvalue, gst_date_time_new(0, 2013, 8, 19, 12, 42, 30)); ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 8, 19), time_duration(12, 42, 30))); g_value_unset(&gvalue); } TEST(TransformTagValueTest, GDate) { const Property property = schema::kDateTaken; GValue gvalue = G_VALUE_INIT; g_value_init(&gvalue, G_TYPE_DATE); g_value_take_boxed(&gvalue, g_date_new_dmy(19, G_DATE_AUGUST, 2013)); Property::Value value; ASSERT_TRUE(property.TransformTagValue(&gvalue, &value)); ASSERT_EQ(value, DateTime(date(2013, 8, 19), time_duration(0, 0, 0))); g_value_unset(&gvalue); } //////////////////////////////////////////////////////////////////////////////// void SetupTestEnvironments() { testing::AddGlobalTestEnvironment (new MediaScannerEnvironment(FileSystemPath())); testing::AddGlobalTestEnvironment (new TheMovieDbEnvironment(std::string())); } } // namespace propertytest } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); grl_init(&argc, &argv); mediascanner::propertytest::SetupTestEnvironments(); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/griloplugintest.cpp0000644000015700001700000015312212232220161024772 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // Grilo #include // Google Tests #include // Boost C++ #include #include #include // Standard Library #include #include #include #include #include using namespace std::placeholders; // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/mediaroot.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" #include "mediascanner/writablemediaindex.h" // Media Scanner Plugin for Grilo #include "grlmediascanner/mediasource.h" // Test Suite #include "testlib/environments.h" #include "testlib/loggingsink.h" #include "testlib/testutils.h" //////////////////////////////////////////////////////////////////////////////// namespace mediascanner { namespace griloplugintest { // Boost C++ using boost::filesystem::current_path; using boost::locale::format; using boost::locale::wformat; using boost::posix_time::time_from_string; //////////////////////////////////////////////////////////////////////////////// static const FileSystemPath kDataPath = current_path() / "run/griloplugintest"; static const FileSystemPath kMediaIndexPath = kDataPath / "index"; static const char kGoodAudioUri[] = "urn:x-test:good-audio"; static const char kGoodAudioTitle[] = "Good Test Audio"; static const char kGoodAudioType[] = "audio/x-fancy"; static const char kGoodAudioLastModified[] = "2012-08-31 12:30:00"; static const char kOtherAudioUri[] = "urn:x-test:other-audio"; static const char kOtherAudioTitle[] = "Other Good Test Audio"; static const char kOtherAudioType[] = "audio/x-awesome"; static const char kOtherAudioLastModified[] = "2012-09-04 13:30:00"; static const char kGoodImageUri[] = "urn:x-test:good-image"; static const char kGoodImageTitle[] = "Good Test Image"; static const char kGoodImageType[] = "image/x-fancy"; static const char kOtherImageUri[] = "urn:x-test:other-image"; static const char kOtherImageTitle[] = "Other Good Test Image"; static const char kOtherImageType[] = "image/x-awesome"; static const char kGoodVideoUri[] = "urn:x-test:good-video"; static const char kGoodVideoTitle[] = "Good Test Video"; static const char kGoodVideoType[] = "video/x-fancy"; static const char kOtherVideoUri[] = "urn:x-test:other-video"; static const char kOtherVideoTitle[] = "Other Good Test Video"; static const char kOtherVideoType[] = "video/x-awesome"; static const char kBadMediaUri[] = "urn:x-test:bad"; //////////////////////////////////////////////////////////////////////////////// typedef std::map MediaInfoMap; static void merge_media(const std::string &url, const MediaInfo &media, MediaInfoMap *media_map) { media_map->insert(std::make_pair(full_url(url), media)); } static void merge_media(const MediaInfoMap &input, std::string uri_filter, MediaInfoMap *output) { if (not uri_filter.empty()) uri_filter = full_url(uri_filter); for (const MediaInfoMap::value_type &p: input) { if (uri_filter.empty() || p.first == uri_filter) output->insert(p); } } static MediaInfoMap make_audio_media() { MediaInfoMap media_map; { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kOtherAudioTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kOtherAudioType))); media.add_single(schema::kDuration.bind_value(402UL)); media.add_single(schema::kSeekable.bind_value(false)); media.add_single(schema::kPlayCount.bind_value(42U)); media.add_single(schema::kLastModified. bind_value(time_from_string(kOtherAudioLastModified))); merge_media(kOtherAudioUri, media, &media_map); } { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kGoodAudioTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kGoodAudioType))); media.add_single(schema::kDuration.bind_value(201UL)); media.add_single(schema::kSeekable.bind_value(true)); media.add_single(schema::kPlayCount.bind_value(13U)); media.add_single(schema::kLastModified. bind_value(time_from_string(kGoodAudioLastModified))); merge_media(kGoodAudioUri, media, &media_map); } return media_map; } static MediaInfoMap make_image_media() { MediaInfoMap media_map; { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kGoodImageTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kGoodImageType))); merge_media(kGoodImageUri, media, &media_map); } { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kOtherImageTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kOtherImageType))); merge_media(kOtherImageUri, media, &media_map); } return media_map; } static MediaInfoMap make_video_media() { MediaInfoMap media_map; { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kGoodVideoTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kGoodVideoType))); merge_media(kGoodVideoUri, media, &media_map); } { MediaInfo media; media.add_single(schema::kTitle. bind_value(ToUnicode(kOtherVideoTitle))); media.add_single(schema::kMimeType. bind_value(ToUnicode(kOtherVideoType))); merge_media(kOtherVideoUri, media, &media_map); } return media_map; } static MediaInfoMap make_known_media (const std::string &uri_filter = std::string()) { MediaInfoMap media; merge_media(make_audio_media(), uri_filter, &media); merge_media(make_image_media(), uri_filter, &media); merge_media(make_video_media(), uri_filter, &media); return media; } //////////////////////////////////////////////////////////////////////////////// static Wrapper GetMediaScannerSource() { const Wrapper registry = wrap(grl_registry_get_default()); return wrap(grl_registry_lookup_source(registry.get(), GRL_MEDIA_SCANNER_PLUGIN_ID)); } static bool has_key(GList *const key_list, GrlKeyID key_id) { return g_list_find(key_list, GRLKEYID_TO_POINTER(key_id)) != nullptr; } //////////////////////////////////////////////////////////////////////////////// TEST(LoadPlugin, Test) { ASSERT_TRUE(GRL_IS_SOURCE(GetMediaScannerSource().get())); } //////////////////////////////////////////////////////////////////////////////// TEST(TestMediaFromUri, Test) { const Wrapper source = GetMediaScannerSource(); EXPECT_TRUE(grl_source_test_media_from_uri(source.get(), full_url(kGoodVideoUri). c_str())); EXPECT_FALSE(grl_source_test_media_from_uri(source.get(), full_url(kBadMediaUri). c_str())); } //////////////////////////////////////////////////////////////////////////////// TEST(MediaFromUri, GoodMedia) { const Wrapper options = take(grl_operation_options_new(nullptr)); Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_INVALID)); const Wrapper source = GetMediaScannerSource(); Wrapper error; const Wrapper media = take(grl_source_get_media_from_uri_sync (source.get(), full_url(kGoodVideoUri).c_str(), keys, options.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_NE(nullptr, media.get()); EXPECT_EQ(std::string(kGoodVideoTitle), safe_string(grl_data_get_string(media.get(), GRL_METADATA_KEY_TITLE))); EXPECT_EQ(std::string(kGoodVideoType), safe_string(grl_data_get_string(media.get(), GRL_METADATA_KEY_MIME))); } TEST(MediaFromUri, BadMedia) { const Wrapper options = take(grl_operation_options_new(nullptr)); Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_INVALID)); const Wrapper source = GetMediaScannerSource(); Wrapper error; const Wrapper media = take(grl_source_get_media_from_uri_sync (source.get(), full_url(kBadMediaUri).c_str(), keys, options.get(), error.out_param())); EXPECT_FALSE(media); ASSERT_TRUE(error); EXPECT_EQ(error->domain, GRL_CORE_ERROR); EXPECT_EQ(error->code, GRL_CORE_ERROR_MEDIA_NOT_FOUND); EXPECT_FALSE(safe_string(error->message).empty()); } //////////////////////////////////////////////////////////////////////////////// class FilterParams { public: typedef std::function (Wrapper)> MakeOptions; enum CapsMode { IgnoreCaps, UseCaps }; FilterParams() { } FilterParams(const std::string &label, const MakeOptions &make_options, const MediaInfoMap &expected_media, CapsMode caps_mode) : label_(label) , make_options_(make_options) , expected_media_(expected_media) , caps_mode_(caps_mode) { } const std::string &label() const { return label_; } Wrapper make_options(Wrapper source, GrlSupportedOps ops) const { Wrapper caps; if (caps_mode_ == UseCaps) caps = wrap(grl_source_get_caps(source.get(), ops)); return make_options_(caps); } const MediaInfoMap &expected_media() const { return expected_media_; } CapsMode caps_mode() const { return caps_mode_; } private: std::string label_; MakeOptions make_options_; MediaInfoMap expected_media_; CapsMode caps_mode_; }; static void PrintTo(const FilterParams &p, std::ostream *os) { *os << p.label() << (p.caps_mode() ? ", use caps" : ", ignore caps"); } static Wrapper DefaultOptions(Wrapper caps) { return take(grl_operation_options_new(caps.get())); } static std::vector DefaultFilters() { return std::vector() << FilterParams("any", DefaultOptions, make_known_media(), FilterParams::IgnoreCaps) << FilterParams("any", DefaultOptions, make_known_media(), FilterParams::UseCaps); } static Wrapper type_filter_options(Wrapper caps, GrlTypeFilter filter) { Wrapper options = take(grl_operation_options_new(caps.get())); grl_operation_options_set_type_filter(options.get(), filter); return options; } static void add_type_filter(const std::string &label, GrlTypeFilter filter, const MediaInfoMap &expected_media, FilterParams::CapsMode caps_mode, std::vector *params) { const FilterParams::MakeOptions options = std::bind(type_filter_options, _1, filter); params->push_back(FilterParams(label, options, expected_media, caps_mode)); } static std::vector TypeFilters() { std::vector params; for (int i = 0; i < 2; ++i) { const FilterParams::CapsMode caps_mode = static_cast(i); add_type_filter("all", GRL_TYPE_FILTER_ALL, make_known_media(), caps_mode, ¶ms); add_type_filter("audios", GRL_TYPE_FILTER_AUDIO, make_audio_media(), caps_mode, ¶ms); add_type_filter("images", GRL_TYPE_FILTER_IMAGE, make_image_media(), caps_mode, ¶ms); add_type_filter("videos", GRL_TYPE_FILTER_VIDEO, make_video_media(), caps_mode, ¶ms); } return params; } static Wrapper key_filter_options (Wrapper caps, const Property::BoundValue &filter) { Wrapper options = take(grl_operation_options_new(caps.get())); const bool key_value_set = grl_operation_options_set_key_filter_value (options.get(), filter.first.metadata_key().id(), take(filter.first.MakeGriloValue(filter.second)).get()); EXPECT_TRUE(key_value_set) << filter.first.field_name(); EXPECT_NE(nullptr, grl_operation_options_get_key_filter_list(options.get())) << filter.first.field_name(); return options; } static void add_key_filter(const std::string &label, const Property::BoundValue &filter, const MediaInfoMap &expected_media, FilterParams::CapsMode caps_mode, std::vector *params) { const FilterParams::MakeOptions options = std::bind(key_filter_options, _1, filter); params->push_back(FilterParams(label, options, expected_media, caps_mode)); } static std::vector KeyFilters() { const DateTime dt = time_from_string(kGoodAudioLastModified); std::vector params; for (int i = 0; i < 2; ++i) { const FilterParams::CapsMode caps_mode = static_cast(i); add_key_filter("title", schema::kTitle.bind_value(ToUnicode(kGoodAudioTitle)), make_known_media(kGoodAudioUri), caps_mode, ¶ms); add_key_filter("title/short", schema::kTitle.bind_value(L"Good"), MediaInfoMap(), caps_mode, ¶ms); add_key_filter("title/long", schema::kTitle.bind_value(ToUnicode(kGoodAudioTitle) + L" Extra"), MediaInfoMap(), caps_mode, ¶ms); add_key_filter("mime-type", schema::kMimeType.bind_value(ToUnicode(kOtherAudioType)), make_known_media(kOtherAudioUri), caps_mode, ¶ms); add_key_filter("duration", schema::kDuration.bind_value(201), make_known_media(kGoodAudioUri), caps_mode, ¶ms); add_key_filter("seekable", schema::kSeekable.bind_value(true), make_known_media(kGoodAudioUri), caps_mode, ¶ms); add_key_filter("seekable", schema::kSeekable.bind_value(false), make_known_media(kOtherAudioUri), caps_mode, ¶ms); add_key_filter("last-modified", schema::kLastModified.bind_value(dt), make_known_media(kGoodAudioUri), caps_mode, ¶ms); } // FIXME(M4): also test lists of key range filters return params; } static Wrapper key_range_filter_options (Wrapper caps, const Property::BoundValue &min_value, const Property::BoundValue &max_value) { Wrapper options = take(grl_operation_options_new(caps.get())); grl_operation_options_set_key_range_filter_value (options.get(), min_value.first.metadata_key().id(), take(min_value.first.MakeGriloValue(min_value.second)).get(), take(min_value.first.MakeGriloValue(max_value.second)).get()); return options; } static void add_key_range_filter(const std::string &label, const Property::BoundValue &min_value, const Property::BoundValue &med_value, const Property::BoundValue &max_value, const MediaInfoMap &expected_media_lower, const MediaInfoMap &expected_media_upper, const MediaInfoMap &expected_media_both, FilterParams::CapsMode caps_mode, std::vector *params) { const FilterParams::MakeOptions options_lower = std::bind(key_range_filter_options, _1, min_value, med_value); const FilterParams::MakeOptions options_upper = std::bind(key_range_filter_options, _1, med_value, max_value); const FilterParams::MakeOptions options_both = std::bind(key_range_filter_options, _1, min_value, max_value); params->push_back(FilterParams(label + "-lower", options_lower, expected_media_lower, caps_mode)); params->push_back(FilterParams(label + "-upper", options_upper, expected_media_upper, caps_mode)); params->push_back(FilterParams(label + "-both", options_both, expected_media_both, caps_mode)); } static std::vector KeyRangeFilters() { const DateTime dt_min = time_from_string("2012-08-01 00:00:00"); const DateTime dt_med = time_from_string("2012-09-01 00:00:00"); const DateTime dt_max = time_from_string("2012-10-01 00:00:00"); std::vector params; for (int i = 0; i < 2; ++i) { const FilterParams::CapsMode caps_mode = static_cast(i); add_key_range_filter("duration", schema::kDuration.bind_value(100), schema::kDuration.bind_value(300), schema::kDuration.bind_value(500), make_known_media(kGoodAudioUri), make_known_media(kOtherAudioUri), make_audio_media(), caps_mode, ¶ms); add_key_range_filter("last-modified", schema::kLastModified.bind_value(dt_min), schema::kLastModified.bind_value(dt_med), schema::kLastModified.bind_value(dt_max), make_known_media(kGoodAudioUri), make_known_media(kOtherAudioUri), make_audio_media(), caps_mode, ¶ms); } // FIXME(M2): also test lists of key range filters return params; } static Wrapper result_count_options(Wrapper caps, uint skip, int count) { Wrapper options = take(grl_operation_options_new(caps.get())); grl_operation_options_set_skip(options.get(), skip); grl_operation_options_set_count(options.get(), count); return options; } static std::vector ResultCountFilters() { const FilterParams::MakeOptions options_limit = std::bind(result_count_options, _1, 0, 100); return std::vector() << FilterParams("limit-100", options_limit, make_known_media(), FilterParams::IgnoreCaps) << FilterParams("limit-100", options_limit, make_known_media(), FilterParams::UseCaps); } //////////////////////////////////////////////////////////////////////////////// class Browse : public testing::TestWithParam { }; INSTANTIATE_TEST_CASE_P(DefaultFilters, Browse, testing::ValuesIn(DefaultFilters())); INSTANTIATE_TEST_CASE_P(TypeFilters, Browse, testing::ValuesIn(TypeFilters())); INSTANTIATE_TEST_CASE_P(KeyFilters, Browse, testing::ValuesIn(KeyFilters())); INSTANTIATE_TEST_CASE_P(KeyRangeFilters, Browse, testing::ValuesIn(KeyRangeFilters())); TEST_P(Browse, BrowseAndResolve) { const Wrapper source = GetMediaScannerSource(); Wrapper error; const Wrapper base_keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_INVALID)); const Wrapper extra_keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_INVALID)); const Wrapper browse_options = GetParam().make_options(source, GRL_OP_BROWSE); const ListWrapper media_list = take(grl_source_browse_sync(source.get(), nullptr, base_keys.get(), browse_options.get(), error.out_param())); ASSERT_FALSE(error) << error->message; MediaInfoMap expected_media = GetParam().expected_media(); const Wrapper resolve_options = take(grl_operation_options_new(nullptr)); for (GList *l = media_list.get(); l; l = l->next) { GrlMedia *const media = static_cast(l->data); const std::string media_url = safe_string(grl_media_get_url(media)); ASSERT_FALSE(media_url.empty()); const MediaInfoMap::iterator it = expected_media.find(media_url); ASSERT_NE(expected_media.end(), it) << "Unexpected media: " << media_url; const std::wstring mime_type = it->second.first(schema::kMimeType); EXPECT_FALSE(mime_type.empty()); ASSERT_EQ(mime_type, safe_wstring(grl_media_get_mime(media))); grl_source_resolve_sync(source.get(), media, extra_keys, resolve_options.get(), error.out_param()); ASSERT_FALSE(error) << error->message; const std::wstring title = it->second.first(schema::kTitle); EXPECT_FALSE(title.empty()); ASSERT_EQ(title, safe_wstring(grl_media_get_title(media))); expected_media.erase(it); } ASSERT_EQ(MediaInfoMap(), expected_media); } TEST(Resolve, BadResolve) { const Wrapper media = take(grl_media_new()); grl_media_set_url(media.get(), kBadMediaUri); const Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_INVALID)); const Wrapper options = take(grl_operation_options_new(nullptr)); const Wrapper source = GetMediaScannerSource(); Wrapper error; grl_source_resolve_sync(source.get(), media.get(), keys.get(), options.get(), error.out_param()); ASSERT_TRUE(error); EXPECT_EQ(error->domain, GRL_CORE_ERROR); EXPECT_EQ(error->code, GRL_CORE_ERROR_MEDIA_NOT_FOUND); EXPECT_FALSE(safe_string(error->message).empty()); } //////////////////////////////////////////////////////////////////////////////// class QueryOrSearch : public testing::TestWithParam > { protected: void RunTest(GrlSupportedOps operations) { const FilterParams &filter = std::tr1::get<0>(GetParam()); const Wrapper source = GetMediaScannerSource(); const Wrapper options = filter.make_options(source, operations); const Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_URL, GRL_METADATA_KEY_INVALID)); Wrapper error; const ListWrapper media_list = take(MakeResultList(source, options, keys, &error)); EXPECT_FALSE(error) << error->message; MediaInfoMap expected_media = filter.expected_media(); for (GList *l = media_list.get(); l; l = l->next) { GrlMedia *const media = static_cast(l->data); const std::string media_url = safe_string(grl_media_get_url(media)); ASSERT_FALSE(media_url.empty()); const MediaInfoMap::iterator it = expected_media.find(media_url); ASSERT_NE(expected_media.end(), it) << "Unexpected media: " << media_url; expected_media.erase(it); } ASSERT_EQ(MediaInfoMap(), expected_media); } virtual GList *MakeResultList(const Wrapper source, const Wrapper options, const Wrapper keys, Wrapper *error) = 0; }; //////////////////////////////////////////////////////////////////////////////// class Query : public QueryOrSearch { protected: GList *MakeResultList(const Wrapper source, const Wrapper options, const Wrapper keys, Wrapper *error) { return grl_source_query_sync(source.get(), std::tr1::get<1>(GetParam()).c_str(), keys, options.get(), error->out_param()); } }; static std::vector QueryStrings() { return std::vector() << std::string("GOOD") << std::string("Good") << std::string("good") << std::string("title:Good") << std::string("title:go*") << std::string("(title:good artist:bad)") << std::string("\"good test\""); } INSTANTIATE_TEST_CASE_P(DefaultFilters, Query, testing::Combine(testing::ValuesIn(DefaultFilters()), testing::ValuesIn(QueryStrings()))); INSTANTIATE_TEST_CASE_P(TypeFilters, Query, testing::Combine(testing::ValuesIn(TypeFilters()), testing::ValuesIn(QueryStrings()))); INSTANTIATE_TEST_CASE_P(KeyFilters, Query, testing::Combine(testing::ValuesIn(KeyFilters()), testing::ValuesIn(QueryStrings()))); INSTANTIATE_TEST_CASE_P(KeyRangeFilters, Query, testing::Combine(testing::ValuesIn(KeyRangeFilters()), testing::ValuesIn(QueryStrings()))); INSTANTIATE_TEST_CASE_P(ResultCountFilters, Query, testing::Combine(testing::ValuesIn(ResultCountFilters()), testing::ValuesIn(QueryStrings()))); TEST_P(Query, GoodQueries) { RunTest(GRL_OP_BROWSE); } TEST(Query, BadQueries) { const Wrapper source = GetMediaScannerSource(); GList *const keys = grl_metadata_key_list_new(GRL_METADATA_KEY_URL, GRL_METADATA_KEY_INVALID); const Wrapper options = take(grl_operation_options_new(nullptr)); Wrapper error; GList *media_list; media_list = grl_source_query_sync(source.get(), "title:Bad", keys, options.get(), error.out_param()); EXPECT_EQ(nullptr, media_list); EXPECT_FALSE(error) << error->message; media_list = grl_source_query_sync(source.get(), "*bad-query", keys, options.get(), error.out_param()); EXPECT_EQ(nullptr, media_list); EXPECT_FALSE(safe_string(error->message).empty()); } //////////////////////////////////////////////////////////////////////////////// class WildcardSearch : public QueryOrSearch { protected: GList *MakeResultList(const Wrapper source, const Wrapper options, const Wrapper keys, Wrapper *error) { g_object_set(source.get(), "search-method", GRL_MEDIA_SCANNER_SEARCH_SUBSTRING, NULL); return grl_source_search_sync(source.get(), std::tr1::get<1>(GetParam()).c_str(), keys, options.get(), error->out_param()); } }; static std::vector WildcardSearchStrings() { return std::vector() << std::string("GOOD") << std::string("Good") << std::string("good") << std::string("good test") << std::string("go") << std::string("oo") << std::string("ood"); } INSTANTIATE_TEST_CASE_P (DefaultFilters, WildcardSearch, testing::Combine(testing::ValuesIn(DefaultFilters()), testing::ValuesIn(WildcardSearchStrings()))); INSTANTIATE_TEST_CASE_P (TypeFilters, WildcardSearch, testing::Combine(testing::ValuesIn(TypeFilters()), testing::ValuesIn(WildcardSearchStrings()))); INSTANTIATE_TEST_CASE_P (KeyFilters, WildcardSearch, testing::Combine(testing::ValuesIn(KeyFilters()), testing::ValuesIn(WildcardSearchStrings()))); INSTANTIATE_TEST_CASE_P (KeyRangeFilters, WildcardSearch, testing::Combine(testing::ValuesIn(KeyRangeFilters()), testing::ValuesIn(WildcardSearchStrings()))); INSTANTIATE_TEST_CASE_P (ResultCountFilters, WildcardSearch, testing::Combine(testing::ValuesIn(ResultCountFilters()), testing::ValuesIn(WildcardSearchStrings()))); TEST_P(WildcardSearch, GoodSearches) { RunTest(GRL_OP_SEARCH); } //////////////////////////////////////////////////////////////////////////////// class FullTextSearch : public QueryOrSearch { GList *MakeResultList(const Wrapper source, const Wrapper options, const Wrapper keys, Wrapper *error) { g_object_set(source.get(), "search-method", GRL_MEDIA_SCANNER_SEARCH_FULL_TEXT, NULL); return grl_source_search_sync(source.get(), std::tr1::get<1>(GetParam()).c_str(), keys, options.get(), error->out_param()); } }; static std::vector FullTextSearchStrings() { // FIXME(M3): Add terms that verify stemming return std::vector() << std::string("GOOD") << std::string("Good") << std::string("good") << std::string("good test"); } INSTANTIATE_TEST_CASE_P (DefaultFilters, FullTextSearch, testing::Combine(testing::ValuesIn(DefaultFilters()), testing::ValuesIn(FullTextSearchStrings()))); INSTANTIATE_TEST_CASE_P (TypeFilters, FullTextSearch, testing::Combine(testing::ValuesIn(TypeFilters()), testing::ValuesIn(FullTextSearchStrings()))); INSTANTIATE_TEST_CASE_P (KeyFilters, FullTextSearch, testing::Combine(testing::ValuesIn(KeyFilters()), testing::ValuesIn(FullTextSearchStrings()))); INSTANTIATE_TEST_CASE_P (KeyRangeFilters, FullTextSearch, testing::Combine(testing::ValuesIn(KeyRangeFilters()), testing::ValuesIn(FullTextSearchStrings()))); INSTANTIATE_TEST_CASE_P (ResultCountFilters, FullTextSearch, testing::Combine(testing::ValuesIn(ResultCountFilters()), testing::ValuesIn(FullTextSearchStrings()))); TEST_P(FullTextSearch, GoodSearches) { RunTest(GRL_OP_SEARCH); } //////////////////////////////////////////////////////////////////////////////// TEST(Search, BadSearches) { const Wrapper source = GetMediaScannerSource(); Wrapper error; GList *const keys = grl_metadata_key_list_new(GRL_METADATA_KEY_URL, GRL_METADATA_KEY_INVALID); const Wrapper options = take(grl_operation_options_new(nullptr)); GList *media_list = grl_source_search_sync(source.get(), "bad", keys, options.get(), error.out_param()); EXPECT_EQ(nullptr, media_list); EXPECT_FALSE(error) << error->message; } //////////////////////////////////////////////////////////////////////////////// class SimpleCaps : public testing::TestWithParam { }; class FilterableCaps : public testing::TestWithParam { }; INSTANTIATE_TEST_CASE_P (, SimpleCaps, testing::Values(GRL_OP_RESOLVE, GRL_OP_STORE, GRL_OP_STORE_PARENT, GRL_OP_STORE_METADATA, GRL_OP_REMOVE, GRL_OP_MEDIA_FROM_URI, GRL_OP_NOTIFY_CHANGE)); INSTANTIATE_TEST_CASE_P (, FilterableCaps, testing::Values(GRL_OP_BROWSE, GRL_OP_SEARCH, GRL_OP_QUERY, GRL_OP_BROWSE | GRL_OP_SEARCH, GRL_OP_BROWSE | GRL_OP_QUERY, GRL_OP_SEARCH | GRL_OP_QUERY, GRL_OP_BROWSE | GRL_OP_SEARCH | GRL_OP_QUERY)); TEST_P(SimpleCaps, Test) { const GrlSupportedOps ops = GetParam(); const Wrapper source = GetMediaScannerSource(); const Wrapper caps = wrap(grl_source_get_caps(source.get(), ops)); ASSERT_NE(nullptr, caps); const GrlTypeFilter filter = grl_caps_get_type_filter(caps.get()); EXPECT_TRUE(filter == GRL_TYPE_FILTER_NONE || filter == GRL_TYPE_FILTER_ALL); EXPECT_EQ(nullptr, grl_caps_get_key_range_filter(caps.get())); EXPECT_EQ(nullptr, grl_caps_get_key_filter(caps.get())); } TEST_P(FilterableCaps, Run) { const GrlSupportedOps ops = GetParam(); const Wrapper source = GetMediaScannerSource(); const Wrapper caps = wrap(grl_source_get_caps(source.get(), ops)); ASSERT_NE(nullptr, caps); EXPECT_TRUE(grl_caps_get_type_filter(caps.get()) & GRL_TYPE_FILTER_AUDIO); EXPECT_TRUE(grl_caps_get_type_filter(caps.get()) & GRL_TYPE_FILTER_IMAGE); EXPECT_TRUE(grl_caps_get_type_filter(caps.get()) & GRL_TYPE_FILTER_VIDEO); { GList *const value_filter = grl_caps_get_key_filter(caps.get()); EXPECT_NE(nullptr, value_filter); EXPECT_TRUE(has_key(value_filter, GRL_METADATA_KEY_TITLE)); EXPECT_TRUE(has_key(value_filter, GRL_METADATA_KEY_DURATION)); EXPECT_TRUE(has_key(value_filter, GRL_METADATA_KEY_MODIFICATION_DATE)); EXPECT_TRUE(has_key(value_filter, GRL_METADATA_KEY_TRACK_NUMBER)); } { GList *const range_filter = grl_caps_get_key_range_filter(caps.get()); EXPECT_NE(nullptr, range_filter); EXPECT_TRUE(has_key(range_filter, GRL_METADATA_KEY_TITLE)); EXPECT_TRUE(has_key(range_filter, GRL_METADATA_KEY_DURATION)); EXPECT_TRUE(has_key(range_filter, GRL_METADATA_KEY_MODIFICATION_DATE)); EXPECT_TRUE(has_key(range_filter, GRL_METADATA_KEY_TRACK_NUMBER)); } } //////////////////////////////////////////////////////////////////////////////// TEST(Store, Test) { const MediaRootManagerPtr root_manager(new MediaRootManager); WritableMediaIndex writer(root_manager); const bool writer_opened = writer.Open(kMediaIndexPath); ASSERT_TRUE(writer_opened); static const char kStoreTestUri[] = "urn:x-test:store"; static const char kStoreTestTitle[] = "Store Test Media"; static const char kStoreTestMimeType[] = "application/binary"; const std::string test_url = full_url(kStoreTestUri); const Wrapper source = GetMediaScannerSource(); EXPECT_FALSE(grl_source_test_media_from_uri(source.get(), test_url.c_str())); Wrapper error; Wrapper media = take(grl_media_new()); grl_media_set_id(media.get(), test_url.c_str()); grl_media_set_title(media.get(), kStoreTestTitle); grl_source_store_sync(source.get(), nullptr, media.get(), GRL_WRITE_NORMAL, error.out_param()); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(grl_source_test_media_from_uri(source.get(), test_url.c_str())); const Wrapper options = take(grl_operation_options_new(nullptr)); Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_INVALID)); media = take(grl_source_get_media_from_uri_sync(source.get(), test_url.c_str(), keys.get(), options.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(media); EXPECT_EQ(test_url, safe_string(grl_media_get_id(media.get()))); EXPECT_EQ(test_url, safe_string(grl_media_get_url(media.get()))); EXPECT_EQ(kStoreTestTitle, safe_string(grl_media_get_title(media.get()))); EXPECT_EQ("", safe_string(grl_media_get_mime(media.get()))); grl_media_set_title(media.get(), "Do not store this"); grl_media_set_mime(media.get(), kStoreTestMimeType); Wrapper mime_key_list = take(grl_metadata_key_list_new(GRL_METADATA_KEY_MIME, GRL_METADATA_KEY_INVALID)); Wrapper failed_keys = take(grl_source_store_metadata_sync(source.get(), media.get(), mime_key_list.get(), GRL_WRITE_NORMAL, error.out_param())); EXPECT_FALSE(error) << to_string(error); EXPECT_FALSE(failed_keys) << g_list_length(failed_keys.get()); media = take(grl_source_get_media_from_uri_sync(source.get(), test_url.c_str(), keys.get(), options.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(media); EXPECT_EQ(test_url, safe_string(grl_media_get_id(media.get()))); EXPECT_EQ(test_url, safe_string(grl_media_get_url(media.get()))); EXPECT_EQ(kStoreTestTitle, safe_string(grl_media_get_title(media.get()))); EXPECT_EQ(kStoreTestMimeType, safe_string(grl_media_get_mime(media.get()))); } //////////////////////////////////////////////////////////////////////////////// TEST(Remove, Test) { static const char kRemoveTestUri[] = "urn:x-test:remove"; const std::string test_url = full_url(kRemoveTestUri); const Wrapper source = GetMediaScannerSource(); EXPECT_FALSE(grl_source_test_media_from_uri(source.get(), test_url.c_str())); Wrapper media = take(grl_media_new()); grl_media_set_id(media.get(), test_url.c_str()); grl_media_set_url(media.get(), test_url.c_str()); grl_media_set_title(media.get(), "Remove this"); Wrapper error; grl_source_store_sync(source.get(), nullptr, media.get(), GRL_WRITE_NORMAL, error.out_param()); EXPECT_FALSE(error) << to_string(error); EXPECT_TRUE(grl_source_test_media_from_uri(source.get(), test_url.c_str())); media = take(grl_media_new()); grl_media_set_id(media.get(), test_url.c_str()); grl_source_remove_sync(source.get(), media.get(), error.out_param()); EXPECT_FALSE(error) << to_string(error); EXPECT_FALSE(grl_source_test_media_from_uri(source.get(), test_url.c_str())); } //////////////////////////////////////////////////////////////////////////////// class Notify : public ::testing::Test { protected: typedef std::map > MediaMap; static void pull_dbus_signals() { const int64_t end = g_get_monotonic_time() + boost::posix_time::milliseconds(50). total_microseconds(); // The plugin uses sync API for D-Bus communication. Therefore only // method reply messages get processed, while the signal messages // still sit in the event loop. // TODO(M5): Verify that theory and maybe switch to async API. // Well, actually the problem also could just be that we process // the D-Bus signal within the task manager's background thread and // then push the result to the main loop by idle source. while (g_main_context_iteration(nullptr, false) || g_get_monotonic_time() < end); } static void Insert(const std::string &title_prefix, const std::string &url_prefix, Wrapper source, MediaMap *collection, MediaMap::iterator *it) { const size_t n = collection->size() + 1; const std::string title = (format("{1} {2}") % title_prefix % n).str(); const std::string url = (format("{1}-{2}") % url_prefix % n).str(); Wrapper media = take(grl_media_new()); grl_media_set_mime(media.get(), "application/binary"); grl_media_set_title(media.get(), title.c_str()); grl_media_set_url(media.get(), url.c_str()); grl_media_set_id(media.get(), url.c_str()); std::pair insert_result = collection->insert (make_pair(url, std::vector() << title)); ASSERT_TRUE(insert_result.second) << "media url: <" << url << ">"; *it = insert_result.first; Wrapper error; grl_source_store_sync(source.get(), nullptr, media.get(), GRL_WRITE_NORMAL, error.out_param()); ASSERT_FALSE(error) << "media url: <" << url << ">" << std::endl << "error: " << to_string(error); ASSERT_TRUE(grl_source_test_media_from_uri(source.get(), url.c_str())) << "media url: <" << url << ">"; pull_dbus_signals(); } static MediaMap::iterator Insert(const std::string &title_prefix, const std::string &url_prefix, Wrapper source, MediaMap *collection) { MediaMap::iterator it; Insert(title_prefix, url_prefix, source, collection, &it); return it; } static MediaMap::iterator InsertInvisible(Wrapper source, MediaMap *collection) { return Insert("Invisible Media", full_url("urn:x-test:notify-invisible").c_str(), source, collection); } static MediaMap::iterator InsertVisible(Wrapper source, MediaMap *collection) { return Insert("Visible Media", full_url("urn:x-test:notify-visible").c_str(), source, collection); } static void Modify(Wrapper source, MediaMap::iterator it) { const std::string &title = it->second.back(); const std::string &url = it->first; Wrapper media = take(grl_media_new()); const std::string new_title = title + " Modified"; grl_media_set_title(media.get(), new_title.c_str()); grl_media_set_url(media.get(), url.c_str()); grl_media_set_id(media.get(), url.c_str()); Wrapper error; Wrapper keys = take(grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_INVALID)); const Wrapper failed_keys = take(grl_source_store_metadata_sync (source.get(), media.get(), keys.get(), GRL_WRITE_NORMAL, error.out_param())); ASSERT_FALSE(error) << "media url: <" << url << ">" << std::endl << "error: " << to_string(error); ASSERT_FALSE(failed_keys); it->second.push_back(new_title); pull_dbus_signals(); } static void Remove(Wrapper source, MediaMap::iterator it) { const std::string &title = it->second.back(); const std::string &url = it->first; Wrapper media = take(grl_media_new()); grl_media_set_id(media.get(), url.c_str()); Wrapper error; grl_source_remove_sync(source.get(), media.get(), error.out_param()); ASSERT_FALSE(error) << "media url: <" << url << ">" << std::endl << "error: " << to_string(error); it->second.push_back(title); pull_dbus_signals(); } struct Notification { Wrapper media; GrlSourceChangeType type; }; typedef std::map > NotificationMap; static void on_media_changed(GrlSource *, GPtrArray *changed_media, GrlSourceChangeType change_type, gboolean location_unknown, NotificationMap *notifications) { EXPECT_FALSE(location_unknown); EXPECT_LT(0, changed_media->len); for (unsigned i = 0; i < changed_media->len; ++i) { const Notification notification = { wrap(GRL_MEDIA(g_ptr_array_index(changed_media, i))), change_type }; const std::string url = grl_media_get_url(notification.media.get()); (*notifications)[url].push_back(notification); } } static void verify_notifications(const MediaMap &visible_media, const MediaMap &invisible_media, const NotificationMap ¬ifications) { // Check that all "visible" media got notified. EXPECT_EQ(visible_media.size(), notifications.size()); for (const auto vm: visible_media) { const std::string &url = vm.first; const NotificationMap::const_iterator nit = notifications.find(url); EXPECT_TRUE(nit != notifications.end()) << "media url: <" << url << ">"; ASSERT_EQ(vm.second.size(), nit->second.size()) << "media url: <" << url << ">"; for (size_t i = 0; i < vm.second.size(); ++i) { const GrlSourceChangeType expected_change_type = i == 0 ? GRL_CONTENT_ADDED : i == 2 ? GRL_CONTENT_REMOVED : GRL_CONTENT_CHANGED; const std::string notified_title = safe_string (grl_media_get_title(nit->second.at(i).media.get())); EXPECT_EQ(expected_change_type, nit->second.at(i).type) << "media url: <" << url << ">#" << i; if (expected_change_type != GRL_CONTENT_REMOVED) { EXPECT_EQ(vm.second.at(i), notified_title) << "media url: <" << url << ">#" << i; } } } // Ensure there were no notifications for "invisible" media. for (const auto vm: invisible_media) { const std::string &url = vm.first; EXPECT_TRUE(notifications.find(url) == notifications.end()) << "media url: <" << url << ">"; } } }; TEST_F(Notify, Test) { const Wrapper source = GetMediaScannerSource(); NotificationMap notifications; MediaMap invisible_media; MediaMap visible_media; Wrapper error; g_signal_connect(source.get(), "content-changed", G_CALLBACK(&Notify::on_media_changed), ¬ifications); for (int i = 0; i < 10 && not HasFailure(); ++i) { // Insert and update "invisible" media... InsertInvisible(source, &invisible_media); verify_notifications(visible_media, invisible_media, notifications); MediaMap::iterator it = InsertInvisible(source, &invisible_media); verify_notifications(visible_media, invisible_media, notifications); Modify(source, it); verify_notifications(visible_media, invisible_media, notifications); Remove(source, it); verify_notifications(visible_media, invisible_media, notifications); // Start notification service... const bool change_notifications_started = grl_source_notify_change_start(source.get(), error.out_param()); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(change_notifications_started); // Insert and update "visible" media... InsertVisible(source, &visible_media); verify_notifications(visible_media, invisible_media, notifications); it = InsertVisible(source, &visible_media); verify_notifications(visible_media, invisible_media, notifications); Modify(source, it); verify_notifications(visible_media, invisible_media, notifications); Remove(source, it); verify_notifications(visible_media, invisible_media, notifications); // Stop notification service... const bool change_notifications_stopped = grl_source_notify_change_stop(source.get(), error.out_param()); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(change_notifications_stopped); } // Some final paranoia... pull_dbus_signals(); verify_notifications(visible_media, invisible_media, notifications); } TEST_F(Notify, Recursive) { const Wrapper source = GetMediaScannerSource(); Wrapper error; ASSERT_TRUE(grl_source_notify_change_start(source.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(grl_source_notify_change_start(source.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(grl_source_notify_change_stop(source.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); ASSERT_TRUE(grl_source_notify_change_stop(source.get(), error.out_param())); EXPECT_FALSE(error) << to_string(error); // Error raised on unbalanced call to notify_change_stop() ASSERT_FALSE(grl_source_notify_change_stop(source.get(), error.out_param())); ASSERT_TRUE(error); pull_dbus_signals(); } //////////////////////////////////////////////////////////////////////////////// class LuceneEnvironment : public mediascanner::LuceneEnvironment { public: LuceneEnvironment() : mediascanner::LuceneEnvironment(kMediaIndexPath) { } void FillMediaIndex(WritableMediaIndex *writer) { for (const auto &p: make_known_media()) { writer->Insert(ToUnicode(p.first), p.second); const std::string error_message = writer->error_message(); ASSERT_TRUE(error_message.empty()) << " Message: " << error_message << std::endl << " Media: " << p.first; } } }; void SetupTestEnvironments() { using testing::AddGlobalTestEnvironment; AddGlobalTestEnvironment(new LuceneEnvironment); AddGlobalTestEnvironment(new DBusEnvironment(kDataPath, kMediaIndexPath)); AddGlobalTestEnvironment(new MediaScannerEnvironment(kMediaIndexPath)); } } // namespace griloplugintest } // namespace mediascanner //////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { mediascanner::InitTests(&argc, argv); grl_init(&argc, &argv); mediascanner::griloplugintest::SetupTestEnvironments(); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/auto/loggingtest.cpp0000644000015700001700000001527412232220161024072 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // C Library #include // Google Tests #include // Boost C++ #include // Standard Library #include #include // Media Scanner #include "mediascanner/locale.h" #include "mediascanner/logging.h" // Test Suite #include "testlib/loggingsink.h" #include "testlib/testutils.h" namespace mediascanner { static std::string to_string(unsigned n) { std::ostringstream oss; oss << n; return oss.str(); } TEST(LoggingTest, DefaultDomains) { LoggingSink sink; // Verify initial state of the domains. ASSERT_TRUE(logging::error()->enabled()); ASSERT_TRUE(logging::warning()->enabled()); ASSERT_TRUE(logging::info()->enabled()); ASSERT_FALSE(logging::debug()->enabled()); ASSERT_FALSE(logging::trace()->enabled()); // Verify initial behavior of the domains. (*logging::debug())(to_string(__LINE__)); EXPECT_TRUE(sink.take().empty()); (*logging::info())(to_string(__LINE__)); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); (*logging::warning())(to_string(__LINE__)); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); (*logging::error())(to_string(__LINE__)); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); // Verify that the debug domain can be enabled. logging::debug()->set_enabled(true); ASSERT_TRUE(logging::debug()->enabled()); ASSERT_TRUE(logging::info()->enabled()); (*logging::debug())(to_string(__LINE__)); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); // Verify that the info domain can be disabled. // Note that the debug domain remains active, as it was enabled explicitly. logging::info()->set_enabled(false); ASSERT_TRUE(logging::debug()->enabled()); ASSERT_FALSE(logging::info()->enabled()); (*logging::debug())(to_string(__LINE__)); EXPECT_FALSE(sink.take().empty()); EXPECT_TRUE(sink.get().empty()); (*logging::info())(to_string(__LINE__)); EXPECT_TRUE(sink.take().empty()); // Verify that the debug domain inherts the info domain's state after reset. logging::debug()->reset(); ASSERT_FALSE(logging::debug()->enabled()); ASSERT_FALSE(logging::info()->enabled()); (*logging::debug())(to_string(__LINE__)); EXPECT_TRUE(sink.take().empty()); (*logging::info())(to_string(__LINE__)); EXPECT_TRUE(sink.take().empty()); } TEST(LoggingTest, CustomDomain) { LoggingSink default_sink; std::ostringstream other_stream; const logging::MessageSinkPtr other_sink (new logging::DefaultMessageSink(&other_stream)); logging::Domain other_domain("other", logging::info()); other_domain.set_message_sink(other_sink); ASSERT_TRUE(logging::info()->enabled()); EXPECT_TRUE(default_sink.get().empty()); EXPECT_TRUE(other_stream.str().empty()); (*logging::info())(to_string(__LINE__)); EXPECT_FALSE(default_sink.take().empty()); EXPECT_TRUE(default_sink.get().empty()); EXPECT_TRUE(other_stream.str().empty()); other_domain(to_string(__LINE__)); EXPECT_TRUE(default_sink.get().empty()); EXPECT_FALSE(other_stream.str().empty()); } TEST(LoggingTest, Strings) { LoggingSink sink; ASSERT_TRUE(logging::info()->enabled()); (*logging::info())("Format {1}") % 123; EXPECT_TRUE(boost::algorithm::ends_with(sink.take(), "Format 123\n")); EXPECT_TRUE(sink.get().empty()); (*logging::info())(L"Wide {1}") % 123; EXPECT_TRUE(boost::algorithm::ends_with(sink.take(), "Wide 123\n")); EXPECT_TRUE(sink.get().empty()); } struct EnvTestParam { EnvTestParam(const std::string &environment_value, const bool first_domain_enabled, const bool second_domain_enabled, const bool third_domain_enabled, const bool nested_domain_enabled) : environment_value(environment_value) , first_domain_enabled(first_domain_enabled) , second_domain_enabled(second_domain_enabled) , third_domain_enabled(third_domain_enabled) , nested_domain_enabled(nested_domain_enabled) { } const std::string environment_value; const bool first_domain_enabled; const bool second_domain_enabled; const bool third_domain_enabled; const bool nested_domain_enabled; }; static void PrintTo(const EnvTestParam &p, std::ostream *os) { *os << '"' << p.environment_value << '"'; } class EnvTest : public testing::TestWithParam { }; INSTANTIATE_TEST_CASE_P(, EnvTest, testing::Values (EnvTestParam("", true, true, false, false), EnvTestParam("::", true, true, false, false), EnvTestParam("third", true, true, true, true), EnvTestParam("-second", true, false, false, false), EnvTestParam("third:-second", true, false, true, true), EnvTestParam("-second:third", true, false, true, true), EnvTestParam("-second:second", true, true, false, false), EnvTestParam("+second:-second", true, false, false, false), EnvTestParam("third/child", true, true, false, true))); TEST_P(EnvTest, Test) { const EnvTestParam &p = GetParam(); ::setenv("MEDIASCANNER_DEBUG", p.environment_value.c_str(), true); ASSERT_EQ(p.environment_value, safe_string(::getenv("MEDIASCANNER_DEBUG"))); logging::Domain first("first"); logging::Domain second("second", &first); logging::Domain third("third", logging::Domain::Disabled, &second); logging::Domain nested("third/child", &third); EXPECT_EQ(p.first_domain_enabled, first.enabled()); EXPECT_EQ(p.second_domain_enabled, second.enabled()); EXPECT_EQ(p.third_domain_enabled, third.enabled()); EXPECT_EQ(p.nested_domain_enabled, nested.enabled()); } } // namespace mediascanner int main(int argc, char *argv[]) { ::setenv("MEDIASCANNER_DEBUG", "0", true); mediascanner::InitTests(&argc, argv); return RUN_ALL_TESTS(); } mediascanner-0.3.93+14.04.20131024.1/tests/grl-mock-data.ini0000644000015700001700000000414612232220161023207 0ustar pbuserpbgroup00000000000000[default] version = 1 ignored-parameters = api%5fkey;api_key [http://api.themoviedb.org/3/configuration] data=data/tmdb-configuration.txt [http://api.themoviedb.org/3/movie/10378] data=data/tmdb-movie-10378.txt [http://api.themoviedb.org/3/movie/10378/casts] data=data/tmdb-movie-10378-casts.txt [http://api.themoviedb.org/3/movie/10378/images] data=data/tmdb-movie-10378-images.txt [http://api.themoviedb.org/3/movie/10378/keywords] data=data/tmdb-movie-10378-keywords.txt [http://api.themoviedb.org/3/movie/10378/releases] data=data/tmdb-movie-10378-releases.txt [http://api.themoviedb.org/3/movie/133701] data=data/tmdb-movie-133701.txt [http://api.themoviedb.org/3/movie/133701/casts] data=data/tmdb-movie-133701-casts.txt [http://api.themoviedb.org/3/movie/133701/images] data=data/tmdb-movie-133701-images.txt [http://api.themoviedb.org/3/movie/133701/keywords] data=data/tmdb-movie-133701-keywords.txt [http://api.themoviedb.org/3/movie/133701/releases] data=data/tmdb-movie-133701-releases.txt [http://api.themoviedb.org/3/movie/45745] data=data/tmdb-movie-45745.txt [http://api.themoviedb.org/3/movie/45745/casts] data=data/tmdb-movie-45745-casts.txt [http://api.themoviedb.org/3/movie/45745/images] data=data/tmdb-movie-45745-images.txt [http://api.themoviedb.org/3/movie/45745/keywords] data=data/tmdb-movie-45745-keywords.txt [http://api.themoviedb.org/3/movie/45745/releases] data=data/tmdb-movie-45745-releases.txt [http://api.themoviedb.org/3/search/movie?query=BigBuckBunny] data=data/tmdb-search-movie-query-bigbuckbunny.txt [http://api.themoviedb.org/3/search/movie?query=Big+Buck+Bunny] data=data/tmdb-search-movie-query-big-buck-bunny.txt [http://api.themoviedb.org/3/search/movie?query=big+buck+bunny] data=data/tmdb-search-movie-query-big-buck-bunny.txt [http://api.themoviedb.org/3/search/movie?query=Sintel] data=data/tmdb-search-movie-query-sintel.txt [http://api.themoviedb.org/3/search/movie?query=tears+of+steel] data=data/tmdb-search-movie-query-tears-of-steel.txt [http://ws.audioscrobbler.com/1.0/album/Butterfingers/Breakfast%20at%20Fatboys/info.xml] data=data/lastfm-albumart-butterfingers.xml mediascanner-0.3.93+14.04.20131024.1/tests/media/0000755000015700001700000000000012232220310021132 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/media/BigBuckBunny_320x180.ogv0000644000015700001700000001036012232220161025202 0ustar pbuserpbgroup00000000000000OggS s3,š*€theora @´ (ÀOggS s3Ÿmó :ÿÿÿÿÿÿÿÿÿÿ?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 s3™7+ ÿÿÿÿÿuC ƒL½÷¾÷Þûßxûßx½÷½ß{ï÷¾÷»ï}à>÷Þ÷}ï¼ÞûÞï½÷€ûÚ⪊i¶¯5TSΪŠi¶¯5TSΪŠi¶¯5TS‡­Pô«íT>:¡éWÚ¨|uCÒ¯µPÀ4iZ¢¨­UjŠ¢µUª*ŠÕ•TUUQUUET· Caã !äxÂÖ s H€ƒ3L0‚ríkFfµZK,` x 0ˆ@„˜@A†¶ÛmAšÚÍF!€ 0€€‡‚€@@A™„a m¶Ñ„­¬Ô` x 0ˆ@„˜@A†¶ÛmAšÚÍF!€ 0€€‡‚€@@A™„a m¶Ñ„­¬Ô` x 0ˆ@„˜@A†¶ÛmAšÚÍFØEvmjž}/Ô¤”ÓéøÔF"X iovLËvÂÖôÑÜ̺ïÒä[„Ga¯ÒýJIJ¨ˆY3,Ûêºä®»ô¿·;ŽÃ_¥ú”’•P#,²fX#¶ÕuÉ]wénÿÿu>a(…­P!!!6)Jbh€ÏÿýS“a¿°GfÇmÏÿÿýD%"!1 ±JS ÿÿЊB;%µ¹ÿÿÿ¨„¢R¤&!!!6)Jb€ÏÿÿúHGd¶b&)C²Ë(s/ÒŽ©ƒÃô¥’ý(t¥)e–)¥R‡Gæ’ý(ê”:R”²ËÒŽ©C£óI~”uJ)JYeŠiGT¡Ñù¤¿J:¥”¥,²Å4£ªPèüÒ_¥R‡JR–YbšQÕ(t~i/ÒŽ©CR¥Ò•­×kµ×MúM&‘ûã|o޾è­[¥jÏ–,5‹.ŠTºR¤õÚívºé¤I¤Ò?|oñ¾7EjÝ+Vk,YtR¥Ò•'®×kµ×M#úM&‘ûã|oñº+VéZ³X±b˧s®ãÑi4šK¤ö¾×Úú];w’I’K®ç]ǤÒi4—Kí}¯µöºw:î2I$›§s®ãÒi4šK¥ö¾×Úû];w$’MÙëÔH‘:xñ$Onˆ‘"xõëר‘"DñãLj‘"Dîé$Hž½zõ$Hž“‚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&˜Dmediascanner-0.3.93+14.04.20131024.1/tests/media/big_buck_bunny_1080p_stereo.ogv0000644000015700001700000003153612232220161027054 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?Rmediascanner-0.3.93+14.04.20131024.1/tests/media/Sintel.2010.720p.ogv0000644000015700001700000002227712232220161024173 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&˜Dmediascanner-0.3.93+14.04.20131024.1/tests/media/Sintel.2010.1080p.ogv0000644000015700001700000003153612232220161024251 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?Rmediascanner-0.3.93+14.04.20131024.1/tests/media/big_buck_bunny_480p_surround.ogv0000644000015700001700000001524512232220161027356 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Øü<Œmediascanner-0.3.93+14.04.20131024.1/tests/media/big_buck_bunny_720p_stereo.ogv0000644000015700001700000002227712232220161026776 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&˜Dmediascanner-0.3.93+14.04.20131024.1/tests/media/tears_of_steel_1080p.ogv0000644000015700001700000003153612232220161025511 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?Rmediascanner-0.3.93+14.04.20131024.1/tests/CMakeLists.txt0000644000015700001700000001511512232220161022622 0ustar pbuserpbgroup00000000000000include_directories(${DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${GTEST_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src) # ============================================================================= # Prepare a Grilo plugin directory for testing # ============================================================================= set(grilo_plugin_dir "${CMAKE_CURRENT_BINARY_DIR}/plugins") get_target_property(mediascanner_LOCATION mediascanner-service LOCATION) add_definitions(-DGRILO_PLUGIN_DIR="${grilo_plugin_dir}" -DMEDIASCANNER_SERVICE="${mediascanner_LOCATION}" -DGUDEV_VERSION=${GUDEV_VERSION}) get_target_property(grlmediascanner_LOCATION grlmediascanner LOCATION) add_custom_target( griloplugindir DEPENDS grlmediascanner COMMAND mkdir -p ${grilo_plugin_dir} COMMAND ln -sf ${grlmediascanner_LOCATION} ${grilo_plugin_dir}/libgrlmediascanner.so COMMAND ln -sf ${CMAKE_SOURCE_DIR}/src/grlmediascanner/*.xml ${grilo_plugin_dir} COMMAND ln -sf ${GRILO_PLUGIN_DIR}/libgrltmdb.so ${grilo_plugin_dir} COMMAND ln -sf ${GRILO_PLUGIN_DIR}/grl-tmdb.xml ${grilo_plugin_dir} COMMAND ln -sf ${GRILO_PLUGIN_DIR}/libgrllastfm-albumart.so ${grilo_plugin_dir} COMMAND ln -sf ${GRILO_PLUGIN_DIR}/grl-lastfm-albumart.xml ${grilo_plugin_dir} ) # ============================================================================= # Prepare a directory with test media # ============================================================================= set(media_dir "${CMAKE_CURRENT_SOURCE_DIR}/media") add_definitions(-DMEDIA_DIR="${media_dir}") # ============================================================================= # Prepare a directory with test settings # ============================================================================= set(user_data_dir "${CMAKE_CURRENT_BINARY_DIR}/data") set(schema_dir "${user_data_dir}/glib-2.0/schemas") add_definitions(-DUSER_DATA_DIR="${user_data_dir}") add_custom_target(settingsdir COMMAND mkdir -p ${schema_dir} COMMAND ln -sf ${MEDIASCANNER_SETTINGS_FILE} ${schema_dir} COMMAND ${GLIB_COMPILE_SCHEMAS} ${schema_dir} DEPENDS checksettings) # ============================================================================= # Enable coverage testing # ============================================================================= if(CMAKE_BUILD_TYPE_LOWER STREQUAL "coverage") add_custom_target(coverage DEPENDS lcov-report gcovr-xml) add_custom_target(coverage-exec COMMAND ${CMAKE_BUILD_TOOL} test) if(LCOV_FOUND) set(LCOV_DATAFILE ${CMAKE_BINARY_DIR}/lcov.info) set(LCOV_REPORT_HTML ${CMAKE_BINARY_DIR}/docs/coverage) add_custom_target(lcov-reset COMMAND ${LCOV} ${LCOV_ARGS} --zerocounters --directory ${CMAKE_BINARY_DIR}) add_custom_target(lcov-capture DEPENDS coverage-exec COMMAND ${LCOV} ${LCOV_ARGS} --capture --directory ${CMAKE_BINARY_DIR} --output-file ${LCOV_DATAFILE}) add_custom_target(lcov-extract DEPENDS lcov-capture COMMAND ${LCOV} ${LCOV_ARGS} --extract ${LCOV_DATAFILE} ${CMAKE_SOURCE_DIR}/* --output-file ${LCOV_DATAFILE} VERBATIM) add_custom_target(lcov-cleanup DEPENDS lcov-extract COMMAND ${CMAKE_SOURCE_DIR}/tools/lcov-cleanup.py ${LCOV_DATAFILE}) add_custom_target(lcov-report DEPENDS lcov-cleanup COMMAND ${GENHTML} --output-directory ${LCOV_REPORT_HTML} ${LCOV_DATAFILE} --demangle-cpp --show-details) set_property(TARGET coverage-exec APPEND PROPERTY DEPENDS lcov-reset) else(LCOV_FOUND) add_custom_target(lcov-report COMMAND echo "WARNING: Could not find lcov, HTML coverage reports are not generated.") endif(LCOV_FOUND) if(GCOVR_FOUND) set(GCOV_REPORT_XML ${CMAKE_BINARY_DIR}/gcovr.xml) add_custom_target(gcovr-xml DEPENDS coverage-exec COMMAND LANG=C gcovr --xml --root=${CMAKE_SOURCE_DIR} --output=${GCOV_REPORT_XML}) else(GCOVR_FOUND) add_custom_target(gcovr-xml COMMAND echo "WARNING: Could not find gcovr, XML coverage reports are not generated.") endif(GCOVR_FOUND) endif(CMAKE_BUILD_TYPE_LOWER STREQUAL "coverage") # ============================================================================= # Build static library with common test facilities # ============================================================================= add_library(testlib STATIC testlib/environments.cpp testlib/environments.h testlib/loggingsink.cpp testlib/loggingsink.h testlib/testutils.cpp testlib/testutils.h) # ============================================================================= # Setup rules for the actual tests # ============================================================================= enable_testing() set(taskmanagertest_args --gtest_repeat=250) add_definitions(-DREQUEST_FILE="${CMAKE_CURRENT_SOURCE_DIR}/grl-mock-data.ini") add_custom_target(testdirs COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/auto COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/manual) foreach(test filesystemscannertest griloplugintest localetest loggingtest glibutilstest mediaartcachetest mediaindextest propertytest taskmanagertest metadataresolvertest) add_executable(auto/${test} auto/${test}.cpp) add_dependencies(auto/${test} testdirs) target_link_libraries(auto/${test} testlib mediascanner gtest) add_test(NAME build/${test} COMMAND ${CMAKE_BUILD_TOOL} auto/${test}) add_test(NAME auto/${test} COMMAND auto/${test} ${${test}_args}) set_property(TEST auto/${test} PROPERTY DEPENDS build/${test}) endforeach(test) set_tests_properties(auto/mediaartcachetest PROPERTIES ENVIRONMENT "XDG_CACHE_HOME=${CMAKE_CURRENT_BINARY_DIR}") add_dependencies(auto/filesystemscannertest griloplugindir) add_dependencies(auto/griloplugintest griloplugindir mediascanner-service) target_link_libraries(auto/griloplugintest ${DEPS_LIBRARIES}) add_dependencies(auto/propertytest griloplugindir mediascanner-service) add_dependencies(auto/metadataresolvertest griloplugindir) add_executable(manual/dbustest manual/dbustest.cpp) target_link_libraries(manual/dbustest mediascanner) add_dependencies(manual/dbustest settingsdir) add_executable(manual/settingstest manual/settingstest.cpp) target_link_libraries(manual/settingstest mediascanner testlib gtest) add_dependencies(manual/settingstest testdirs settingsdir) mediascanner-0.3.93+14.04.20131024.1/tests/data/0000755000015700001700000000000012232220310020764 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-search-movie-query-bigbuckbunny.txt0000644000015700001700000000007112232220161030660 0ustar pbuserpbgroup00000000000000{"page":1,"results":[],"total_pages":0,"total_results":0}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-search-movie-query-big-buck-bunny.txt0000644000015700001700000000050412232220161031013 0ustar pbuserpbgroup00000000000000{"page":1,"results":[{"adult":false,"backdrop_path":"/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg","id":10378,"original_title":"Big Buck Bunny","release_date":"2008-04-10","poster_path":"/pzUAeshAqvnLnICU4rYB0WtDFg3.jpg","popularity":15.324,"title":"Big Buck Bunny","vote_average":8.1,"vote_count":6}],"total_pages":1,"total_results":1}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-133701-images.txt0000644000015700001700000000251312232220161025574 0ustar pbuserpbgroup00000000000000{"id":133701,"backdrops":[{"file_path":"/ogUCcoqloPuPFaHL3zKIjf8dmzY.jpg","width":1421,"height":800,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":10.0,"vote_count":1},{"file_path":"/pFxM20Q2KWX8MSWyL46Vw3QEqrz.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.5,"vote_count":1},{"file_path":"/zS3kyNsIeZ4tLTWmei30pK83ftG.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.5,"vote_count":1},{"file_path":"/WdkFduq0sblrsoCHIhxfjbrcNp.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.0,"vote_count":2},{"file_path":"/2D0iqz2ptyiRgAuiqztyLYw5cmk.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.0,"vote_count":1},{"file_path":"/4SOadxw1J9mLuRfMLo3Vnu4XqeK.jpg","width":1920,"height":1080,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.0,"vote_count":2},{"file_path":"/svjk2YaPchYtT3Qjs1IaSMcdkWe.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":7.5,"vote_count":1},{"file_path":"/iqmbnuGk6LDgRYGYF2KApjzU38N.jpg","width":1280,"height":720,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":6.5,"vote_count":1}],"posters":[{"file_path":"/3ATmNnbuE7ROuknGGCYCQx3d4sI.jpg","width":691,"height":1024,"iso_639_1":null,"aspect_ratio":0.67,"vote_average":10.0,"vote_count":1}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-45745.txt0000644000015700001700000000215412232220161024264 0ustar pbuserpbgroup00000000000000{"adult":false,"backdrop_path":"/sztmefbemAzeq5Dz8S5eEajOPn9.jpg","belongs_to_collection":{"id":113510,"name":"Blender Open Movies","poster_path":"/2CKssa0LOqL6eqIYWTn23yXvk7o.jpg","backdrop_path":"/mQHlU85B1bY5d9Z5rYxCe1deFV7.jpg"},"budget":550000,"genres":[{"id":16,"name":"Animation"},{"id":14,"name":"Fantasy"},{"id":10755,"name":"Short"}],"homepage":"http://durian.blender.org/","id":45745,"imdb_id":"tt1727587","original_title":"Sintel","overview":"A wandering warrior finds an unlikely friend in the form of a young dragon. The two develop a close bond, until one day the dragon is snatched away. She then sets out on a relentless quest to reclaim her friend, finding in the end that her quest exacts a far greater price than she had ever imagined.","popularity":48.253,"poster_path":"/bV1FokqAUeK69NBFsgOeWZQL6Fs.jpg","production_companies":[{"name":"Blender Foundation","id":6908}],"production_countries":[{"iso_3166_1":"NL","name":"Netherlands"}],"release_date":"2010-09-30","revenue":0,"runtime":14,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"tagline":"","title":"Sintel","vote_average":8.3,"vote_count":14}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-45745-casts.txt0000644000015700001700000000142612232220161025400 0ustar pbuserpbgroup00000000000000{"id":45745,"cast":[{"id":39847,"name":"Halina Reijn","character":"Sintel","order":0,"cast_id":5,"profile_path":"/wkXlorWKdVziK9h3g0Lizc8aO2J.jpg"},{"id":7572,"name":"Thom Hoffman","character":"Shaman","order":1,"cast_id":6,"profile_path":"/uqWhSQzfBScIHzYhAr4GsHKyrvP.jpg"}],"crew":[{"id":133631,"name":"Colin Levy","department":"Directing","job":"Director","profile_path":"/82OA969wEqz7EaS7zLsE6l6Xr89.jpg"},{"id":133632,"name":"Ton Roosendaal","department":"Production","job":"Producer","profile_path":"/w5NbjQk0E14A5LMTtn5nm3oU5cD.jpg"},{"id":133633,"name":"Esther Wouda","department":"Writing","job":"Writer","profile_path":"/sN89hQog4HIM28lx3es8UlDQt0z.jpg"},{"id":133634,"name":"Jan Morgenstern","department":"Sound","job":"Music","profile_path":"/f9NBnRMCdOTZg1Y8QDPiXsHxY5p.jpg"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-45745-keywords.txt0000644000015700001700000000016612232220161026132 0ustar pbuserpbgroup00000000000000{"id":45745,"keywords":[{"id":12554,"name":"dragons"},{"id":10841,"name":"action heroine "},{"id":2551,"name":"pet"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-search-movie-query-sintel.txt0000644000015700001700000000046512232220161027503 0ustar pbuserpbgroup00000000000000{"page":1,"results":[{"adult":false,"backdrop_path":"/sztmefbemAzeq5Dz8S5eEajOPn9.jpg","id":45745,"original_title":"Sintel","release_date":"2010-09-30","poster_path":"/bV1FokqAUeK69NBFsgOeWZQL6Fs.jpg","popularity":82.635,"title":"Sintel","vote_average":8.3,"vote_count":14}],"total_pages":1,"total_results":1}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-10378-images.txt0000644000015700001700000000120212232220161025512 0ustar pbuserpbgroup00000000000000{"id":10378,"backdrops":[{"file_path":"/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg","width":1920,"height":1080,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":7.0,"vote_count":3},{"file_path":"/rIK10zkOKfJ1j6W0nhYjQR5skBg.jpg","width":1920,"height":1080,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":5.666666666666667,"vote_count":3}],"posters":[{"file_path":"/pzUAeshAqvnLnICU4rYB0WtDFg3.jpg","width":1000,"height":1500,"iso_639_1":"en","aspect_ratio":0.67,"vote_average":10.0,"vote_count":2},{"file_path":"/vKipMuCVwfPsPljCCbvz2FUHIJz.jpg","width":1500,"height":2107,"iso_639_1":null,"aspect_ratio":0.71,"vote_average":4.25,"vote_count":2}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-133701-keywords.txt0000644000015700001700000000055712232220161026204 0ustar pbuserpbgroup00000000000000{"id":133701,"keywords":[{"id":12575,"name":"robots"},{"id":587,"name":"amsterdam"},{"id":10568,"name":"science fiction"},{"id":169440,"name":"cyborg"},{"id":14946,"name":"fictional war"},{"id":160106,"name":"future"},{"id":155934,"name":"post apocalypse"},{"id":1373,"name":"killer robot"},{"id":83,"name":"saving the world"},{"id":312,"name":"man versus machine"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-45745-images.txt0000644000015700001700000000142112232220161025523 0ustar pbuserpbgroup00000000000000{"id":45745,"backdrops":[{"file_path":"/sztmefbemAzeq5Dz8S5eEajOPn9.jpg","width":1920,"height":1080,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":10.0,"vote_count":2},{"file_path":"/6bOku5MQNLCnufoaJsGh4weoZCF.jpg","width":1550,"height":872,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":8.0,"vote_count":2},{"file_path":"/vp102ikTt10EWHsVFIUSyED4GFz.jpg","width":1550,"height":872,"iso_639_1":null,"aspect_ratio":1.78,"vote_average":0.5,"vote_count":1}],"posters":[{"file_path":"/9kBupxzoWaNeNiFb41vuUphREO2.jpg","width":1000,"height":1500,"iso_639_1":null,"aspect_ratio":0.67,"vote_average":8.5,"vote_count":2},{"file_path":"/bV1FokqAUeK69NBFsgOeWZQL6Fs.jpg","width":905,"height":1280,"iso_639_1":"en","aspect_ratio":0.71,"vote_average":5.833333333333333,"vote_count":3}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-10378-casts.txt0000644000015700001700000000175112232220161025373 0ustar pbuserpbgroup00000000000000{"id":10378,"cast":[],"crew":[{"id":64936,"name":"Sacha Goedegebure","department":"Directing","job":"Director","profile_path":null},{"id":58958,"name":"Andreas Goralczyk","department":"Art","job":"Art Direction","profile_path":null},{"id":64937,"name":"Enrico Valenza","department":"Art","job":"Art Direction","profile_path":null},{"id":64938,"name":"Nathan Vegdahl","department":"Visual Effects","job":"Animation","profile_path":null},{"id":64939,"name":"William Reynish","department":"Visual Effects","job":"Animation","profile_path":null},{"id":64940,"name":"Campbell Barton","department":"Crew","job":"Technical Supervisor","profile_path":null},{"id":64941,"name":"Brecht van Lommel","department":"Crew","job":"Technical Supervisor","profile_path":null},{"id":64942,"name":"Jan Morgenstern","department":"Sound","job":"Original Music Composer","profile_path":null},{"id":133632,"name":"Ton Roosendaal","department":"Production","job":"Producer","profile_path":"/w5NbjQk0E14A5LMTtn5nm3oU5cD.jpg"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/lastfm-albumart-butterfingers.xml0000644000015700001700000000562512232220161027476 0ustar pbuserpbgroup00000000000000 4480 http://www.last.fm/music/Butterfingers/Breakfast+at+Fatboys 30 Nov 2003, 00:00 http://userserve-ak.last.fm/serve/34s/18717421.jpg http://userserve-ak.last.fm/serve/64s/18717421.jpg http://userserve-ak.last.fm/serve/126/18717421.jpg 0b24db51-3c03-4e7d-b666-a46edf1ff840 1663 http://www.last.fm/music/Butterfingers/_/Hook+Up 1516 http://www.last.fm/music/Butterfingers/_/Mandarines 3192 http://www.last.fm/music/Butterfingers/_/Everytime 5048 http://www.last.fm/music/Butterfingers/_/Yo+Mama 1411 http://www.last.fm/music/Butterfingers/_/Girl+From+Gore 1395 http://www.last.fm/music/Butterfingers/_/Is+It+Just+Me 1320 http://www.last.fm/music/Butterfingers/_/Hurt+Me+So+Bad 1289 http://www.last.fm/music/Butterfingers/_/Sorry 1163 http://www.last.fm/music/Butterfingers/_/Piss+on+Ya 1167 http://www.last.fm/music/Butterfingers/_/Smell+You+on+Me 1203 http://www.last.fm/music/Butterfingers/_/Snatch+&+Grab 4128 http://www.last.fm/music/Butterfingers/_/I+Love+Work 1205 http://www.last.fm/music/Butterfingers/_/Speak+Your+Mind mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-10378.txt0000644000015700001700000000232412232220161024255 0ustar pbuserpbgroup00000000000000{"adult":false,"backdrop_path":"/aHLST0g8sOE1ixCxRDgM35SKwwp.jpg","belongs_to_collection":{"id":113510,"name":"Blender Open Movies","poster_path":"/2CKssa0LOqL6eqIYWTn23yXvk7o.jpg","backdrop_path":"/mQHlU85B1bY5d9Z5rYxCe1deFV7.jpg"},"budget":150000,"genres":[{"id":16,"name":"Animation"},{"id":35,"name":"Comedy"},{"id":10755,"name":"Short"}],"homepage":"http://www.bigbuckbunny.org","id":10378,"imdb_id":"tt1254207","original_title":"Big Buck Bunny","overview":"Follow a day of the life of Big Buck Bunny when he meets three bullying rodents: Frank, Rinky, and Gamera. The rodents amuse themselves by harassing helpless creatures by throwing fruits, nuts and rocks at them. After the deaths of two of Bunny's favorite butterflies, and an offensive attack on Bunny himself, Bunny sets aside his gentle nature and orchestrates a complex plan for revenge.","popularity":15.324,"poster_path":"/pzUAeshAqvnLnICU4rYB0WtDFg3.jpg","production_companies":[{"name":"Blender Foundation","id":6908}],"production_countries":[{"iso_3166_1":"NL","name":"Netherlands"}],"release_date":"2008-04-10","revenue":0,"runtime":8,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"tagline":"","title":"Big Buck Bunny","vote_average":8.1,"vote_count":6}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-10378-keywords.txt0000644000015700001700000000016012232220161026116 0ustar pbuserpbgroup00000000000000{"id":10378,"keywords":[{"id":3208,"name":"bunny"},{"id":3698,"name":"mobbing"},{"id":1522,"name":"repayment"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-10378-releases.txt0000644000015700001700000000034412232220161026056 0ustar pbuserpbgroup00000000000000{"id":10378,"countries":[{"iso_3166_1":"US","certification":"G","release_date":"2008-04-10"},{"iso_3166_1":"FI","certification":"","release_date":"2008-05-30"},{"iso_3166_1":"NL","certification":"","release_date":"2008-04-10"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-45745-releases.txt0000644000015700001700000000034512232220161026065 0ustar pbuserpbgroup00000000000000{"id":45745,"countries":[{"iso_3166_1":"NL","certification":"","release_date":"2010-09-30"},{"iso_3166_1":"US","certification":"PG","release_date":"2010-09-30"},{"iso_3166_1":"FI","certification":"","release_date":"2010-09-30"}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-configuration.txt0000644000015700001700000000043412232220161025325 0ustar pbuserpbgroup00000000000000{"images":{"base_url":"http://cf2.imgobject.com/t/p/","poster_sizes":["w92","w154","w185","w342","w500","original"],"backdrop_sizes":["w300","w780","w1280","original"],"profile_sizes":["w45","w185","h632","original"],"logo_sizes":["w45","w92","w154","w185","w300","w500","original"]}}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-search-movie-query-tears-of-steel.txt0000644000015700001700000000050512232220161031032 0ustar pbuserpbgroup00000000000000{"page":1,"results":[{"adult":false,"backdrop_path":"/ogUCcoqloPuPFaHL3zKIjf8dmzY.jpg","id":133701,"original_title":"Tears of Steel","release_date":"2012-09-28","poster_path":"/3ATmNnbuE7ROuknGGCYCQx3d4sI.jpg","popularity":10.363,"title":"Tears of Steel","vote_average":0.0,"vote_count":0}],"total_pages":1,"total_results":1}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-133701-casts.txt0000644000015700001700000000453312232220161025450 0ustar pbuserpbgroup00000000000000{"id":133701,"cast":[{"id":9128,"name":"Derek de Lint","character":"Old Thom","order":0,"cast_id":1,"profile_path":"/1BhDuHRp2ffTkYVeGKGQM46xGyN.jpg"},{"id":237150,"name":"Sergio Hasselbaink","character":"Barly","order":1,"cast_id":2,"profile_path":"/os6qi7XixYCMsUW8BRAP7FfKhkd.jpg"},{"id":1097815,"name":"Vanja Rukavina","character":"Thom","order":2,"cast_id":3,"profile_path":"/vYgHG1ZZXqRJjOpiVHAomGKCGDI.jpg"},{"id":1097816,"name":"Denise Rebergen","character":"Celia","order":3,"cast_id":4,"profile_path":null},{"id":217240,"name":"Rogier Schippers","character":"Kapitän","order":4,"cast_id":5,"profile_path":"/sLvVCpAOLkU51ntEmCglDgKfdkh.jpg"},{"id":1097817,"name":"Chris Haley","character":"Tech head","order":5,"cast_id":6,"profile_path":null},{"id":1097818,"name":"Jody Bhe","character":"Djenghis","order":6,"cast_id":7,"profile_path":null}],"crew":[{"id":137897,"name":"Ian Hubert","department":"Directing","job":"Director","profile_path":"/hvaQFXaERvkivlup2oAohIgSdeU.jpg"},{"id":137897,"name":"Ian Hubert","department":"Writing","job":"Writer","profile_path":"/hvaQFXaERvkivlup2oAohIgSdeU.jpg"},{"id":133632,"name":"Ton Roosendaal","department":"Production","job":"Producer","profile_path":"/w5NbjQk0E14A5LMTtn5nm3oU5cD.jpg"},{"id":1097824,"name":"Joris Kerbosch","department":"Camera","job":"Director of Photography","profile_path":null},{"id":1097826,"name":"David Revoy","department":"Art","job":"Conceptual Design","profile_path":null},{"id":1097830,"name":"Jeremy Davidson","department":"Visual Effects","job":"Animation","profile_path":null},{"id":1097832,"name":"Sebastian Koenig","department":"Visual Effects","job":"Animation","profile_path":null},{"id":1097833,"name":"Kjartan Tysdal","department":"Visual Effects","job":"Animation","profile_path":null},{"id":1097835,"name":"Nicolò Zubbini","department":"Art","job":"Background Designer","profile_path":null},{"id":1097836,"name":"Roman Volodin","department":"Visual Effects","job":"Animation","profile_path":null},{"id":1097837,"name":"Andy Goralczyk","department":"Lighting","job":"Lighting Artist","profile_path":null},{"id":1097838,"name":"Rob Tuytel","department":"Crew","job":"Production Intern","profile_path":null},{"id":1097839,"name":"Joram Letwory","department":"Sound","job":"Sound Designer","profile_path":null},{"id":1097840,"name":"Joram Letwory","department":"Sound","job":"Music","profile_path":null}]}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-133701.txt0000644000015700001700000000172712232220161024337 0ustar pbuserpbgroup00000000000000{"adult":false,"backdrop_path":"/ogUCcoqloPuPFaHL3zKIjf8dmzY.jpg","belongs_to_collection":{"id":113510,"name":"Blender Open Movies","poster_path":"/2CKssa0LOqL6eqIYWTn23yXvk7o.jpg","backdrop_path":"/mQHlU85B1bY5d9Z5rYxCe1deFV7.jpg"},"budget":0,"genres":[],"homepage":"http://www.tearsofsteel.org/","id":133701,"imdb_id":"tt2285752","original_title":"Tears of Steel","overview":"The film’s premise is about a group of warriors and scientists, who gathered at the “Oude Kerk†in Amsterdam to stage a crucial event from the past, in a desperate attempt to rescue the world from destructive robots.","popularity":0.776,"poster_path":"/3ATmNnbuE7ROuknGGCYCQx3d4sI.jpg","production_companies":[{"name":"Blender Foundation","id":6908}],"production_countries":[{"iso_3166_1":"NL","name":"Netherlands"}],"release_date":"2012-09-28","revenue":0,"runtime":12,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"tagline":"","title":"Tears of Steel","vote_average":0.0,"vote_count":0}mediascanner-0.3.93+14.04.20131024.1/tests/data/tmdb-movie-133701-releases.txt0000644000015700001700000000044712232220161026136 0ustar pbuserpbgroup00000000000000{"id":133701,"countries":[{"iso_3166_1":"NL","certification":"","release_date":"2012-09-26"},{"iso_3166_1":"US","certification":"","release_date":"2012-09-28"},{"iso_3166_1":"DE","certification":"","release_date":"2012-09-26"},{"iso_3166_1":"FR","certification":"","release_date":"2012-09-28"}]}mediascanner-0.3.93+14.04.20131024.1/tests/manual/0000755000015700001700000000000012232220310021330 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tests/manual/dbustest.cpp0000644000015700001700000000541712232220161023704 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include #include #include #include "mediascanner/dbusservice.h" using mediascanner::take; using mediascanner::Wrapper; using mediascanner::dbus::InterfaceProxy; using mediascanner::dbus::MediaScannerProxy; using boost::tuples::element; using boost::tuples::null_type; // FIXME(M2): This must be an automatic test class TestProxy : public MediaScannerProxy { protected: void connected() { GError *err = 0; std::cout << "Connected!" << std::endl; const std::string good_uri = "file:///home/mathias/Musik/" "Jim%20Pavloff%20-%20Voodoo%20People%20(tribute).mp3"; { Wrapper param = take(g_variant_new("(s)", good_uri.c_str())); Wrapper result = take(g_dbus_proxy_call_sync(handle().get(), "MediaInfoExists", param.release(), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err)); if (err) { std::cout << "DBus error: " << err->message << std::endl; return; } std::cout << g_variant_print(result.get(), true) << std::endl; } { Wrapper error; bool result = MediaInfoExists [G_DBUS_CALL_FLAGS_NONE] [boost::posix_time::minutes(1)] [Wrapper()] (good_uri, error.out_param()); std::cout << "media_info_exists:" << result << ", error: " << to_string(error) << std::endl; } } }; int main() { TestProxy proxy; proxy.connect(); g_main_loop_run(take(g_main_loop_new(nullptr, false)).get()); return 0; } mediascanner-0.3.93+14.04.20131024.1/tests/manual/settingstest.cpp0000644000015700001700000000344612232220161024607 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/dbustypes.h" #include "mediascanner/settings.h" #include "mediascanner/utilities.h" // Test Suite #include "testlib/testutils.h" namespace mediascanner { std::ostream& operator<<(std::ostream &os, const Settings::MediaFormat &f) { return os << "(format-name=" << f.name << ", caps=" << f.caps << ")"; } std::ostream& operator<<(std::ostream &os, const Settings::MetadataSource &s) { return os << "(plugin-id=" << s.plugin_id << ", source-id=" << s.source_id << ", config=" << s.config << ")"; } } // namespace mediascanner int main(int argc, char **argv) { mediascanner::InitTests(&argc, argv); mediascanner::Settings settings; std::cout << settings.mandatory_containers() << std::endl; std::cout << settings.mandatory_decoders() << std::endl; std::cout << settings.media_root_urls() << std::endl; std::cout << settings.media_root_paths() << std::endl; std::cout << settings.metadata_sources() << std::endl; return 0; } mediascanner-0.3.93+14.04.20131024.1/cmake/0000755000015700001700000000000012232220310017771 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/cmake/CheckLucenePlusPlus.cmake0000644000015700001700000000365712232220161024673 0ustar pbuserpbgroup00000000000000# Check for proper version of Lucene++ # ============================================================================= # Copyright (C) 2012 Canonical Ltd. - All Rights Reserved # Contact: Jim Hodapp # ============================================================================= MACRO(CHECK_LUCENE_PLUSPLUS) INCLUDE(CheckCXXSourceCompiles) MESSAGE(STATUS "Checking features of Lucene++") # Backup include directories SET(_CMAKE_REQUIRED_INCLUDES_TMP ${CMAKE_REQUIRED_INCLUDES}) SET(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${DEPS_INCLUDE_DIRS}) # Backup libraries SET(_CMAKE_REQUIRED_LIBRARIES_TMP ${CMAKE_REQUIRED_LIBRARIES}) SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${DEPS_LDFLAGS} ${Boost_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES( " #include int main() { return 0; } " HAVE_LUCENE_H) IF(NOT HAVE_LUCENE_H) MESSAGE(FATAL_ERROR "Lucene++ does not work.") ENDIF() # Build a small test program checking Lucene++ configuration CHECK_CXX_SOURCE_COMPILES( " #include #include int main() { #ifdef LPP_USE_ALLOCATOR #error Disable LPP_USE_ALLOCATOR in Lucene++ #endif #ifndef LuceneAllocator #error LuceneAllocator is not defined #endif std::allocator(LuceneAllocator()); LuceneAllocator(std::allocator()); return 0; } " HAVE_LUCENE_WITH_STANDARD_ALLOCATOR) # Resource include directories and libraries SET(CMAKE_REQUIRED_INCLUDES ${_CMAKE_REQUIRED_INCLUDES_TMP}) SET(CMAKE_REQUIRED_LIBRARIES ${_CMAKE_REQUIRED_LIBRARIES_TMP}) # Report results IF(NOT HAVE_LUCENE_WITH_STANDARD_ALLOCATOR) MESSAGE(FATAL_ERROR "Please disable LPP_USE_ALLOCATOR in your Lucene++ build") ENDIF(NOT HAVE_LUCENE_WITH_STANDARD_ALLOCATOR) ENDMACRO(CHECK_LUCENE_PLUSPLUS) mediascanner-0.3.93+14.04.20131024.1/cmake/FindGoogleTests.cmake0000644000015700001700000000216212232220161024040 0ustar pbuserpbgroup00000000000000# Check for Google Tests # # These lines for google test are from Unity's CMakeList.txt: # http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/CMakeLists.txt # # It seems to be a way to use the gtest source code directly, instead of # via a library. This is possible because gtest has a CMakeList.txt too. MACRO(FIND_GOOGLE_TESTS) SET(GTEST_ROOT_DIR "/usr/src/gtest" CACHE PATH "Path to Google Tests sources") MESSAGE(STATUS "Checking for Google Tests at \"${GTEST_ROOT_DIR}\"") IF(NOT EXISTS ${GTEST_ROOT_DIR}/CMakeLists.txt) MESSAGE(FATAL_ERROR "Cannot find Google Tests in \"${GTEST_ROOT_DIR}\". " "Please install Google Tests and set the " "GTEST_ROOT_DIR option accordingly") ENDIF(NOT EXISTS ${GTEST_ROOT_DIR}/CMakeLists.txt) FIND_PATH(GTEST_INCLUDE_DIR gtest/gtest.h) IF(NOT GTEST_INCLUDE_DIR) MESSAGE(FATAL_ERROR "Cannot find Google Tests headers. " "Please install Google Tests and set the " "GTEST_INCLUDE_DIR option accordingly") ENDIF(NOT GTEST_INCLUDE_DIR) ENDMACRO(FIND_GOOGLE_TESTS) mediascanner-0.3.93+14.04.20131024.1/cmake/DummyCCFile.c0000644000015700001700000000003312232220161022236 0ustar pbuserpbgroup00000000000000int main() { return 0; } mediascanner-0.3.93+14.04.20131024.1/cmake/TestCCAcceptsFlag.cmake0000644000015700001700000000353612232220161024230 0ustar pbuserpbgroup00000000000000# - Test CC compiler for a flag # Check if the CC compiler accepts a flag # # Macro CHECK_CC_ACCEPTS_FLAG(FLAGS VARIABLE) - # checks if the function exists # FLAGS - the flags to try # VARIABLE - variable to store the result # #============================================================================= # Copyright 2002-2009 Kitware, Inc. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) MACRO(CHECK_CC_ACCEPTS_FLAG FLAGS VARIABLE) IF(NOT DEFINED ${VARIABLE}) MESSAGE(STATUS "Checking to see if CC compiler accepts flag ${FLAGS}") TRY_COMPILE(${VARIABLE} ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/cmake/DummyCCFile.c CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${FLAGS} OUTPUT_VARIABLE OUTPUT) IF(${VARIABLE}) MESSAGE(STATUS "Checking to see if CC compiler accepts flag ${FLAGS} - yes") FILE(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log "Determining if the CC compiler accepts the flag ${FLAGS} passed with " "the following output:\n${OUTPUT}\n\n") ELSE(${VARIABLE}) MESSAGE(STATUS "Checking to see if CC compiler accepts flag ${FLAGS} - no") FILE(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log "Determining if the CC compiler accepts the flag ${FLAGS} failed with " "the following output:\n${OUTPUT}\n\n") ENDIF(${VARIABLE}) ENDIF(NOT DEFINED ${VARIABLE}) ENDMACRO(CHECK_CC_ACCEPTS_FLAG) mediascanner-0.3.93+14.04.20131024.1/cmake/EnableCompilerWarnings.cmake0000644000015700001700000000363712232220161025402 0ustar pbuserpbgroup00000000000000# Check supported compiler warnings and enable them # ============================================================================= # Copyright (C) 2012 Canonical Ltd. - All Rights Reserved # Contact: Jim Hodapp # ============================================================================= MACRO(ENABLE_COMPILER_WARNINGS) INCLUDE(TestCCAcceptsFlag) INCLUDE(TestCXXAcceptsFlag) # Error flags OPTION(ENABLE_WARNINGS "Enable compiler warnings" OFF) OPTION(FATAL_WARNINGS "Turn compiler warnings into errors" OFF) IF(ENABLE_WARNINGS) SET(WARNING_FLAGS -Wall -Wextra -w1 -Wno-long-long -Wno-variadic-macros -DGSEAL_ENABLE -DG_DISABLE_DEPRECATED -DGST_DISABLE_DEPRECATED) ELSE(ENABLE_WARNINGS) SET(WARNING_FLAGS -Wall -w1) ENDIF(ENABLE_WARNINGS) IF(FATAL_WARNINGS) SET(WARNING_FLAGS ${WARNING_FLAGS} -Werror) ENDIF(FATAL_WARNINGS) FOREACH(flag IN LISTS WARNING_FLAGS) IF(NOT DEFINED CC_ACCEPTS_FLAG_${flag}) CHECK_CC_ACCEPTS_FLAG(${flag} accepted) SET(CC_ACCEPTS_FLAG_${flag} ${accepted} CACHE INTERNAL "Weither the C compiler supports ${flag}") UNSET(accepted CACHE) ENDIF(NOT DEFINED CC_ACCEPTS_FLAG_${flag}) IF(CC_ACCEPTS_FLAG_${flag}) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") ENDIF(CC_ACCEPTS_FLAG_${flag}) IF(NOT DEFINED CXX_ACCEPTS_FLAG_${flag}) CHECK_CXX_ACCEPTS_FLAG(${flag} accepted) SET(CXX_ACCEPTS_FLAG_${flag} ${accepted} CACHE INTERNAL "Weither the C++ compiler supports ${flag}") UNSET(accepted CACHE) ENDIF(NOT DEFINED CXX_ACCEPTS_FLAG_${flag}) IF(CXX_ACCEPTS_FLAG_${flag}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") ENDIF(CXX_ACCEPTS_FLAG_${flag}) ENDFOREACH(flag) MESSAGE(STATUS "Using this C compiler flags:${CMAKE_C_FLAGS}") MESSAGE(STATUS "Using this C++ compiler flags:${CMAKE_CXX_FLAGS}") ENDMACRO(ENABLE_COMPILER_WARNINGS) mediascanner-0.3.93+14.04.20131024.1/src/0000755000015700001700000000000012232220310017500 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/0000755000015700001700000000000012232220310022636 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/grl-mediascanner.xml0000644000015700001700000000047012232220161026600 0ustar pbuserpbgroup00000000000000 Media Scanner libgrlmediascanner A plugin for browsing media scanner indices Canonical Ltd All Rights Reserved http://www.ubuntu.com/devices/tv mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/logging.cpp0000644000015700001700000000471012232220161024776 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "grlmediascanner/logging.h" // Standard Library #include // Media Scanner #include "mediascanner/logging.h" GRL_LOG_DOMAIN(grl_media_scanner_log_domain); namespace mediascanner { namespace logging { class GriloMessageSink : public MessageSink { public: explicit GriloMessageSink(GrlLogLevel level) : level_(level) { } void Report(const std::string &domain_name, const std::string &message) { grl_log(grl_media_scanner_log_domain, level_, "", "%s: %s", domain_name.c_str(), message.c_str()); } private: const GrlLogLevel level_; }; } // namespace logging } // namespace mediascanner void grl_media_scanner_logging_init() { GRL_LOG_DOMAIN_INIT(grl_media_scanner_log_domain, "media_scanner"); // Avoid recursion when already capturing GLib messages, // like our unit tests are doing. if (mediascanner::logging::is_capturing_glib_messages()) return; using mediascanner::logging::GriloMessageSink; using mediascanner::logging::MessageSinkPtr; mediascanner::logging::error()->set_message_sink (MessageSinkPtr(new GriloMessageSink(GRL_LOG_LEVEL_ERROR))); mediascanner::logging::warning()->set_message_sink (MessageSinkPtr(new GriloMessageSink(GRL_LOG_LEVEL_WARNING))); mediascanner::logging::info()->set_message_sink (MessageSinkPtr(new GriloMessageSink(GRL_LOG_LEVEL_INFO))); mediascanner::logging::debug()->set_message_sink (MessageSinkPtr(new GriloMessageSink(GRL_LOG_LEVEL_DEBUG))); mediascanner::logging::trace()->set_message_sink (MessageSinkPtr(new GriloMessageSink(GRL_LOG_LEVEL_DEBUG))); } mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/plugin.c0000644000015700001700000000503412232220161024306 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored By: Mathias Hasselmann */ #include "grlmediascanner/logging.h" #include "grlmediascanner/mediasource.h" #include static gboolean grl_media_scanner_plugin_init(GrlRegistry *registry, GrlPlugin *plugin, GList *configs) { grl_media_scanner_logging_init(); GRL_DEBUG("%s", __func__); GrlMediaScannerSource *const source = grl_media_scanner_source_new(); GList *l; for (l = configs; l; l = l->next) { GrlConfig *const config = GRL_CONFIG(l->data); char *str; str = grl_config_get_string(config, GRL_MEDIA_SCANNER_CONFIG_INDEX_PATH); if (str) { grl_media_scanner_source_set_index_path(source, str); g_free(str); continue; } str = grl_config_get_string(config, GRL_MEDIA_SCANNER_CONFIG_SEARCH_METHOD); if (str) { GrlMediaScannerSearchMethod search_method; if (strcmp(str, GRL_MEDIA_SCANNER_CONFIG_SEARCH_SUBSTRING) == 0) search_method = GRL_MEDIA_SCANNER_SEARCH_SUBSTRING; else if (strcmp(str, GRL_MEDIA_SCANNER_CONFIG_SEARCH_FULL_TEXT) == 0) search_method = GRL_MEDIA_SCANNER_SEARCH_FULL_TEXT; else { GRL_WARNING("Ignoring unknown search method: %s", str); g_free(str); continue; } grl_media_scanner_source_set_search_method(source, search_method); g_free(str); continue; } } grl_registry_register_source(registry, plugin, GRL_SOURCE(source), NULL); return TRUE; } GRL_PLUGIN_REGISTER(grl_media_scanner_plugin_init, NULL, GRL_MEDIA_SCANNER_PLUGIN_ID); mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/enums.h.in0000644000015700001700000000236112232220161024551 0ustar pbuserpbgroup00000000000000/*** BEGIN file-header ***/ /* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored By: Mathias Hasselmann */ #ifndef GRL_MEDIA_SCANNER_ENUMS_H #define GRL_MEDIA_SCANNER_ENUMS_H #include G_BEGIN_DECLS /*** END file-header ***/ /*** BEGIN enumeration-production ***/ #define GRL_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) GType @enum_name@_get_type(void) G_GNUC_CONST; /*** END enumeration-production ***/ /*** BEGIN file-tail ***/ G_END_DECLS #endif // GRL_MEDIA_SCANNER_ENUMS_H /*** END file-tail ***/ mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/mediasource.h0000644000015700001700000000647712232220161025331 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef GRL_MEDIA_SCANNER_SOURCE_H #define GRL_MEDIA_SCANNER_SOURCE_H // Grilo #include #define GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" #define GRL_MEDIA_SCANNER_CONFIG_INDEX_PATH "index-path" #define GRL_MEDIA_SCANNER_CONFIG_SEARCH_METHOD "search-method" #define GRL_MEDIA_SCANNER_CONFIG_SEARCH_SUBSTRING "substring" #define GRL_MEDIA_SCANNER_CONFIG_SEARCH_FULL_TEXT "fulltext" #define GRL_TYPE_MEDIA_SCANNER_SOURCE \ (grl_media_scanner_source_get_type()) #define GRL_MEDIA_SCANNER_SOURCE(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), GRL_TYPE_MEDIA_SCANNER_SOURCE, \ GrlMediaScannerSource)) #define GRL_IS_MEDIA_SCANNER_SOURCE(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE((obj), GRL_TYPE_MEDIA_SCANNER_SOURCE)) #define GRL_MEDIA_SCANNER_SOURCE_CLASS(cls) \ (G_TYPE_CHECK_CLASS_CAST((cls), GRL_TYPE_MEDIA_SCANNER_SOURCE, \ GrlMediaScannerSourceClass)) #define GRL_IS_MEDIA_SCANNER_SOURCE_CLASS(cls) \ (G_TYPE_CHECK_CLASS_TYPE((cls), GRL_TYPE_MEDIA_SCANNER_SOURCE)) #define GRL_MEDIA_SCANNER_SOURCE_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS((obj), GRL_TYPE_MEDIA_SCANNER_SOURCE, \ GrlMediaScannerSourceClass)) G_BEGIN_DECLS typedef enum { GRL_MEDIA_SCANNER_SEARCH_SUBSTRING, GRL_MEDIA_SCANNER_SEARCH_FULL_TEXT } GrlMediaScannerSearchMethod; typedef struct _GrlMediaScannerSource GrlMediaScannerSource; typedef struct _GrlMediaScannerSourceClass GrlMediaScannerSourceClass; typedef struct _GrlMediaScannerSourcePrivate GrlMediaScannerSourcePrivate; struct _GrlMediaScannerSource { GrlSource parent; GrlMediaScannerSourcePrivate *priv; }; struct _GrlMediaScannerSourceClass { GrlSourceClass parent_class; }; GType grl_media_scanner_source_get_type(void) G_GNUC_CONST; GrlMediaScannerSource *grl_media_scanner_source_new(void); void grl_media_scanner_source_set_index_path(GrlMediaScannerSource *source, const char *path); const char *grl_media_scanner_source_get_index_path (GrlMediaScannerSource *source); void grl_media_scanner_source_set_search_method(GrlMediaScannerSource *source, GrlMediaScannerSearchMethod method); GrlMediaScannerSearchMethod grl_media_scanner_source_get_search_method (GrlMediaScannerSource *source); G_END_DECLS #endif // GRL_MEDIA_SCANNER_SOURCE_H mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/CMakeLists.txt0000644000015700001700000000263712232220161025412 0ustar pbuserpbgroup00000000000000include_directories(${DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src) add_definitions(-DGRL_LOG_DOMAIN_DEFAULT=grl_media_scanner_log_domain) set(grlmediascanner_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/enums.c ${CMAKE_CURRENT_BINARY_DIR}/enums.h logging.cpp logging.h mediasource.cpp mediasource.h plugin.c) # subdir/CMakeLists.txt add_custom_command(OUTPUT enums.h DEPENDS enums.h.in COMMAND ${GLIB_MKENUMS} --template ${CMAKE_CURRENT_SOURCE_DIR}/enums.h.in ${CMAKE_CURRENT_SOURCE_DIR}/*.h > ${CMAKE_CURRENT_BINARY_DIR}/enums.h.tmp COMMAND mv ${CMAKE_CURRENT_BINARY_DIR}/enums.h.tmp ${CMAKE_CURRENT_BINARY_DIR}/enums.h) add_custom_command(OUTPUT enums.c DEPENDS enums.c.in COMMAND ${GLIB_MKENUMS} --template ${CMAKE_CURRENT_SOURCE_DIR}/enums.c.in ${CMAKE_CURRENT_SOURCE_DIR}/*.h > ${CMAKE_CURRENT_BINARY_DIR}/enums.c.tmp COMMAND mv ${CMAKE_CURRENT_BINARY_DIR}/enums.c.tmp ${CMAKE_CURRENT_BINARY_DIR}/enums.c) add_library(grlmediascanner MODULE ${grlmediascanner_SOURCES}) target_link_libraries(grlmediascanner mediascanner ${DEPS_LDFLAGS} ${Boost_LIBRARIES}) install(TARGETS grlmediascanner DESTINATION ${GRILO_PLUGIN_DIR}) install(FILES grl-mediascanner.xml DESTINATION ${GRILO_PLUGIN_DIR}) mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/enums.c.in0000644000015700001700000000305212232220161024542 0ustar pbuserpbgroup00000000000000/*** BEGIN file-header ***/ /* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored By: Mathias Hasselmann */ #include "grlmediascanner/enums.h" #include "grlmediascanner/mediasource.h" /*** END file-header ***/ /*** BEGIN value-header ***/ GType @enum_name@_get_type() { static volatile GType static_type_id = G_TYPE_INVALID; if (g_once_init_enter(&static_type_id)) { GType type_id; static const GEnumValue values[] = { /*** END value-header ***/ /*** BEGIN value-production ***/ { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, /*** END value-production ***/ /*** BEGIN value-tail ***/ { 0, NULL, NULL } }; type_id = g_enum_register_static("@EnumName@", values); g_once_init_leave(&static_type_id, type_id); } return static_type_id; } /*** END value-tail ***/ mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/logging.h0000644000015700001700000000207412232220161024444 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef GRL_MEDIA_SCANNER_LOGGING_H #define GRL_MEDIA_SCANNER_LOGGING_H // Grilo #include G_BEGIN_DECLS GRL_LOG_DOMAIN_EXTERN(grl_media_scanner_log_domain); void grl_media_scanner_logging_init(void); G_END_DECLS #endif // GRL_MEDIA_SCANNER_LOGGING_H mediascanner-0.3.93+14.04.20131024.1/src/grlmediascanner/mediasource.cpp0000644000015700001700000013022412232220161025650 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "grlmediascanner/mediasource.h" // Boost C++ #include #include // C++ Standard Library #include #include #include #include #include // MediaScanner Plugin for Grilo #include "grlmediascanner/enums.h" // Media Scanner Library #include "mediascanner/dbusservice.h" #include "mediascanner/filter.h" #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaindex.h" #include "mediascanner/mediaroot.h" #include "mediascanner/mediautils.h" #include "mediascanner/property.h" #include "mediascanner/propertyschema.h" #include "mediascanner/taskfacades.h" #include "mediascanner/taskmanager.h" #include "mediascanner/utilities.h" // TODO(M3): L10N: properly pull gettext()/boost::translate #define _(MsgId) (MsgId) enum { PROP_NONE, PROP_INDEX_PATH, PROP_SEARCH_METHOD, N_PROPERTIES }; static GParamSpec *properties[N_PROPERTIES] = { NULL, }; struct _GrlMediaScannerSourcePrivate { _GrlMediaScannerSourcePrivate() : root_manager_(new mediascanner::MediaRootManager) , task_facade_(root_manager_) , task_manager_("media_scanner media source") , change_id_(0) , change_notify_count_(0) { root_manager_->initialize(); } mediascanner::MediaRootManagerPtr root_manager_; mediascanner::MediaIndexFacade task_facade_; mediascanner::TaskManager task_manager_; mediascanner::Wrapper simple_caps_; mediascanner::Wrapper filter_caps_; GrlMediaScannerSearchMethod search_method_; mediascanner::dbus::MediaScannerProxy service_proxy_; unsigned change_id_; int change_notify_count_; }; namespace mediascanner { // Boost C++ using boost::locale::format; using boost::gregorian::date; using boost::posix_time::time_duration; // Standard Library using std::string; using std::vector; using std::wstring; // Context specific logging domains static const logging::Domain kWarning("warning/plugin", logging::warning()); static const logging::Domain kDebug("debug/plugin", logging::debug()); static const logging::Domain kTrace("trace/plugin", logging::trace()); G_DEFINE_TYPE(GrlMediaScannerSource, grl_media_scanner_source, GRL_TYPE_SOURCE) static const GrlTypeFilter kSupportedTypeFilters = GRL_TYPE_FILTER_AUDIO | GRL_TYPE_FILTER_VIDEO | GRL_TYPE_FILTER_IMAGE; static bool StoreMedia(dbus::MediaScannerProxy *service, const Wrapper media, const GList *const keys_to_store, GList **failed_keys, string *error_message) { MediaInfo metadata; if (not metadata.fill_from_media(media.get(), keys_to_store, failed_keys, error_message)) return false; // Connected to D-Bus if needed. Wrapper error; if (not service->handle() && not service->ConnectAndWait(Wrapper(), error.out_param())) { const string message = to_string(error); *error_message = (format("Mediascanner service not available: {1}") % message).str(); return false; } const std::set service_failed_keys = service->StoreMediaInfo(metadata, error.out_param()); for (const string &field_name: service_failed_keys) { const Property p = Property::FromFieldName(ToUnicode(field_name)); if (p) { *failed_keys = g_list_prepend (*failed_keys, GRLKEYID_TO_POINTER(p.metadata_key().id())); } } if (error) { const string message = to_string(error); *error_message = (format("Store operation failed: {1}") % message).str(); return false; } return true; } // Filter Creation Routines //////////////////////////////////////////////////// static bool make_type_filters(GrlOperationOptions *options, BooleanFilter *filter) { const GrlTypeFilter types = grl_operation_options_get_type_filter(options); if (types == GRL_TYPE_FILTER_NONE) return true; // FIXME(M3): Error reporting on bad meta data keys. if (types & ~kSupportedTypeFilters) return false; BooleanFilter type_filter; if (types & GRL_TYPE_FILTER_AUDIO) { type_filter.add_clause(PrefixFilter(schema::kMimeType, MimeType::kAudioPrefix.str()), BooleanFilter::SHOULD); type_filter.add_clause(ValueFilter(schema::kMimeType, MimeType::kApplicationOgg.str()), BooleanFilter::SHOULD); } if (types & GRL_TYPE_FILTER_IMAGE) { type_filter.add_clause(PrefixFilter(schema::kMimeType, MimeType::kImagePrefix.str()), BooleanFilter::SHOULD); } if (types & GRL_TYPE_FILTER_VIDEO) { type_filter.add_clause(PrefixFilter(schema::kMimeType, MimeType::kVideoPrefix.str()), BooleanFilter::SHOULD); } filter->add_clause(type_filter, BooleanFilter::MUST); return true; } static bool make_key_value_filters(GrlOperationOptions *options, BooleanFilter *filter) { const Wrapper key_list = take(grl_operation_options_get_key_filter_list(options)); for (GList *l = key_list.get(); l; l = l->next) { const GrlKeyID key = GRLPOINTER_TO_KEYID(l->data); const Property property = Property::FromMetadataKey(key); // FIXME(M3): Error reporting on bad meta data keys. if (not property) return false; Property::Value value; // FIXME(M3): Error reporting on bad meta data keys. if (not property.TransformGriloValue (grl_operation_options_get_key_filter(options, key), &value)) return false; filter->add_clause(ValueFilter(property, value), BooleanFilter::MUST); } return true; } static bool make_key_range_filters(GrlOperationOptions *options, BooleanFilter *filter) { const Wrapper key_list = take(grl_operation_options_get_key_range_filter_list(options)); for (GList *l = key_list.get(); l; l = l->next) { const GrlKeyID key = GRLPOINTER_TO_KEYID(l->data); const Property property = Property::FromMetadataKey(key); // FIXME(M3): Error reporting on bad meta data keys. if (not property) return false; GValue *lower_value_ptr; GValue *upper_value_ptr; grl_operation_options_get_key_range_filter (options, key, &lower_value_ptr, &upper_value_ptr); Property::Value lower_value, upper_value; // FIXME(M3): Error reporting on bad meta data keys. if (not property.TransformGriloValue(lower_value_ptr, &lower_value)) return false; if (not property.TransformGriloValue(upper_value_ptr, &upper_value)) return false; filter->add_clause(RangeFilter(property, lower_value, upper_value), BooleanFilter::MUST); } return true; } static bool make_filter(GrlOperationOptions *options, BooleanFilter *filter) { return make_type_filters(options, filter) && make_key_value_filters(options, filter) && make_key_range_filters(options, filter); } // Task Handling /////////////////////////////////////////////////////////////// typedef MediaIndexFacade::TaskFunction TaskFunction; typedef std::function error)> ErrorFunction; static Wrapper make_error(int code, const string &message) { Wrapper error; g_set_error_literal(error.out_param(), GRL_CORE_ERROR, code, message.c_str()); return error; } static void report_wrapped_error(const ErrorFunction &report_error, const std::string &error_message) { report_error(make_error(GRL_CORE_ERROR_LOAD_PLUGIN_FAILED, error_message)); } static void push_task(GrlMediaScannerSource *source, unsigned opid, const TaskFunction &task, const ErrorFunction &report_error) { const MediaIndexFacade::ErrorFunction &on_error = std::bind(report_wrapped_error, report_error, std::placeholders::_1); const TaskManager::TaskFunction &decorated_task = source->priv->task_facade_.bind(task, on_error); source->priv->task_manager_.AppendTask(decorated_task, opid); } // Browse Operation //////////////////////////////////////////////////////////// static void browse_report_error(const GrlSourceBrowseSpec *bs, Wrapper error) { Idle::AddOnce(std::bind(bs->callback, bs->source, bs->operation_id, nullptr, 0, bs->user_data, error)); } static void browse_report_error_message(const GrlSourceBrowseSpec *bs, const char *message) { browse_report_error(bs, make_error(GRL_CORE_ERROR_BROWSE_FAILED, message)); } static void browse_visit_item(GrlSourceBrowseSpec *bs, const MediaInfo &metadata, int32_t remaining_items) { Idle::AddOnce(std::bind(bs->callback, bs->source, bs->operation_id, metadata.make_media(nullptr), remaining_items, bs->user_data, nullptr)); } static void browse_run(GrlSourceBrowseSpec *bs, MediaIndex *media_index) { BooleanFilter filter; // TODO(M3): L10N if (not make_filter(bs->options, &filter)) { browse_report_error_message(bs, "Cannot create filter from options"); return; } if (media_index->Query(std::bind(&browse_visit_item, bs, std::placeholders::_1, std::placeholders::_2), filter, grl_operation_options_get_count(bs->options), grl_operation_options_get_skip(bs->options))) { Idle::AddOnce(std::bind(bs->callback, bs->source, bs->operation_id, nullptr, 0, bs->user_data, nullptr)); } else { browse_report_error_message(bs, media_index->error_message().c_str()); } } static void browse(GrlSource *source, GrlSourceBrowseSpec *bs) { kTrace("{1}: opid={2}") % __func__ % bs->operation_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run browse operation push_task(media_scanner_source, bs->operation_id, std::bind(&browse_run, bs, std::placeholders::_1), std::bind(&browse_report_error, bs, std::placeholders::_1)); } // Resolve Operation /////////////////////////////////////////////////////////// static void resolve_report_error(const GrlSourceResolveSpec *rs, Wrapper error) { Idle::AddOnce(std::bind(rs->callback, rs->source, rs->operation_id, rs->media, rs->user_data, error)); } static void resolve_run(GrlSourceResolveSpec *rs, MediaIndex *media_index) { const char *const url = grl_media_get_id(rs->media); MediaInfo metadata; if (url) metadata = media_index->Lookup(ToUnicode(url)); if (metadata.empty()) { const string message = (format("Media not found for <{1}>.") % url).str(); resolve_report_error (rs, make_error(GRL_CORE_ERROR_MEDIA_NOT_FOUND, message)); } else { metadata.copy_to_media(rs->keys, rs->media); Idle::AddOnce(std::bind(rs->callback, rs->source, rs->operation_id, rs->media, rs->user_data, nullptr)); } } static void resolve(GrlSource *source, GrlSourceResolveSpec *rs) { kTrace("{1}: opid={2}") % __func__ % rs->operation_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run browse operation push_task(media_scanner_source, rs->operation_id, std::bind(&resolve_run, rs, std::placeholders::_1), std::bind(&resolve_report_error, rs, std::placeholders::_1)); } // TestMediaFromUri Operation ////////////////////////////////////////////////// static void test_media_from_uri_report_error(const char *uri, Wrapper error, GMutex *mutex, GCond *waiter, volatile bool *finished) { const string error_string = to_string(error); kWarning("Testing URI failed for {1}: {2}") % uri % error_string; g_mutex_lock(mutex); *finished = true; g_cond_broadcast(waiter); g_mutex_unlock(mutex); } static void test_media_from_uri_run(const char *uri, MediaIndex *media_index, GMutex *mutex, GCond *waiter, volatile bool *result, volatile bool *finished) { g_mutex_lock(mutex); *result = uri && media_index->Exists(ToUnicode(uri)); *finished = true; g_cond_broadcast(waiter); g_mutex_unlock(mutex); } static gboolean test_media_from_uri(GrlSource *source, const char *uri) { kTrace("{1}: uri=<{2}>") % __func__ % uri; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_val_if_fail(media_scanner_source != NULL, false); // Run operation static GMutex mutex; static GCond waiter; volatile bool result = false; volatile bool run_finished = false; volatile bool error_finished = false; g_mutex_lock(&mutex); TaskFunction task_fun = std::bind(&test_media_from_uri_run, uri, std::placeholders::_1, &mutex, &waiter, &result, &run_finished); ErrorFunction error_fun = std::bind(&test_media_from_uri_report_error, uri, std::placeholders::_1, &mutex, &waiter, &error_finished); push_task(media_scanner_source, TaskManager::kInstantly, task_fun, error_fun); while(!run_finished && !error_finished) { g_cond_wait(&waiter, &mutex); } g_mutex_unlock(&mutex); return result; } // MediaFromUri Operation ////////////////////////////////////////////////////// static void media_from_uri_report_error(const GrlSourceMediaFromUriSpec *mfus, Wrapper error) { Idle::AddOnce(std::bind(mfus->callback, mfus->source, mfus->operation_id, nullptr, mfus->user_data, error)); } static void media_from_uri_run(GrlSourceMediaFromUriSpec *mfus, MediaIndex *media_index) { const wstring url = ToUnicode(mfus->uri); const MediaInfo metadata = media_index->Lookup(url); if (metadata.empty()) { const string message = (format("Media not found for <{1}>.") % mfus->uri).str(); media_from_uri_report_error (mfus, make_error(GRL_CORE_ERROR_MEDIA_NOT_FOUND, message)); } else { Idle::AddOnce(std::bind(mfus->callback, mfus->source, mfus->operation_id, metadata.make_media(mfus->keys), mfus->user_data, nullptr)); } } static void media_from_uri(GrlSource *source, GrlSourceMediaFromUriSpec *mfus) { kTrace("{1}: opid={2}") % __func__ % mfus->operation_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run browse operation push_task(media_scanner_source, mfus->operation_id, std::bind(&media_from_uri_run, mfus, std::placeholders::_1), std::bind(&media_from_uri_report_error, mfus, std::placeholders::_1)); } // Query Operation ///////////////////////////////////////////////////////////// static void query_report_error(const GrlSourceQuerySpec *qs, Wrapper error) { Idle::AddOnce(std::bind(qs->callback, qs->source, qs->operation_id, nullptr, 0, qs->user_data, error)); } static void query_report_error_message(const GrlSourceQuerySpec *qs, const char *message) { query_report_error(qs, make_error(GRL_CORE_ERROR_QUERY_FAILED, message)); } static void query_visit_item(GrlSourceQuerySpec *qs, const MediaInfo &metadata, int32_t remaining_items) { const wstring url = metadata.first(schema::kUrl); kTrace("{1}: url=<{2}>") % __func__ % url; Idle::AddOnce(std::bind(qs->callback, qs->source, qs->operation_id, metadata.make_media(qs->keys), remaining_items, qs->user_data, nullptr)); } static void query_run(GrlSourceQuerySpec *qs, MediaIndex *media_index) { BooleanFilter filter; // TODO(M3): L10N if (not make_filter(qs->options, &filter)) { query_report_error_message(qs, "Cannot create filter from options"); return; } if (qs->query) { filter.add_clause(QueryStringFilter(ToUnicode(qs->query)), BooleanFilter::MUST); } if (media_index->Query(std::bind(&query_visit_item, qs, std::placeholders::_1, std::placeholders::_2), filter, grl_operation_options_get_count(qs->options), grl_operation_options_get_skip(qs->options))) { Idle::AddOnce(std::bind(qs->callback, qs->source, qs->operation_id, nullptr, 0, qs->user_data, nullptr)); } else { query_report_error_message(qs, media_index->error_message().c_str()); } } static void query(GrlSource *source, GrlSourceQuerySpec *qs) { kTrace("{1}: opid={2}") % __func__ % qs->operation_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run browse operation push_task(media_scanner_source, qs->operation_id, std::bind(&query_run, qs, std::placeholders::_1), std::bind(&query_report_error, qs, std::placeholders::_1)); } // Query Operation ///////////////////////////////////////////////////////////// static void search_report_error(const GrlSourceSearchSpec *ss, Wrapper error) { Idle::AddOnce(std::bind(ss->callback, ss->source, ss->operation_id, nullptr, 0, ss->user_data, error)); } static void search_report_error_message(const GrlSourceSearchSpec *ss, const string &message) { search_report_error(ss, make_error(GRL_CORE_ERROR_SEARCH_FAILED, message)); } static void search_visit_item(GrlSourceSearchSpec *ss, const MediaInfo &metadata, int32_t remaining_items) { Idle::AddOnce(std::bind(ss->callback, ss->source, ss->operation_id, metadata.make_media(ss->keys), remaining_items, ss->user_data, nullptr)); } static void search_run(GrlSourceSearchSpec *ss, GrlMediaScannerSearchMethod search_method, MediaIndex *media_index) { BooleanFilter filter; // TODO(M3): L10N if (not make_filter(ss->options, &filter)) { search_report_error_message(ss, "Cannot create filter from options"); return; } if (ss->text) { switch (search_method) { case GRL_MEDIA_SCANNER_SEARCH_SUBSTRING: filter.add_clause(SubStringFilter(ToUnicode(ss->text)), BooleanFilter::MUST); break; case GRL_MEDIA_SCANNER_SEARCH_FULL_TEXT: filter.add_clause(FullTextFilter(ToUnicode(ss->text)), BooleanFilter::MUST); break; } } if (media_index->Query(std::bind(&search_visit_item, ss, std::placeholders::_1, std::placeholders::_2), filter, grl_operation_options_get_count(ss->options), grl_operation_options_get_skip(ss->options))) { Idle::AddOnce(std::bind(ss->callback, ss->source, ss->operation_id, nullptr, 0, ss->user_data, nullptr)); } else { search_report_error_message(ss, media_index->error_message().c_str()); } } static void search(GrlSource *source, GrlSourceSearchSpec *ss) { kTrace("{1}: opid={2}") % __func__ % ss->operation_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run search operation push_task(media_scanner_source, ss->operation_id, std::bind(&search_run, ss, media_scanner_source->priv->search_method_, std::placeholders::_1), std::bind(&search_report_error, ss, std::placeholders::_1)); } //////////////////////////////////////////////////////////////////////////////// static void store_real_report_result(const GrlSourceStoreSpec *ss, Wrapper failed_keys, Wrapper error) { ss->callback(ss->source, ss->media, failed_keys.get(), ss->user_data, error); } static void store_report_result(const GrlSourceStoreSpec *ss, GList *failed_keys, Wrapper error) { Idle::AddOnce(std::bind(store_real_report_result, ss, take(failed_keys), error)); } static void store_report_error_message(const GrlSourceStoreSpec *ss, GList *failed_keys, const string &message) { store_report_result(ss, failed_keys, make_error(GRL_CORE_ERROR_STORE_FAILED, message)); } static void store_run(GrlSourceStoreSpec *ss, MediaIndex *, dbus::MediaScannerProxy *service) { const Wrapper keys = take(grl_data_get_keys(GRL_DATA(ss->media))); Wrapper failed_keys; string error_message; if (StoreMedia(service, wrap(ss->media), keys, failed_keys.out_param(), &error_message)) { store_report_result(ss, nullptr, Wrapper()); } else { store_report_error_message(ss, failed_keys.get(), error_message); } } static void store(GrlSource *source, GrlSourceStoreSpec *ss) { const string url = safe_string(grl_media_get_url(ss->media)); kTrace("{1}: url=<{2}>") % __func__ % url; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run store operation dbus::MediaScannerProxy *const service = &media_scanner_source->priv->service_proxy_; push_task(media_scanner_source, TaskManager::kInstantly, std::bind(&store_run, ss, std::placeholders::_1, service), std::bind(&store_report_result, ss, nullptr, std::placeholders::_1)); } //////////////////////////////////////////////////////////////////////////////// static void store_metadata_real_report_result (const GrlSourceStoreMetadataSpec *ss, Wrapper failed_keys, Wrapper error) { ss->callback(ss->source, ss->media, failed_keys.get(), ss->user_data, error); } static void store_metadata_report_result (const GrlSourceStoreMetadataSpec *ss, GList *failed_keys, Wrapper error) { Idle::AddOnce(std::bind(store_metadata_real_report_result, ss, take(failed_keys), error)); } static void store_metadata_report_error_message (const GrlSourceStoreMetadataSpec *ss, GList *failed_keys, const string &message) { store_metadata_report_result (ss, failed_keys, make_error(GRL_CORE_ERROR_STORE_FAILED, message)); } static void store_metadata_run(GrlSourceStoreMetadataSpec *ss, MediaIndex *, dbus::MediaScannerProxy *service) { Wrapper failed_keys; string error_message; if (StoreMedia(service, wrap(ss->media), ss->keys, failed_keys.out_param(), &error_message)) { store_metadata_report_result(ss, nullptr, Wrapper()); } else { store_metadata_report_error_message(ss, failed_keys.get(), error_message); } } static void store_metadata(GrlSource *source, GrlSourceStoreMetadataSpec *ss) { const string url = safe_string(grl_media_get_url(ss->media)); kTrace("{1}: url=<{2}>") % __func__ % url; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run store operation dbus::MediaScannerProxy *const service = &media_scanner_source->priv->service_proxy_; push_task(media_scanner_source, TaskManager::kInstantly, std::bind(&store_metadata_run, ss, std::placeholders::_1, service), std::bind(&store_metadata_report_result, ss, nullptr, std::placeholders::_1)); } //////////////////////////////////////////////////////////////////////////////// static void remove_report_result(const GrlSourceRemoveSpec *rs, Wrapper error) { Idle::AddOnce(std::bind(rs->callback, rs->source, rs->media, rs->user_data, error)); } static void remove_report_error_message(const GrlSourceRemoveSpec *rs, const string &message) { remove_report_result(rs, make_error(GRL_CORE_ERROR_REMOVE_FAILED, message)); } static void remove_run(const GrlSourceRemoveSpec *rs, MediaIndex *, dbus::MediaScannerProxy *service) { Wrapper error; if (not service->handle() && not service->ConnectAndWait(Wrapper(), error.out_param())) { const string error_message = to_string(error); kWarning("Mediascanner service not available: {1}") % error_message; remove_report_error_message(rs, error->message); return; } service->RemoveMediaInfo(rs->media_id, error.out_param()); if (error) { const string error_message = to_string(error); kWarning("Remove operation failed: {1}") % error_message; remove_report_error_message(rs, error->message); } else { remove_report_result(rs, error); } } static void remove(GrlSource *source, GrlSourceRemoveSpec *rs) { kTrace("{1}: media={2}") % __func__ % rs->media_id; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != NULL); // Run remove operation dbus::MediaScannerProxy *const service = &media_scanner_source->priv->service_proxy_; push_task(media_scanner_source, TaskManager::kInstantly, std::bind(&remove_run, rs, std::placeholders::_1, service), std::bind(&remove_report_result, rs, std::placeholders::_1)); } //////////////////////////////////////////////////////////////////////////////// static void notify_report_error_message(Wrapper error) { const string error_message = to_string(error); kWarning("While preparing change notification: {1}") % error_message; } static string to_string(GrlSourceChangeType change_type) { switch (change_type) { case GRL_CONTENT_ADDED: return "content-added"; case GRL_CONTENT_CHANGED: return "content-changed"; case GRL_CONTENT_REMOVED: return "content-removed"; } std::ostringstream oss; oss << "unknown-content-change-" << change_type; return oss.str(); } static void real_notify_changed_media(Wrapper source, GrlSourceChangeType change_type, Wrapper changed_medias) { const string change_type_name = to_string(change_type); kTrace("{1}: {2} notification for {3} media(s)") % __func__ % change_type_name % changed_medias->len; grl_source_notify_change_list(source.get(), changed_medias.release(), change_type, false); } static void notify_changed_media(MediaIndex *media_index, Wrapper source, GrlSourceChangeType change_type, const std::vector &media_urls) { const string change_type_name = to_string(change_type); kTrace("{1}: {2} notification for {3}") % __func__ % change_type_name % media_urls; const Wrapper changed_medias = take(g_ptr_array_sized_new(media_urls.size())); for (const std::string &url: media_urls) { const MediaInfo metadata = media_index->Lookup(ToUnicode(url)); // Properties might contain no URL. So we have to attach manually. Wrapper media = take(metadata.make_media(nullptr, url)); grl_media_set_id(media.get(), url.c_str()); g_ptr_array_add(changed_medias.get(), media.release()); } // Wrap data to ensure it survives moving to main thread. Idle::AddOnce(std::bind(&real_notify_changed_media, source, change_type, changed_medias), G_PRIORITY_HIGH_IDLE); } static bool translate_change_type(dbus::MediaChangeType media_change_type, GrlSourceChangeType *source_change_type) { switch (media_change_type) { case dbus::MEDIA_INFO_CREATED: *source_change_type = GRL_CONTENT_ADDED; return true; case dbus::MEDIA_INFO_UPDATED: *source_change_type = GRL_CONTENT_CHANGED; return true; case dbus::MEDIA_INFO_REMOVED: *source_change_type = GRL_CONTENT_REMOVED; return true; } return false; } static void notify_changed_media_cb(GDBusConnection */*connection*/, const char */*sender_name*/, const char */*object_path*/, const char */*interface_name*/, const char */*signal_name*/, GVariant *parameters, void *data) { // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(data); g_return_if_fail(media_scanner_source != NULL); typedef dbus::MediaScannerProxy::MediaInfoChangedSignal signal_type; const signal_type::args_type::value_type args = signal_type::args_type::make_value(parameters); GrlSourceChangeType change_type; g_return_if_fail(translate_change_type(args.get<0>(), &change_type)); push_task(media_scanner_source, TaskManager::kInstantly, std::bind(¬ify_changed_media, std::placeholders::_1, wrap(GRL_SOURCE(media_scanner_source)), change_type, args.get<1>()), notify_report_error_message); } gboolean notify_change_start(GrlSource *source, GError **error) { kTrace(__func__); // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_val_if_fail(media_scanner_source != NULL, false); // Has change notification already been enabled? media_scanner_source->priv->change_notify_count_++; if (media_scanner_source->priv->change_notify_count_ > 1) { return true; } // Unsubscribe to D-Bus signal // FIXME(M5): Wrap nicely in dbus::MediaScannerProxy *const service = &media_scanner_source->priv->service_proxy_; if (not service->handle()) { Wrapper dbus_error; if (not service->ConnectAndWait(Wrapper(), dbus_error.out_param())) { g_set_error(error, GRL_CORE_ERROR, GRL_CORE_ERROR_NOTIFY_CHANGED_FAILED, "Mediascanner service not available: %s", dbus_error->message); return false; } } media_scanner_source->priv->change_id_ = g_dbus_connection_signal_subscribe (service->connection().get(), service->service_name().c_str(), service->interface_name().c_str(), service->media_info_changed.name().c_str(), service->object_path().c_str(), nullptr, G_DBUS_SIGNAL_FLAGS_NONE, notify_changed_media_cb, g_object_ref(source), g_object_unref); return true; } gboolean notify_change_stop(GrlSource *source, GError **error) { kTrace(__func__); // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_val_if_fail(media_scanner_source != NULL, false); if (not media_scanner_source->priv->change_id_ || media_scanner_source->priv->change_notify_count_ <= 0) { g_set_error_literal(error, GRL_CORE_ERROR, GRL_CORE_ERROR_NOTIFY_CHANGED_FAILED, "Not subsribed to change notifications"); return false; } // If change notification has been enabled more than once, only // disable it on an equal number of notify_change_stop() calls. media_scanner_source->priv->change_notify_count_--; if (media_scanner_source->priv->change_notify_count_ > 0) { return true; } // Unsubscribe from D-Bus signal // FIXME(M5): Wrap nicely in dbus::MediaScannerProxy *const service = &media_scanner_source->priv->service_proxy_; const unsigned id = media_scanner_source->priv->change_id_; media_scanner_source->priv->change_id_ = 0; g_dbus_connection_signal_unsubscribe(service->connection().get(), id); return true; } //////////////////////////////////////////////////////////////////////////////// static GrlSupportedOps supported_operations(GrlSource *source) { kTrace(__func__); // Check preconditions. g_return_val_if_fail(GRL_IS_SOURCE(source), GRL_OP_NONE); // Return supported operations. return GRL_OP_RESOLVE | GRL_OP_BROWSE | GRL_OP_SEARCH | GRL_OP_QUERY | GRL_OP_STORE | GRL_OP_STORE_METADATA | GRL_OP_REMOVE | GRL_OP_MEDIA_FROM_URI | GRL_OP_NOTIFY_CHANGE; } static bool collect_keys(const Property &p, GList **const keys) { *keys = g_list_prepend(*keys, GRLKEYID_TO_POINTER(p.metadata_key().id())); return false; } static const GList *supported_keys(GrlSource *source) { kTrace(__func__); // Check preconditions. g_return_val_if_fail(GRL_IS_SOURCE(source), NULL); // Collect ids of supported keys. GList *keys = NULL; Property::VisitAll(std::bind(&collect_keys, std::placeholders::_1, &keys)); return keys; } static const GList *writable_keys(GrlSource *source) { kTrace(__func__); // Check preconditions. g_return_val_if_fail(GRL_IS_SOURCE(source), NULL); // FIXME(M3): Report only writable keys. return supported_keys(source); } static void cancel(GrlSource *source, unsigned opid) { kTrace("{1}: opid={2}") % __func__ % opid; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_if_fail(media_scanner_source != nullptr); // Cancel the task identified by opid. media_scanner_source->priv->task_manager_.CancelByGroupId(opid); } static bool collect_property_filters(const Property &property, GList **key_filter, GList **key_range_filter) { const gpointer key = GRLKEYID_TO_POINTER(property.metadata_key().id()); *key_range_filter = g_list_prepend(*key_range_filter, key); *key_filter = g_list_prepend(*key_filter, key); return false; } static Wrapper make_filter_caps(void) { Wrapper caps = take(grl_caps_new()); Wrapper key_filter; Wrapper key_range_filter; Property::VisitAll(std::bind(collect_property_filters, std::placeholders::_1, key_filter.out_param(), key_range_filter.out_param())); grl_caps_set_key_filter(caps.get(), key_filter.get()); grl_caps_set_key_range_filter(caps.get(), key_range_filter.get()); grl_caps_set_type_filter(caps.get(), kSupportedTypeFilters); return caps; } static GrlCaps *get_caps(GrlSource *source, GrlSupportedOps ops) { kTrace("{1}: ops={2}") % __func__ % ops; // Check preconditions. GrlMediaScannerSource *const media_scanner_source = GRL_MEDIA_SCANNER_SOURCE(source); g_return_val_if_fail(media_scanner_source != nullptr, nullptr); if (ops & ~(GRL_OP_BROWSE | GRL_OP_SEARCH | GRL_OP_QUERY)) { if (media_scanner_source->priv->simple_caps_ == nullptr) media_scanner_source->priv->simple_caps_ = take(grl_caps_new()); return media_scanner_source->priv->simple_caps_.get(); } if (media_scanner_source->priv->filter_caps_ == nullptr) media_scanner_source->priv->filter_caps_ = make_filter_caps(); return media_scanner_source->priv->filter_caps_.get(); } static void set_property(GObject *object, unsigned property_id, const GValue *value, GParamSpec *pspec) { GrlMediaScannerSource *const source = GRL_MEDIA_SCANNER_SOURCE(object); switch (property_id) { case PROP_INDEX_PATH: grl_media_scanner_source_set_index_path( source, g_value_get_string(value)); break; case PROP_SEARCH_METHOD: grl_media_scanner_source_set_search_method(source, static_cast (g_value_get_enum(value))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void get_property(GObject *object, unsigned property_id, GValue *value, GParamSpec *pspec) { GrlMediaScannerSource *const source = GRL_MEDIA_SCANNER_SOURCE(object); switch (property_id) { case PROP_INDEX_PATH: g_value_set_string( value, grl_media_scanner_source_get_index_path(source)); break; case PROP_SEARCH_METHOD: g_value_set_enum( value, grl_media_scanner_source_get_search_method(source)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; } } static void finalize(GObject *object) { GrlMediaScannerSource *const source = GRL_MEDIA_SCANNER_SOURCE(object); g_return_if_fail(source != nullptr); delete source->priv; source->priv = NULL; G_OBJECT_CLASS(grl_media_scanner_source_parent_class)->finalize(object); } static void grl_media_scanner_source_class_init (GrlMediaScannerSourceClass *klass) { GrlSourceClass *const source_class = GRL_SOURCE_CLASS(klass); source_class->supported_operations = supported_operations; source_class->supported_keys = supported_keys; source_class->writable_keys = writable_keys; source_class->get_caps = get_caps; source_class->resolve = resolve; source_class->test_media_from_uri = test_media_from_uri; source_class->media_from_uri = media_from_uri; source_class->browse = browse; source_class->query = query; source_class->search = search; source_class->store = store; source_class->store_metadata = store_metadata; source_class->remove = remove; source_class->cancel = cancel; source_class->notify_change_start = notify_change_start; source_class->notify_change_stop = notify_change_stop; GObjectClass *const object_class = G_OBJECT_CLASS(klass); object_class->set_property = set_property; object_class->get_property = get_property; object_class->finalize = finalize; properties[PROP_INDEX_PATH] = g_param_spec_string("index-path", "Index Path", "Local file system path of the media index", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); properties[PROP_SEARCH_METHOD] = g_param_spec_enum("search-method", "Search Method", "Current search method of the source", GRL_TYPE_MEDIA_SCANNER_SEARCH_METHOD, GRL_MEDIA_SCANNER_SEARCH_SUBSTRING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(object_class, N_PROPERTIES, properties); if (not mediascanner::CheckLocaleFacets()) mediascanner::SetupLocale(); } static void grl_media_scanner_source_init(GrlMediaScannerSource *source) { source->priv = new GrlMediaScannerSourcePrivate; source->priv->search_method_ = GRL_MEDIA_SCANNER_SEARCH_FULL_TEXT; } } // namespace mediascanner GType grl_media_scanner_source_get_type() { return mediascanner::grl_media_scanner_source_get_type(); } GrlMediaScannerSource *grl_media_scanner_source_new() { return GRL_MEDIA_SCANNER_SOURCE(g_object_new (GRL_TYPE_MEDIA_SCANNER_SOURCE, "source-id", GRL_MEDIA_SCANNER_PLUGIN_ID, "source-name", _("Media Scanner"), "source-desc", _("A media source using Media Scanner indices"), NULL)); } void grl_media_scanner_source_set_index_path(GrlMediaScannerSource *source, const char *path) { g_return_if_fail(GRL_IS_MEDIA_SCANNER_SOURCE(source)); const std::string safe_path = mediascanner::safe_string(path); if (safe_path != source->priv->task_facade_.media_index_path()) { source->priv->task_facade_.set_media_index_path(safe_path); g_object_notify_by_pspec(G_OBJECT(source), properties[PROP_INDEX_PATH]); } } const char *grl_media_scanner_source_get_index_path (GrlMediaScannerSource *source) { g_return_val_if_fail(GRL_IS_MEDIA_SCANNER_SOURCE(source), 0); return source->priv->task_facade_.media_index_path().string().c_str(); } void grl_media_scanner_source_set_search_method (GrlMediaScannerSource *source, GrlMediaScannerSearchMethod method) { g_return_if_fail(GRL_IS_MEDIA_SCANNER_SOURCE(source)); if (source->priv->search_method_ != method) { source->priv->search_method_ = method; g_object_notify_by_pspec(G_OBJECT(source), properties[PROP_SEARCH_METHOD]); } } GrlMediaScannerSearchMethod grl_media_scanner_source_get_search_method (GrlMediaScannerSource *source) { g_return_val_if_fail(GRL_IS_MEDIA_SCANNER_SOURCE(source), GrlMediaScannerSearchMethod(~0)); return source->priv->search_method_; } mediascanner-0.3.93+14.04.20131024.1/src/mediascanner-service/0000755000015700001700000000000012232220310023567 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/src/mediascanner-service/main.cpp0000644000015700001700000010142612232220161025227 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ // GLib based libraries #include #include #include // Boost C++ #include #include #include #include // C++ Standard Library #include #include #include #include #include // Media Scanner Library #include "mediascanner/commitpolicy.h" #include "mediascanner/dbusservice.h" #include "mediascanner/filesystemscanner.h" #include "mediascanner/filter.h" #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaartdownloader.h" #include "mediascanner/mediaroot.h" #include "mediascanner/metadataresolver.h" #include "mediascanner/property.h" #include "mediascanner/propertyschema.h" #include "mediascanner/settings.h" #include "mediascanner/taskfacades.h" #include "mediascanner/taskmanager.h" #include "mediascanner/utilities.h" #include "mediascanner/writablemediaindex.h" // TODO(M3): L10N: properly pull gettext() #define _(MsgId) (MsgId) #define N_(MsgId) (MsgId) namespace boost { template<> inline void checked_delete(GOptionContext *p) { g_option_context_free(p); } } // namespace boost namespace mediascanner { // Boost C++ using boost::algorithm::ends_with; using std::shared_ptr; using boost::locale::format; // C++ Standard Library using std::list; using std::string; using std::vector; using std::wstring; // Context specific logging domains static const logging::Domain kError("error/main", logging::error()); static const logging::Domain kWarning("warning/main", logging::warning()); static const logging::Domain kInfo("info/main", logging::info()); static const logging::Domain kTraceGSt("trace/gstreamer", logging::trace()); static const logging::Domain kTraceDBus("trace/dbus", logging::trace()); static const std::string kErrorSetupFailed = "com.canonical.MediaScanner.MediaIndexError.SetupFailed"; static const std::string kErrorQueryFailed = "com.canonical.MediaScanner.MediaIndexError.QueryFailed"; static const std::string kErrorStoreFailed = "com.canonical.MediaScanner.MediaIndexError.StoreFailed"; static const std::string kErrorRemoveFailed = "com.canonical.MediaScanner.MediaIndexError.RemoveFailed"; /** * @brief The class implements the media scanner service. * It manages the file system scanner and it hosts the D-Bus service giving * access to the media index. */ class MediaScannerService : public dbus::Service { public: int Run(int argc, char *argv[]); protected: void Connected(); typedef std::function TaskFunction; void setup_media_index(WritableMediaIndex *index) const; void push_task(const TaskFunction &task, dbus::MethodInvocationPtr invocation); void RunTask(const TaskFunction &task); bool ParseCommandLine(int argc, char *argv[]); bool LoadMetadataSources(); void OnMediaRootsChanged(); void OnSourcesChanged(); private: class Interface; class CommitNotifier; typedef MediaIndexFacade TaskFacade; std::shared_ptr interface_; std::shared_ptr notifier_; std::shared_ptr metadata_resolver_; std::shared_ptr root_manager_; std::shared_ptr task_manager_; std::shared_ptr task_facade_; std::shared_ptr scanner_; Settings settings_; Settings::MetadataSourceList metadata_sources_; }; /** * @brief This class describes the the media scanner's D-Bus service. * @ingroup dbus */ class MediaScannerService::Interface : public dbus::MediaScannerSkeleton { public: explicit Interface(MediaScannerService *service); class MediaInfoExistsMethod; class LookupMediaInfoMethod; class QueryMediaInfoMethod; class StoreMediaInfoMethod; class RemoveMediaInfoMethod; class IndexPathProperty; class MediaRootsProperty; shared_ptr media_info_available() const { return media_info_available_; } shared_ptr media_info_changed() const { return media_info_changed_; } private: std::shared_ptr media_info_available_; std::shared_ptr media_info_changed_; }; static Property::Set ResolveFields(const vector &field_names, dbus::MethodInvocation *invocation) { Property::Set properties; for (const string &name: field_names) { const Property &p = Property::FromFieldName(ToUnicode(name)); if (p.is_null()) { invocation->return_error (G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, (format("Unknown field name: \"{1}\"") % name).str()); return Property::Set(); } properties.insert(p); } return properties; } class MediaScannerService::Interface::MediaInfoExistsMethod : public dbus::MediaScannerSkeleton::MediaInfoExistsMethod { public: explicit MediaInfoExistsMethod(MediaScannerService *service) : service_(service) { } protected: void Invoke(InvocationPtr invocation) const { const string url = invocation->arg<0>(); kTraceDBus("{1}: url=<{2}>") % name() % url; service_->push_task(std::bind(&MediaInfoExistsMethod::RunQuery, this, std::placeholders::_1, ToUnicode(url), invocation), invocation); } void RunQuery(WritableMediaIndex *media_index, const wstring &url, InvocationPtr invocation) const { const bool media_exists = media_index->Exists(url); Idle::AddOnce(std::bind(&MediaInfoExistsMethod::return_value, this, media_exists, invocation)); } void return_value(bool media_exists, InvocationPtr invocation) const { invocation->return_value(output_value_type(media_exists)); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::LookupMediaInfoMethod : public dbus::MediaScannerSkeleton::LookupMediaInfoMethod { public: explicit LookupMediaInfoMethod(MediaScannerService *service) : service_(service) { } protected: void Invoke(InvocationPtr invocation) const { const string url = invocation->arg<0>(); const vector field_names = invocation->arg<1>(); kTraceDBus("{1}: url=<{2}>") % name() % url; service_->push_task(std::bind(&LookupMediaInfoMethod::RunQuery, this, std::placeholders::_1, ToUnicode(url), ResolveFields(field_names, invocation.get()), invocation), invocation); } void RunQuery(WritableMediaIndex *media_index, const wstring &url, const Property::Set &properties, InvocationPtr invocation) const { const MediaInfo media = not properties.empty() ? media_index->Lookup(url, properties) : media_index->Lookup(url); Idle::AddOnce(std::bind(&LookupMediaInfoMethod::return_value, media, invocation)); } static void return_value(const MediaInfo &media, InvocationPtr invocation) { invocation->return_value(output_value_type(media)); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::QueryMediaInfoMethod : public dbus::MediaScannerSkeleton::QueryMediaInfoMethod { public: explicit QueryMediaInfoMethod(MediaScannerService *service) : service_(service) { } protected: void Invoke(InvocationPtr invocation) const { const string query = invocation->arg<0>(); const vector field_names = invocation->arg<1>(); const int32_t offset = invocation->arg<2>(); const int32_t limit = invocation->arg<3>(); kTraceDBus("{1}: query=\"{2}\", offset={3}, limit={4}") % name() % query % offset % limit; service_->push_task(std::bind(&QueryMediaInfoMethod::RunQuery, this, std::placeholders::_1, ToUnicode(query), ResolveFields(field_names, invocation.get()), offset, limit, invocation), invocation); } void RunQuery(WritableMediaIndex *media_index, const wstring &query, const Property::Set &/*properties*/, int32_t offset, int32_t limit, InvocationPtr invocation) const { // FIXME(M4): Pass property set to Query() Filter filter; if (not query.empty()) filter = QueryStringFilter(query); vector items; items.reserve(batch_size()); const uint32_t serial = invocation->serial(); InvocationPtr origin(new Invocation(invocation->dup())); if (media_index->Query(std::bind(&QueryMediaInfoMethod::OnItem, this, std::placeholders::_1, serial, &items, origin), filter, limit, offset)) { if (not items.empty()) { // OK, if not empty, notify emit_media_info_available(serial, &items, origin); } // TODO(someone): Dubious. Why notify again? emit_media_info_available(serial, &items, origin); Idle::AddOnce(std::bind(&QueryMediaInfoMethod::return_value, invocation)); } else { Idle::AddOnce(std::bind(&Invocation::return_dbus_error, invocation, kErrorQueryFailed, media_index->error_message())); } } void OnItem(const MediaInfo &media, uint32_t serial, vector *items, InvocationPtr origin) const { items->push_back(media); if (items->size() > batch_size()) emit_media_info_available(serial, items, origin); } void emit_media_info_available(uint32_t serial, vector *items, InvocationPtr origin) const { MediaInfoAvailableSignal::args_type::value_type args(serial, *items); items->clear(); service_->interface_-> media_info_available()-> emit_result_on_idle(args, origin); } size_t batch_size() const { // TOOD(M5): Read batch size from settings. return 32; } static void return_value(InvocationPtr invocation) { invocation->return_value(output_value_type()); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::StoreMediaInfoMethod : public dbus::MediaScannerSkeleton::StoreMediaInfoMethod { public: explicit StoreMediaInfoMethod(MediaScannerService *service) : service_(service) { } protected: void Invoke(InvocationPtr invocation) const { std::set failed_keys; const MediaInfo item = invocation->arg<0>(&failed_keys); const wstring url = item.first(schema::kUrl); kTraceDBus("{1}: url=<{2}>") % name() % url; if (url.empty()) { invocation->return_error(G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Valid URL required for storing media " "information."); return; } service_->push_task(std::bind(&StoreMediaInfoMethod::RunQuery, this, std::placeholders::_1, url, item, failed_keys, invocation), invocation); } void RunQuery(WritableMediaIndex *media_index, const wstring &url, const MediaInfo &item, const std::set &failed_keys, InvocationPtr invocation) const { if (media_index->Insert(url, item)) { Idle::AddOnce(std::bind(&StoreMediaInfoMethod::return_value, failed_keys, invocation)); } else { Idle::AddOnce(std::bind(&Invocation::return_dbus_error, invocation, kErrorStoreFailed, media_index->error_message())); } // TODO(M3): Figure out if we should trigger metadata discovery here. } static void return_value(const std::set &failed_keys, InvocationPtr invocation) { invocation->return_value(output_value_type(failed_keys)); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::RemoveMediaInfoMethod : public dbus::MediaScannerSkeleton::RemoveMediaInfoMethod { public: explicit RemoveMediaInfoMethod(MediaScannerService *service) : service_(service) { } protected: void Invoke(InvocationPtr invocation) const { const string url = invocation->arg<0>(); kTraceDBus("{1}: url=<{2}>") % name() % url; service_->push_task(std::bind(&RemoveMediaInfoMethod::RunQuery, this, std::placeholders::_1, ToUnicode(url), invocation), invocation); } void RunQuery(WritableMediaIndex *media_index, const wstring &url, InvocationPtr invocation) const { if (media_index->Delete(url)) { Idle::AddOnce(std::bind(&RemoveMediaInfoMethod::return_value, invocation)); } else { Idle::AddOnce(std::bind(&Invocation::return_dbus_error, invocation, kErrorRemoveFailed, media_index->error_message())); } } static void return_value(InvocationPtr invocation) { invocation->return_value(output_value_type()); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::IndexPathProperty : public dbus::MediaScannerSkeleton::IndexPathProperty { public: explicit IndexPathProperty(MediaScannerService *service) : service_(service) { } protected: GVariant* GetValue(const string &, const string &, GError **) const { return dbus_type::make_variant(service_->task_facade_-> media_index_path().string()); } private: MediaScannerService *const service_; }; class MediaScannerService::Interface::MediaRootsProperty : public dbus::MediaScannerSkeleton::MediaRootsProperty { public: explicit MediaRootsProperty(MediaScannerService *service) : service_(service) { } protected: GVariant* GetValue(const string &, const string &, GError **) const { return dbus_type::make_variant(service_->scanner_->directories()); } private: MediaScannerService *const service_; }; MediaScannerService::Interface::Interface(MediaScannerService *service) : media_info_available_(register_signal()) , media_info_changed_(register_signal()) { // FIXME(M5): Do this in MediaScannerInterface() register_method(service); register_method(service); register_method(service); register_method(service); register_method(service); register_property(service); register_property(service); } class MediaScannerService::CommitNotifier : public CommitPolicy { public: explicit CommitNotifier(MediaScannerService *service, CommitPolicyPtr parent = default_policy()) : service_(service) , parent_(parent) { } bool OnCreate(const std::vector &media_urls, WritableMediaIndex *media_index) { queue_notification(dbus::MEDIA_INFO_CREATED, media_urls); if (not parent_->OnCreate(media_urls, media_index)) return false; SendNotifications(); return true; } bool OnUpdate(const std::vector &media_urls, WritableMediaIndex *media_index) { queue_notification(dbus::MEDIA_INFO_UPDATED, media_urls); if (not parent_->OnUpdate(media_urls, media_index)) return false; SendNotifications(); return true; } bool OnRemove(const std::vector &media_urls, WritableMediaIndex *media_index) { queue_notification(dbus::MEDIA_INFO_REMOVED, media_urls); if (not parent_->OnRemove(media_urls, media_index)) return false; SendNotifications(); return true; } protected: void queue_notification(dbus::MediaChangeType change_type, const std::vector &media_urls) { kTraceDBus("Queuing {1} notification...") % to_string(change_type); pending_notifications_.push_back(Notification(change_type, media_urls)); } void SendNotifications() { if (not service_->interface_) return; while (not pending_notifications_.empty()) { const Notification ¬ify = pending_notifications_.front(); kTraceDBus("Emitting {1} notification for {2}.") % to_string(notify.change_type()) % notify.media_urls(); const Interface::MediaInfoChangedSignal::args_type::value_type args(notify.change_type(), notify.media_urls()); service_->interface_->media_info_changed()->emit_signal_on_idle (args, service_->interface_->object_path(), service_->interface_->name(), service_->connection()); pending_notifications_.pop_front(); } } private: class Notification { public: Notification(dbus::MediaChangeType change_type, const std::vector &media_urls) : change_type_(change_type) { media_urls_.reserve(media_urls.size()); for (const std::wstring &url: media_urls) media_urls_.push_back(FromUnicode(url)); } dbus::MediaChangeType change_type() const { return change_type_; } const std::vector& media_urls() const { return media_urls_; } private: dbus::MediaChangeType change_type_; std::vector media_urls_; }; std::list pending_notifications_; MediaScannerService *const service_; const CommitPolicyPtr parent_; }; void MediaScannerService::Connected() { Service::Connected(); if (not interface_) interface_.reset(new Interface(this)); RegisterObject(interface_->object_path(), interface_); } static void report_error(dbus::MethodInvocationPtr invocation, const string &error_message) { invocation->return_dbus_error(kErrorSetupFailed, error_message); } static void report_error_on_idle(dbus::MethodInvocationPtr invocation, const string &error_message) { // Cannot directly bind MethodInvocation::return_dbus_message because // we must keep the MethodInvocation instance alive until the idle handler // is called. Idle::AddOnce(std::bind(&report_error, invocation, error_message)); } void MediaScannerService::setup_media_index(WritableMediaIndex *index) const { index->set_commit_policy(notifier_); } void MediaScannerService::push_task(const TaskFunction &task, dbus::MethodInvocationPtr invocation) { const TaskFacade::ErrorFunction on_error = std::bind(&report_error_on_idle, invocation, std::placeholders::_1); task_manager_->AppendTask(task_facade_->bind(task, on_error)); } static void print_error(const string &error_message) { kError("{1}") % error_message; } void MediaScannerService::RunTask(const TaskFunction &task) { const TaskFacade::ErrorFunction on_error = std::bind(&print_error, std::placeholders::_1); task_manager_->RunTask(task_facade_->bind(task, on_error)); } static std::shared_ptr list_gst_decoders() { GList *decoders = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DECODER, GST_RANK_NONE); return std::shared_ptr(decoders, gst_plugin_feature_list_free); } static std::shared_ptr list_gst_demuxers() { GList *decoders = gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_DEMUXER, GST_RANK_NONE); return std::shared_ptr(decoders, gst_plugin_feature_list_free); } static bool VerifyMediaFeatures(const std::shared_ptr available, const Settings::MediaFormatList &mandatory) { Wrapper available_caps = take(gst_caps_new_empty()); bool success = true; for (const GList *p = available.get(); p; p = p->next) { GstElementFactory *const factory = static_cast(p->data); kTraceGSt("scanning plugin feature \"{1}\"") % gst_plugin_feature_get_name(p->data); const GList *const templates = gst_element_factory_get_static_pad_templates(factory); for (const GList *l = templates; l; l = l->next) { GstStaticPadTemplate *const t = static_cast(l->data); if (t->direction != GST_PAD_SINK) continue; Wrapper caps = take(gst_static_caps_get(&t->static_caps)); if (gst_caps_is_any(caps)) continue; caps.take(gst_caps_make_writable(caps.release())); while (Wrapper st = take(gst_caps_steal_structure(caps.get(), 0))) { if (ends_with(gst_structure_get_name(st.get()), "/x-raw")) continue; gst_caps_append_structure(available_caps.get(), st.release()); } } } available_caps.take(gst_caps_simplify(available_caps.get())); kTraceGSt("available: {1}") % to_string(available_caps); for (const Settings::MediaFormat &feature: mandatory) { const Wrapper mandatory_caps = take(gst_caps_from_string(feature.caps.c_str())); if (not mandatory_caps) { kWarning("Invalid capability string for {1}: \"{2}\"") % feature.name % feature.caps; continue; } const Wrapper common_caps = take(gst_caps_intersect_full(mandatory_caps.get(), available_caps.get(), GST_CAPS_INTERSECT_FIRST)); std::string mand_caps_str = to_string(mandatory_caps); std::string common_caps_str = to_string(common_caps); kTraceGSt("mandatory: {1}") % mand_caps_str; kTraceGSt("common: {1}") % common_caps_str; if (gst_caps_is_empty(common_caps)) { // TODO(M5): L10N or maybe trigger plugin installer kWarning("No GStreamer support found for {1} ({2})") % feature.name % feature.caps; success = false; continue; } if (gst_caps_get_size(common_caps) < gst_caps_get_size(mandatory_caps)) { // TODO(M5): L10N or maybe trigger plugin installer kWarning("Only partial GStreamer support found for {1} ({2})") % feature.name % feature.caps; success = false; continue; } } return success; } /** * @brief Parses the command line arguments passed to the program. * @param argc The number of command line arguments. * @param argv The arguments passed to this program. * @param @c true if parsing succeeded */ bool MediaScannerService::ParseCommandLine(int argc, char *argv[]) { gboolean enable_scanning = true; gboolean enable_file_monitor = true; gboolean enable_volume_monitor = true; Wrapper media_index_path; Wrapper metadata_sources; const GOptionEntry entries[] = { { "disable-scanning", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &enable_scanning, N_("Disables filesystem scanning"), nullptr }, { "enable-scanning", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &enable_scanning, nullptr, nullptr }, { "disable-file-monitor", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &enable_file_monitor, N_("Disables monitoring of directory changes"), nullptr }, { "enable-file-monitor", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &enable_file_monitor, nullptr, nullptr }, { "disable-volume-monitor", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &enable_volume_monitor, N_("Disables monitoring of removable media"), nullptr }, { "enable-volume-monitor", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &enable_volume_monitor, nullptr, nullptr }, { "media-index-path", 0, 0, G_OPTION_ARG_FILENAME, media_index_path.out_param(), N_("Path of the media index database"), N_("PATH") }, { "metadata-sources", 0, 0, G_OPTION_ARG_STRING, metadata_sources.out_param(), N_("Names of the Grilo metadata sources to use"), N_("PLUGIN1:SOURCE1[,PLUGIN2:SOURCE2[,...]]") }, { nullptr, 0, 0, static_cast(0), nullptr, nullptr, nullptr } }; boost::scoped_ptr context (g_option_context_new(_("[MEDIA-ROOTS...]"))); g_option_context_add_group(context.get(), gst_init_get_option_group()); g_option_context_add_group(context.get(), grl_init_get_option_group()); g_option_context_add_main_entries(context.get(), entries, GETTEXT_DOMAIN); Wrapper error; if (not g_option_context_parse(context.get(), &argc, &argv, error.out_param())) { kError("{1}") % error->message; return false; } // Now really initialize underlaying frameworks. If we'd do early we'd // get trouble with command line parsing. Well, _init() functions are evil. // Especially if they parse command line options. grl_init(nullptr, nullptr); if (not gst_init_check(nullptr, nullptr, error.out_param())) { const string error_message = to_string(error); kError("Could not initialize GStreamer: {1}") % error_message; return false; } // Make sure the network monitor is initialised early, since it is // not reliable if initialised from a thread. GNetworkMonitor *network_monitor = g_network_monitor_get_default(); if (not network_monitor) { kError("Could not initialize GNetworkMonitor"); return false; } // Really check all plugins. Avoid lazy evaluation. // Don't abort on missing codecs. We already print a warning. VerifyMediaFeatures(list_gst_demuxers(), settings_.mandatory_containers()); VerifyMediaFeatures(list_gst_decoders(), settings_.mandatory_decoders()); root_manager_.reset(new MediaRootManager); // Setup media roots. std::vector media_roots; if (argc == 1) { // No media roots specified. Consult settings and listen for changes. settings_.connect (Settings::kMediaRoots, std::bind(&MediaScannerService::OnMediaRootsChanged, this)); media_roots = settings_.media_root_paths(); } else { // Setup media roots according to the command line. for (int i = 1; i < argc; ++i) { std::string path = argv[i]; boost::algorithm::trim(path); if (path.empty()) continue; const Wrapper file = take(g_file_new_for_commandline_arg(path.c_str())); if (g_file_query_file_type(file.get(), G_FILE_QUERY_INFO_NONE, nullptr) != G_FILE_TYPE_DIRECTORY) continue; media_roots.push_back(take(g_file_get_path(file.get())).get()); } } for (const std::string &p: media_roots) root_manager_->AddManualRoot(p); // Setup metadata sources. if (not metadata_sources) { // No sources specified. Consult settings and listen for changes. settings_.connect (Settings::kMetadataSources, std::bind(&MediaScannerService::OnSourcesChanged, this)); metadata_sources_ = settings_.metadata_sources(); } else { metadata_sources_.clear(); std::istringstream iss(metadata_sources.get()); for (std::string id; std::getline(iss, id, ','); ) { const std::string::size_type i = id.find(':'); Settings::MetadataSource info; if (i != std::string::npos) { info.plugin_id = id.substr(0, i); info.source_id = id.substr(i + 1); } else { info.plugin_id = id; info.source_id = id; } boost::algorithm::trim(info.plugin_id); boost::algorithm::trim(info.source_id); metadata_sources_.push_back(info); } } // Setup the metadata resolver metadata_resolver_.reset(new MetadataResolver); if (not LoadMetadataSources()) return false; // Set up the media art downloader MediaArtDownloader::LoadGriloPlugin(); // Setup the scanner itself. notifier_.reset(new CommitNotifier(this)); task_manager_.reset(new TaskManager("media scanner service")); task_facade_.reset(new TaskFacade(root_manager_)); scanner_.reset(new FileSystemScanner(metadata_resolver_, task_manager_, task_facade_)); scanner_->set_directories(media_roots); if (media_index_path) task_facade_->set_media_index_path(media_index_path.get()); // Start scanning scanner_->set_file_monitor_enabled(enable_file_monitor); root_manager_->set_enabled(enable_volume_monitor); if (enable_scanning) { kInfo("Starting the file system scanner"); scanner_->start_scanning(); } else { kInfo("Not starting the file system scanner"); } return true; } void MediaScannerService::OnMediaRootsChanged() { // FIXME(M5): Reconfigure scanner kInfo("media roots changed..."); } void MediaScannerService::OnSourcesChanged() { // FIXME(M5): Reconfigure scanner kInfo("metadata sources changed..."); } bool MediaScannerService::LoadMetadataSources() { const std::vector available_sources = settings_.LoadMetadataSources(metadata_sources_); if (available_sources.size() != metadata_sources_.size()) return false; return metadata_resolver_->SetupSources(available_sources); } /** * @brief Entry point of the program. * @param argc The number of command line arguments. * @param argv The arguments passed to this program. * @return The exit code of the program. */ int MediaScannerService::Run(int argc, char *argv[]) { if (not ParseCommandLine(argc, argv)) return EXIT_FAILURE; RunTask(std::bind(&MediaScannerService::setup_media_index, this, std::placeholders::_1)); Service::Connect(dbus::MediaScannerSkeleton::service_name()); g_main_loop_run(take(g_main_loop_new(0, false)).get()); return EXIT_SUCCESS; } } // namespace mediascanner int main(int argc, char *argv[]) { std::set_terminate(mediascanner::abort_with_backtrace); mediascanner::logging::capture_glib_messages(); mediascanner::SetupLocale(); return mediascanner::MediaScannerService().Run(argc, argv); } mediascanner-0.3.93+14.04.20131024.1/src/mediascanner-service/CMakeLists.txt0000644000015700001700000000056412232220161026340 0ustar pbuserpbgroup00000000000000include_directories(${DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src) add_executable(mediascanner-service main.cpp) target_link_libraries(mediascanner-service mediascanner) install(TARGETS mediascanner-service DESTINATION ${CMAKE_INSTALL_BINDIR}) add_dependencies(mediascanner-service settingsdir) mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/0000755000015700001700000000000012232220310022131 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbusservice.cpp0000644000015700001700000000734412232220161025167 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/dbusservice.h" // C++ Standard Library #include // Media Scanner Library #include "mediascanner/dbustypes.h" namespace mediascanner { namespace dbus { void MediaScannerProxy::connect(Wrapper cancellable) { InterfaceProxy::connect (G_BUS_TYPE_SESSION, MediaScannerSkeleton::service_name(), MediaScannerSkeleton::object_path(), cancellable); } void MediaScannerProxy::connect(Wrapper connection, Wrapper cancellable) { InterfaceProxy::connect (connection, MediaScannerSkeleton::service_name(), MediaScannerSkeleton::object_path(), cancellable); } bool MediaScannerProxy::ConnectAndWait(Wrapper cancellable, GError **error) { const char *const address = g_getenv("DBUS_SESSION_BUS_ADDRESS"); Wrapper connection; if (address) { connection = take(g_dbus_connection_new_for_address_sync (address, G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION | G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, nullptr, nullptr, error)); } else { connection = take(g_bus_get_sync(G_BUS_TYPE_SESSION, cancellable.get(), error)); } return connection && ConnectAndWait(connection, cancellable, error); } bool MediaScannerProxy::ConnectAndWait(Wrapper connection, Wrapper cancellable, GError **error) { return InterfaceProxy::ConnectAndWait (connection, MediaScannerSkeleton::service_name(), MediaScannerSkeleton::object_path(), cancellable, error); } const std::string& MediaScannerSkeleton::object_path() { static const std::string object_path = "/com/canonical/MediaScanner"; return object_path; } const std::string& MediaScannerSkeleton::service_name() { static const std::string service_name = "com.canonical.MediaScanner"; return service_name; } template<> const Signature& Type::signature() { return Type::signature(); } template<> GVariant* Type::make_variant(MediaChangeType value) { return Type::make_variant(static_cast(value)); } template<> MediaChangeType Type::make_value(GVariant *variant) { return static_cast(Type::make_value(variant)); } std::string to_string(MediaChangeType change_type) { switch (change_type) { case MEDIA_INFO_CREATED: return "media-info-created"; case MEDIA_INFO_UPDATED: return "media-info-updated"; case MEDIA_INFO_REMOVED: return "media-info-removed"; } std::ostringstream oss; oss << "unknown-media-change-" << change_type; return oss.str(); } } // namespace dbus } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filesystemscanner.cpp0000644000015700001700000002723612232220161026411 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/filesystemscanner.h" // C++ Standard Library #include #include #include #include #include // Media Scanner Library #include "mediascanner/filesystemwalker.h" #include "mediascanner/glibutils.h" #include "mediascanner/logging.h" #include "mediascanner/mediaroot.h" #include "mediascanner/metadataresolver.h" #include "mediascanner/mediaartdownloader.h" #include "mediascanner/taskfacades.h" #include "mediascanner/taskmanager.h" #include "mediascanner/writablemediaindex.h" #include "mediascanner/utilities.h" namespace mediascanner { // Context specific logging domains static const logging::Domain kError("error/fs-scanner", logging::error()); static const logging::Domain kWarning("warning/fs-scanner", logging::warning()); static const logging::Domain kDebug("debug/fs-scanner", logging::debug()); static const logging::Domain kInfo("info/fs-scanner", logging::info()); typedef std::shared_ptr FileSystemWalkerPtr; class FileSystemScanner::Private : public MediaRootManager::Listener { friend class FileSystemScanner; typedef std::vector MediaRootVector; typedef MediaRootVector::iterator MediaRootIterator; typedef std::vector ListenerVector; Private(MetadataResolverPtr resolver, TaskManagerPtr index_task_manager, TaskFacadePtr index_task_facade) : metadata_resolver_(resolver) , art_downloader_(new MediaArtDownloader()) , file_task_manager_(new TaskManager("file system scanner")) , index_task_manager_(index_task_manager) , index_task_facade_(index_task_facade) , file_monitor_enabled_(true) { index_task_facade_->root_manager()->add_listener(this); g_mutex_init(&mutex_); } ~Private() { index_task_facade_->root_manager()->remove_listener(this); // Ensure no more tasks are running in background. CancelWalkers(); file_task_manager_->Shutdown(); // Verify we really shutdown all walkers. for (const auto &walker: walkers_) { BOOST_ASSERT_MSG(walker.use_count() == 1, walker->media_root().path().c_str()); } walkers_.clear(); // Ensure we really shutdown the task manager. BOOST_ASSERT(file_task_manager_.use_count() == 1); file_task_manager_.reset(); } MediaRootManagerPtr root_manager() const { return index_task_facade_->root_manager(); } bool is_idle() const { if (not pending_roots_.empty()) return false; if (metadata_resolver_ && not metadata_resolver_->is_idle()) return false; return true; } bool add_media_root(const MediaRoot &root); bool remove_media_root(const std::string &path); bool remove_media_root(MediaRootIterator it); void OnMediaRootAdded(const MediaRoot &root); void OnMediaRootRemoved(const MediaRoot &root); void OnMediaRootFinished(FileSystemWalkerPtr walker); void NotifyScanningFinished(std::string error_message); void CancelWalkers() { for (const auto &walker: walkers_) { file_task_manager_->CancelByGroupId(walker->task_group()); } } MetadataResolverPtr metadata_resolver_; MediaArtDownloaderPtr art_downloader_; TaskManagerPtr file_task_manager_; TaskManagerPtr index_task_manager_; TaskFacadePtr index_task_facade_; bool file_monitor_enabled_; typedef std::vector PendingRootVector; PendingRootVector pending_roots_; MediaRootVector walkers_; ListenerVector listeners_; std::vector directories_; std::vector metadata_sources_; GMutex mutex_; }; FileSystemScanner::FileSystemScanner(MetadataResolverPtr resolver, TaskManagerPtr media_task_manager, TaskFacadePtr media_task_facade) : d(new Private(resolver, media_task_manager, media_task_facade)) { } FileSystemScanner::~FileSystemScanner() { delete d; } // Attributes ////////////////////////////////////////////////////////////////// bool FileSystemScanner::Private::add_media_root(const MediaRoot &root) { for (const auto &walker: walkers_) { if (walker->media_root().path() == root.path()) return false; } FileSystemWalkerPtr walker(new FileSystemWalker(root, metadata_resolver_, art_downloader_, file_task_manager_, index_task_manager_, index_task_facade_)); // Don't add failed media roots. if (walker->is_cancelled()) return false; const std::string relative_path = root.relative_path(); const std::string base_path = root.base_path(); kInfo("Starting to monitor \"/{1}\" mounted at \"{2}\"") % relative_path % base_path; walkers_.push_back(walker); return true; } bool FileSystemScanner::Private::remove_media_root(const std::string &path) { for (MediaRootIterator it = walkers_.begin(); it != walkers_.end(); ++it) { if ((*it)->media_root().path() == path) return remove_media_root(it); } return false; } bool FileSystemScanner::Private::remove_media_root(MediaRootIterator it) { // Move into scoped pointer to delete at return. const FileSystemWalkerPtr walker = *it; const std::string relative_path = walker->media_root().relative_path(); const std::string base_path = walker->media_root().base_path(); kInfo("Stopping to monitor \"/{1}\" mounted at \"{2}\"") % relative_path % base_path; // First remove from list and cancel later to minimize possible races. walkers_.erase(it); file_task_manager_->CancelByGroupId(walker->task_group()); return true; } void FileSystemScanner::set_directories(const std::vector &paths) { g_mutex_lock(&d->mutex_); d->CancelWalkers(); d->walkers_.clear(); for (const auto &root_path: paths) { d->add_media_root(d->root_manager()->make_root(root_path)); } g_mutex_unlock(&d->mutex_); } bool FileSystemScanner::add_directory(const std::string &path) { g_mutex_lock(&d->mutex_); const bool result = d->add_media_root(d->root_manager()->make_root(path)); g_mutex_unlock(&d->mutex_); return result; } bool FileSystemScanner::remove_directory(const std::string &path) { g_mutex_lock(&d->mutex_); const bool result = d->remove_media_root(path); g_mutex_unlock(&d->mutex_); return result; } std::vector FileSystemScanner::directories() const { std::vector paths; g_mutex_lock(&d->mutex_); for (const auto &walker: d->walkers_) paths.push_back(walker->media_root().path()); g_mutex_unlock(&d->mutex_); return paths; } void FileSystemScanner::add_listener(Listener *listener) { d->listeners_.push_back(listener); } void FileSystemScanner::remove_listener(Listener *listener) { Private::ListenerVector::iterator it = d->listeners_.begin(); while (it != d->listeners_.end()) { if (*it == listener) { it = d->listeners_.erase(it); continue; } ++it; } } void FileSystemScanner::set_file_monitor_enabled(bool enable) { if (enable != d->file_monitor_enabled_) { for (const auto &walker: d->walkers_) { walker->set_file_monitor_enabled(enable); } } // FIXME(M5): Trigger rescan? } bool FileSystemScanner::file_monitor_enabled() const { return d->file_monitor_enabled_; } bool FileSystemScanner::is_idle() const { g_mutex_lock(&d->mutex_); const bool result = d->is_idle(); g_mutex_unlock(&d->mutex_); return result; } // Actions ///////////////////////////////////////////////////////////////////// bool FileSystemScanner::start_scanning() { bool started = false; g_mutex_lock(&d->mutex_); if (not d->is_idle()) { kWarning("File system scanner was started already."); } else { for (const auto walker: d->walkers_) { g_mutex_unlock(&d->mutex_); const bool walker_started = walker->start(); g_mutex_lock(&d->mutex_); if (walker_started) { d->file_task_manager_->AppendGroupedTask (walker->task_group(), std::bind(&Private::OnMediaRootFinished, d, walker)); d->pending_roots_.push_back(walker->task_group()); } } started = true; } g_mutex_unlock(&d->mutex_); return started; } void FileSystemScanner::Private::OnMediaRootAdded(const MediaRoot &root) { g_mutex_lock(&mutex_); add_media_root(root); g_mutex_unlock(&mutex_); } void FileSystemScanner::Private::OnMediaRootRemoved(const MediaRoot &root) { g_mutex_lock(&mutex_); remove_media_root(root.path()); g_mutex_unlock(&mutex_); } void FileSystemScanner::Private::OnMediaRootFinished (FileSystemWalkerPtr walker) { const std::string root_path = walker->media_root().path(); kDebug("Scanning \"{1}\" just finished.") % root_path; g_mutex_lock(&mutex_); const std::string error_message = walker->error_message(); if (not error_message.empty()) { // FIXME(future): Maybe should escalate this even further, // e.g. to the media scanner service? kWarning("Scanning failed for \"{1}\": {2}.") % root_path % error_message; } // Stop tracking this walker as pending. const PendingRootVector::iterator it = std::find(pending_roots_.begin(), pending_roots_.end(), walker->task_group()); if (it != pending_roots_.end()) { pending_roots_.erase(it); } else { kWarning("Cannot find scanner task for \"{1}\".") % root_path; } // Wait for remaining metadata resolvers if this was the last walker. if (pending_roots_.empty()) { kDebug("Last media root scanned, waiting for pending metadata."); if (metadata_resolver_) metadata_resolver_->WaitForFinished(); if (art_downloader_) art_downloader_->WaitForFinished(); BOOST_ASSERT(is_idle()); Idle::AddOnce(std::bind(&Private::NotifyScanningFinished, this, std::string())); } g_mutex_unlock(&mutex_); } void FileSystemScanner::Private::NotifyScanningFinished (std::string error_message) { std::for_each(listeners_.begin(), listeners_.end(), std::bind(&FileSystemScanner::Listener::OnScanningFinished, std::placeholders::_1, error_message)); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/propertyschema.cpp0000644000015700001700000014150612232220161025715 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/propertyschema.h" // GStreamer #include #include // Boost C++ #include // C++ Standard Library #include #include // Media Scanner #include "mediascanner/glibutils.h" #include "mediascanner/propertyprivate.h" #include "mediascanner/utilities.h" // TODO(M3): L10N: properly pull gettext()/boost::translate #define N_(MsgId) (MsgId) namespace mediascanner { namespace schema { // Generic Properties ////////////////////////////////////////////////////////// ExternalUrl::ExternalUrl() : StringProperty(L"external-url", GRL_METADATA_KEY_EXTERNAL_URL, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_LOCATION)) { } HomepageUrl::HomepageUrl() : StringProperty(L"homepage-url", GRL_METADATA_KEY_SITE, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_HOMEPAGE)) { } Author::Author() : TextProperty(L"author", GRL_METADATA_KEY_AUTHOR, Property::Category::Generic, Property::MergeAppend, merge_nothing) { } License::License() : StringProperty(L"license", GRL_METADATA_KEY_LICENSE, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_LICENSE)) { } LicenseUri::LicenseUri() : StringProperty(L"license-uri", define("lucene-license-uri", N_("License Uri (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_LICENSE_URI)) { } Copyright::Copyright() : StringProperty(L"copyright", define("lucene-copyright", N_("Copyright (Lucene)"), N_("Copyright notice of this media")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_COPYRIGHT)) { } CopyrightUri::CopyrightUri() : StringProperty(L"copyright-uri", define("lucene-copyright-uri", N_("Copyright Uri (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_COPYRIGHT_URI)) { } Description::Description() : TextProperty(L"description", GRL_METADATA_KEY_DESCRIPTION, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_DESCRIPTION)) { } Rating::Rating() : NumericProperty(L"rating", GRL_METADATA_KEY_RATING, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_USER_RATING)) { } Title::Title() : TextProperty(L"title", GRL_METADATA_KEY_TITLE, Property::Category::Generic, Property::MergeReplace, bind_tag(GST_TAG_TITLE)) { } OriginalTitle::OriginalTitle() : TextProperty(L"original-title", GRL_METADATA_KEY_ORIGINAL_TITLE, Property::Category::Generic, Property::MergeReplace, merge_nothing) { } Certification::Certification() : StringProperty(L"certification", GRL_METADATA_KEY_CERTIFICATE, Property::Category::Generic, Property::MergeAppend, merge_nothing) { } Region::Region() : StringProperty(L"region", GRL_METADATA_KEY_REGION, Property::Category::Generic, Property::MergeAppend, merge_nothing) { } Comment::Comment() : TextProperty(L"comment", define("lucene-comment", N_("Comment (Lucene)"), N_("User comment about this media")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_COMMENT)) { } // FIXME(M3): Do we need a special tag merger here? Keyword::Keyword() : StringProperty(L"keyword", GRL_METADATA_KEY_KEYWORD, Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_KEYWORDS)) { } Language::Language() : StringProperty(L"language", define("lucene-language", N_("Language (Lucene)"), N_("Languages this media is adopted to")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_LANGUAGE_CODE)) { } Version::Version() : StringProperty(L"version", define("lucene-version", N_("Version (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_VERSION)) { } Organization::Organization() : StringProperty(L"organization", define("lucene-organization", N_("Organization (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_ORGANIZATION)) { } Contact::Contact() : StringProperty(L"contact", define("lucene-contact", N_("Contact (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeAppend, bind_tag(GST_TAG_CONTACT)) { } Favourite::Favourite() : NumericProperty(L"favourite", GRL_METADATA_KEY_FAVOURITE, Property::Category::Generic, Property::MergeReplace, merge_nothing) { } static bool get_network_available(const GstDiscovererInfo *media) { GNetworkMonitor *monitor = g_network_monitor_get_default(); return g_network_monitor_get_network_available(monitor); } NetworkAvailable::NetworkAvailable() : NumericProperty(L"network-available", define("lucene-network-available", N_("Network available (Lucene)"), N_("...")), Property::Category::Generic, Property::MergeReplace, bind_attr(get_network_available)) { } // File Properties ///////////////////////////////////////////////////////////// Url::Url() : StringProperty(L"url", GRL_METADATA_KEY_URL, Property::Category::File, Property::MergeAppend, merge_nothing) { } ETag::ETag() : StringProperty(L"etag", define("lucene-etag", N_("ETag (Lucene)"), N_("A sophisticated last change indicator, " "similiar to the ETag header of HTTP 1.1")), Property::Category::File, Property::MergeAppend, merge_nothing) { } MimeType::MimeType() : StringProperty(L"mime-type", GRL_METADATA_KEY_MIME, Property::Category::File, Property::MergeAppend, merge_nothing) { } LastModified::LastModified() : DateTimeProperty(L"modification-date", GRL_METADATA_KEY_MODIFICATION_DATE, Property::Category::File, Property::MergeAppend, merge_nothing) { } FileSize::FileSize() : NumericProperty(L"file-size", define("lucene-file-size", N_("File Size (Lucene"), N_("Size of the media file in bytes")), Property::Category::File, Property::MergeAppend, merge_nothing) { } LastAccessed::LastAccessed() : DateTimeProperty(L"last-accessed", define("lucene-last-accessed", N_("Last access time (Lucene)"), N_("Time when the media file was accessed " "the last time")), Property::Category::File, Property::MergeAppend, merge_nothing) { } // Media Properties //////////////////////////////////////////////////////////// static int get_duration(const GstDiscovererInfo *media) { return GST_TIME_AS_SECONDS(gst_discoverer_info_get_duration(media)); } Duration::Duration() : NumericProperty(L"duration", GRL_METADATA_KEY_DURATION, Property::Category::Media, Property::MergeAppend, bind_any(bind_attr(get_duration), bind_tag(GST_TAG_DURATION))) { } static bool get_seekable(const GstDiscovererInfo *media) { return gst_discoverer_info_get_seekable(media); } Seekable::Seekable() : NumericProperty(L"seekable", define("lucene-seekable", N_("Seekable (Lucene)"), N_("The media is seekable"), true), Property::Category::Media, Property::MergeAppend, bind_attr(get_seekable)) { } LastPlayed::LastPlayed() : DateTimeProperty(L"last-played", GRL_METADATA_KEY_LAST_PLAYED, Property::Category::Media, Property::MergeReplace, merge_nothing) { } PlayCount::PlayCount() : NumericProperty(L"play-count", GRL_METADATA_KEY_PLAY_COUNT, Property::Category::Media, Property::MergeReplace, merge_nothing) { } LastPosition::LastPosition() : NumericProperty(L"last-position", GRL_METADATA_KEY_LAST_POSITION, Property::Category::Media, Property::MergeReplace, merge_nothing) { } Producer::Producer() : TextProperty(L"producer", GRL_METADATA_KEY_PRODUCER, Property::Category::Media, Property::MergeAppend, merge_nothing) { } Performer::Performer() : TextProperty(L"performer", GRL_METADATA_KEY_PERFORMER, Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_PERFORMER)) { } // TODO(jamesh): GST_TAG_IMAGE is actually a GstSegment containing the // image data rather than a file URI. Cover::Cover() : StringProperty(L"cover", GRL_METADATA_KEY_THUMBNAIL, Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_IMAGE)) { } Poster::Poster() : StringProperty(L"poster", define("tmdb-poster", N_("Poster (Lucene, TMDB)"), N_("URLs pointing to posters for this media")), Property::Category::Media, Property::MergeAppend, merge_nothing) { } PublicationDate::PublicationDate() : DateTimeProperty(L"publication-date", GRL_METADATA_KEY_PUBLICATION_DATE, Property::Category::Media, Property::MergeAppend, merge_nothing) { } Studio::Studio() : StringProperty(L"studio", GRL_METADATA_KEY_STUDIO, Property::Category::Media, Property::MergeAppend, merge_nothing) { } Genre::Genre() : StringProperty(L"genre", GRL_METADATA_KEY_GENRE, Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_GENRE)) { } EncodedBy::EncodedBy() : StringProperty(L"encoded-by", define("lucene-encoded-by", N_("Encoded By (Lucene)"), N_("...")), Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_ENCODED_BY)) { } ContainerFormat::ContainerFormat() : StringProperty(L"container-format", define("lucene-container-format", N_("Container Format (Lucene)"), N_("...")), Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_CONTAINER_FORMAT)) { } Encoder::Encoder() : StringProperty(L"encoder", define("lucene-encoder", N_("Encoder (Lucene)"), N_("...")), Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_ENCODER)) { } EncoderVersion::EncoderVersion() : NumericProperty(L"encoder-version", define("lucene-encoder-version", N_("Encoder Version (Lucene)"), N_("...")), Property::Category::Media, Property::MergeAppend, bind_tag(GST_TAG_ENCODER_VERSION)) { } // Music Properties //////////////////////////////////////////////////////////// static int get_audio_bitrate(const GstDiscovererAudioInfo *info) { return gst_discoverer_audio_info_get_bitrate(info); } AudioBitRate::AudioBitRate() : NumericProperty(L"audio-bitrate", GRL_METADATA_KEY_BITRATE, Property::Category::Music, Property::MergeAppend, bind_attr(get_audio_bitrate)) { } static int get_max_audio_bitrate(const GstDiscovererAudioInfo *info) { return gst_discoverer_audio_info_get_max_bitrate(info); } MaximumAudioBitRate::MaximumAudioBitRate() : NumericProperty(L"maximum-audio-bitrate", define("lucene-maximum-audio-bitrate", N_("Maximum Audio Bitrate (Lucene)"), N_("The maximum bitrate of this media's " "audio stream")), Property::Category::Music, Property::MergeAppend, bind_attr(get_max_audio_bitrate)) { } AudioCodec::AudioCodec() : StringProperty(L"audio-codec", define("lucene-audio-codec", N_("Audio Codec (Lucene)"), N_("The codec used for this media's audio stream")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_AUDIO_CODEC)) { } int get_sample_rate(const GstDiscovererAudioInfo *info) { return gst_discoverer_audio_info_get_sample_rate(info); } SampleRate::SampleRate() : NumericProperty(L"sample-rate", define("lucene-sample-rate", N_("Sample Rate (Lucene"), N_("The sample rate of this media's " "audio stream")), Property::Category::Music, Property::MergeAppend, bind_attr(get_sample_rate)) { } int get_channels(const GstDiscovererAudioInfo *info) { return gst_discoverer_audio_info_get_channels(info); } ChannelCount::ChannelCount() : NumericProperty(L"channel-count", define("lucene-channel-count", N_("Channel Count (Lucene"), N_("The number of this media's " "audio stream channels")), Property::Category::Music, Property::MergeAppend, bind_attr(get_channels)) { } AlbumArtist::AlbumArtist() : TextProperty(L"album-artist", define("lucene-artist", N_("Artist (Lucene)"), N_("The artists of the album " "this song was released with")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_ARTIST)) { } Composer::Composer() : TextProperty(L"composer", define("lucene-composer", N_("Composer (Lucene)"), N_("The composers of this song")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_COMPOSER)) { } TrackCount::TrackCount() : NumericProperty(L"track-count", define("lucene-track-count", N_("Track Count (Lucene"), N_("The number of individual tracks " "on this song's album")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_TRACK_COUNT)) { } DiscNumber::DiscNumber() : NumericProperty(L"disc-number", define("lucene-disc-number", N_("Disc Number (Lucene"), N_("The disc number within the album " "of this song")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_VOLUME_NUMBER)) { } Artist::Artist() : TextProperty(L"artist", GRL_METADATA_KEY_ARTIST, Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ARTIST)) { } Album::Album() : TextProperty(L"album", GRL_METADATA_KEY_ALBUM, Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM)) { } Lyrics::Lyrics() : TextProperty(L"lyrics", GRL_METADATA_KEY_LYRICS, Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_LYRICS)) { } TrackNumber::TrackNumber() : NumericProperty(L"track-number", GRL_METADATA_KEY_TRACK_NUMBER, Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_TRACK_NUMBER)) { } AlbumVolumeNumber::AlbumVolumeNumber() : NumericProperty(L"album-volume-number", define("lucene-album-volume-number", N_("Album Volume Number (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_VOLUME_NUMBER)) { } AlbumVolumeCount::AlbumVolumeCount() : NumericProperty(L"album-volume-count", define("lucene-album-volume-count", N_("Album Volume Count (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_VOLUME_COUNT)) { } ISRC::ISRC() : StringProperty(L"isrc", define("lucene-isrc", N_("ISRC (Lucene)"), N_("International Standard Recording Code")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ISRC)) { } TrackGain::TrackGain() : NumericProperty(L"track-gain", define("lucene-track-gain", N_("Track Gain (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_TRACK_GAIN)) { } TrackPeak::TrackPeak() : NumericProperty(L"track-peak", define("lucene-track-peak", N_("Track Peak (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_TRACK_PEAK)) { } AlbumGain::AlbumGain() : NumericProperty(L"album-gain", define("lucene-album-gain", N_("Album Gain (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_GAIN)) { } AlbumPeak::AlbumPeak() : NumericProperty(L"album-peak", define("lucene-album-peak", N_("Album Peak (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_ALBUM_PEAK)) { } ReferenceLevel::ReferenceLevel() : NumericProperty(L"reference-level", define("lucene-reference-level", N_("Reference Level (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_REFERENCE_LEVEL)) { } BeatsPerMinute::BeatsPerMinute() : NumericProperty(L"beats-per-minute", define("lucene-beats-per-minute", N_("Beats Per Minute (Lucene)"), N_("...")), Property::Category::Music, Property::MergeAppend, bind_tag(GST_TAG_BEATS_PER_MINUTE)) { } // Image Properties //////////////////////////////////////////////////////////// static int get_width(const GstDiscovererVideoInfo *info) { return gst_discoverer_video_info_get_width(info); } Width::Width() : NumericProperty(L"width", GRL_METADATA_KEY_WIDTH, Property::Category::Image, Property::MergeAppend, bind_attr(get_width)) { } static int get_height(const GstDiscovererVideoInfo *info) { return gst_discoverer_video_info_get_height(info); } Height::Height() : NumericProperty(L"height", GRL_METADATA_KEY_HEIGHT, Property::Category::Image, Property::MergeAppend, bind_attr(get_height)) { } class ImageOrientation::Private : public StringProperty::Private { public: typedef StringProperty::Private inherited; Private(const String &field_name, const Property::MetadataKey &metadata_key, Property::Category category, Property::MergeStrategy merge_strategy, const Property::StreamInfoFunction &stream_info) : inherited(field_name, metadata_key, category, merge_strategy, stream_info) { } bool TransformGriloValue(const GValue *input, Value *output) const { if (G_VALUE_TYPE(input) != G_TYPE_INT) { // TODO(M5): Maybe also permit GStreamer orientationt tags? GValue safe_value = G_VALUE_INIT; g_value_init(&safe_value, G_TYPE_INT); // FIXME(M3): Print warning, move to property.cpp? if (not g_value_transform(input, &safe_value)) return false; return TransformGriloValue(&safe_value, output); } std::wostringstream oss; oss << "rotate-" << (g_value_get_int(input) % 360); *output = oss.str(); return true; } Wrapper MakeGriloValue(const Value &value) const { std::wstring text = boost::get(value); const std::wstring kFlip = L"flip-"; if (boost::algorithm::starts_with(text, kFlip)) { // FIXME(M3): Add related image-flipped metadata key text = text.substr(kFlip.length()); } const std::wstring kRotate = L"rotate-"; if (boost::algorithm::starts_with(text, kRotate)) { text = text.substr(kRotate.length()); int rotation; if (not (std::wistringstream(text) >> rotation)) return Wrapper(); GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_INT); g_value_set_int(&result, rotation % 360); return wrap(&result); } return Wrapper(); } }; ImageOrientation::ImageOrientation() : StringProperty(new Private(L"orientation", GRL_METADATA_KEY_ORIENTATION, Property::Category::Image, Property::MergeAppend, bind_tag(GST_TAG_IMAGE_ORIENTATION))) { } HorizontalResolution::HorizontalResolution() : NumericProperty(L"horizontal-resolution", define("lucene-horizontal-resolution", N_("Horizontal Image Resolution (Lucene)"), N_("...")), Property::Category::Image, Property::MergeAppend, bind_tag(GST_TAG_IMAGE_HORIZONTAL_PPI)) { } VerticalResolution::VerticalResolution() : NumericProperty(L"vertical-resolution", define("lucene-vertical-resolution", N_("Vertical Image Resolution (Lucene)"), N_("...")), Property::Category::Image, Property::MergeAppend, bind_tag(GST_TAG_IMAGE_VERTICAL_PPI)) { } // Photo Properties //////////////////////////////////////////////////////////// DateTaken::DateTaken() : DateTimeProperty(L"date-taken", GRL_METADATA_KEY_CREATION_DATE, Property::Category::Photo, Property::MergeAppend, bind_any(bind_tag(GST_TAG_DATE_TIME), bind_tag(GST_TAG_DATE))) { } DeviceModel::DeviceModel() : TextProperty(L"camera-model", GRL_METADATA_KEY_CAMERA_MODEL, Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_DEVICE_MODEL)) { } IsoSpeed::IsoSpeed() : NumericProperty(L"iso-speed", GRL_METADATA_KEY_ISO_SPEED, Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_ISO_SPEED)) { } ExposureTime::ExposureTime() : NumericProperty(L"exposure-time", GRL_METADATA_KEY_EXPOSURE_TIME, Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_SHUTTER_SPEED)) { } FlashUsed::FlashUsed() : NumericProperty(L"flash-used", GRL_METADATA_KEY_FLASH_USED, Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_FLASH_FIRED)) { } DeviceManufacturer::DeviceManufacturer() : TextProperty(L"camera-manufacturer", define("lucene-camera-manufacturer", N_("Camera Manufacturer (Lucene)"), N_("The manufacturer of the camera used " "for capturing this photo")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_DEVICE_MANUFACTURER)) { } FocalRatio::FocalRatio() : NumericProperty(L"focal-ratio", define("lucene-focal-ratio", N_("Focal Ratio (Lucene"), N_("Focal length used when capturing this photo")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_FOCAL_RATIO)) { } FocalLength::FocalLength() : NumericProperty(L"focal-length", define("lucene-focal-length", N_("Focal Length (Lucene"), N_("Denominator of the focal length used " "when capturing this photo")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_FOCAL_LENGTH)) { } MeteringMode::MeteringMode() : StringProperty(L"metering-mode", define("lucene-metering-mode", N_("Metering mode (Lucene)"), N_("The metering mode used when capturing " "this photo")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_METERING_MODE)) { } WhiteBalance::WhiteBalance() : StringProperty(L"white-balance", define("lucene-white-balance", N_("White Balance (Lucene)"), N_("The white balance parameters " "applied to this photo")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_WHITE_BALANCE)) { } GeoLocationName::GeoLocationName() : StringProperty(L"geo-location-name", define("lucene-geo-location-name", N_("Geo Location Name (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_NAME)) { } GeoLocationLatitude::GeoLocationLatitude() : NumericProperty(L"geo-location-latitude", define("lucene-geo-location-latitude", N_("Geo Location Latitude (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_LATITUDE)) { } GeoLocationLongitude::GeoLocationLongitude() : NumericProperty(L"geo-location-longitude", define("lucene-geo-location-longitude", N_("Geo Location Longitude (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_LONGITUDE)) { } GeoLocationElevation::GeoLocationElevation() : NumericProperty(L"geo-location-elevation", define("lucene-geo-location-elevation", N_("Geo Location Elevation (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_ELEVATION)) { } GeoLocationCountry::GeoLocationCountry() : StringProperty(L"geo-location-country", define("lucene-geo-location-country", N_("Geo Location Country (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_COUNTRY)) { } GeoLocationCity::GeoLocationCity() : StringProperty(L"geo-location-city", define("lucene-geo-location-city", N_("Geo Location City (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_CITY)) { } GeoLocationSublocation::GeoLocationSublocation() : StringProperty(L"geo-location-sublocation", define("lucene-geo-location-sublocation", N_("Geo Location Sublocation (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_SUBLOCATION)) { } GeoLocationHorizontalError::GeoLocationHorizontalError() : NumericProperty(L"geo-location-horizontal-error", define("lucene-geo-location-horizontal-error", N_("Geo Location Horizontal Error (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR)) { } GeoLocationMovementSpeed::GeoLocationMovementSpeed() : NumericProperty(L"geo-location-movementSpeed", define("lucene-geo-location-movement-speed", N_("Geo Location Movement Speed (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_MOVEMENT_SPEED)) { } GeoLocationMovementDirection::GeoLocationMovementDirection() : NumericProperty(L"geo-location-movementDirection", define("lucene-geo-location-movement-direction", N_("Geo Location Movement Direction (Lucene)"), N_("..."), 0.0, 360.0), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION)) { } GeoLocationCaptureDirection::GeoLocationCaptureDirection() : NumericProperty(L"geo-location-captureDirection", define("lucene-geo-location-capture-direction", N_("Geo Location Capture Direction (Lucene)"), N_("..."), 0.0, 360.0), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION)) { } Grouping::Grouping() : StringProperty(L"grouping", define("lucene-grouping", N_("Grouping (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_GROUPING)) { } ApplicationName::ApplicationName() : StringProperty(L"application-name", define("lucene-application-name", N_("Application Name (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_APPLICATION_NAME)) { } CapturingDigitalZoomRatio::CapturingDigitalZoomRatio() : NumericProperty(L"capturing-digital-zoom-ratio", define("lucene-capturing-digital-zoom-ratio", N_("Capturing Digital Zoom Ratio (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_DIGITAL_ZOOM_RATIO)) { } CapturingExposureProgram::CapturingExposureProgram() : StringProperty(L"capturing-exposure-program", define("lucene-capturing-exposure-program", N_("Capturing Exposure Program (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_EXPOSURE_PROGRAM)) { } CapturingExposureMode::CapturingExposureMode() : StringProperty(L"capturing-exposure-mode", define("lucene-capturing-exposure-mode", N_("Capturing Exposure Mode (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_EXPOSURE_MODE)) { } CapturingExposureCompensation::CapturingExposureCompensation() : NumericProperty(L"capturing-exposure-compensation", define("lucene-capturing-exposure-compensation", N_("Capturing Exposure Compensation (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_EXPOSURE_COMPENSATION)) { } CapturingSceneCaptureType::CapturingSceneCaptureType() : StringProperty(L"capturing-scene-captureType", define("lucene-capturing-scene-capture-type", N_("Capturing Scene Capture Type (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_SCENE_CAPTURE_TYPE)) { } CapturingGainAdjustment::CapturingGainAdjustment() : StringProperty(L"capturing-gain-adjustment", define("lucene-capturing-gain-adjustment", N_("Capturing Gain Adjustment (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_GAIN_ADJUSTMENT)) { } CapturingContrast::CapturingContrast() : StringProperty(L"capturing-contrast", define("lucene-capturing-contrast", N_("Capturing Contrast (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_CONTRAST)) { } CapturingSaturation::CapturingSaturation() : StringProperty(L"capturing-saturation", define("lucene-capturing-saturation", N_("Capturing Saturation (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_SATURATION)) { } CapturingSharpness::CapturingSharpness() : StringProperty(L"capturing-sharpness", define("lucene-capturing-sharpness", N_("Capturing Sharpness (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_SHARPNESS)) { } CapturingFlashMode::CapturingFlashMode() : StringProperty(L"capturing-flash-mode", define("lucene-capturing-flash-mode", N_("Capturing Flash Mode (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_FLASH_MODE)) { } CapturingSource::CapturingSource() : StringProperty(L"capturing-source", define("lucene-capturing-source", N_("Capturing Source (Lucene)"), N_("...")), Property::Category::Photo, Property::MergeAppend, bind_tag(GST_TAG_CAPTURING_SOURCE)) { } // Movie Properties //////////////////////////////////////////////////////////// static Fraction get_framerate(const GstDiscovererVideoInfo *info) { return Fraction(gst_discoverer_video_info_get_framerate_num(info), gst_discoverer_video_info_get_framerate_denom(info)); } FrameRate::FrameRate() : NumericProperty(L"frame-rate", GRL_METADATA_KEY_FRAMERATE, Property::Category::Movie, Property::MergeAppend, bind_attr(get_framerate)) { } ShowName::ShowName() : TextProperty(L"show", GRL_METADATA_KEY_SHOW, Property::Category::Movie, Property::MergeAppend, bind_tag(GST_TAG_SHOW_NAME)) { } Episode::Episode() : NumericProperty(L"episode", GRL_METADATA_KEY_EPISODE, Property::Category::Movie, Property::MergeAppend, bind_tag(GST_TAG_SHOW_EPISODE_NUMBER)) { } Season::Season() : NumericProperty(L"season", GRL_METADATA_KEY_SEASON, Property::Category::Movie, Property::MergeAppend, bind_tag(GST_TAG_SHOW_SEASON_NUMBER)) { } Backdrop::Backdrop() : StringProperty(L"backdrop", define("tmdb-backdrop", N_("Backdrop (Lucene, TMDB)"), N_("Background information on this movie")), Property::Category::Movie, Property::MergeAppend, merge_nothing) { } VideoCodec::VideoCodec() : StringProperty(L"video-codec", define("lucene-video-code", N_("Video Codec (Lucene)"), N_("The codec used for this media's video stream")), Property::Category::Movie, Property::MergeAppend, bind_tag(GST_TAG_VIDEO_CODEC)) { } int get_video_bitrate(const GstDiscovererVideoInfo *info) { return gst_discoverer_video_info_get_bitrate(info); } VideoBitRate::VideoBitRate() : NumericProperty(L"video-bitrate", define("lucene-video-bitrate", N_("Video Bitrate (Lucene"), N_("The bitrate of this media's video stream")), Property::Category::Movie, Property::MergeAppend, bind_attr(get_video_bitrate)) { } int get_max_video_bitrate(const GstDiscovererVideoInfo *info) { return gst_discoverer_video_info_get_max_bitrate(info); } MaximumVideoBitRate::MaximumVideoBitRate() : NumericProperty(L"maximum-video-bitrate", define("lucene-maximum-video-bitrate", N_("Maximum Video Bitrate (Lucene)"), N_("The maximum bitrate of this media's " "video stream")), Property::Category::Movie, Property::MergeAppend, bind_attr(get_max_video_bitrate)) { } Director::Director() : TextProperty(L"director", GRL_METADATA_KEY_DIRECTOR, Property::Category::Movie, Property::MergeAppend, merge_nothing) { } ImdbId::ImdbId() : StringProperty(L"imdb-id", define("tmdb-imdb-id", N_("IMDB ID (Lucene, TMDB)"), N_("Internet Movie Database ID of this movie")), Property::Category::Movie, Property::MergeReplace, merge_nothing) { } TmdbId::TmdbId() : StringProperty(L"tmdb-id", define("tmdb-id", N_("TMDB ID (Lucene)"), N_("The Movie Database ID of this movie")), Property::Category::Movie, Property::MergeReplace, merge_nothing) { } // Property Definitions //////////////////////////////////////////////////////// const AlbumArtist kAlbumArtist; const AlbumGain kAlbumGain; const Album kAlbum; const AlbumPeak kAlbumPeak; const AlbumVolumeCount kAlbumVolumeCount; const AlbumVolumeNumber kAlbumVolumeNumber; const ApplicationName kApplicationName; const Artist kArtist; const AudioBitRate kAudioBitRate; const AudioCodec kAudioCodec; const Author kAuthor; const Backdrop kBackdrop; const BeatsPerMinute kBeatsPerMinute; const CapturingContrast kCapturingContrast; const CapturingDigitalZoomRatio kCapturingDigitalZoomRatio; const CapturingExposureCompensation kCapturingExposureCompensation; const CapturingExposureMode kCapturingExposureMode; const CapturingExposureProgram kCapturingExposureProgram; const CapturingFlashMode kCapturingFlashMode; const CapturingGainAdjustment kCapturingGainAdjustment; const CapturingSaturation kCapturingSaturation; const CapturingSceneCaptureType kCapturingSceneCaptureType; const CapturingSharpness kCapturingSharpness; const CapturingSource kCapturingSource; const Certification kCertification; const ChannelCount kChannelCount; const Comment kComment; const Composer kComposer; const Contact kContact; const ContainerFormat kContainerFormat; const Copyright kCopyright; const CopyrightUri kCopyrightUri; const Cover kCover; const DateTaken kDateTaken; const Description kDescription; const DeviceManufacturer kDeviceManufacturer; const DeviceModel kDeviceModel; const Director kDirector; const DiscNumber kDiscNumber; const Duration kDuration; const EncodedBy kEncodedBy; const Encoder kEncoder; const EncoderVersion kEncoderVersion; const Episode kEpisode; const ETag kETag; const ExposureTime kExposureTime; const ExternalUrl kExternalUrl; const Favourite kFavourite; const FileSize kFileSize; const FlashUsed kFlashUsed; const FocalLength kFocalLength; const FocalRatio kFocalRatio; const FrameRate kFrameRate; const Genre kGenre; const GeoLocationCaptureDirection kGeoLocationCaptureDirection; const GeoLocationCity kGeoLocationCity; const GeoLocationCountry kGeoLocationCountry; const GeoLocationElevation kGeoLocationElevation; const GeoLocationHorizontalError kGeoLocationHorizontalError; const GeoLocationLatitude kGeoLocationLatitude; const GeoLocationLongitude kGeoLocationLongitude; const GeoLocationMovementDirection kGeoLocationMovementDirection; const GeoLocationMovementSpeed kGeoLocationMovementSpeed; const GeoLocationName kGeoLocationName; const GeoLocationSublocation kGeoLocationSublocation; const Grouping kGrouping; const Height kHeight; const HomepageUrl kHomepageUrl; const HorizontalResolution kHorizontalResolution; const ImageOrientation kImageOrientation; const ImdbId kImdbId; const IsoSpeed kIsoSpeed; const ISRC kISRC; const Keyword kKeyword; const Language kLanguage; const LastAccessed kLastAccessed; const LastModified kLastModified; const LastPlayed kLastPlayed; const LastPosition kLastPosition; const License kLicense; const LicenseUri kLicenseUri; const Lyrics kLyrics; const MaximumAudioBitRate kMaximumAudioBitRate; const MaximumVideoBitRate kMaximumVideoBitRate; const MeteringMode kMeteringMode; const MimeType kMimeType; const NetworkAvailable kNetworkAvailable; const Organization kOrganization; const OriginalTitle kOriginalTitle; const Performer kPerformer; const PlayCount kPlayCount; const Poster kPoster; const Producer kProducer; const PublicationDate kPublicationDate; const Rating kRating; const ReferenceLevel kReferenceLevel; const Region kRegion; const SampleRate kSampleRate; const Season kSeason; const Seekable kSeekable; const ShowName kShowName; const Studio kStudio; const Title kTitle; const TmdbId kTmdbId; const TrackCount kTrackCount; const TrackGain kTrackGain; const TrackNumber kTrackNumber; const TrackPeak kTrackPeak; const Url kUrl; const Version kVersion; const VerticalResolution kVerticalResolution; const VideoBitRate kVideoBitRate; const VideoCodec kVideoCodec; const WhiteBalance kWhiteBalance; const Width kWidth; } // namespace schema } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/propertyschema.h0000644000015700001700000004005012232220161025352 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_PROPERTYSCHEMA_H #define MEDIASCANNER_PROPERTYSCHEMA_H // Media Scanner #include "mediascanner/property.h" namespace mediascanner { namespace schema { // Generic Properties ////////////////////////////////////////////////////////// // Public playback URL of the media, taken from GStreamer struct ExternalUrl : public StringProperty { ExternalUrl(); } extern const kExternalUrl; // Home page URL of the media, taken from GStreamer struct HomepageUrl : public StringProperty { HomepageUrl(); } extern const kHomepageUrl; // Author of the media, taken from Internet struct Author : public TextProperty { Author(); } extern const kAuthor; // License of the media, taken from Internet struct License : public StringProperty { License(); } extern const kLicense; // Copyright of the media, taken from Internet struct Copyright : public StringProperty { Copyright(); } extern const kCopyright; struct CopyrightUri : public StringProperty { CopyrightUri(); } extern const kCopyrightUri; struct LicenseUri : public StringProperty { LicenseUri(); } extern const kLicenseUri; // Description of the media, taken from Internet struct Description : public TextProperty { Description(); } extern const kDescription; // Rating of the media, taken from Internet and User struct Rating : public NumericProperty { Rating(); } extern const kRating; // Title of the media, taken from Internet struct Title : public TextProperty { Title(); } extern const kTitle; // Original, untranslated title of the media, taken from Internet struct OriginalTitle : public TextProperty { OriginalTitle(); } extern const kOriginalTitle; // Age certification of the media, taken from Internet struct Certification : public StringProperty { Certification(); } extern const kCertification; // Region of an age certification struct Region : public StringProperty { Region(); } extern const kRegion; // Comments of the media, taken from Internet struct Comment : public TextProperty { Comment(); } extern const kComment; // Keywords of the media, taken from Internet and User struct Keyword : public StringProperty { Keyword(); } extern const kKeyword; // Languages of the media, taken from Internet struct Language : public StringProperty { Language(); } extern const kLanguage; struct Version : public StringProperty { Version(); } extern const kVersion; struct Organization : public StringProperty { Organization(); } extern const kOrganization; struct Contact : public StringProperty { Contact(); } extern const kContact; struct Grouping : public StringProperty { Grouping(); } extern const kGrouping; struct ApplicationName : public StringProperty { ApplicationName(); } extern const kApplicationName; struct Favourite : public NumericProperty { Favourite(); } extern const kFavourite; struct NetworkAvailable : public NumericProperty { NetworkAvailable(); } extern const kNetworkAvailable; // File Properties ///////////////////////////////////////////////////////////// // Local URL of the media, taken from File System struct Url : public StringProperty { Url(); } extern const kUrl; // Last-change identifier of the media, taken from File System struct ETag : public StringProperty { ETag(); } extern const kETag; // MIME type of the media, taken from GStreamer struct MimeType : public StringProperty { MimeType(); } extern const kMimeType; // Last modification time of the media, taken from File System struct LastModified : public DateTimeProperty { LastModified(); } extern const kLastModified; // File size of the media, taken from File System struct FileSize : public NumericProperty { FileSize(); } extern const kFileSize; // Last accessed time of the media, taken from File System struct LastAccessed : public DateTimeProperty { LastAccessed(); } extern const kLastAccessed; // Media Properties //////////////////////////////////////////////////////////// // Duration of the media, taken from GStreamer struct Duration : public NumericProperty { Duration(); } extern const kDuration; // Seekability of the media, taken from GStreamer struct Seekable : public NumericProperty { Seekable(); } extern const kSeekable; // Last played time of the media, taken from Client Apps struct LastPlayed : public DateTimeProperty { LastPlayed(); } extern const kLastPlayed; // Play count of the media, taken from Client Apps struct PlayCount : public NumericProperty { PlayCount(); } extern const kPlayCount; // Last playing position of the media, taken from Client Apps struct LastPosition : public NumericProperty { LastPosition(); } extern const kLastPosition; // Producer of the media, taken from Internet struct Producer : public TextProperty { Producer(); } extern const kProducer; // Performer of the media, taken from Internet struct Performer : public TextProperty { Performer(); } extern const kPerformer; // Cover URLs for the media, taken from Internet struct Cover : public StringProperty { Cover(); } extern const kCover; // Poster URLs for the media, taken from Internet struct Poster : public StringProperty { Poster(); } extern const kPoster; // Date when the media was first published struct PublicationDate : public DateTimeProperty { PublicationDate(); } extern const kPublicationDate; // Date when the media was first published struct Studio : public StringProperty { Studio(); } extern const kStudio; // Genre of the media, taken from GStreamer struct Genre : public StringProperty { Genre(); } extern const kGenre; struct EncodedBy : public StringProperty { EncodedBy(); } extern const kEncodedBy; struct ContainerFormat : public StringProperty { ContainerFormat(); } extern const kContainerFormat; struct Encoder : public StringProperty { Encoder(); } extern const kEncoder; struct EncoderVersion : public NumericProperty { EncoderVersion(); } extern const kEncoderVersion; // Music Properties //////////////////////////////////////////////////////////// // Bitrate of the audio, taken from GStreamer struct AudioBitRate : public NumericProperty { AudioBitRate(); } extern const kAudioBitRate; // Maximum bitrate of the audio, taken from GStreamer struct MaximumAudioBitRate : public NumericProperty { MaximumAudioBitRate(); } extern const kMaximumAudioBitRate; // Codec of the audio, taken from GStreamer struct AudioCodec : public StringProperty { AudioCodec(); } extern const kAudioCodec; // Sample rate of the audio, taken from GStreamer struct SampleRate : public NumericProperty { SampleRate(); } extern const kSampleRate; // Channel count of the audio, taken from GStreamer struct ChannelCount : public NumericProperty { ChannelCount(); } extern const kChannelCount; // Album artist of the audio, taken from GStreamer struct AlbumArtist : public TextProperty { AlbumArtist(); } extern const kAlbumArtist; // Composer of the audio, taken from GStreamer struct Composer : public TextProperty { Composer(); } extern const kComposer; // Track count of the audio, taken from GStreamer struct TrackCount : public NumericProperty { TrackCount(); } extern const kTrackCount; // Disc number of the audio, taken from GStreamer struct DiscNumber : public NumericProperty { DiscNumber(); } extern const kDiscNumber; // Artist of the music, taken from GStreamer struct Artist : public TextProperty { Artist(); } extern const kArtist; // Album of the music, taken from GStreamer struct Album : public TextProperty { Album(); } extern const kAlbum; // Lyrics of the music, taken from GStreamer struct Lyrics : public TextProperty { Lyrics(); } extern const kLyrics; // Track number of the music, taken from GStreamer struct TrackNumber : public NumericProperty { TrackNumber(); } extern const kTrackNumber; struct AlbumVolumeNumber : public NumericProperty { AlbumVolumeNumber(); } extern const kAlbumVolumeNumber; struct AlbumVolumeCount : public NumericProperty { AlbumVolumeCount(); } extern const kAlbumVolumeCount; struct ISRC : public StringProperty { ISRC(); } extern const kISRC; struct TrackGain : public NumericProperty { TrackGain(); } extern const kTrackGain; struct TrackPeak : public NumericProperty { TrackPeak(); } extern const kTrackPeak; struct AlbumGain : public NumericProperty { AlbumGain(); } extern const kAlbumGain; struct AlbumPeak : public NumericProperty { AlbumPeak(); } extern const kAlbumPeak; struct ReferenceLevel : public NumericProperty { ReferenceLevel(); } extern const kReferenceLevel; struct BeatsPerMinute : public NumericProperty { BeatsPerMinute(); } extern const kBeatsPerMinute; // Image Properties //////////////////////////////////////////////////////////// // Width of the media, taken from GStreamer struct Width : public NumericProperty { Width(); } extern const kWidth; // Height of the media, taken from GStreamer struct Height : public NumericProperty { Height(); } extern const kHeight; // Orientation of the image, taken from GStreamer/EXIF struct ImageOrientation : public StringProperty { class Private; ImageOrientation(); } extern const kImageOrientation; struct HorizontalResolution : public NumericProperty { HorizontalResolution(); } extern const kHorizontalResolution; struct VerticalResolution : public NumericProperty { VerticalResolution(); } extern const kVerticalResolution; // Photo Properties //////////////////////////////////////////////////////////// // Date taken of the photo, taken from GStreamer/EXIF struct DateTaken : public DateTimeProperty { DateTaken(); } extern const kDateTaken; // Camera model of the photo, taken from GStreamer/EXIF struct DeviceModel : public TextProperty { DeviceModel(); } extern const kDeviceModel; // ISO speed of the photo, taken from GStreamer/EXIF struct IsoSpeed : public NumericProperty { IsoSpeed(); } extern const kIsoSpeed; // Exposure time of the photo, taken from GStreamer/EXIF struct ExposureTime : public NumericProperty { ExposureTime(); } extern const kExposureTime; // Flash used of the photo, taken from GStreamer/EXIF struct FlashUsed : public NumericProperty { FlashUsed(); } extern const kFlashUsed; // Camera manufacturer of the photo, taken from GStreamer/EXIF struct DeviceManufacturer : public TextProperty { DeviceManufacturer(); } extern const kDeviceManufacturer; // Focal ratio of the photo, taken from GStreamer/EXIF struct FocalRatio : public NumericProperty { FocalRatio(); } extern const kFocalRatio; // Focal length of the photo, taken from GStreamer/EXIF struct FocalLength : public NumericProperty { FocalLength(); } extern const kFocalLength; // Metering mode of the photo, taken from GStreamer/EXIF struct MeteringMode : public StringProperty { MeteringMode(); } extern const kMeteringMode; // White balance of the photo, taken from GStreamer/EXIF struct WhiteBalance : public StringProperty { WhiteBalance(); } extern const kWhiteBalance; struct GeoLocationName : public StringProperty { GeoLocationName(); } extern const kGeoLocationName; struct GeoLocationLatitude : public NumericProperty { GeoLocationLatitude(); } extern const kGeoLocationLatitude; struct GeoLocationLongitude : public NumericProperty { GeoLocationLongitude(); } extern const kGeoLocationLongitude; struct GeoLocationElevation : public NumericProperty { GeoLocationElevation(); } extern const kGeoLocationElevation; struct GeoLocationCountry : public StringProperty { GeoLocationCountry(); } extern const kGeoLocationCountry; struct GeoLocationCity : public StringProperty { GeoLocationCity(); } extern const kGeoLocationCity; struct GeoLocationSublocation : public StringProperty { GeoLocationSublocation(); } extern const kGeoLocationSublocation; struct GeoLocationHorizontalError : public NumericProperty { GeoLocationHorizontalError(); } extern const kGeoLocationHorizontalError; struct GeoLocationMovementSpeed : public NumericProperty { GeoLocationMovementSpeed(); } extern const kGeoLocationMovementSpeed; struct GeoLocationMovementDirection : public NumericProperty { GeoLocationMovementDirection(); } extern const kGeoLocationMovementDirection; struct GeoLocationCaptureDirection : public NumericProperty { GeoLocationCaptureDirection(); } extern const kGeoLocationCaptureDirection; struct CapturingDigitalZoomRatio : public NumericProperty { CapturingDigitalZoomRatio(); } extern const kCapturingDigitalZoomRatio; struct CapturingExposureProgram : public StringProperty { CapturingExposureProgram(); } extern const kCapturingExposureProgram; struct CapturingExposureMode : public StringProperty { CapturingExposureMode(); } extern const kCapturingExposureMode; struct CapturingExposureCompensation : public NumericProperty { CapturingExposureCompensation(); } extern const kCapturingExposureCompensation; struct CapturingSceneCaptureType : public StringProperty { CapturingSceneCaptureType(); } extern const kCapturingSceneCaptureType; struct CapturingGainAdjustment : public StringProperty { CapturingGainAdjustment(); } extern const kCapturingGainAdjustment; struct CapturingContrast : public StringProperty { CapturingContrast(); } extern const kCapturingContrast; struct CapturingSaturation : public StringProperty { CapturingSaturation(); } extern const kCapturingSaturation; struct CapturingSharpness : public StringProperty { CapturingSharpness(); } extern const kCapturingSharpness; struct CapturingFlashMode : public StringProperty { CapturingFlashMode(); } extern const kCapturingFlashMode; struct CapturingSource : public StringProperty { CapturingSource(); } extern const kCapturingSource; // Movie Properties //////////////////////////////////////////////////////////// // Frame rate of the video, taken from GStreamer struct FrameRate : public NumericProperty { FrameRate(); } extern const kFrameRate; // Show of the video, taken from Internet struct ShowName : public TextProperty { ShowName(); } extern const kShowName; // Episode of the video, taken from Internet struct Episode : public NumericProperty { Episode(); } extern const kEpisode; // Season of the video, taken from Internet struct Season : public NumericProperty { Season(); } extern const kSeason; // Backdrops of the video, taken from Internet struct Backdrop : public StringProperty { Backdrop(); } extern const kBackdrop; // Codec of the video, taken from GStreamer struct VideoCodec : public StringProperty { VideoCodec(); } extern const kVideoCodec; // Bitrate of the video, taken from GStreamer struct VideoBitRate : public NumericProperty { VideoBitRate(); } extern const kVideoBitRate; // Maximum bitrate of the video, taken from GStreamer struct MaximumVideoBitRate : public NumericProperty { MaximumVideoBitRate(); } extern const kMaximumVideoBitRate; // Director of the video, taken from Internet struct Director : public TextProperty { Director(); } extern const kDirector; // Internet Movie Database ID, taken from TMDB struct ImdbId : public StringProperty { ImdbId(); } extern const kImdbId; // The Movie Database ID, taken from TMDB struct TmdbId : public StringProperty { TmdbId(); } extern const kTmdbId; } // namespace schema } // namespace mediascanner #endif // MEDIASCANNER_PROPERTYSCHEMA_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/commitpolicy.cpp0000644000015700001700000001441012232220161025351 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/commitpolicy.h" // Boost C++ #include // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/writablemediaindex.h" namespace mediascanner { CommitPolicy::~CommitPolicy() { } CommitPolicyPtr CommitPolicy::default_policy() { // TODO(M5): Decide on proper default commit policy static const CommitPolicyPtr default_policy(new InstantCommitPolicy); return default_policy; } bool InstantCommitPolicy::OnCreate(const std::vector &, WritableMediaIndex *media_index) { media_index->CommitPendingChanges(); return true; } bool InstantCommitPolicy::OnUpdate(const std::vector &, WritableMediaIndex *media_index) { media_index->CommitPendingChanges(); return true; } bool InstantCommitPolicy::OnRemove(const std::vector &, WritableMediaIndex *media_index) { media_index->CommitPendingChanges(); return true; } DelayedCommitPolicy::DelayedCommitPolicy() : maximum_batch_size_(default_maximum_batch_size()) , maximum_delay_(default_maximum_delay()) { } DelayedCommitPolicy::~DelayedCommitPolicy() { DelayedCommitMap::iterator it = delayed_commits_.begin(); while (it != delayed_commits_.end()) { it->second->cancel_timeout(); it->first->CommitPendingChanges(); delayed_commits_.erase(it++); } } unsigned DelayedCommitPolicy::default_maximum_batch_size() { return 10; } void DelayedCommitPolicy::set_maximum_batch_size(unsigned batch_size) { maximum_batch_size_ = batch_size; UpdateDelayedCommits(); } unsigned DelayedCommitPolicy::maximum_batch_size() const { return maximum_batch_size_; } DelayedCommitPolicy::Duration DelayedCommitPolicy::default_maximum_delay() { return boost::posix_time::seconds(5); } void DelayedCommitPolicy::set_maximum_delay(Duration delay) { maximum_delay_ = delay; UpdateDelayedCommits(); } DelayedCommitPolicy::Duration DelayedCommitPolicy::maximium_delay() const { return maximum_delay_; } bool DelayedCommitPolicy::OnCreate(const std::vector &media_urls, WritableMediaIndex *media_index) { return PushDelayedCommit(media_urls, media_index); } bool DelayedCommitPolicy::OnUpdate(const std::vector &media_urls, WritableMediaIndex *media_index) { return PushDelayedCommit(media_urls, media_index); } bool DelayedCommitPolicy::OnRemove(const std::vector &media_urls, WritableMediaIndex *media_index) { return PushDelayedCommit(media_urls, media_index); } void DelayedCommitPolicy::UpdateDelayedCommits() { const TimeStamp now = Clock::universal_time(); DelayedCommitMap::iterator it = delayed_commits_.begin(); while (it != delayed_commits_.end()) { WritableMediaIndex *const media_index = it->first; const DelayedCommitPtr delayed_commit = it->second; delayed_commit->cancel_timeout(); if (delayed_commit->is_due(this, now)) { media_index->CommitPendingChanges(); delayed_commits_.erase(it++); } else { delayed_commit->reset_timeout(this); ++it; } } } bool DelayedCommitPolicy::PushDelayedCommit (const std::vector &media_urls, WritableMediaIndex *media_index) { DelayedCommitMap::iterator it = delayed_commits_.find(media_index); if (it == delayed_commits_.end()) { DelayedCommitPtr commit(new DelayedCommit(this, media_index)); it = delayed_commits_.insert(std::make_pair(media_index, commit)).first; } else { it->second->increase_pressure(media_urls.size()); } if (not it->second->is_due(this, Clock::universal_time())) return false; it->second->cancel_timeout(); media_index->CommitPendingChanges(); delayed_commits_.erase(it); return true; } DelayedCommitPolicy::DelayedCommit::DelayedCommit(DelayedCommitPolicy *policy, WritableMediaIndex *index) : media_index_(index) , due_time_(Clock::universal_time() + policy->maximium_delay()) , num_commits_(0) , timeout_id_(0) { reset_timeout(policy); } DelayedCommitPolicy::DelayedCommit::~DelayedCommit() { cancel_timeout(); } bool DelayedCommitPolicy::DelayedCommit::is_due(DelayedCommitPolicy *policy, TimeStamp t) const { return due_time_ >= t || num_commits_ >= policy->maximum_batch_size(); } void DelayedCommitPolicy::DelayedCommit::cancel_timeout() { if (timeout_id_) { Source::Remove(timeout_id_); timeout_id_ = 0; } } void DelayedCommitPolicy::DelayedCommit::reset_timeout (DelayedCommitPolicy *policy) { cancel_timeout(); const Source::OneCallFunction commit_function = [this] () { media_index_->CommitPendingChanges(); timeout_id_ = 0; }; due_time_ = Clock::universal_time() + policy->maximium_delay(); timeout_id_ = Timeout::AddOnce(policy->maximium_delay(), commit_function); } void DelayedCommitPolicy::DelayedCommit::increase_pressure(size_t amount) { num_commits_ += amount; } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/settings.cpp0000644000015700001700000002062212232220161024503 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/settings.h" // Boost C++ #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/dbustypes.h" #include "mediascanner/logging.h" #include "mediascanner/utilities.h" namespace mediascanner { // Boost C++ using boost::algorithm::starts_with; // Context specific logging domains static const logging::Domain kWarning("warning/settings", logging::warning()); // Key constants const Settings::Key Settings::kMandatoryContainers("mandatory-containers"); const Settings::Key Settings::kMandatoryDecoders("mandatory-decoders"); const Settings::Key Settings::kMetadataSources("metadata-sources"); const Settings::Key Settings::kMediaRoots("media-roots"); // FIXME(M5): Apparently "dbus" is not the proper namespace for this things namespace dbus { template<> struct BoxedTypeTrait { typedef std::pair boxed_type; }; template<> struct BoxedTypeTrait { typedef boost::tuples::tuple boxed_type; }; template<> class Type : public BoxedType { }; template<> class Type : public BoxedType { }; template<> Settings::MediaFormat BoxedType::make_unboxed(boxed_type value) { return Settings::MediaFormat(value.first, value.second); } template<> Settings::MetadataSource BoxedType::make_unboxed(boxed_type value) { return Settings::MetadataSource(boost::get<0>(value), boost::get<1>(value), boost::get<2>(value)); } } // namespace dbus // TODO(M4): Handle change notifications Settings::Settings() : settings_(take(g_settings_new(MEDIASCANNER_SETTINGS_ID))) { } template T Settings::lookup(const Key &key) const { if (const Wrapper settings_value = take(g_settings_get_value(settings_.get(), key.name_.c_str()))) return dbus::Type::make_value(settings_value.get()); return T(); } static void on_change(void *data) { (*static_cast(data))(); } unsigned Settings::connect(const KeyName &key, const ChangeListener &listener) const { return g_signal_connect_data (settings_.get(), ("changed::" + key.name_).c_str(), G_CALLBACK(&on_change), new ChangeListener(listener), ClosureNotify, static_cast(0)); } void Settings::disconnect(unsigned handler_id) const { g_signal_handler_disconnect(settings_.get(), handler_id); } Settings::MediaFormatList Settings::mandatory_containers() const { return lookup(kMandatoryContainers); } Settings::MediaFormatList Settings::mandatory_decoders() const { return lookup(kMandatoryDecoders); } Settings::MetadataSourceList Settings::metadata_sources() const { return lookup(kMetadataSources); } Settings::StringList Settings::media_root_urls() const { return lookup(kMediaRoots); } static std::string user_dir_url(GUserDirectory dir) { switch (dir) { case G_USER_DIRECTORY_DESKTOP: return "user:desktop"; case G_USER_DIRECTORY_DOCUMENTS: return "user:documents"; case G_USER_DIRECTORY_DOWNLOAD: return "user:download"; case G_USER_DIRECTORY_MUSIC: return "user:music"; case G_USER_DIRECTORY_PICTURES: return "user:pictures"; case G_USER_DIRECTORY_PUBLIC_SHARE: return "user:public"; case G_USER_DIRECTORY_TEMPLATES: return "user:templates"; case G_USER_DIRECTORY_VIDEOS: return "user:videos"; case G_USER_N_DIRECTORIES: break; } return std::string(); } static std::string resolve_url(const std::string &url) { if (starts_with(url, "user:")) { for (unsigned i = 0; i < G_USER_N_DIRECTORIES; ++i) { const GUserDirectory dir = static_cast(i); if (url == user_dir_url(dir)) { const char *const path = g_get_user_special_dir(dir); if (not path || not g_file_test(path, G_FILE_TEST_IS_DIR)) break; return path; } } return std::string(); } Wrapper file = take(g_file_new_for_uri(url.c_str())); if (not g_file_is_native(file.get())) return std::string(); if (G_FILE_TYPE_DIRECTORY != g_file_query_file_type (file.get(), G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, nullptr)) return std::string(); return safe_string(g_file_get_path(file.get())); } Settings::StringList Settings::media_root_paths() const { const StringList urls = media_root_urls(); StringList paths; paths.reserve(urls.size()); for (const std::string &root_url: urls) { std::string root_path = resolve_url(root_url); if (root_path.empty()) { kWarning("Ignoring unsupported media root <{1}>") % root_url; continue; } paths.push_back(root_path); } return paths; } std::vector Settings::LoadMetadataSources() const { return LoadMetadataSources(metadata_sources()); } std::vector Settings::LoadMetadataSources (const MetadataSourceList &sources) { const Wrapper registry = wrap(grl_registry_get_default()); std::vector available_sources; std::set loaded_plugins; for (const Settings::MetadataSource &s: sources) { if (s.config.empty()) continue; Wrapper config = take(grl_config_new(s.plugin_id.c_str(), s.source_id.c_str())); typedef Settings::MetadataSource::Config::value_type ConfigParam; for (const ConfigParam &p: s.config) { grl_config_set_string(config.get(), p.first.c_str(), p.second.c_str()); } Wrapper error; if (not grl_registry_add_config(registry.get(), config.release(), error.out_param())) { const std::string error_message = to_string(error); kWarning("Cannot configure the {1} plugin's {2} source: {3}") % s.plugin_id % s.source_id % error_message; } } for (const Settings::MetadataSource &s: sources) { Wrapper error; if (loaded_plugins.find(s.plugin_id) == loaded_plugins.end()) { if (not grl_registry_load_plugin_by_id(registry.get(), s.plugin_id.c_str(), error.out_param())) { const std::string error_message = to_string(error); kWarning("Cannot load metadata plugin {1}: {2}") % s.plugin_id % error_message; continue; } loaded_plugins.insert(s.plugin_id); } if (not grl_registry_lookup_source(registry.get(), s.source_id.c_str())) { kWarning("No such metadata source: {1}") % s.source_id; continue; } available_sources.push_back(s.source_id); } return available_sources; } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/propertyprivate.h0000644000015700001700000001165212232220161025572 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_PROPERTYPRIVATE_H #define MEDIASCANNER_PROPERTYPRIVATE_H // Media Scanner Library #include "mediascanner/property.h" namespace mediascanner { class Property::Private { friend class Property; public: Private(const String &field_name, const MetadataKey &metadata_key, Category category, MergeStrategy merge_strategy, const StreamInfoFunction &merge_stream_info) : field_name_(field_name) , metadata_key_(metadata_key) , category_(category) , merge_strategy_(merge_strategy) , merge_stream_info_(merge_stream_info) { } virtual ~Private() { } protected: virtual LuceneFields MakeFields(const Value &value) const = 0; virtual Value TransformFields(const LuceneFields &fields) const = 0; virtual Value TransformSingleField(Lucene::FieldablePtr field) const = 0; virtual Lucene::QueryPtr MakeTermQuery(const Value &value) const = 0; virtual Lucene::QueryPtr MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const = 0; virtual bool TransformGriloValue(const GValue *input, Value *output) const = 0; virtual bool TransformDBusVariant(GVariant *input, Value *output) const = 0; virtual Wrapper MakeGriloValue(const Value &value) const; virtual bool supports_full_text_search() const = 0; protected: /// Value of Property::field_name(). const String field_name_; /// Value of Property::metadata_key(). MetadataKey metadata_key_; /// Value of Property::category(). Category category_; /// How to resolve merge conflicts for this property. MergeStrategy merge_strategy_; /// Function for resolving stream information. StreamInfoFunction merge_stream_info_; }; template class GenericProperty::Private : public Property::Private { public: typedef Property::Private inhertied; Private(const String &field_name, const MetadataKey &metadata_key, Category category, MergeStrategy merge_strategy, const StreamInfoFunction &merge_stream_info) : inhertied(field_name, metadata_key, category, merge_strategy, merge_stream_info) { } protected: LuceneFields MakeFields(const Value &value) const; Lucene::FieldablePtr MakeSingleField(const Value &value) const; Value TransformFields(const LuceneFields &fields) const; Value TransformSingleField(Lucene::FieldablePtr field) const; Lucene::QueryPtr MakeTermQuery(const Value &value) const; Lucene::QueryPtr MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const; bool TransformGriloValue(const GValue *input, Value *output) const; virtual bool TransformSafeGriloValue(const GValue *input, Value *output) const; bool TransformDBusVariant(GVariant *input, Value *output) const; bool supports_full_text_search() const; }; class DateTimeProperty::Private : public DateTimeProperty::inherited::Private { public: typedef DateTimeProperty::inherited::Private inherited; Private(const String &field_name, const MetadataKey &metadata_key, Category category, MergeStrategy merge_strategy, const StreamInfoFunction &merge_stream_info) : inherited(field_name, metadata_key, category, merge_strategy, merge_stream_info) { } bool TransformGriloValue(const GValue *input, Value *output) const; Wrapper MakeGriloValue(const Value &value) const; }; } // namespace mediascanner #endif // MEDIASCANNER_PROPERTYPRIVATE_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbusutils.h0000644000015700001700000011315412232220161024331 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_DBUSUTILS_H #define MEDIASCANNER_DBUSUTILS_H // Boost C++ #include // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/dbustypes.h" #include "mediascanner/glibutils.h" #include "mediascanner/utilities.h" namespace mediascanner { namespace dbus { //////////////////////////////////////////////////////////////////////////////// using boost::tuples::element; template class Member; typedef Member MethodInfo; typedef std::shared_ptr MethodInfoPtr; typedef Member PropertyInfo; typedef std::shared_ptr PropertyInfoPtr; typedef Member SignalInfo; typedef std::shared_ptr SignalInfoPtr; typedef std::shared_ptr InterfaceProxyPtr; typedef std::shared_ptr InterfaceSkeletonPtr; typedef std::shared_ptr MethodInvocationPtr; //////////////////////////////////////////////////////////////////////////////// template struct SkeletonTrait; template class Member { public: /** * @brief Type of the GDBus introspection structure for this members. */ typedef InfoType dbus_info_type; typedef typename SkeletonTrait::type skeleton_type; protected: Member(const std::string &name, const skeleton_type *skeleton) : name_(name) , skeleton_(skeleton) { } public: virtual ~Member() { } const std::string& name() const { return name_; } const skeleton_type* skeleton() const { return skeleton_; } virtual Wrapper info() const = 0; private: std::string name_; const skeleton_type *const skeleton_; }; //////////////////////////////////////////////////////////////////////////////// /** * @brief Description of an D-Bus interface's method or signal argument. */ template class Argument { public: typedef T value_type; typedef Type dbus_type; explicit Argument(const std::string &name) : name_(name) { } const std::string& name() const { return name_; } const Signature& signature() const { return dbus_type::signature(); } static GVariant* make_variant(const value_type &value) { return Type::make_variant(value); } Wrapper info() const { if (info_ == nullptr) { const GDBusArgInfo info = { 1, g_strdup(name().c_str()), g_variant_type_dup_string(signature()), nullptr }; info_ = wrap_static(&info); } return info_; } private: std::string name_; mutable Wrapper info_; }; //////////////////////////////////////////////////////////////////////////////// namespace internal { template class ArgumentTail { typedef ArgumentTail predecessor; public: static void ref(const T *args, GDBusArgInfo **info) { info[N - 1] = boost::tuples::get(*args).info().dup(); predecessor::ref(args, info); } static void ref(GDBusArgInfo *const *other, GDBusArgInfo **info) { info[N - 1] = g_dbus_arg_info_ref(other[N - 1]); predecessor::ref(other, info); } static void unref(GDBusArgInfo **info) { g_dbus_arg_info_unref(info[N - 1]); predecessor::unref(info); } }; template struct ArgumentTail { static void ref(const T *, GDBusArgInfo **) { } static void ref(GDBusArgInfo *const *, GDBusArgInfo **) { } static void unref(GDBusArgInfo **/*info*/) { } }; template struct ArgumentTrait { typedef Argument type; }; template<> struct ArgumentTrait { typedef boost::tuples::null_type type; }; } // namespace internal //////////////////////////////////////////////////////////////////////////////// template class ArgumentList : public boost::tuples::tuple ::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type> { public: typedef boost::tuples::tuple ::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type, typename internal::ArgumentTrait::type> inherited; typedef boost::tuples::tuple value_type; typedef boost::tuples::length length; typedef T0 value0_type; typedef T1 value1_type; typedef T2 value2_type; typedef T3 value3_type; typedef T4 value4_type; typedef T5 value5_type; typedef T6 value6_type; typedef T7 value7_type; private: typedef internal::ArgumentTail tail; typedef boost::tuples::null_type null_type; public: ArgumentList() : inherited() , info_(nullptr) { } explicit ArgumentList (const typename internal::ArgumentTrait::type &a0, const typename internal::ArgumentTrait::type &a1 = null_type(), const typename internal::ArgumentTrait::type &a2 = null_type(), const typename internal::ArgumentTrait::type &a3 = null_type(), const typename internal::ArgumentTrait::type &a4 = null_type(), const typename internal::ArgumentTrait::type &a5 = null_type(), const typename internal::ArgumentTrait::type &a6 = null_type(), const typename internal::ArgumentTrait::type &a7 = null_type()) : inherited(a0, a1, a2, a3, a4, a5, a6, a7) , info_(nullptr) { } ~ArgumentList() { if (info_) { tail::unref(info_); g_free(info_); } } GDBusArgInfo** info() const { if (info_ == nullptr) { GDBusArgInfo **info = g_new(GDBusArgInfo *, length::value + 1); info[length::value] = nullptr; tail::ref(this, info); info_ = info; } return info_; } static GDBusArgInfo** dup(GDBusArgInfo *const *const other) { GDBusArgInfo **info = g_new(GDBusArgInfo *, length::value + 1); info[length::value] = nullptr; tail::ref(other, info); return info; } static value_type make_value(GVariant *variant) { return Type::make_value(variant); } static GVariant* make_variant(const value_type &value) { return Type::make_variant(value); } static const Signature& signature() { return Type::signature(); } private: mutable GDBusArgInfo **info_; }; //////////////////////////////////////////////////////////////////////////////// class MethodSkeleton { public: virtual ~MethodSkeleton() { } virtual void Invoke(MethodInvocation call) const = 0; }; template<> struct SkeletonTrait { typedef MethodSkeleton type; }; template< typename InputArguments = ArgumentList<>, typename OutputArguments = ArgumentList<> > class Method : public Member { public: typedef InputArguments input_args_type; typedef OutputArguments output_args_type; protected: Method(const std::string &name, MethodSkeleton *skeleton, const input_args_type &input = input_args_type(), const output_args_type &output = output_args_type()) : Member(name, skeleton) , input_(input) , output_(output) { } public: Wrapper info() const { if (not info_) { const GDBusMethodInfo static_info = { 1, g_strdup(name().c_str()), input_args_type::dup(input_.info()), output_args_type::dup(output_.info()), nullptr }; info_ = wrap_static(&static_info); } return info_; } private: const input_args_type input_; const output_args_type output_; mutable Wrapper info_; }; class MethodInvocation { public: explicit MethodInvocation(GDBusMethodInvocation *invocation) : invocation_(invocation) , origin_(g_thread_self()) { } bool is_active() const { return invocation_; } uint32_t serial() const { return g_dbus_message_get_serial(message()); } std::string sender() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_sender(invocation_); } std::string target() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_object_path(invocation_); } std::string interface_name() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_interface_name(invocation_); } std::string method_name() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_method_name(invocation_); } GVariant* parameters() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_parameters(invocation_); } void return_error(GQuark domain, int code, const std::string &message); void return_dbus_error(const std::string &error_name, const std::string &error_message); void return_error(Wrapper error); void emit_signal(const SignalInfo *signal, GVariant *args, GError **error) const; GDBusMethodInvocation* dup() const; GDBusMethodInvocation* swap(GDBusMethodInvocation *other = nullptr); // NOLINT:build/include_what_you_use protected: GDBusConnection* connection() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_connection(invocation_); } GDBusMessage* message() const { BOOST_ASSERT(is_active()); return g_dbus_method_invocation_get_message(invocation_); } void assert_called_from_original_thread() const { BOOST_ASSERT(g_thread_self() == origin_); } private: GDBusMethodInvocation *invocation_; GThread *const origin_; }; template class MethodProxyCall { }; using boost::tuples::null_type; template class MethodProxyCall { public: void operator()(const A0 &a0, GError **error) const { const typename MethodProxy::input_value_type args(a0); static_cast(this)->CallAndWait(args, error); } }; template class MethodProxyCall { public: void operator()(const A0 &a0, const A1 &a1, GError **error) const { const typename MethodProxy::input_value_type args(a0, a1); static_cast(this)->CallAndWait(args, error); } }; template class MethodProxyCall { public: R0 operator()(const A0 &a0, GError **error) const { const typename MethodProxy::input_value_type args(a0); return boost::tuples::get<0> (static_cast(this)-> CallAndWait(args, error)); } }; template struct MethodProxyCallTrait { typedef Method method_type; typedef typename method_type::input_args_type input_args_type; typedef typename method_type::output_args_type output_args_type; typedef MethodProxyCall type; }; template< typename InputArguments = ArgumentList<>, typename OutputArguments = ArgumentList<> > class MethodProxy : public Method , public MethodProxyCallTrait, InputArguments, OutputArguments>::type { public: typedef Method method_type; typedef typename method_type::input_args_type input_args_type; typedef typename input_args_type::value_type input_value_type; typedef typename method_type::output_args_type output_args_type; typedef typename output_args_type::value_type output_value_type; typedef boost::posix_time::time_duration timeout_type; MethodProxy(InterfaceProxy *proxy, const std::string &name, const input_args_type &input = input_args_type(), const output_args_type &output = output_args_type()) : method_type(name, nullptr, input, output) , proxy_(proxy) , flags_(G_DBUS_CALL_FLAGS_NONE) , timeout_(boost::date_time::not_a_date_time) { } output_value_type CallAndWait(const input_value_type &args, GError **error) const; template MethodProxy& operator[](const T &value) { set_attribute(value); return *this; } template MethodProxy operator[](const T &value) const { MethodProxy dup(*this); dup.set_attribute(value); return dup; } private: void set_attribute(Wrapper cancellable) { cancellable_ = cancellable; } void set_attribute(GDBusCallFlags flags) { flags_ = flags; } void set_attribute(timeout_type timeout) { timeout_ = timeout; } int timeout_ms() const { if (timeout_.is_not_a_date_time()) return -1; return timeout_.total_milliseconds(); } InterfaceProxy *const proxy_; Wrapper cancellable_; GDBusCallFlags flags_; timeout_type timeout_; }; template< typename InputArguments = ArgumentList<>, typename OutputArguments = ArgumentList<> > class MethodImplementation : public Method , protected MethodSkeleton { public: typedef Method method_type; typedef typename method_type::input_args_type input_args_type; typedef typename input_args_type::value_type input_value_type; typedef typename method_type::output_args_type output_args_type; typedef typename output_args_type::value_type output_value_type; class Invocation : public MethodInvocation { public: explicit Invocation(GDBusMethodInvocation *other) : MethodInvocation(other) { } explicit Invocation(MethodInvocation *other) : MethodInvocation(other->swap()) { } template typename element::type arg() const { const Wrapper child_variant = take(g_variant_get_child_value(parameters(), N)); typedef typename element::type return_type; return Type::make_value(child_variant.get()); } template typename element::type arg (ErrorDetailType *error) const { const Wrapper child_variant = take(g_variant_get_child_value(parameters(), N)); typedef typename element::type return_type; return Type::make_value(child_variant.get(), error); } void return_value(const output_value_type &value) { GVariant *const variant = output_args_type::make_variant(value); g_dbus_method_invocation_return_value(swap(), variant); } }; typedef std::shared_ptr InvocationPtr; protected: explicit MethodImplementation (InterfaceSkeleton *, const std::string &name, const input_args_type &input = input_args_type(), const output_args_type &output = output_args_type()) : method_type(name, this, input, output) { } void Invoke(MethodInvocation invocation) const { Invoke(InvocationPtr(new Invocation(&invocation))); } virtual void Invoke(InvocationPtr invocation) const = 0; }; //////////////////////////////////////////////////////////////////////////////// class PropertySkeleton { public: virtual ~PropertySkeleton() { } virtual bool readable() const = 0; virtual bool writable() const = 0; /** * @brief Retrieves the value of this D-Bus property. * @param[in] sender The sender of this D-Bus request. * @param[in] target The target of this D-Bus request. * @param[out] error Location for storing a possible error. * @return The current value of this property. */ virtual GVariant* GetValue(const std::string &sender, const std::string &target, GError **error) const = 0; /** * @brief Updates the value of this D-Bus property. * @param[in] sender The sender of this D-Bus request. * @param[in] target The target of this D-Bus request. * @param[in] value The new value for this property. * @param[out] error Location for storing a possible error. * @return @c true on success */ virtual bool SetValue(const std::string &sender, const std::string &target, GVariant *value, GError **error) const = 0; }; template<> struct SkeletonTrait { typedef PropertySkeleton type; }; template class Property : public Member { public: typedef T value_type; typedef Type dbus_type; protected: Property(const std::string &name, PropertySkeleton *skeleton) : Member(name, skeleton) { } public: /** * @brief Predicate if this property can be read. * @see GetValue(), @flags() */ static bool readable() { return Flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE; } /** * @brief Predicate if this property can be written. * @see SetValue(), @flags() */ static bool writable() { return Flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE; } Wrapper info() const { if (not info_) { const GDBusPropertyInfo static_info = { 1, g_strdup(name().c_str()), g_variant_type_dup_string(dbus_type::signature()), Flags, nullptr }; info_ = wrap_static(&static_info); } return info_; } private: mutable Wrapper info_; }; template class PropertyProxy : public Property { public: typedef Property property_type; explicit PropertyProxy(const std::string &name) : property_type(name, nullptr) { } }; template class ReadOnlyPropertyProxy : public PropertyProxy { public: typedef PropertyProxy inherited; explicit ReadOnlyPropertyProxy(const std::string &name) : inherited(name) { } T ReadCachedValue(const InterfaceProxy *proxy) const; bool ReadValue(const InterfaceProxy *proxy, T *value) const; }; template class PropertyImplementation : public Property , protected PropertySkeleton { public: typedef Property property_type; explicit PropertyImplementation(const std::string &name) : property_type(name, this) { } bool readable() const { return property_type::readable(); } bool writable() const { return property_type::writable(); } }; template class ReadOnlyPropertyImplementation : public PropertyImplementation { public: typedef PropertyImplementation inherited; explicit ReadOnlyPropertyImplementation(const std::string &name) : inherited(name) { } bool SetValue(const std::string &, const std::string &, GVariant *, GError **error) const { g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Property \"%s\" is for reading only", inherited::name().c_str()); return false; } }; //////////////////////////////////////////////////////////////////////////////// class SignalSkeleton { }; template<> struct SkeletonTrait { typedef SignalSkeleton type; }; template< typename Arguments = ArgumentList<> > class Signal : public Member { public: typedef Arguments args_type; protected: Signal(const std::string &name, SignalSkeleton *skeleton, const args_type &args = args_type()) : Member(name, skeleton) , args_(args) { } protected: Wrapper info() const { if (not info_) { const GDBusSignalInfo static_info = { 1, g_strdup(name().c_str()), args_type::dup(args_.info()), nullptr }; info_ = wrap_static(&static_info); } return info_; } private: const args_type args_; mutable Wrapper info_; }; template< typename Arguments = ArgumentList<> > class SignalProxy : public Signal { public: typedef Signal signal_type; typedef typename signal_type::args_type args_type; typedef typename args_type::value_type value_type; explicit SignalProxy(const std::string &name, const args_type &args = args_type()) : Signal(name, nullptr, args) { } }; template< typename Arguments = ArgumentList<> > class SignalImplementation : public Signal , protected SignalSkeleton { public: typedef Signal signal_type; typedef typename signal_type::args_type args_type; typedef typename args_type::value_type value_type; explicit SignalImplementation(const std::string &name, const args_type &args = args_type()) : Signal(name, this, args) { } void emit_result(const value_type &args, MethodInvocationPtr invocation) const { // FIXME(M5): Handle errors on signal emission invocation->emit_signal(this, args_type::make_variant(args), nullptr); } void emit_result_on_idle(const value_type &args, MethodInvocationPtr invocation) const { Idle::AddOnce(std::bind(&SignalImplementation::emit_result, this, args, invocation)); } void emit_signal(const value_type &args, const std::string &object_path, const std::string &interface_name, Wrapper connection) const { // FIXME(M5): Handle errors on signal emission // FIXME(M5): Find better way to pass object_path and interface_name. Wrapper error; if (not g_dbus_connection_emit_signal(connection.get(), nullptr, object_path.c_str(), interface_name.c_str(), Signal::name().c_str(), args_type::make_variant(args), error.out_param())) std::cerr << to_string(error)<< std::endl; } void emit_signal_on_idle(const value_type &args, const std::string &object_path, const std::string &interface_name, Wrapper connection) const { Idle::AddOnce(std::bind(&SignalImplementation::emit_signal, this, args, object_path, interface_name, connection)); } }; //////////////////////////////////////////////////////////////////////////////// class InterfaceInfo { protected: explicit InterfaceInfo(const std::string &name) : name_(name) { } public: const std::string& name() const { return name_; } Wrapper info() const; protected: template std::shared_ptr register_method(const A0 &a0) { const std::shared_ptr method(new T(a0)); add_method(method); return method; } template std::shared_ptr register_property(const A0 &a0) { const std::shared_ptr property(new T(a0)); add_property(property); return property; } template std::shared_ptr register_signal() { const std::shared_ptr signal(new T); add_signal(signal); return signal; } void add_method(MethodInfoPtr method); void add_property(PropertyInfoPtr property); void add_signal(SignalInfoPtr signal); MethodInfoPtr find_method(const std::string &name) const; PropertyInfoPtr find_property(const std::string &name) const; SignalInfoPtr find_signal(const std::string &name) const; private: typedef std::map MethodInfoMap; typedef std::map PropertyInfoMap; typedef std::map SignalInfoMap; std::string name_; MethodInfoMap methods_; PropertyInfoMap properties_; SignalInfoMap signals_; mutable Wrapper info_; }; //////////////////////////////////////////////////////////////////////////////// class InterfaceProxy : public InterfaceInfo { public: explicit InterfaceProxy(const std::string &name) : InterfaceInfo(name) { } virtual ~InterfaceProxy() {} bool ConnectAndWait(Wrapper connection, const std::string &service_name, const std::string &object_path, Wrapper cancellable, GError **error); bool ConnectAndWait(Wrapper connection, const std::string &service_name, const std::string &object_path, GError **error) { return ConnectAndWait(connection, service_name, object_path, Wrapper(), error); } void connect(GBusType bus_type, const std::string &service_name, const std::string &object_path, Wrapper cancellable = Wrapper()); void connect(Wrapper connection, const std::string &service_name, const std::string &object_path, Wrapper cancellable = Wrapper()); Wrapper ReadPropertyValue(const std::string &property_name) const; Wrapper handle() const { return handle_; } Wrapper connection() const; std::string interface_name() const; std::string service_name() const; std::string object_path() const; protected: virtual void connected(); virtual void connect_failed(Wrapper error) const; private: static void on_connect_for_bus(GObject *object, GAsyncResult *result, void *data); static void on_connect(GObject *object, GAsyncResult *result, void *data); Wrapper handle_; }; //////////////////////////////////////////////////////////////////////////////// class InterfaceSkeleton : public InterfaceInfo { public: explicit InterfaceSkeleton(const std::string &name) : InterfaceInfo(name) { } const MethodSkeleton *find_method_skeleton(const std::string &name) const; const PropertySkeleton* find_property_skeleton (const std::string &name) const; void InvokeMethod(MethodInvocation invocation) const; GVariant* GetProperty(const std::string &sender, const std::string &target, const std::string &name, GError **error) const; bool SetProperty(const std::string &sender, const std::string &target, const std::string &name, GVariant *value, GError **error) const; }; //////////////////////////////////////////////////////////////////////////////// template< typename InterfaceType, typename InputArguments = ArgumentList<>, typename OutputArguments = ArgumentList<> > struct MethodTrait; template struct MethodTrait { typedef MethodProxy type; }; template struct MethodTrait { typedef MethodImplementation type; }; // TODO(M5): Maybe also use for PropertyInfo class? enum PropertyAccess { ReadOnly = 1, WriteOnly = 2, ReadWrite = 3 }; template struct PropertyTrait; template struct PropertyTrait { typedef ReadOnlyPropertyProxy type; }; template struct PropertyTrait { typedef ReadOnlyPropertyImplementation type; }; template< typename InterfaceType, typename Arguments = ArgumentList<> > struct SignalTrait; template struct SignalTrait { typedef SignalProxy type; }; template struct SignalTrait { typedef SignalImplementation type; }; //////////////////////////////////////////////////////////////////////////////// class Service { public: Service(); virtual ~Service(); Wrapper connection() const; void Connect(const std::string &service_name); void Disconnect(); void RegisterObject(const std::string &path, InterfaceSkeletonPtr skeleton); InterfaceSkeletonPtr find_object(const std::string &name) const; protected: virtual void Connected(); virtual void NameAcquired(); virtual void NameLost(); virtual void InvokeMethod(GDBusMethodInvocation *call); virtual GVariant* GetProperty(const std::string &sender, const std::string &target, const std::string &interface, const std::string &property, GError **error); virtual bool SetProperty(const std::string &sender, const std::string &target, const std::string &interface, const std::string &property, GVariant *value, GError **error); private: static void OnDBusConnected(GDBusConnection *connection, const char *name, void *data); static void OnDBusNameAcquired(GDBusConnection *connection, const char *name, void *data); static void OnDBusNameLost(GDBusConnection *connection, const char *name, void *data); static void OnMethodCall(GDBusConnection *, const char *sender, const char *target, const char *interface, const char *method, GVariant *parameters, GDBusMethodInvocation *call, void *data); static GVariant* OnGetProperty(GDBusConnection *, const char *sender, const char *target, const char *interface, const char *property, GError **error, void *data); static gboolean OnSetProperty(GDBusConnection *, const char *sender, const char *target, const char *interface, const char *property, GVariant *value, GError **error, void *data); private: unsigned name_id_; Wrapper connection_; struct ObjectInfo { ObjectInfo(unsigned object_id, InterfaceSkeletonPtr skeleton) : object_id(object_id) , skeleton(skeleton) { } unsigned object_id; InterfaceSkeletonPtr skeleton; }; typedef std::map ObjectMap; ObjectMap objects_; }; //////////////////////////////////////////////////////////////////////////////// template< typename In, typename Out> inline typename MethodProxy::output_value_type MethodProxy::CallAndWait(const input_value_type &args, GError **error) const { const Wrapper result = take (g_dbus_proxy_call_sync(proxy_->handle().get(), method_type::name().c_str(), input_args_type::make_variant(args), flags_, timeout_ms(), cancellable_.get(), error)); return output_args_type::make_value(result.get()); } template inline T ReadOnlyPropertyProxy::ReadCachedValue (const InterfaceProxy *proxy) const { return Type::make_value(g_dbus_proxy_get_cached_property (proxy->handle().get(), PropertyInfo::name().c_str())); } template inline bool ReadOnlyPropertyProxy::ReadValue(const InterfaceProxy *proxy, T *value) const { const Wrapper boxed_value = proxy->ReadPropertyValue(PropertyInfo::name()); if (not boxed_value) return false; const Wrapper unboxed_value = take(g_variant_get_variant(boxed_value.get())); *value = Type::make_value(unboxed_value.get()); return true; } std::string ConnectionName(Wrapper connection); } // namespace dbus } // namespace mediascanner #endif // MEDIASCANNER_DBUSUTILS_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/utilities.cpp0000644000015700001700000000555612232220161024667 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include // C Standard Library #include #include #include // C++ Standard Library #include // Media Scanner Library #include "mediascanner/locale.h" #include "mediascanner/property.h" namespace mediascanner { void abort_with_backtrace() { static const int max_depth = 100; void *stack_addrs[max_depth]; const int depth = backtrace(stack_addrs, max_depth); char **symbols = backtrace_symbols(stack_addrs, depth); std::string backtrace; if (symbols) { for (int i = 1; i < depth; ++i) { std::string line = symbols[i]; std::string::size_type begin = line.find('('); std::string::size_type end = line.find('+', begin); if (end - begin > 1 && begin++ != std::string::npos && end != std::string::npos) { int status; char *const name = abi::__cxa_demangle (line.substr(begin, end - begin).c_str(), nullptr, nullptr, &status); if (name) { if (status == 0) line = line.substr(0, begin) + name + line.substr(end); free(name); } } backtrace += line + "\n"; } const size_t len = write(1, backtrace.data(), backtrace.length()); BOOST_ASSERT(len == backtrace.length()); free(symbols); } else { const std::string message = "No backtrace available.\n"; const size_t len = write(1, message.data(), message.length()); BOOST_ASSERT(len == message.length()); } abort(); } std::wstring safe_wstring(const char *str) { return str ? ToUnicode(str) : std::wstring(); } std::ostream &operator<<(std::ostream &os, const Property &p) { return os << "property: " << p.field_name(); } } // namespace mediascanner namespace std { ostream& operator<<(ostream &os, const wstring &s) { return os << ::mediascanner::FromUnicode(s); } } // namespace std mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/metadataresolver.h0000644000015700001700000000322112232220161025646 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_METADATARESOLVER_H #define MEDIASCANNER_METADATARESOLVER_H // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { class MetadataResolver { class Private; struct Request; public: typedef std::function StoreFunction; MetadataResolver(); ~MetadataResolver(); bool SetupSources(const std::vector &sources); void WaitForFinished(); void cancel(); void push(const std::string &url, const MediaInfo &metadata, const StoreFunction &store_metadata); bool is_idle() const; private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_METADATARESOLVER_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/refreshpolicy.h0000644000015700001700000000446212232220161025172 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_REFRESHPOLICY_H #define MEDIASCANNER_REFRESHPOLICY_H // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { /** * @brief Shared pointer holding a RefreshPolicy. */ typedef std::shared_ptr RefreshPolicyPtr; /** * @brief A refresh policy decides when changes to the media index must be * re-read. A policy is needed because, to optimize performance, * Lucene++ doesn't automatically refresh its index readers upon changes. */ class RefreshPolicy { public: virtual ~RefreshPolicy(); /** * @brief The default policy - currently an instance of InstantRefreshPolicy. */ static RefreshPolicyPtr default_policy(); /** * @brief This method is called by the MediaIndex before it starts any * read operation. The policy can now check if the media index needs to * be reopened, can can do so if needed. * @param index */ virtual bool OnBeginReading(MediaIndex *index) = 0; /** * @brief This method is called by the WritableMediaIndex before it * starts any write operation. The policy can now check if the media index * needs to be reopened, can can do so if needed. * @param index */ virtual bool OnBeginWriting(WritableMediaIndex *index) = 0; }; class InstantRefreshPolicy : public RefreshPolicy { public: bool OnBeginReading(MediaIndex *index); bool OnBeginWriting(WritableMediaIndex *index); }; } // namespace mediascanner #endif // MEDIASCANNER_REFRESHPOLICY_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filter.h0000644000015700001700000000746412232220161023606 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_FILTER_H #define MEDIASCANNER_FILTER_H // Standard Library #include #include // Media Scanner #include "mediascanner/property.h" namespace Lucene { typedef boost::shared_ptr QueryPtr; typedef boost::shared_ptr QueryParserPtr; } namespace mediascanner { class Filter { protected: class Private; explicit Filter(Private *d); public: Filter(); ~Filter(); Filter(const Filter &other); Filter &operator=(const Filter &other); Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, std::wstring *error_message) const; protected: template T* data() { return static_cast(d.get()); } private: std::shared_ptr d; }; //////////////////////////////////////////////////////////////////////////////// class QueryStringFilter : public Filter { class Private; public: explicit QueryStringFilter(const std::wstring &text); }; class SubStringFilter : public Filter { class Private; public: explicit SubStringFilter(const std::wstring &text); }; class FullTextFilter : public Filter { class Private; public: explicit FullTextFilter(const std::wstring &text); }; //////////////////////////////////////////////////////////////////////////////// class PrefixFilter : public Filter { class Private; public: explicit PrefixFilter(const StringProperty &key, const std::wstring &value); }; class ValueFilter : public Filter { class Private; public: explicit ValueFilter(const Property::BoundValue &value); explicit ValueFilter(const Property &property, const Property::Value &value); }; class RangeFilter : public Filter { class Private; public: explicit RangeFilter(const Property &property, const Property::Value &lower_value, const Property::Value &upper_value); explicit RangeFilter(const Property &property, const Property::Value &lower_value, const Property::Boundary &lower_boundary, const Property::Value &upper_value, const Property::Boundary &upper_boundary); }; //////////////////////////////////////////////////////////////////////////////// class BooleanFilter : public Filter { class Private; public: enum Occur { MUST, SHOULD, MUST_NOT }; class Clause { public: Clause(const Filter &filter, Occur occur) : filter_(filter) , occur_(occur) { } Filter filter() const { return filter_; } Occur occur() const { return occur_; } private: Filter filter_; Occur occur_; }; BooleanFilter(); void add_clause(const Filter &filter, Occur occur) { add_clause(Clause(filter, occur)); } void add_clause(const Clause &clause); }; } // namespace mediascanner #endif // MEDIASCANNER_FILTER_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/locale.cpp0000644000015700001700000000404512232220161024103 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/locale.h" // Boost C++ #include #include // Standard Library #include #include // Media Scanner #include "mediascanner/logging.h" // Standard Library using std::locale; using std::string; namespace mediascanner { static const logging::Domain kError("error/locale", logging::error()); namespace internal { void PrintLocaleWarning(const std::locale &locale) { const std::string locale_name = locale.name(); kError("WARNING: Generating Unicode facets for {1} locale. " "Please call mediascanner::SetupLocale().") % locale_name; } } // namespace internal bool CheckLocaleFacets(const std::locale &loc) { return std::has_facet >(loc) && std::has_facet >(loc); } locale SetupLocale(const string &id) { const locale new_locale = SetupLocale(locale(), id); locale::global(new_locale); return new_locale; } // Ensure the passed locale has the required Boost facets. locale SetupLocale(const locale &base, const string &id) { static const boost::locale::generator generator; return generator.generate(base, id); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbusutils.cpp0000644000015700001700000004246512232220161024672 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/dbusutils.h" // GLib related libraries #include // Boost C++ #include // Standard Library #include #include #include // Media Scanner Library #include "mediascanner/logging.h" namespace mediascanner { namespace dbus { // Boost C++ using boost::locale::format; // Standard Library using std::string; static const logging::Domain kError("error/dbus", logging::error()); static const logging::Domain kInfo("info/dbus", logging::info()); static const logging::Domain kTrace("trace/dbus", logging::trace()); //////////////////////////////////////////////////////////////////////////////// void InterfaceInfo::add_method(MethodInfoPtr method) { methods_.insert(std::make_pair(method->name(), method)); info_.reset(); } void InterfaceInfo::add_property(PropertyInfoPtr property) { properties_.insert(std::make_pair(property->name(), property)); info_.reset(); } void InterfaceInfo::add_signal(SignalInfoPtr signal) { signals_.insert(std::make_pair(signal->name(), signal)); info_.reset(); } template static T find_member(const std::map &members, const string &name) { const typename std::map::const_iterator it = members.find(name); return it != members.end() ? it->second : T(); } MethodInfoPtr InterfaceInfo::find_method(const string &name) const { return find_member(methods_, name); } PropertyInfoPtr InterfaceInfo::find_property(const string &name) const { return find_member(properties_, name); } SignalInfoPtr InterfaceInfo::find_signal(const string &name) const { return find_member(signals_, name); } template static typename T::element_type::dbus_info_type** make_member_info(const std::map &members) { typedef typename T::element_type::dbus_info_type *dbus_info_type; dbus_info_type *const info = g_new(dbus_info_type, members.size() + 1); dbus_info_type *dst_it = info; for (const auto &m: members) *(dst_it++) = m.second->info().dup(); *dst_it = nullptr; return info; } Wrapper InterfaceInfo::info() const { if (not info_) { const GDBusInterfaceInfo static_info = { 1, g_strdup(name().c_str()), make_member_info(methods_), make_member_info(signals_), make_member_info(properties_), nullptr }; info_ = wrap_static(&static_info); } return info_; } //////////////////////////////////////////////////////////////////////////////// bool InterfaceProxy::ConnectAndWait(Wrapper connection, const string &service_name, const string &object_path, Wrapper cancellable, GError **error) { const string iface_name = interface_name(); kTrace("Trying connection to interface \"{1}\" at \"{2}\" on \"{3}\"") % iface_name % object_path % service_name; handle_ = take(g_dbus_proxy_new_sync (connection.get(), G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, info().get(), service_name.c_str(), object_path.c_str(), interface_name().c_str(), cancellable.get(), error)); return handle_ != nullptr; } void InterfaceProxy::connect(GBusType bus_type, const string &service_name, const string &object_path, Wrapper cancellable) { const string iface_name = interface_name(); kTrace("Trying connection to interface \"{1}\" at \"{2}\" on \"{3}\"") % iface_name % object_path % service_name; handle_.reset(); g_dbus_proxy_new_for_bus (bus_type, G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, info().get(), service_name.c_str(), object_path.c_str(), interface_name().c_str(), cancellable.get(), &InterfaceProxy::on_connect_for_bus, this); } void InterfaceProxy::connect(Wrapper connection, const string &service_name, const string &object_path, Wrapper cancellable) { const string iface_name = interface_name(); kTrace("Trying connection to interface \"{1}\" at \"{2}\" on \"{3}\"") % iface_name % object_path % service_name; handle_.reset(); g_dbus_proxy_new (connection.get(), G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, info().get(), service_name.c_str(), object_path.c_str(), interface_name().c_str(), cancellable.get(), &InterfaceProxy::on_connect, this); } void InterfaceProxy::on_connect_for_bus(GObject *, GAsyncResult *result, void *data) { InterfaceProxy *const self = static_cast(data); Wrapper error; self->handle_ = take(g_dbus_proxy_new_for_bus_finish(result, error.out_param())); if (self->handle_) { self->connected(); } else { self->connect_failed(error); } } void InterfaceProxy::on_connect(GObject *, GAsyncResult *result, void *data) { InterfaceProxy *const self = static_cast(data); Wrapper error; self->handle_ = take(g_dbus_proxy_new_finish(result, error.out_param())); if (self->handle_) { self->connected(); } else { self->connect_failed(error); } } Wrapper InterfaceProxy::ReadPropertyValue (const string &property_name) const { Wrapper error; Wrapper params = take(g_variant_new("(ss)", interface_name().c_str(), property_name.c_str())); Wrapper result = take(g_dbus_connection_call_sync(connection().get(), service_name().c_str(), object_path().c_str(), "org.freedesktop.DBus.Properties", "Get", params.release(), G_VARIANT_TYPE("(v)"), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, error.out_param())); if (not result) { kError(to_string(error)); return Wrapper(); } return take(g_variant_get_child_value(result.get(), 0)); } Wrapper InterfaceProxy::connection() const { if (handle()) return wrap(g_dbus_proxy_get_connection(handle().get())); return Wrapper(); } string InterfaceProxy::interface_name() const { return InterfaceInfo::name(); } string InterfaceProxy::object_path() const { if (handle()) return g_dbus_proxy_get_object_path(handle().get()); return string(); } string InterfaceProxy::service_name() const { if (handle()) return g_dbus_proxy_get_name(handle().get()); return string(); } void InterfaceProxy::connected() { const string iface_name = interface_name(); const string path = object_path(); const string svc_name = service_name(); kTrace("Connected to interface \"{1}\" at \"{2}\" on \"{3}\"") % iface_name % path % svc_name; } void InterfaceProxy::connect_failed(Wrapper error) const { const string error_message = to_string(error); kError("Connecting to D-Bus service failed: {1}") % error_message; } //////////////////////////////////////////////////////////////////////////////// const MethodSkeleton* InterfaceSkeleton::find_method_skeleton (const string &name) const { if (const MethodInfoPtr method = find_method(name)) return method->skeleton(); return nullptr; } const PropertySkeleton* InterfaceSkeleton::find_property_skeleton (const string &name) const { if (const PropertyInfoPtr property = find_property(name)) return property->skeleton(); return nullptr; } void InterfaceSkeleton::InvokeMethod(MethodInvocation invocation) const { if (const MethodSkeleton *const method = find_method_skeleton(invocation.method_name())) method->Invoke(MethodInvocation(invocation.swap())); } GVariant* InterfaceSkeleton::GetProperty(const string &sender, const string &target, const string &name, GError **error) const { if (const PropertySkeleton *const property = find_property_skeleton(name)) { if (property->readable()) return property->GetValue(sender, target, error); } return nullptr; } bool InterfaceSkeleton::SetProperty(const string &sender, const string &target, const string &name, GVariant *value, GError **error) const { if (const PropertySkeleton *const property = find_property_skeleton(name)) { if (property->writable()) return property->SetValue(sender, target, value, error); } return false; } //////////////////////////////////////////////////////////////////////////////// void MethodInvocation::return_error(GQuark domain, int code, const string &message) { g_dbus_method_invocation_return_error_literal (swap(), domain, code, message.c_str()); } void MethodInvocation::return_dbus_error(const string &error_name, const string &error_message) { g_dbus_method_invocation_return_dbus_error (swap(), error_name.c_str(), error_message.c_str()); } void MethodInvocation::return_error(Wrapper error) { g_dbus_method_invocation_return_gerror(swap(), error.get()); } void MethodInvocation::emit_signal(const SignalInfo *signal, GVariant *args, GError **error) const { g_dbus_connection_emit_signal(connection(), sender().c_str(), target().c_str(), interface_name().c_str(), signal->name().c_str(), args, error); } GDBusMethodInvocation* MethodInvocation::dup() const { return wrap(invocation_).dup(); } GDBusMethodInvocation* MethodInvocation::swap(GDBusMethodInvocation *other) { assert_called_from_original_thread(); std::swap(invocation_, other); return other; } //////////////////////////////////////////////////////////////////////////////// Service::Service() : name_id_(0) { } Service::~Service() { if (name_id_) Disconnect(); while (not objects_.empty()) { const ObjectMap::iterator it = objects_.begin(); g_dbus_connection_unregister_object(connection_.get(), it->second.object_id); objects_.erase(it); } } Wrapper Service::connection() const { return connection_; } void Service::Connect(const string &service_name) { g_return_if_fail(name_id_ == 0); name_id_ = g_bus_own_name(G_BUS_TYPE_SESSION, service_name.c_str(), G_BUS_NAME_OWNER_FLAGS_REPLACE, &Service::OnDBusConnected, &Service::OnDBusNameAcquired, &Service::OnDBusNameLost, this, nullptr); } void Service::Disconnect() { g_bus_unown_name(name_id_); } void Service::RegisterObject(const string &path, InterfaceSkeletonPtr skeleton) { static const GDBusInterfaceVTable kInterfaceVTable = { &Service::OnMethodCall, &Service::OnGetProperty, &Service::OnSetProperty, { 0 } }; Wrapper error; const unsigned object_id = g_dbus_connection_register_object (connection_.get(), path.c_str(), skeleton->info().get(), &kInterfaceVTable, this, nullptr, error.out_param()); if (object_id == 0) { const string error_message = to_string(error); kError("Cannot register D-Bus object at {1}: {2}") % path % error_message; } else { const ObjectInfo registration(object_id, skeleton); objects_.insert(std::make_pair(skeleton->name(), registration)); } } InterfaceSkeletonPtr Service::find_object(const string &name) const { const ObjectMap::const_iterator it = objects_.find(name); if (it != objects_.end()) return it->second.skeleton; return InterfaceSkeletonPtr(); } void Service::Connected() { const string connection_name = ConnectionName(connection_); kInfo("Published to D-Bus connection at <{1}>") % connection_name; } void Service::NameAcquired() { kInfo("D-Bus name acquired"); } void Service::NameLost() { kInfo("D-Bus name lost"); } void Service::InvokeMethod(GDBusMethodInvocation *invocation) { const MethodInvocation method_invocation(invocation); if (const InterfaceSkeletonPtr object = find_object(method_invocation.interface_name())) object->InvokeMethod(method_invocation); } GVariant* Service::GetProperty(const string &sender, const string &target, const string &interface, const string &property, GError **error) { if (const InterfaceSkeletonPtr object = find_object(interface)) return object->GetProperty(sender, target, property, error); return nullptr; } bool Service::SetProperty(const string &sender, const string &target, const string &interface, const string &property, GVariant *value, GError **error) { if (const InterfaceSkeletonPtr object = find_object(interface)) return object->SetProperty(sender, target, property, value, error); return false; } void Service::OnDBusConnected(GDBusConnection *connection, const char *, void *data) { Service *const service = static_cast(data); service->connection_ = wrap(connection); service->Connected(); } void Service::OnDBusNameAcquired(GDBusConnection *, const char *, void *data) { static_cast(data)->NameAcquired(); } void Service::OnDBusNameLost(GDBusConnection *, const char *, void *data) { static_cast(data)->NameLost(); } void Service::OnMethodCall(GDBusConnection *, const char *, const char *, const char *, const char *, GVariant *, GDBusMethodInvocation *invocation, void *data) { static_cast(data)->InvokeMethod(invocation); } GVariant* Service::OnGetProperty(GDBusConnection *, const char *sender, const char *target, const char *interface, const char *property, GError **error, void *data) { return static_cast(data)->GetProperty (sender, target, interface, property, error); } gboolean Service::OnSetProperty(GDBusConnection *, const char *sender, const char *target, const char *interface, const char *property, GVariant *value, GError **error, void *data) { return static_cast(data)->SetProperty (sender, target, interface, property, value, error); } string ConnectionName(Wrapper connection) { if (not connection) return "none"; GIOStream *stream = g_dbus_connection_get_stream(connection.get()); if (G_IS_SOCKET_CONNECTION(stream)) { GSocketAddress *address = g_socket_connection_get_remote_address (G_SOCKET_CONNECTION(stream), nullptr); if (G_IS_UNIX_SOCKET_ADDRESS(address)) { GUnixSocketAddress *unix_address = G_UNIX_SOCKET_ADDRESS(address); return (format("unix:path={1},type={2}") % g_unix_socket_address_get_path(unix_address) % g_unix_socket_address_get_address_type(unix_address)). str(); } return (format("unknown:address={1}") % G_OBJECT_TYPE_NAME(address)).str(); } return (format("unknown:{1}") % G_OBJECT_TYPE_NAME(stream)).str(); } } // namespace dbus } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbusservice.h0000644000015700001700000002375212232220161024635 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_DBUSSERVICE_H #define MEDIASCANNER_DBUSSERVICE_H // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/dbusutils.h" #include "mediascanner/mediautils.h" namespace mediascanner { namespace dbus { using std::set; using std::string; using std::vector; enum MediaChangeType { MEDIA_INFO_CREATED, MEDIA_INFO_UPDATED, MEDIA_INFO_REMOVED }; template class MediaScannerInterface : public InterfaceType { private: typedef typename MethodTrait < InterfaceType, ArgumentList, ArgumentList >:: type MediaInfoExistsMethodType; typedef typename MethodTrait < InterfaceType, ArgumentList >, ArgumentList >:: type LookupMediaInfoMethodType; typedef typename MethodTrait < InterfaceType, ArgumentList, int32_t, int32_t> >:: type QueryMediaInfoMethodType; typedef typename MethodTrait < InterfaceType, ArgumentList< MediaInfo >, ArgumentList< set > >:: type StoreMediaInfoMethodType; typedef typename MethodTrait < InterfaceType, ArgumentList >:: type RemoveMediaInfoMethodType; typedef typename PropertyTrait < InterfaceType, ReadOnly, string >:: type IndexPathPropertyType; typedef typename PropertyTrait < InterfaceType, ReadOnly, vector >:: type MediaRootsPropertyType; typedef typename SignalTrait < InterfaceType, ArgumentList > >:: type MediaInfoAvailableSignalType; typedef typename SignalTrait < InterfaceType, ArgumentList > >:: type MediaInfoChangedSignalType; public: class MediaInfoExistsMethod : public MediaInfoExistsMethodType { public: typedef MediaInfoExistsMethodType inherited; typedef typename inherited::input_args_type input_args_type; typedef typename inherited::output_args_type output_args_type; explicit MediaInfoExistsMethod(InterfaceType *parent = nullptr) : inherited(parent, "MediaInfoExists", input_args_type(Argument("url")), output_args_type(Argument("exists"))) { } }; class LookupMediaInfoMethod : public LookupMediaInfoMethodType { public: typedef LookupMediaInfoMethodType inherited; typedef typename inherited::input_args_type input_args_type; typedef typename inherited::output_args_type output_args_type; explicit LookupMediaInfoMethod(InterfaceType *parent = nullptr) : inherited(parent, "LookupMediaInfo", input_args_type(Argument("url"), Argument >("fields")), output_args_type(Argument("item"))) { } }; class QueryMediaInfoMethod : public QueryMediaInfoMethodType { public: typedef QueryMediaInfoMethodType inherited; typedef typename inherited::input_args_type input_args_type; typedef typename inherited::output_args_type output_args_type; explicit QueryMediaInfoMethod(InterfaceType *parent = nullptr) : inherited(parent, "QueryMediaInfo", input_args_type(Argument("query"), Argument >("fields"), Argument("offset"), Argument("limit"))) { } }; /** * @brief This class implements the @c StoreMediaInfo method of the media * scanner's D-Bus service. It inserts new media information into the index. * The actual URL of the media information is retreived from @p item. * @param[in] item A key-value map of the information to store. * @see WritableMediaIndex::Insert() * @ingroup dbus */ class StoreMediaInfoMethod : public StoreMediaInfoMethodType { public: typedef StoreMediaInfoMethodType inherited; typedef typename inherited::input_args_type input_args_type; typedef typename inherited::output_args_type output_args_type; explicit StoreMediaInfoMethod(InterfaceType *parent = nullptr) : inherited(parent, "StoreMediaInfo", input_args_type(Argument("item")), output_args_type(Argument >("bad_keys"))) { } }; /** * @brief This class implements the @c RemoveMediaInfo method of the media * scanner's D-Bus service. It removes all information about the media * referenced by @p url. * @param[in] url The URL of the media to remove. * @see WritableMediaIndex::Delete() * @ingroup dbus */ class RemoveMediaInfoMethod : public RemoveMediaInfoMethodType { public: typedef RemoveMediaInfoMethodType inherited; typedef typename inherited::input_args_type input_args_type; typedef typename inherited::output_args_type output_args_type; explicit RemoveMediaInfoMethod(InterfaceType *parent = nullptr) : inherited(parent, "RemoveMediaInfo", input_args_type(Argument("url"))) { } }; /** * @brief This class implements the @c IndexPath property of the media * scanner's D-Bus service. It contains the local file system path of * the media index. * @see MediaIndex::path() * @ingroup dbus */ class IndexPathProperty : public IndexPathPropertyType { public: typedef IndexPathPropertyType inherited; IndexPathProperty() : inherited("IndexPath") { } }; /** * @brief This class implements the @c MediaRoots property of the media * scanner's D-Bus service. It contains the paths of all the directories the * file system scanner is watching. * the media index. * @see FileSystemScanner::directories() * @ingroup dbus */ class MediaRootsProperty : public MediaRootsPropertyType { public: typedef MediaRootsPropertyType inherited; MediaRootsProperty() : inherited("MediaRoots") { } }; class MediaInfoAvailableSignal : public MediaInfoAvailableSignalType { public: typedef MediaInfoAvailableSignalType inherited; typedef typename inherited::args_type args_type; MediaInfoAvailableSignal() : inherited("MediaInfoAvailable", args_type(Argument("serial"), Argument >("items"))) { } }; /** * @brief This class describes the @c MediaInfoChanged signal of the media * scanner's D-Bus service. This signal is emitted when the media index * updates media information. It is emitted in batches to reduce load. * @param items A list of key-value maps of the stored information. * @ingroup dbus */ class MediaInfoChangedSignal : public MediaInfoChangedSignalType { public: typedef MediaInfoChangedSignalType inherited; typedef typename inherited::args_type args_type; MediaInfoChangedSignal() : inherited("MediaInfoChanged", args_type(Argument("type"), Argument >("urls"))) { } }; MediaScannerInterface() : InterfaceType("com.canonical.MediaScanner") { // FIXME(M5): Initialize members here } }; class MediaScannerProxy : public MediaScannerInterface { public: MediaScannerProxy() : MediaInfoExists(this) , LookupMediaInfo(this) , QueryMediaInfo(this) , StoreMediaInfo(this) , RemoveMediaInfo(this) { } void connect(Wrapper connection, Wrapper cancellable = Wrapper()); void connect(Wrapper cancellable = Wrapper()); bool ConnectAndWait(Wrapper connection, Wrapper cancellable, GError **error); bool ConnectAndWait(Wrapper cancellable, GError **error); const MediaInfoExistsMethod MediaInfoExists; const LookupMediaInfoMethod LookupMediaInfo; const QueryMediaInfoMethod QueryMediaInfo; const StoreMediaInfoMethod StoreMediaInfo; const RemoveMediaInfoMethod RemoveMediaInfo; const IndexPathProperty index_path; const MediaRootsProperty media_roots; const MediaInfoAvailableSignal media_info_available; const MediaInfoChangedSignal media_info_changed; }; class MediaScannerSkeleton : public MediaScannerInterface { public: static const string& object_path(); static const string& service_name(); }; string to_string(MediaChangeType change_type); } // namespace dbus } // namespace mediascanner #endif // MEDIASCANNER_DBUSSERVICE_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaartdownloader.h0000644000015700001700000000240212232220161026151 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: James Henstridge */ #ifndef MEDIAARTDOWNLOADER_H #define MEDIAARTDOWNLOADER_H #include namespace mediascanner { class MediaArtDownloader { class Private; friend class Private; private: public: MediaArtDownloader(); ~MediaArtDownloader(); static void LoadGriloPlugin(); void resolve(const std::string &artist, const std::string &album); bool is_idle() const; void WaitForFinished(); private: Private *const d; }; } #endif mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/logging.cpp0000644000015700001700000002470212232220161024274 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/logging.h" // GLib based libraries #include // C Standard Library #include #include #include #include // Boost C++ #include #include #include // GCC Extensions to the C++ Standard Library #include // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/locale.h" // Boost C++ using boost::algorithm::starts_with; using boost::algorithm::to_lower_copy; using boost::locale::format; namespace mediascanner { namespace logging { const Domain::SettingsMap &Domain::ReadSettings() { const char *const current_env = ::getenv("MEDIASCANNER_DEBUG"); static const char *parsed_env = nullptr; static SettingsMap settings; if (current_env != parsed_env) { std::istringstream env(safe_string(current_env)); settings.clear(); for (std::string name; std::getline(env, name, ':'); ) { if (name.empty()) continue; Flags flags = Explicit | Enabled; if (name[0] == '-' || name[0] == '!') { name = name.substr(1); flags = Explicit | Disabled; } else if (name[0] == '+') { name = name.substr(1); } SettingsMap::iterator it = settings.find(name); if (it == settings.end()) it = settings.insert(std::make_pair(name, flags)).first; else it->second = flags; } parsed_env = current_env; } return settings; } Domain::Flags Domain::LookupFlags(const std::string &name, Flags preset) { std::string best_match; Flags flags = preset; for (const auto &entry: ReadSettings()) { if (entry.first == name) { flags = entry.second; break; } if ((preset & (Explicit | Enabled | Disabled)) == (Explicit | Disabled)) continue; if (starts_with(name, entry.first) && name[entry.first.length()] == '/' && entry.first.length() > best_match.length()) { best_match = entry.first; flags = entry.second; } } return flags; } // Leave parent as const to permit static const declarations of logging // domains in application code. Of course once could work arround by using // the parent() Domain::Domain(const std::string &name, Flags flags, const Domain *parent, MessageSinkPtr sink) : message_sink_(sink) , parent_(const_cast(parent)) , name_(to_lower_copy(name)) , initial_flags_(LookupFlags(name_, flags)) , flags_(initial_flags_) , default_message_sink_(message_sink()) { VerifyGraph(); VerifyPrefix(); } Domain::Domain(const std::string &name, const Domain *parent, MessageSinkPtr sink) : message_sink_(sink) , parent_(const_cast(parent)) , name_(to_lower_copy(name)) , initial_flags_(LookupFlags(name_, Enabled)) , flags_(initial_flags_) , default_message_sink_(message_sink()) { VerifyGraph(); VerifyPrefix(); } void Domain::VerifyGraph() const { for (const Domain *other = parent_; other; other = other->parent_) BOOST_ASSERT_MSG(this != other, "Domain graphs must be cycle-free"); } void Domain::VerifyPrefix() const { const std::string::size_type n = name().rfind('/'); if (n != std::string::npos) { BOOST_ASSERT_MSG(parent_, "Parent needed for prefixed domain names"); const std::string prefix = name().substr(0, n); if (parent_->name() != prefix) { const std::string parent_name = parent_->name(); const std::string domain_name = name(); const std::string error_message = (format("The parent domain's name (\"{1}\") must match " "the prefix of this domain's name (\"{2}\").") % parent_name % domain_name).str(); BOOST_ASSERT_MSG(parent_name == prefix, error_message.c_str()); } } } static MessageSinkPtr new_sink(const std::string &prefix, DefaultMessageSink::Color color) { return MessageSinkPtr(new DefaultMessageSink(&std::clog, prefix, color)); } static Domain* unknown() { static Domain domain("unknown", nullptr, new_sink("UNKNOWN", DefaultMessageSink::Red)); return &domain; } Domain* error() { static Domain domain("error", unknown(), new_sink("ERROR", DefaultMessageSink::Red)); return &domain; } Domain* critical() { static Domain domain("critical", error(), new_sink("CRITICAL", DefaultMessageSink::Brown)); return &domain; } Domain* warning() { static Domain domain("warning", critical(), new_sink("WARNING", DefaultMessageSink::Brown)); return &domain; } Domain* message() { static Domain domain("message", warning(), new_sink("WARNING", DefaultMessageSink::Default)); return &domain; } Domain* info() { static Domain domain("info", message(), new_sink("INFO", DefaultMessageSink::Blue)); return &domain; } Domain* debug() { static Domain domain("debug", Domain::Disabled, info(), new_sink("DEBUG", DefaultMessageSink::Green)); return &domain; } Domain* trace() { static Domain domain("trace", Domain::Disabled, debug(), new_sink("TRACE", DefaultMessageSink::Cyan)); return &domain; } static const MessageSinkPtr initial_default_sink(new DefaultMessageSink); MessageSinkPtr MessageSink::default_instance_ = initial_default_sink; MessageSink::~MessageSink() { } void MessageSink::Report(const std::string &domain_name, const std::wstring &message) { Report(domain_name, FromUnicode(message)); } void MessageSink::set_default_instance(MessageSinkPtr instance) { if (instance == initial_default_sink) { for (Domain *domain = trace(); domain; domain->parent()) { if (domain->message_sink() == default_instance_) domain->set_message_sink(domain->default_message_sink()); } } else { for (Domain *domain = trace(); domain; domain->parent()) { if (domain->message_sink() == domain->default_message_sink()) domain->set_message_sink(instance); } } default_instance_ = instance; } static bool supports_color_codes(std::ostream *stream) { using __gnu_cxx::stdio_sync_filebuf; if (stdio_sync_filebuf *const stdio = dynamic_cast *>(stream->rdbuf())) // NOLINT:runtime/rtti return ::isatty(::fileno(stdio->file())); return false; } static std::string ansi_color_code(DefaultMessageSink::Color color) { switch (color) { case DefaultMessageSink::Default: return "\033[0m"; case DefaultMessageSink::Black: return "\033[1;30m"; case DefaultMessageSink::Red: return "\033[1;31m"; case DefaultMessageSink::Green: return "\033[1;32m"; case DefaultMessageSink::Brown: return "\033[0;33m"; case DefaultMessageSink::Blue: return "\033[1;34m"; case DefaultMessageSink::Magenta: return "\033[1;35m"; case DefaultMessageSink::Cyan: return "\033[1;36m"; case DefaultMessageSink::White: return "\033[1;37m"; case DefaultMessageSink::Bold: return "\033[1m"; } return std::string(); } DefaultMessageSink::DefaultMessageSink(std::ostream *stream, const std::string &prefix, Color color) : stream_(stream) , prefix_(prefix) { if (not prefix_.empty()) { if (color != Default && supports_color_codes(stream_)) prefix_ = ansi_color_code(color) + prefix_ + ansi_color_code(Default); prefix_ += " "; } } void DefaultMessageSink::Report(const std::string &domain_name, const std::string &message) { std::ostringstream buffer; buffer << program_invocation_short_name << "[" << getpid() << "]: " << prefix_ << domain_name << ": " << message << std::endl; // Write formatted message in one go // to avoid overlapping of messages from different threads. const std::string result = buffer ? buffer.str() : message + "/n"; stream_->write(result.data(), result.size()); } static void on_glib_message(const char *domain_name, GLogLevelFlags log_level, const char *text, void *) { Domain *domain; switch (log_level & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_ERROR: domain = error(); break; case G_LOG_LEVEL_CRITICAL: domain = critical(); break; case G_LOG_LEVEL_WARNING: domain = warning(); break; case G_LOG_LEVEL_MESSAGE: domain = message(); case G_LOG_LEVEL_INFO: domain = info(); break; case G_LOG_LEVEL_DEBUG: domain = debug(); break; default: domain = unknown(); } if (domain_name) { Domain(domain->name() + "/" + domain_name, domain).print(text); } else { domain->print(text); } } static bool capturing_glib_messages = false; void capture_glib_messages() { g_log_set_default_handler(on_glib_message, 0); capturing_glib_messages = true; } bool is_capturing_glib_messages() { return capturing_glib_messages; } } // namespace logging } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbustypes.cpp0000644000015700001700000002661112232220161024671 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/dbustypes.h" // Boost C++ #include #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/locale.h" namespace mediascanner { namespace dbus { Signature::Signature(const std::string &signature) : signature_(signature) { } Signature::Signature(const GVariantType *type) : signature_(g_variant_type_peek_string(type)) { } Signature::operator const char*() const { return signature_.c_str(); } Signature::operator const GVariantType*() const { return G_VARIANT_TYPE(signature_.c_str()); } const std::string& Signature::str() const { return signature_; } Signature Signature::array(const Signature &element_type) { return "a" + element_type.str(); } Signature Signature::dictionary(const Signature &key_type, const Signature &value_type) { return "a{" + key_type.str() + value_type.str() + "}"; } Signature Signature::tuple(const Signature &element) { return "(" + element.str() + ")"; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_BOOLEAN; return signature; } template<> GVariant* Type::make_variant(bool value) { return g_variant_new(signature(), value ? TRUE : FALSE); } template<> bool Type::make_value(GVariant *variant) { return variant ? g_variant_get_boolean(variant) : false; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_INT32; return signature; } template<> GVariant* Type::make_variant(int32_t value) { return g_variant_new(signature(), value); } template<> int32_t Type::make_value(GVariant *variant) { return variant ? g_variant_get_int32(variant) : 0; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_UINT32; return signature; } template<> GVariant* Type::make_variant(uint32_t value) { return g_variant_new(signature(), value); } template<> uint32_t Type::make_value(GVariant *variant) { return variant ? g_variant_get_uint32(variant) : 0; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_UINT64; return signature; } template<> GVariant* Type::make_variant(uint64_t value) { return g_variant_new(signature(), value); } template<> uint64_t Type::make_value(GVariant *variant) { return variant ? g_variant_get_uint64(variant) : 0; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_DOUBLE; return signature; } template<> GVariant* Type::make_variant(double value) { return g_variant_new(signature(), value); } template<> double Type::make_value(GVariant *variant) { return variant ? g_variant_get_double(variant) : 0; } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_DOUBLE; return signature; } template<> GVariant* Type::make_variant(float value) { return g_variant_new(signature(), static_cast(value)); } template<> float Type::make_value(GVariant *variant) { return variant ? g_variant_get_double(variant) : 0; } //////////////////////////////////////////////////////////////////////////////// template<> BoxedType::boxed_type BoxedType::make_boxed(Fraction value) { return boxed_type(value.numerator(), value.denominator()); } template<> Fraction BoxedType::make_unboxed(boxed_type value) { return Fraction(value.first, value.second); } //////////////////////////////////////////////////////////////////////////////// static const DateTime kEpoch(boost::gregorian::date(1970, 1, 1)); template<> uint64_t BoxedType::make_boxed(DateTime value) { return (value - kEpoch).total_microseconds(); } template<> DateTime BoxedType::make_unboxed(uint64_t value) { return kEpoch + boost::posix_time::microseconds(value); } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_STRING; return signature; } template<> GVariant* Type::make_variant(std::string value) { return g_variant_new(signature(), value.c_str()); } template<> std::string Type::make_value(GVariant *variant) { if (variant == nullptr) return value_type(); size_t len = 0; const char *const str = g_variant_get_string(variant, &len); return std::string(str, len); } // FIXME(M5): Figure out how to avoid this duplication. template<> const std::string Type::make_value(GVariant *variant) { return Type::make_value(variant); } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { return Type::signature(); } template<> GVariant* Type::make_variant(std::wstring value) { return Type::make_variant(FromUnicode(value)); } template<> std::wstring Type::make_value(GVariant *variant) { if (variant == nullptr) return value_type(); return ToUnicode(Type::make_value(variant)); } //////////////////////////////////////////////////////////////////////////////// template<> const Signature& Type::signature() { return Type::signature(); } template<> GVariant* Type::make_variant(Property value) { return Type::make_variant(value.field_name()); } template<> Property Type::make_value(GVariant *variant) { if (variant == nullptr) return value_type(); const std::wstring &field_name = Type::make_value(variant); return mediascanner::Property::FromFieldName(field_name); } //////////////////////////////////////////////////////////////////////////////// const Signature& Type::signature() { static const Signature signature = G_VARIANT_TYPE_VARIANT; return signature; } class MakeVariantVisitor : public boost::static_visitor { public: MakeVariantVisitor() { // clang++ requires a user-provided default constructor } template GVariant* operator()(const T &value) const { return g_variant_new_variant(Type::make_variant(value)); } result_type operator()(const boost::blank &) const { return result_type(); } }; GVariant* Type::make_variant(const Property::Value &value) { static const MakeVariantVisitor make_variant_visitor; return value.apply_visitor(make_variant_visitor); } //////////////////////////////////////////////////////////////////////////////// const Signature& Type::signature() { static const Signature signature = Signature::dictionary(Type::signature(), Type::signature()); return signature; } GVariant* Type::make_variant (const Property::ValueMap &value) { GVariantBuilder builder; g_variant_builder_init(&builder, signature()); for (typename Property::ValueMap::const_iterator it = value.begin(), end = value.end(); it != end; ++it) { GVariant *const element = g_variant_new_dict_entry (Type::make_variant(it->first), Type::make_variant(it->second)); g_variant_builder_add_value(&builder, element); } return g_variant_builder_end(&builder); } Property::ValueMap Type::make_value (GVariant *variant, std::set *bad_keys) { if (variant == nullptr) return Property::ValueMap(); Property::ValueMap properties; GVariantIter iter; g_variant_iter_init(&iter, variant); while (const Wrapper element = take(g_variant_iter_next_value(&iter))) { const Wrapper key = take(g_variant_get_child_value(element.get(), 0)); const std::string name = g_variant_get_string(key.get(), nullptr); const Property property = Property::FromFieldName(ToUnicode(name)); if (not property) { if (bad_keys) bad_keys->insert(name); continue; } const Wrapper boxed_value = take(g_variant_get_child_value(element.get(), 1)); Property::Value property_value; if (not property.TransformDBusVariant(boxed_value.get(), &property_value)) { if (bad_keys) bad_keys->insert(name); continue; } if (not properties.insert(std::make_pair(property, property_value)).second) { if (bad_keys) bad_keys->insert(name); continue; } } return properties; } //////////////////////////////////////////////////////////////////////////////// const Signature& Type::signature() { static const Signature signature = Signature::array(Type::signature()); return signature; } GVariant* Type::make_variant(const MediaInfo &value) { return internal::SequenceType::make_variant(value); } MediaInfo Type::make_value(GVariant *variant, std::set *bad_keys) { MediaInfo metadata; if (variant) { for (size_t i = 0, l = g_variant_n_children(variant); i < l; ++i) { GVariant *const child = g_variant_get_child_value(variant, i); metadata.add_related(Type:: make_value(child, bad_keys)); } } return metadata; } } // namespace dbus } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filesystemscanner.h0000644000015700001700000000424412232220161026050 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_FILESYSTEMSCANNER_H #define MEDIASCANNER_FILESYSTEMSCANNER_H // Standard Library #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { class FileSystemScanner { NONCOPYABLE(FileSystemScanner); class Private; public: class Listener { public: virtual ~Listener() { } virtual void OnScanningStarted() { } virtual void OnScanningFinished(const std::string &/*error_message*/) { } }; typedef MediaIndexFacade TaskFacade; typedef std::shared_ptr TaskFacadePtr; FileSystemScanner(MetadataResolverPtr resolver, TaskManagerPtr media_task_manager, TaskFacadePtr media_task_facade); ~FileSystemScanner(); void set_directories(const std::vector &paths); bool add_directory(const std::string &path); bool remove_directory(const std::string &path); std::vector directories() const; void add_listener(Listener *listener); void remove_listener(Listener *listener); void set_file_monitor_enabled(bool enable); bool file_monitor_enabled() const; bool start_scanning(); bool is_idle() const; private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_FILESYSTEMSCANNER_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/locale.h0000644000015700001700000000422112232220161023544 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_LOCALE_H #define MEDIASCANNER_LOCALE_H // Boost C++ #include // Standard Library #include #include namespace mediascanner { namespace internal { void PrintLocaleWarning(const std::locale &loc); } bool CheckLocaleFacets(const std::locale &loc = std::locale()); std::locale SetupLocale(const std::string &id = std::string()); std::locale SetupLocale(const std::locale &base, const std::string &id); inline std::wstring ToUnicode(const std::string &str, const std::locale &loc = std::locale()) { try { return boost::locale::conv::to_utf(str, loc); } catch(std::bad_cast &) { internal::PrintLocaleWarning(loc); const std::locale new_locale = SetupLocale(loc, std::string()); return boost::locale::conv::to_utf(str, new_locale); } } inline std::string FromUnicode(const std::wstring &str, const std::locale &loc = std::locale()) { try { return boost::locale::conv::from_utf(str, loc); } catch(std::bad_cast &) { internal::PrintLocaleWarning(loc); const std::locale new_locale = SetupLocale(loc, std::string()); return boost::locale::conv::from_utf(str, new_locale); } } } // namespace mediascanner #endif // MEDIASCANNER_LOCALE_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/settings.h0000644000015700001700000000663212232220161024155 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_SETTINGS_H #define MEDIASCANNER_SETTINGS_H // C++ Standard Library #include #include #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" namespace mediascanner { class Settings { public: class KeyName { friend class Settings; protected: explicit KeyName(const std::string &name) : name_(name) { } private: const std::string name_; }; template class Key : public KeyName { friend class Settings; private: typedef T value_type; explicit Key(const std::string &name) : KeyName(name) { } }; struct MediaFormat { MediaFormat() { } MediaFormat(const std::string &name, const std::string &caps) : name(name) , caps(caps) { } std::string name; std::string caps; }; struct MetadataSource { typedef std::map Config; MetadataSource() { } MetadataSource(const std::string &plugin_id, const std::string &source_id, const Config &config) : plugin_id(plugin_id) , source_id(source_id) , config(config) { } std::string plugin_id; std::string source_id; Config config; }; typedef std::function ChangeListener; typedef std::vector MediaFormatList; typedef std::vector MetadataSourceList; typedef std::vector StringList; static const Key kMandatoryContainers; static const Key kMandatoryDecoders; static const Key kMetadataSources; static const Key kMediaRoots; Settings(); MediaFormatList mandatory_containers() const; MediaFormatList mandatory_decoders() const; MetadataSourceList metadata_sources() const; StringList media_root_urls() const; StringList media_root_paths() const; template T lookup(const Key &key) const; unsigned connect(const KeyName &key, const ChangeListener &listener) const; void disconnect(unsigned handler_id) const; std::vector LoadMetadataSources() const; static std::vector LoadMetadataSources (const MetadataSourceList &sources); private: Wrapper settings_; }; } // namespace mediascanner #endif // MEDIASCANNER_SETTINGS_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/metadataresolver.cpp0000644000015700001700000003576412232220161026222 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/metadataresolver.h" // Boost C++ #include // C++ Standard Library #include #include #include #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/logging.h" #include "mediascanner/mediautils.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" namespace mediascanner { // Context specific logging domains static const logging::Domain kDebug("debug/metadata", logging::debug()); static const logging::Domain kWarning("warning/metadata", logging::warning()); static const logging::Domain kInfo("info/metadata", logging::info()); static const logging::Domain kError("error/metadata", logging::error()); static GList *make_initial_key_list() { return grl_metadata_key_list_new(GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_ARTIST, GRL_METADATA_KEY_ALBUM, GRL_METADATA_KEY_INVALID); } struct MetadataSource { Wrapper source; bool needs_id_lookup; Wrapper id_keys; Wrapper resolve_keys; explicit MetadataSource(Wrapper source) : source(source), needs_id_lookup(false) { const GList *supported_keys = grl_source_supported_keys(source.get()); // If the backend supports resolving the title key, check to // see whether it also supports a known ID field for two step // resolution. if (g_list_find(const_cast(supported_keys), GRLKEYID_TO_POINTER(GRL_METADATA_KEY_TITLE))) { GList *list = NULL; GrlKeyID key = schema::kTmdbId.metadata_key().id(); if (key != 0 && g_list_find(const_cast(supported_keys), GRLKEYID_TO_POINTER(key))) { list = g_list_prepend(list, GRLKEYID_TO_POINTER(key)); } key = schema::kImdbId.metadata_key().id(); if (key != 0 && g_list_find(const_cast(supported_keys), GRLKEYID_TO_POINTER(key))) { list = g_list_prepend(list, GRLKEYID_TO_POINTER(key)); } if (list != NULL) { needs_id_lookup = true; id_keys = take(list); } } // Intersect supported_keys with the ones the media scanner // cares about. GList *list = NULL; for (const GList *tmp = supported_keys; tmp != NULL; tmp = tmp->next) { GrlKeyID key = GRLPOINTER_TO_KEYID(tmp->data); const Property property = Property::FromMetadataKey(key); if (property) { list = g_list_prepend(list, GRLKEYID_TO_POINTER(key)); } } resolve_keys = take(list); } }; class MetadataResolver::Private { public: struct ResolveData { ResolveData(Private *d, const std::string &url, const MediaInfo &metadata, const StoreFunction &store_metadata) : d(d) , it(d->available_sources_.begin()) , url(url) , metadata(metadata) , store_metadata(store_metadata) { } Private *const d; std::vector::iterator it; const std::string url; MediaInfo metadata; const StoreFunction store_metadata; }; typedef std::shared_ptr ResolveDataPtr; static ResolveDataPtr take_resolve_data(gpointer user_data); Private() : options_(take(grl_operation_options_new(nullptr))) , initial_key_list_(take(make_initial_key_list())) , is_canceled_(false) , last_push_id_(0) , cancel_id_(0) { g_mutex_init(&mutex_); g_cond_init(&cond_); } ~Private() { cancel(); } bool is_idle() const { return pending_pushes_.empty() && current_ops_.empty(); } void push(unsigned push_id, const ResolveDataPtr &data); void resolve_next(GrlMedia *media, const ResolveDataPtr &data); void resolve_media_ids(GrlMedia *media, const ResolveDataPtr &data); void resolve_details(GrlMedia *media, const ResolveDataPtr &data); void fill_metadata(GrlMedia *media, const ResolveDataPtr &data); void cancel(); static void OnResolveMediaIds(GrlSource *source, unsigned opid, GrlMedia *media, void *user_data, const GError *error); static void OnResolveDetails(GrlSource *source, unsigned opid, GrlMedia *media, void *user_data, const GError *error); std::vector available_sources_; Wrapper options_; std::vector current_ops_; const Wrapper initial_key_list_; volatile bool is_canceled_; typedef std::map PendingPushesMap; PendingPushesMap pending_pushes_; unsigned last_push_id_; unsigned cancel_id_; GMutex mutex_; GCond cond_; }; MetadataResolver::MetadataResolver() : d(new Private) { } MetadataResolver::~MetadataResolver() { delete d; } bool MetadataResolver::SetupSources(const std::vector &sources) { const Wrapper registry = wrap(grl_registry_get_default()); d->available_sources_.clear(); for (const std::string &id: sources) { const Wrapper source = wrap(grl_registry_lookup_source(registry.get(), id.c_str())); if (not source) { kWarning("Unsupported metadata source \"{1}\".") % id; continue; } if ((grl_source_supported_operations (source.get()) & GRL_OP_RESOLVE) == 0) { kWarning("Metadata source \"{1}\" doesn't support " "metadata resolution.") % id; } GrlPlugin *const plugin = grl_source_get_plugin(source.get()); kInfo("Using \"{1}\" from \"{2}\" to resolve metadata.") % id % grl_plugin_get_filename(plugin); d->available_sources_.push_back(MetadataSource(source)); } return d->available_sources_.size() == sources.size(); } void MetadataResolver::cancel() { g_mutex_lock(&d->mutex_); if (d->cancel_id_ == 0) { d->cancel_id_ = Idle::AddOnce(std::bind(&Private::cancel, d), G_PRIORITY_HIGH_IDLE); } g_mutex_unlock(&d->mutex_); } void MetadataResolver::push(const std::string &url, const MediaInfo &metadata, const StoreFunction &store_metadata) { g_mutex_lock(&d->mutex_); const Private::ResolveDataPtr data (new Private::ResolveData(d, url, metadata, store_metadata)); kDebug("Scheduling metadata resolution for {1}") % url; const unsigned push_id = ++d->last_push_id_; const unsigned idle_id = Idle::AddOnce(std::bind(&Private::push, d, push_id, data)); d->pending_pushes_.insert(std::make_pair(push_id, idle_id)); g_mutex_unlock(&d->mutex_); } void MetadataResolver::Private::cancel() { g_mutex_lock(&mutex_); if (cancel_id_) { Source::Remove(cancel_id_); cancel_id_ = 0; } is_canceled_ = true; for (const auto p: pending_pushes_) { Source::Remove(p.second); } for (const unsigned opid: current_ops_) { kDebug("Canceling resolver task {1}") % opid; grl_operation_cancel(opid); } pending_pushes_.clear(); current_ops_.clear(); g_mutex_unlock(&mutex_); } void MetadataResolver::Private::push(unsigned push_id, const ResolveDataPtr &data) { const Wrapper media = take(data->metadata.make_media(initial_key_list_.get(), data->url)); g_mutex_lock(&mutex_); resolve_next(media.get(), data); const size_t removals = pending_pushes_.erase(push_id); BOOST_ASSERT(removals == 1); if (is_idle()) g_cond_signal(&cond_); g_mutex_unlock(&mutex_); } void MetadataResolver::Private::resolve_next(GrlMedia *media, const ResolveDataPtr &data) { if (data->it == available_sources_.end()) { fill_metadata(media, data); } else if (data->it->needs_id_lookup) { resolve_media_ids(media, data); } else { resolve_details(media, data); } } void MetadataResolver::Private::resolve_media_ids(GrlMedia *media, const ResolveDataPtr &data) { const unsigned opid = grl_source_resolve (data->it->source.get(), media, data->it->id_keys.get(), options_.get(), &Private::OnResolveMediaIds, new ResolveDataPtr(data)); const std::string media_url = safe_string(grl_media_get_url(media)); kDebug("Pushing resolver task {1}@{2} for media ids of <{3}>") % opid % grl_source_get_id(data->it->source.get()) % media_url; current_ops_.push_back(opid); } void MetadataResolver::Private::resolve_details(GrlMedia *media, const ResolveDataPtr &data) { const unsigned next_opid = grl_source_resolve (data->it->source.get(), media, data->it->resolve_keys.get(), options_.get(), &Private::OnResolveDetails, new ResolveDataPtr(data)); const std::string media_url = safe_string(grl_media_get_url(media)); kDebug("Pushing resolver task {1}@{2} for details of <{3}>") % next_opid % grl_source_get_id(data->it->source.get()) % media_url; current_ops_.push_back(next_opid); } MetadataResolver::Private::ResolveDataPtr MetadataResolver::Private::take_resolve_data(gpointer user_data) { ResolveDataPtr *const data = static_cast(user_data); std::unique_ptr deleter(data); return ResolveDataPtr(*deleter); } template static bool erase(const T &val, std::vector *vec) { typedef typename std::vector::iterator iterator; const iterator it = std::find(vec->begin(), vec->end(), val); if (it == vec->end()) return false; vec->erase(it); return true; } void MetadataResolver::Private::OnResolveMediaIds (GrlSource *source, unsigned opid, GrlMedia *media, void *user_data, const GError *error) { const ResolveDataPtr data = take_resolve_data(user_data); Private *const d = data->d; bool media_id_found = false; bool retry = false; for (const GList *l = data->it->id_keys; l; l = l->next) { if (grl_data_has_key(GRL_DATA(media), GRLPOINTER_TO_KEYID(l->data))) { grl_data_remove(GRL_DATA(media), GRL_METADATA_KEY_TITLE); media_id_found = true; break; } } if (not media_id_found) { static const boost::regex::flag_type rx_flags = (boost::regex_constants::normal | boost::regex_constants::optimize); static const boost::regex rx_camel_case("(\\l)(\\u)", rx_flags); const std::string original_title = safe_string(grl_media_get_title(media)); const std::string modified_title = boost::regex_replace(original_title, rx_camel_case, "\\1 \\2"); if (original_title.length() < modified_title.length()) { grl_data_remove(GRL_DATA(media), GRL_METADATA_KEY_TITLE); grl_media_set_title(media, modified_title.c_str()); retry = true; } } g_mutex_lock(&d->mutex_); if (not erase(opid, &d->current_ops_)) kError("Bad operation id {1} in {2}") % opid % __func__; if (not d->is_canceled_) { if (error) { const std::string error_message = to_string(error); kWarning("Metadata resolving task {1}@{2} failed: {3}") % opid % grl_source_get_id(source) % error_message; } else if (retry) { d->resolve_media_ids(media, data); } else { d->resolve_details(media, data); } } if (d->is_idle()) g_cond_signal(&d->cond_); g_mutex_unlock(&d->mutex_); } void MetadataResolver::Private::OnResolveDetails (GrlSource *source, unsigned opid, GrlMedia *media, void *user_data, const GError *error) { const ResolveDataPtr data = take_resolve_data(user_data); Private *const d = data->d; g_mutex_lock(&d->mutex_); if (not erase(opid, &d->current_ops_)) kError("Bad operation id {1} in {2}") % opid % __func__; if (not d->is_canceled_) { if (error) { const std::string error_message = to_string(error); kWarning("Metadata resolving task {1}@{2} failed: {3}") % opid % grl_source_get_id(source) % error_message; } else { kDebug("Metadata resolving task {1}@{2} finished") % opid % grl_source_get_id(source); data->it++; d->resolve_next(media, data); } } if (d->is_idle()) g_cond_signal(&d->cond_); g_mutex_unlock(&d->mutex_); } void MetadataResolver::Private::fill_metadata(GrlMedia *media, const ResolveDataPtr &data) { std::string error_message; kDebug("Filling in metadata from media"); if (data->metadata.fill_from_media (media, grl_data_get_keys(GRL_DATA(media)), nullptr, &error_message)) { const std::wstring url = safe_wstring(grl_media_get_url(media)); data->store_metadata(url, data->metadata); } else { kWarning("Failed to collect metadata: {1}") % error_message; } } void MetadataResolver::WaitForFinished() { g_mutex_lock(&d->mutex_); kInfo("Waiting for pending metadata resolvers."); while (not is_idle()) { g_cond_wait(&d->cond_, &d->mutex_); } kInfo("Metadata resolving finished."); g_mutex_unlock(&d->mutex_); } bool MetadataResolver::is_idle() const { return d->is_idle(); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaartdownloader.cpp0000644000015700001700000002212212232220161026505 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: James Henstridge */ #include "mediascanner/logging.h" #include "mediascanner/glibutils.h" #include "mediascanner/mediaartcache.h" #include "mediascanner/mediaartdownloader.h" #define ALBUM_ART_PLUGIN "grl-lastfm-albumart" #define ALBUM_ART_SOURCE "grl-lastfm-albumart" namespace mediascanner { static const logging::Domain kInfo("info/mediaartdownloader", logging::info()); static const logging::Domain kWarning("warning/mediaartdownloader", logging::warning()); class MediaArtDownloader::Private { public: struct ResolveData { ResolveData(Private *d, const std::string &artist, const std::string &album) : d(d) , artist(artist) , album(album) { } Private *const d; const std::string artist; const std::string album; guint operation_id; // Grilo operation Wrapper message; }; typedef std::shared_ptr ResolveDataPtr; Private() { g_mutex_init(&mutex); g_cond_init(&cond); setup_resolver(); setup_soup_session(); } ~Private() { cancel(); } bool is_idle() const { return jobs.empty(); } GMutex mutex; GCond cond; MediaArtCache cache; Wrapper art_resolver; Wrapper session; std::vector jobs; void cancel(); void setup_resolver(); void setup_soup_session(); void add_job(ResolveDataPtr data); void complete(ResolveData *data); static void OnResolveThumbnail(GrlSource *source, unsigned operation_id, GrlMedia *media, void *user_data, const GError *error); static void OnResponseReceived(SoupSession *session, SoupMessage *message, void *user_data); }; MediaArtDownloader::MediaArtDownloader() : d(new Private) { } MediaArtDownloader::~MediaArtDownloader() { delete d; } void MediaArtDownloader::LoadGriloPlugin() { const Wrapper registry = wrap(grl_registry_get_default()); Wrapper error; if (not grl_registry_load_plugin_by_id( registry.get(), ALBUM_ART_PLUGIN, error.out_param())) { const std::string error_message = to_string(error); kWarning("Could not load album art plugin: {1}") % error_message; return; } } void MediaArtDownloader::Private::setup_resolver() { const Wrapper registry = wrap(grl_registry_get_default()); art_resolver = wrap(grl_registry_lookup_source( registry.get(), ALBUM_ART_SOURCE)); if (not art_resolver) { kWarning("Could not load album art source"); return; } } void MediaArtDownloader::Private::setup_soup_session() { session = wrap(soup_session_async_new_with_options( SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, SOUP_SESSION_SSL_STRICT, TRUE, SOUP_SESSION_USER_AGENT, "Ubuntu-Media-Scanner/1.0", NULL)); soup_session_add_feature_by_type( session.get(), SOUP_TYPE_PROXY_RESOLVER_DEFAULT); } void MediaArtDownloader::Private::add_job(ResolveDataPtr data) { jobs.push_back(data); } void MediaArtDownloader::Private::complete(ResolveData *data) { for (auto it = jobs.begin(); it != jobs.end(); it++) { if (it->get() == data) { jobs.erase(it); break; } } if (is_idle()) g_cond_signal(&cond); } void MediaArtDownloader::resolve(const std::string &artist, const std::string &album) { // If we couldn't configure either of these objects, then we won't // have any luck resolving the album art. if (not d->art_resolver || not d->session) return; kInfo("Resolving art for {1} - {2}") % artist % album; if (d->cache.has_art(artist, album)) { return; } g_mutex_lock(&d->mutex); /* First check whether there is any queued requests for this art */ for (auto it = d->jobs.begin(); it != d->jobs.end(); it++) { if ((*it)->artist == artist && (*it)->album == album) { kInfo("Art for {1} - {2} already being resolved") % artist % album; g_mutex_unlock(&d->mutex); return; } } const Private::ResolveDataPtr data( new Private::ResolveData(d, artist, album)); d->add_job(data); Wrapper media = take(grl_media_audio_new()); grl_media_audio_set_artist(GRL_MEDIA_AUDIO(media.get()), artist.c_str()); grl_media_audio_set_album(GRL_MEDIA_AUDIO(media.get()), album.c_str()); Wrapper resolve_keys = take(grl_metadata_key_list_new( GRL_METADATA_KEY_THUMBNAIL, GRL_METADATA_KEY_INVALID)); Wrapper options = take(grl_operation_options_new(nullptr)); data->operation_id = grl_source_resolve( d->art_resolver.get(), media.get(), resolve_keys, options.get(), &Private::OnResolveThumbnail, data.get()); g_mutex_unlock(&d->mutex); } void MediaArtDownloader::Private::OnResolveThumbnail( GrlSource *source, unsigned operation_id, GrlMedia *media, void *user_data, const GError *error) { ResolveData *data = static_cast(user_data); Private *const d = data->d; g_mutex_lock(&d->mutex); data->operation_id = 0; if (error != NULL) { kWarning("Error determining album art for {1} - {2}: {3}") % data->artist % data->album % error->message; d->complete(data); g_mutex_unlock(&d->mutex); return; } const char *thumbnail = grl_media_get_thumbnail(media); if (thumbnail == NULL) { kInfo("No thumbnail found for {1} - {2}") % data->artist % data->album; d->complete(data); g_mutex_unlock(&d->mutex); return; } kInfo("Thumbnail for {1} - {2}: {3}") % data->artist % data->album % thumbnail; data->message = take(soup_message_new("GET", thumbnail)); // queue_message() takes ownership of the message passed to it. soup_session_queue_message(d->session.get(), data->message.dup(), &OnResponseReceived, data); g_mutex_unlock(&d->mutex); } void MediaArtDownloader::Private::OnResponseReceived( SoupSession *session, SoupMessage *message, void *user_data) { // If we have been cancelled, then our data pointer has been // cleaned up. if (message->status_code == SOUP_STATUS_CANCELLED) return; ResolveData *data = static_cast(user_data); Private *const d = data->d; g_mutex_lock(&d->mutex); data->message = NULL; if (message->status_code != SOUP_STATUS_OK) { kWarning("Failed to retrieve album art for {1} - {2}") % data->artist % data->album; d->complete(data); g_mutex_unlock(&d->mutex); return; } std::string content_type = soup_message_headers_get_content_type( message->response_headers, NULL); if (content_type != "image/jpeg") { kWarning("Expected album art for {1} - {2} to be image/jpeg, but got {3}") % data->artist % data->album % content_type; d->complete(data); g_mutex_unlock(&d->mutex); return; } kInfo("Storing album art for {1} - {2}") % data->artist % data->album; d->cache.add_art( data->artist, data->album, message->response_body->data, message->response_body->length); d->complete(data); g_mutex_unlock(&d->mutex); } bool MediaArtDownloader::is_idle() const { return d->is_idle(); } void MediaArtDownloader::WaitForFinished() { g_mutex_lock(&d->mutex); kInfo("Waiting for pending album art downloads."); while (not is_idle()) { g_cond_wait(&d->cond, &d->mutex); } kInfo("Album art downloads complete."); g_mutex_unlock(&d->mutex); } void MediaArtDownloader::Private::cancel() { g_mutex_lock(&mutex); for (const auto &data: jobs) { if (data->operation_id != 0) { grl_operation_cancel(data->operation_id); } data->operation_id = 0; if (data->message) { soup_session_cancel_message( session.get(), data->message.get(), SOUP_STATUS_CANCELLED); } data->message = NULL; } g_mutex_unlock(&mutex); } } mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/glibutils.h0000644000015700001700000007633612232220161024323 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_GLIBUTILS_H #define MEDIASCANNER_GLIBUTILS_H // GLib Based Libraries #include #include #include #include #include #include // Boost C++ #include // C++ Standard Library #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { namespace internal { // GObject structure to GType mappings ///////////////////////////////////////// template GType GetGType(); template<> inline GType GetGType() { return G_TYPE_ARRAY; } template<> inline GType GetGType() { return G_TYPE_BYTE_ARRAY; } template<> inline GType GetGType() { return G_TYPE_CLOSURE; } template<> inline GType GetGType() { return G_TYPE_DATE; } template<> inline GType GetGType() { return G_TYPE_DATE_TIME; } template<> inline GType GetGType() { return G_TYPE_DRIVE; } template<> inline GType GetGType() { return G_TYPE_ERROR; } template<> inline GType GetGType() { return G_TYPE_FILE; } template<> inline GType GetGType() { return G_TYPE_FILE_MONITOR; } template<> inline GType GetGType() { return G_TYPE_FILE_TYPE; } template<> inline GType GetGType() { return G_TYPE_HASH_TABLE; } template<> inline GType GetGType() { return G_TYPE_KEY_FILE; } template<> inline GType GetGType() { return G_TYPE_ICON; } template<> inline GType GetGType() { return G_TYPE_MAIN_CONTEXT; } template<> inline GType GetGType() { return G_TYPE_MAIN_LOOP; } template<> inline GType GetGType() { return G_TYPE_MATCH_INFO; } template<> inline GType GetGType() { return G_TYPE_MOUNT; } template<> inline GType GetGType() { return G_TYPE_OBJECT; } template<> inline GType GetGType() { return G_TYPE_PTR_ARRAY; } template<> inline GType GetGType() { return G_TYPE_REGEX; } template<> inline GType GetGType() { return G_TYPE_SETTINGS; } template<> inline GType GetGType() { return G_TYPE_SOURCE; } template<> inline GType GetGType() { return G_TYPE_GSTRING; } template<> inline GType GetGType() { return G_TYPE_VALUE; } template<> inline GType GetGType() { return G_TYPE_VOLUME; } template<> inline GType GetGType() { return G_TYPE_VOLUME_MONITOR; } template<> inline GType GetGType() { return G_TYPE_VARIANT_BUILDER; } template<> inline GType GetGType() { return G_TYPE_VARIANT; } template<> inline GType GetGType() { return GRL_CAPS_TYPE; } template<> inline GType GetGType() { return GRL_TYPE_CONFIG; } template<> inline GType GetGType() { return GRL_TYPE_DATA; } template<> inline GType GetGType() { return GRL_TYPE_MEDIA; } template<> inline GType GetGType() { return GRL_TYPE_PLUGIN; } template<> inline GType GetGType() { return GRL_TYPE_REGISTRY; } template<> inline GType GetGType() { return GRL_TYPE_RELATED_KEYS; } template<> inline GType GetGType() { return GRL_TYPE_SOURCE; } template<> inline GType GetGType() { return GST_TYPE_DISCOVERER_INFO; } template<> inline GType GetGType() { return GST_TYPE_DISCOVERER_STREAM_INFO; } template<> inline GType GetGType() { return GST_TYPE_DISCOVERER_AUDIO_INFO; } template<> inline GType GetGType() { return GST_TYPE_DISCOVERER_CONTAINER_INFO; } template<> inline GType GetGType() { return GST_TYPE_DISCOVERER_VIDEO_INFO; } template<> inline GType GetGType() { return G_UDEV_TYPE_CLIENT; } template<> inline GType GetGType() { return G_UDEV_TYPE_DEVICE; } // GLib boxing and GObject reference counting ////////////////////////////////// template struct CopyHelper { static Type *Copy(Type *p); static void Free(Type *p); }; template struct BoxedCopyHelper { static Type *Copy(Type *p) { return static_cast(g_boxed_copy(GetGType(), p)); } static void Free(Type *p) { g_boxed_free(GetGType(), p); } }; template struct MiniObjectCopyHelper { static Type *Copy(Type *p) { return reinterpret_cast (gst_mini_object_ref(GST_MINI_OBJECT_CAST(p))); } static void Free(Type *p) { gst_mini_object_unref(GST_MINI_OBJECT_CAST(p)); } }; template struct ObjectCopyHelper { static Type *Copy(Type *p) { return static_cast(g_object_ref(p)); } static void Free(Type *p) { g_object_unref(p); } }; template struct ListCopyHelper { static List *Copy(List *const other) { List *const copy = CopyHelper::Copy(other); for (List *l = copy; l; l = l->next) { Type *const data = static_cast(l->data); l->data = CopyHelper::Copy(data); } return copy; } static void Free(GList *list) { for (List *l = list; l; l = l->next) { Type *const data = static_cast(l->data); CopyHelper::Free(data); } CopyHelper::Free(list); } }; template<> struct CopyHelper : public BoxedCopyHelper { }; template<> struct CopyHelper : public BoxedCopyHelper { }; template<> struct CopyHelper : public BoxedCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public MiniObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper : public ObjectCopyHelper { }; template<> struct CopyHelper { static char *Copy(char *p) { return g_strdup(p); } static void Free(char *p) { g_free(p); } }; template<> struct CopyHelper { static char **Copy(char **p) { return g_strdupv(p); } static void Free(char **p) { g_strfreev(p); } }; template<> struct CopyHelper { static GDBusArgInfo *Copy(GDBusArgInfo *p) { return g_dbus_arg_info_ref(p); } static void Free(GDBusArgInfo *p) { g_dbus_arg_info_unref(p); } }; template<> struct CopyHelper { static GDBusInterfaceInfo *Copy(GDBusInterfaceInfo *p) { return g_dbus_interface_info_ref(p); } static void Free(GDBusInterfaceInfo *p) { g_dbus_interface_info_unref(p); } }; template<> struct CopyHelper { static GDBusMethodInfo *Copy(GDBusMethodInfo *p) { return g_dbus_method_info_ref(p); } static void Free(GDBusMethodInfo *p) { g_dbus_method_info_unref(p); } }; template<> struct CopyHelper { static GDBusPropertyInfo *Copy(GDBusPropertyInfo *p) { return g_dbus_property_info_ref(p); } static void Free(GDBusPropertyInfo *p) { g_dbus_property_info_unref(p); } }; template<> struct CopyHelper { static GDBusSignalInfo *Copy(GDBusSignalInfo *p) { return g_dbus_signal_info_ref(p); } static void Free(GDBusSignalInfo *p) { g_dbus_signal_info_unref(p); } }; template<> struct CopyHelper { static GError *Copy(GError *p) { return g_error_copy(p); } static void Free(GError *p) { g_error_free(p); } }; template<> struct CopyHelper { static GKeyFile *Copy(GKeyFile *p) { return g_key_file_ref(p); } static void Free(GKeyFile *p) { g_key_file_unref(p); } }; template<> struct CopyHelper { static GList *Copy(GList *p) { return g_list_copy(p); } static void Free(GList *p) { g_list_free(p); } }; template<> struct CopyHelper { static GParamSpec *Copy(GParamSpec *p) { return g_param_spec_ref(p); } static void Free(GParamSpec *p) { g_param_spec_unref(p); } }; template<> struct CopyHelper { static GPtrArray *Copy(GPtrArray *p) { return g_ptr_array_ref(p); } static void Free(GPtrArray *p) { g_ptr_array_unref(p); } }; template<> struct CopyHelper { static GThread *Copy(GThread *p) { return g_thread_ref(p); } static void Free(GThread *p) { g_thread_unref(p); } }; template<> struct CopyHelper { static void Free(GUnixMountEntry *p) { g_unix_mount_free(p); } }; template<> struct CopyHelper { static GVariant *Copy(GVariant *p) { return g_variant_ref(p); } static void Free(GVariant *p) { g_variant_unref(p); } }; template<> struct CopyHelper { static GstStructure *Copy(GstStructure *p) { return gst_structure_copy(p); } static void Free(GstStructure *p) { gst_structure_free(p); } }; // Cast helpers //////////////////////////////////////////////////////////////// template struct CastHelper { static TargetType *cast(SourceType *p); }; template struct TypeInstanceCastHelper { static TargetType *Apply(SourceType *p) { if (G_TYPE_CHECK_INSTANCE_TYPE(p, GetGType())) return reinterpret_cast(p); return 0; } }; template struct TypeInstanceCastHelper { static GTypeInstance *Apply(SourceType *p) { return reinterpret_cast(p); } }; template struct CastHelper : public TypeInstanceCastHelper { }; template struct CastHelper : public TypeInstanceCastHelper { }; template struct CastHelper : public TypeInstanceCastHelper { }; } // namespace internal // Smart pointers for GLib boxed types and GObjects //////////////////////////// /** * @brief A shared smart-pointer for GLib related types. Via its helper classes * this smart-pointer supports a wide variety of GLib related types, such as * GObjects, GstMiniObjects, boxed types, and even simple GLists. * @see internal::CopyHelper, internal::CastHelper */ template class Wrapper { public: /** * @brief Creates an emtpy pointer not holding any object. */ Wrapper() : m_ptr(0) { } /** * @brief Copies another pointer instance. For reference counted objects * this new pointer will hold a new reference to the object. For boxed * types and similiar a new copy of the original object is created. * @param other The instance to initialize from. * @see take(), wrap(), wrap_static() */ Wrapper(const Wrapper &other) : m_ptr(0) { reset(other.get()); } /** * @brief Destroys the smart-pointer. For reference counted objects * this drops the reference hold, for other types this frees the object * instance. */ ~Wrapper() { if (m_ptr) CopyHelper::Free(m_ptr); } /** * @brief Releases the wrapped object. This detaches the wrapped object * from this smart-pointer without dropping the reference or freeing it. * Use this function to transfer ownership of the wrapped object. * @return A pointer to the wrapped object, or @c null if no object * was wrapped. */ T *release() { T *const p = m_ptr; m_ptr = 0; return p; } /** * @brief This function gives access to the wrapped object. * @return A pointer to the wrapped object. */ T *get() const { return m_ptr; } /** * @brief Creates a new reference to, or a new copy of the wrapped object. * This function is useful to initialize C structures or arrays. * @return A pointer to the new reference, or the new object. */ T *dup() const { return CopyHelper::Copy(get()); } /** * @brief This function casts the wrapped object to a different type. * @return A pointer to the wrapped object. */ template B *get() const { return internal::CastHelper::Apply(get()); } /** * @brief This operator gives access to the wrapped object's members. * @return A pointer to the wrapped object. */ T *operator->() const { return get(); } /** * @brief This function requests to wrap a different object. * If the wrapped objects are reference-counted, the reference to the * old object is dropped, and a reference to the new object is stored. * For other types the old object is freed and a copy of the new object * is stored. * @param p The new object to store, or @c null. * @see take(), out_param() */ void reset(T *p = 0) { if (p != m_ptr) { if (m_ptr) CopyHelper::Free(m_ptr); m_ptr = 0; if (p) m_ptr = CopyHelper::Copy(p); } } /** * @brief This function requests to take ownership of a different object. * If the wrapped objects are reference-counted, the reference to the * old object is dropped, for other types the old object is freed. * As this function takes ownership of the passed object, no new reference * is created, and no copy is created. * @param p The new object to store, or @c null. * @see reset(), out_param() */ void take(T *p) { if (p != m_ptr) { if (m_ptr) CopyHelper::Free(m_ptr); m_ptr = p; } } /** * @brief Resets the smart-pointer and returns a pointer to the internal * object pointer. This is useful to wrap objects retreived by output * parameters. * @return A pointer to the internal object pointer. * @see reset(), take() */ T **out_param() { reset(); return &m_ptr; } /** * @brief The assignment operator is an alias of the reset() method. * @param p The new object to store, or @c null * @return A reference to this smart-pointer. */ Wrapper &operator=(T *p) { reset(p); return *this; } /** * @brief The assignment operator is an alias of the reset() method. * @param p The new object to store, or @c null * @return A reference to this smart-pointer. */ Wrapper &operator=(const Wrapper &other) { return operator=(other.get()); } /** * @brief This operator casts the wrapped object to another type. * This cast operator also serves as safe bool cast. The more natural * operator bool() is problematic because it leads to unwanted implicit * casts. Also note that there is no non-const "operatpr T *()" to avoid * ambiguity problems for the compiler. */ operator const T *() const { return get(); } /** * @brief This operator checks if this pointer actually wraps an object. * @return @c true if this pointer wraps an object. */ bool operator!() const { return get() == 0; } /** * @brief This operator compares two pointers for equality. * @param p The other pointer to compare with. * @return @c true if both pointers are equal. */ bool operator==(const T *p) const { return m_ptr == p; } /** * @brief This operator compares two pointers for inequality. * @param p The other pointer to compare with. * @return @c true if both pointers are not equal. */ bool operator!=(const T *p) const { return !operator==(p); } private: T *m_ptr; }; template struct ListWrapper : public Wrapper > { typedef Wrapper > inherited; ListWrapper(const Wrapper &other) // NOLINT: runtime/explicit : inherited(other) { } /** * @brief Constructs a ListWrapper that directly takes ownership of @list. * This constructor is useful since creating a deep copy of a list can be * pretty expensive. */ explicit ListWrapper(List *list) { inherited::take(list); } }; /** * @brief A type-safe function for creating shallow copies of structures. * @param T The structure's type. * @param p A pointer to the structure to copy. * @return A new copy of the structure, as returned by @c g_memdup(). */ template inline T* shallow_copy(const T *p) { return static_cast(g_memdup(p, sizeof(T))); } /** * @brief A type-safe function for creating shallow copies of pointer arrays. * @param T The array's element type. * @param N The number of elements in the array. * @param p A pointer to the array to copy. * @return A new copy of the array, as returned by @c g_memdup(). */ template static T* array_copy(const T (&p)[N]) { return static_cast(g_memdup(p, sizeof p)); } /** * @brief Wraps a pointer to an object by a shared smart-pointer. * This function doesn't take ownership of the object. It therefore @b does * increase the object's reference count. * @param T The type of the object to wrap. * @param p The pointer to the object to wrap. * @return A new Wrapper instance that holds a new reference to the object. */ template inline Wrapper wrap(T *p) { Wrapper wrapped; wrapped.reset(p); return wrapped; } /** * @brief Wraps a pointer to an object by a shared smart-pointer. * This function takes ownership of the object. It therefore @b doesn't * increase the object's reference count. * @param T The type of the object to wrap. * @param p The pointer to the object to wrap. * @return A new Wrapper instance that holds now owns the object. */ template inline Wrapper take(T *p) { Wrapper owned; owned.take(p); return owned; } /** * @brief Wraps a statically allocated structure by a shared smart-pointer. * This function creates a shallow copy of the structure and then takes * ownership of that copy. * @param T The type of the structure to wrap. * @param p The pointer to the structure to wrap. * @return A new Wrapper instance that holds owns a copy of the structure. */ template inline Wrapper wrap_static(const T *p) { return take(shallow_copy(p)); } // GLib event loop integration ///////////////////////////////////////////////// /** * @brief Type-safe destroy notifier. * This function can be used as destroy notifier for GLib functions. * It automatically invokes the proper @c delete operator of @p T. * @param T The type of the object to destroy. * @param user_data A pointer to the object to destroy. */ template inline void DestroyNotify(gpointer user_data) { delete static_cast(user_data); } template inline void ClosureNotify(gpointer user_data, GClosure *) { delete static_cast(user_data); } /** * @brief The Source class provides access to the GLib event source mechanism. */ class Source { public: /** * @brief The signature of a regular idle source. * @return If this function returns @c false it is automatically removed * from the list of event sources and will not be called again. */ typedef std::function SourceFunction; /** * @brief The signature of a single-call idle source. * This function is called excactly once. After returning it is * automatically removed from the list of event sources and will not be * called again. */ typedef std::function OneCallFunction; /** * @brief Removes an event source handler. * @param id The source handler's identifier. * @return @c True if the identifier was valid and the handler got removed. */ static bool Remove(unsigned id) { return g_source_remove(id); } protected: static gboolean on_source_function(gpointer data) { SourceFunction *const function = static_cast(data); if (function) return (*function)(); return false; } static gboolean on_one_call_function(gpointer data) { OneCallFunction *const function = static_cast(data); if (function) (*function)(); return false; } }; /** * @brief The Idle class provides access to GLib's idle source mechanism. * It manages functions which get called whenever there are no higher priority * events pending to the default main loop. * * @see Timeout */ class Idle : public Source { public: /** * @brief Installs a regular idle source. This source will be invoked * until the @p function returns @c false. After that it be removed * automatically from the list of event sources. * @param function The function to be called on idle. * @param priority The priority of the idle source. * @return The identifier (greater than 0) of the event source. * @see AddOnce(), Source::Remove() */ static unsigned Add(const SourceFunction &function, int priority = G_PRIORITY_DEFAULT) { return g_idle_add_full(priority, &Source::on_source_function, new SourceFunction(function), &DestroyNotify); } /** * @brief Installs a single-call idle source. This source will be called * exactly once. After that it be removed automatically from the list of * event sources. * @param function The function to be called on idle. * @param priority The priority of the idle source. * @return The identifier (greater than 0) of the event source. * @see Add(), Source::Remove() */ static unsigned AddOnce(const OneCallFunction &function, int priority = G_PRIORITY_DEFAULT) { return g_idle_add_full(priority, &Source::on_one_call_function, new OneCallFunction(function), &DestroyNotify); } }; /** * @brief The Timeout class provides access to GLib's timeout mechanism. * It manages functions which get called whenever a given time interval * has passed. * * Note that timeout functions might get delayed if the event loop gets * blocked by other sources. * * @see Idle */ class Timeout : public Source { public: /** * @brief This type describes time durations. Note that the duration's * resolution impacts which resolution the timeout source will have. */ typedef boost::posix_time::time_duration Duration; /** * @brief Adds a new timeout function to the event loop. The timeout * gets automatically removed from the list of event sources if @p function * returns @c false. * * Note that the exact timeout mechanism is selected upon the @p interval * parameter's resolution. If the resolution is in seconds (or even less * granular) timeout sources of seconds precision are created. Otherwise * the sources have milliseconds resolution. * * @param interval The time between calls to the function. * @param function The function to be called on idle. * @param priority The priority of the idle source. * @return The identifier (greater than 0) of the event source. * @see AddOnce(), Source::Remove() */ static unsigned Add(Duration interval, const SourceFunction &function, int priority = G_PRIORITY_DEFAULT_IDLE) { if (interval.resolution() == boost::date_time::sec) { return g_timeout_add_seconds_full(priority, interval.total_seconds(), &Source::on_source_function, new SourceFunction(function), &DestroyNotify); } return g_timeout_add_full(priority, interval.total_milliseconds(), &Source::on_source_function, new SourceFunction(function), &DestroyNotify); } /** * @brief Adds a new timeout function to the event loop that is called * exactly once. After its invocation the timeout gets automatically * removed from the list of event sources if @p function returns @c false. * * Note that the exact timeout mechanism is selected upon the @p interval * parameter's resolution. If the resolution is in seconds (or even less * granular) timeout sources of seconds precision are created. Otherwise * the sources have milliseconds resolution. * * @param interval The time between calls to the function. * @param function The function to be called on idle. * @param priority The priority of the idle source. * @return The identifier (greater than 0) of the event source. * @see AddOnce(), Source::Remove() */ static unsigned AddOnce(Duration interval, const OneCallFunction &function, int priority = G_PRIORITY_DEFAULT_IDLE) { if (interval.fractional_seconds() == 0) { return g_timeout_add_seconds_full(priority, interval.total_seconds(), &Source::on_one_call_function, new OneCallFunction(function), &DestroyNotify); } return g_timeout_add_full(priority, interval.total_milliseconds(), &Source::on_one_call_function, new OneCallFunction(function), &DestroyNotify); } }; // String conversions ////////////////////////////////////////////////////////// /** * @brief Describes a @c GError. The returned string contains * the error domain, the error code, and of course the error message. * @param error The error to describe, or @c null * @return A description of the error if appliable. */ std::string to_string(const GError *error); /** * @brief Describes GStreamer capabilities * @param caps The capabilities to describe, or @c null * @return A describption of the capabilities if appliable */ std::string to_string(const GstCaps *caps); // GParamSpec helpers ////////////////////////////////////////////////////////// /** * @brief Creates a @c GParamSpec for boxed types. This function is useful in * static variable initialization to defer @c G_TYPE_* calls that could lead * to warnings about the missing @c g_type_init() invocation. * @param BoxedType G_TYPE_BOXED derived type of this property. * @param name Canonical name of the property specified. * @param nick Nick name for the property specified. * @param blurb Description of the property specified. * @param flags Flags for the property specified. * @return A newly created parameter specification. */ template inline GParamSpec *MakeParamSpecBoxed(const char *name, const char *nick, const char *blurb, GParamFlags flags) { return ::g_param_spec_boxed(name, nick, blurb, internal::GetGType(), flags); } } // namespace mediascanner #endif // MEDIASCANNER_GLIBUTILS_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaroot.h0000644000015700001700000000507012232220161024273 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_MEDIAROOT_H #define MEDIASCANNER_MEDIAROOT_H // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { class MediaRoot { friend class MediaRootManager; class Private; protected: explicit MediaRoot(Private *d); public: ~MediaRoot(); std::string error_message() const; bool is_valid() const; std::string path() const; std::string base_path() const; std::string relative_path() const; std::string group_id() const; Wrapper file() const; bool operator==(const MediaRoot &other) const; bool operator<(const MediaRoot &other) const; private: std::shared_ptr d; }; class MediaRootManager { class Private; public: class Listener { public: virtual ~Listener() { } virtual void OnMediaRootAdded(const MediaRoot &/*root*/) { } virtual void OnMediaRootRemoved(const MediaRoot &/*root*/) { } }; MediaRootManager(); ~MediaRootManager(); /** * @brief Explicitly runs the initializer instead of waiting for the main * loop to trigger initialization. */ void initialize(); void add_listener(Listener *listener); void remove_listener(Listener *listener); std::vector media_roots() const; MediaRoot AddRelativeRoot(const std::string &relative_path); void AddManualRoot(const std::string &path); std::vector manual_roots() const; void set_enabled(bool enabled); bool enabled() const; MediaRoot make_root(const std::string &path) const; private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_MEDIAROOT_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/CMakeLists.txt0000644000015700001700000000331012232220161024672 0ustar pbuserpbgroup00000000000000include_directories(${DEPS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/src) add_definitions(-DG_LOG_DOMAIN="${PROJECT_NAME}") set(mediascanner_HEADERS commitpolicy.h dbusservice.h dbustypes.h declarations.h dbusutils.h filesystemscanner.h filesystemwalker.h filter.h locale.h mediaindex.h mediaroot.h mediautils.h property.h propertyschema.h settings.h refreshpolicy.h writablemediaindex.h ) set(mediascanner_SOURCES commitpolicy.cpp dbusservice.cpp dbustypes.cpp dbusutils.cpp filesystemscanner.cpp filesystemwalker.cpp filter.cpp glibutils.cpp glibutils.h locale.cpp logging.cpp logging.h mediaartcache.cpp mediaartdownloader.cpp mediaindex.cpp mediaroot.cpp mediautils.cpp metadataresolver.h metadataresolver.cpp property.cpp propertyprivate.h propertyschema.cpp refreshpolicy.cpp settings.cpp taskfacades.cpp taskfacades.h taskmanager.cpp taskmanager.h utilities.h utilities.cpp writablemediaindex.cpp ) add_library(mediascanner SHARED ${mediascanner_SOURCES}) target_link_libraries(mediascanner ${DEPS_LDFLAGS} ${Boost_LIBRARIES}) set_target_properties(mediascanner PROPERTIES OUTPUT_NAME mediascanner-${MEDIASCANNER_API_VERSION} SOVERSION "${MEDIASCANNER_ABI}" VERSION "${MEDIASCANNER_VERSION}") install(TARGETS mediascanner LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(FILES ${mediascanner_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mediascanner-${MEDIASCANNER_API_VERSION}/mediascanner ) mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/writablemediaindex.cpp0000644000015700001700000003735412232220161026516 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/writablemediaindex.h" // Lucene++ #include #include #include #include #include #include #include #include #include #include // Boost C++ #include #include #include #include // C++ Standard Library #include #include #include // Media Scanner #include "mediascanner/commitpolicy.h" #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaroot.h" #include "mediascanner/mediautils.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" namespace mediascanner { // Boost C++ using boost::locale::format; // Lucene++ using Lucene::LuceneException; using Lucene::newLucene; // Context specific logging domains static const logging::Domain kError("error/index", logging::error()); static const logging::Domain kInfo("info/index", logging::info()); static const logging::Domain kDebug("debug/index", logging::debug()); static const logging::Domain kTrace("trace/index", logging::trace()); static const logging::Domain kTraceLucene("trace/index/lucene", logging::Domain::Disabled | logging::Domain::Explicit, &kTrace); namespace internal { class LoggingInfoStream : public Lucene::InfoStream { public: Lucene::InfoStream& operator<<(const String& text) { kTraceLucene(L"{1}") % text; return *this; } }; } // namespace internal class WritableMediaIndex::Private { friend class WritableMediaIndex; Private() : write_lock_count_(0) , write_lock_timeout_(Lucene::IndexWriter::WRITE_LOCK_TIMEOUT) { } Lucene::IndexWriterPtr find_writer(const std::wstring &url) const; bool obtain_write_lock(const FileSystemPath &path, WritableMediaIndex *index) { if (not write_lock_) { const std::wstring w_path = ToUnicode(path.string()); const Lucene::LockFactoryPtr lf = newLucene(w_path); kDebug("Obtaining write lock for \"{1}\".") % path; write_lock_ = lf->makeLock(Lucene::IndexWriter::WRITE_LOCK_NAME); if (not write_lock_->obtain(write_lock_timeout_)) { index->report_error(format("Cannot obtain write lock for " "\"{1}\" within permitted time.") % path); return false; } } ++write_lock_count_; return true; } void release_write_lock() { if (not write_lock_) { kError("Cannot release non-existent write lock."); return; } if (--write_lock_count_ == 0) { kDebug("Releasing write lock."); write_lock_->release(); write_lock_.reset(); } } typedef std::map IndexWriterMap; IndexWriterMap writers_; CommitPolicyPtr commit_policy_; unsigned write_lock_count_; Lucene::LockPtr write_lock_; int64_t write_lock_timeout_; }; WritableMediaIndex::WritableMediaIndex(MediaRootManagerPtr root_manager) : MediaIndex(root_manager) , d(new Private) { set_commit_policy(CommitPolicy::default_policy()); } WritableMediaIndex::~WritableMediaIndex() { delete d; } void WritableMediaIndex::set_commit_policy(CommitPolicyPtr policy) { if (not policy) policy = CommitPolicy::default_policy(); if (d->commit_policy_ != policy && d->commit_policy_) CommitPendingChanges(); d->commit_policy_ = policy; } CommitPolicyPtr WritableMediaIndex::commit_policy() const { return d->commit_policy_; } void WritableMediaIndex::set_write_lock_timeout(int64_t timeout) { d->write_lock_timeout_ = timeout; for (const auto &w: d->writers_) { w.second->setWriteLockTimeout(timeout); } } int64_t WritableMediaIndex::write_lock_timeout() const { return d->write_lock_timeout_; } bool WritableMediaIndex::Open(const FileSystemPath &path) { if (not d->obtain_write_lock(path, this)) return false; const bool success = MediaIndex::Open(path); d->release_write_lock(); return success; } // NOTE: About WritableMediaIndex::Reopen(): Currently there is no need for // reopening the writer since write conflicts are handled internally by Lucene++ // using lock files. Therefore reopening the reader is sufficient. void WritableMediaIndex::Close() { for (const auto &w: d->writers_) { const FileSystemPath index_path = path(); kDebug("Closing media index index for \"{1}\" at \"{2}\".") % w.first % index_path; w.second->close(); } d->writers_.clear(); MediaIndex::Close(); } static std::string make_uuid() { boost::uuids::random_generator make_uuid; std::ostringstream oss; oss << make_uuid(); return oss.str(); } bool WritableMediaIndex::ReadParams() { const FileSystemPath base_path = params_path().parent_path(); if (not d->obtain_write_lock(base_path.string(), this)) return false; const bool success = ReadParamsUnlocked(); d->release_write_lock(); return success; } bool WritableMediaIndex::ReadParamsUnlocked() { const FileSystemPath info_path = params_path(); const FileSystemPath base_path = info_path.parent_path(); if (not boost::filesystem::is_directory(base_path) && not boost::filesystem::create_directories(base_path)) { report_error(format("Cannot create index base directory \"{1}\".") % base_path); return false; } if (not boost::filesystem::is_regular_file(info_path)) { kDebug("Writing initial settings for \"{1}\".") % base_path; std::ostringstream oss; oss << "[global]" << std::endl << "format = " << kMetaIndexFormat << std::endl; Wrapper error; if (not g_file_set_contents(info_path.string().c_str(), oss.str().data(), oss.str().length(), error.out_param())) { const std::string message = to_string(error); report_error(format("Cannot write initial settings: {1}.") % message); return false; } } if (not MediaIndex::ReadParams()) return false; const std::string data_dirname = get_param("global", kParamSegments); const FileSystemPath data_path = base_path / data_dirname; if (not boost::filesystem::is_directory(data_path) && not boost::filesystem::create_directories(data_path)) { report_error(format("Cannot create index data directory \"{1}\".") % data_path); return false; } return true; } bool WritableMediaIndex::FlushParams(Wrapper params) { const FileSystemPath info_path = params_path(); const FileSystemPath base_path = info_path.parent_path(); kDebug("Flushing settings for \"{1}\".") % base_path; if (not d->obtain_write_lock(base_path.string(), this)) return false; size_t length; Wrapper error; const Wrapper data = take(g_key_file_to_data(params.get(), &length, error.out_param())); const bool success = data && g_file_set_contents(info_path.string().c_str(), data.get(), length, error.out_param()); if (not success) { const std::string message = to_string(error); report_error(format("Cannot write settings for \"{1}\": {2}") % base_path % message); } d->release_write_lock(); return success; } Lucene::IndexReaderPtr WritableMediaIndex::OpenIndex() { return OpenChildIndex(root_manager()->make_root(path().string())); } Lucene::IndexReaderPtr WritableMediaIndex::OpenChildIndex (const MediaRoot &root) { const FileSystemPath root_path = root.path(); const FileSystemPath index_path = path(); kInfo("Opening child index for \"{1}\" at \"{2}\"") % root_path % index_path; if (not root.is_valid()) { report_error(format("Cannot open child index for invalid media root")); return Lucene::IndexReaderPtr(); } if (not d->obtain_write_lock(path(), this)) return Lucene::IndexReaderPtr(); const std::wstring w_media_root = ToUnicode(root.path()); const Private::IndexWriterMap::const_iterator it = d->writers_.find(w_media_root); Lucene::IndexReaderPtr reader; if (it != d->writers_.end()) { reader = it->second->getReader(); } else { std::string index_dirname = get_param(root.group_id(), kParamSegments); if (index_dirname.empty()) { index_dirname = make_uuid(); // Store child index parameters. We explicitly store the volume // UUID and the relative path, although this information also is // used to build the group id, and therefore interested modules // could retrieve that information by parsing the group id. Still // such an approach would be rather fragile and therefore should // be avoided. const std::string group_id = root.group_id(); set_param(group_id, kParamSegments, index_dirname); set_param(group_id, kParamRelativePath, root.relative_path()); } const FileSystemPath index_path = path() / index_dirname; kDebug("Creating new child index for \"{1}\" at \"{2}\"") % w_media_root % index_path; try { const Lucene::FSDirectoryPtr directory = Lucene::FSDirectory::open(ToUnicode(index_path.string())); const Lucene::IndexWriterPtr writer = newLucene (directory, analyzer(), Lucene::IndexWriter:: MaxFieldLengthUNLIMITED); writer->setWriteLockTimeout(write_lock_timeout()); // NOTE: This is a bit static, but Lucene++'s logging is extremely // verbose and I don't think we'll have to enable it at runtime. if (kTraceLucene.enabled()) { using internal::LoggingInfoStream; writer->setInfoStream(newLucene()); } d->writers_.insert(std::make_pair(w_media_root, writer)); reader = writer->getReader(); } catch(const LuceneException &ex) { const std::string message = FromUnicode(ex.getError()); report_error(format("Cannot open index writer: {1}") % message); } } d->release_write_lock(); return reader; } void WritableMediaIndex::CommitPendingChanges() { for (const auto &w: d->writers_) { const FileSystemPath index_path = path(); kTrace("Writing changes for \"{1}\" to \"{2}\".") % w.first % index_path; w.second->commit(); } } bool WritableMediaIndex::Insert(const std::wstring &url, const MediaInfo &metadata) { kTrace(L"Inserting for <{1}>") % url; const Lucene::IndexWriterPtr writer = find_index_writer(url); if (not writer) { report_error(format("This media index doesn't cover <{1}>.") % url); return false; } refresh_policy()->OnBeginWriting(this); const Lucene::IndexReaderPtr reader = writer->getReader(); const Lucene::TermPtr url_term = MakeLookupTerm(url); const Lucene::TermDocsPtr url_docs = reader->termDocs(url_term); const bool document_exists = url_docs->next(); Lucene::DocumentPtr document; if (document_exists) document = reader->document(url_docs->doc()); if (not document) { document = newLucene(); document->add(newLucene (schema::kUrl.field_name(), url, Lucene::Field::STORE_YES, Lucene::Field::INDEX_NOT_ANALYZED)); } else { // For existing documents, first clear all field values we // know about from the index. // FIXME(jamesh): could we just clear all fields but kURL from // the Lucene document here rather than just fields the // MediaInfo knows about? for (const Property::ValueMap &properties: metadata) { for (const auto &p: properties) { if (p.first == schema::kUrl) continue; document->removeField(p.first.field_name()); } } } // FIXME(M3): Really deal with related properties. for (const Property::ValueMap &properties: metadata) { for (const auto &p: properties) { // FIXME(M3): Check for "writable" attribute instead if (p.first == schema::kUrl) continue; const String field_name = p.first.field_name(); kTrace(L" - {1}: {2}") % field_name % p.second; for (const Lucene::FieldablePtr field: p.first.MakeFields(p.second)) { document->add(field); } } } if (document_exists) { kTrace(" - updating document"); writer->updateDocument(url_term, document); d->commit_policy_->OnUpdate(std::vector() << url, this); } else { kTrace(" - creating document"); writer->addDocument(document); d->commit_policy_->OnCreate(std::vector() << url, this); } return true; } bool WritableMediaIndex::Delete(const std::wstring &url) { kTrace(L"Deleting <{1}>...") % url; const Lucene::IndexWriterPtr writer = find_index_writer(url); if (not writer) { report_error(format("This media index doesn't cover <{1}>.") % url); return false; } refresh_policy()->OnBeginWriting(this); writer->deleteDocuments(MakeLookupTerm(url)); d->commit_policy_->OnRemove(std::vector() << url, this); return true; } Lucene::IndexWriterPtr WritableMediaIndex::find_index_writer (const std::wstring &url) { if (not boost::algorithm::starts_with(url, L"file://")) { report_error(format("Unsupported URL: <{1}>") % url); return Lucene::IndexWriterPtr(); } boost::filesystem::wpath path = url.substr(strlen("file://")); while (not path.empty()) { const Private::IndexWriterMap::iterator it = d->writers_.find(path.wstring()); if (it != d->writers_.end()) { kTrace(L"Using writer for \"{1}\"") % path; return it->second; } path = path.parent_path(); } return Lucene::IndexWriterPtr(); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/property.h0000644000015700001700000004524612232220161024205 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_PROPERTY_H #define MEDIASCANNER_PROPERTY_H // Boost C++ #include #include #include // C++ Standard Library #include #include #include #include #include #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { enum FullTextSearchMode { DisableFullTextSearch, EnableFullTextSearch }; /// The relational data type we support. /// /// Fractions are used for instance to preciscely describe /// a photo's exposure time. typedef boost::rational Fraction; /// The date-time type we support. typedef boost::posix_time::ptime DateTime; /// The string type we support. /// /// We use std::wstring instead of std::string to match Lucene++. typedef std::wstring String; /// Definition of a property value. /// /// This class provides all the information to descibe data mappings between /// all the involved subsystems such as [Lucene++], [Grilo] and [GStreamer]. /// It also provides facilities to convert property values among those systems. /// /// Properties cannot be instantiated directly. Instead refer the constants /// declared in the mediascanner::schema namespace. /// /// [Lucene++]: https://github.com/luceneplusplus/LucenePlusPlus /// [Grilo]: https://live.gnome.org/Grilo/ /// [GStreamer]: http://gstreamer.freedesktop.org/ class Property { protected: /// Internal fields of a Property. class Private; /// Shared pointer to internal property fields. typedef std::shared_ptr PrivatePtr; public: typedef boost::variant ValueBase; /// Container class for all supported property values. class Value : public ValueBase { public: Value() : ValueBase() { } template Value(const T &v) : ValueBase(v) { } bool operator<(const Value &other) const { return ValueBase::operator<(static_cast(other)); } bool operator==(const Value &other) const { return ValueBase::operator==(static_cast(other)); } }; class Category { protected: explicit Category(unsigned id) : id_(id) { } public: bool operator==(const Category &other) const { return id_ == other.id_; } bool operator<(const Category &other) const { return id_ < other.id_; } bool is_a(const Category &base) const { return (id_ & base.id_) == base.id_; } static const Category Generic; static const Category File; static const Category Media; static const Category Music; static const Category Image; static const Category Photo; static const Category Movie; private: unsigned id_; }; /// A property value bound to an actual property. typedef std::pair BoundValue; /// Maps properties to their actual values. typedef std::map ValueMap; /// A set of distinct properties. typedef std::set Set; /// Collection of Lucene++ fields. typedef Lucene::Collection LuceneFields; /// Boundaries of a range query. enum Boundary { Inclusive, Exclusive }; /// How to resolve merge conflicts for this property. enum MergeStrategy { MergeAppend, MergeReplace, MergePreserve }; public: class MetadataKey { public: /// Functions of this kind are used to register custom metadata keys. typedef std::function SpecFunction; /// Grilo metadata key for properties with pending custom metadata keys. /// /// Custom metadata keys need lazy initialization since they rely on the /// GObject type system which must be initialized with g_type_init(), or /// any other function such as grl_init() that implicitly calls this /// function. static const GrlKeyID kPending; /// Invalid Grilo metadata key. static const GrlKeyID kInvalid; MetadataKey(GrlKeyID id) // NOLINT: runtime/explicit : id_(id) { } explicit MetadataKey(const SpecFunction &make_spec) : id_(kPending) , make_spec_(make_spec) { } GrlKeyID id() const { return id_; } bool is_custom() const { return make_spec_ != nullptr; } std::string name() const; /// A human-readable description of this property. /// This is used only to generate documentation. std::string description() const; /// The type of the data stored in this property, /// such as G_TYPE_STRING, G_TYPE_UINT, etc. /// This is used only to generate documentation. GType gtype() const; bool RegisterCustomKey(); const GList *relation() const; private: /// Numeric ID of the metadata key. /// /// Initialized with kPendingMetadataKey for custom metadata keys. GrlKeyID id_; /// Function for registering custom metadata keys. SpecFunction make_spec_; }; public: /// Functions of this kind are passed to the VisitAll() method. typedef std::function PropertyVisitor; /// Functions of this kind are merge the discoverer's stream information. typedef std::function StreamInfoFunction; public: /// Constructs an unidentified property. /// /// Such properties are needed to support uninitialized variables and /// to support various container operations. Property() { } /// Constructs a copy of the other property. /// /// @param[in] other - the copied property Property(const Property &other) : d(other.d) { } ~Property(); protected: /// Constructs a new property instance. /// /// This constructor is used create specializations of the Property class. /// /// @param[in] impl - the private fields of the new property explicit Property(PrivatePtr impl); public: /// Name of the Lucene++ field. String field_name() const; /// Id of the Grilo metadata key. const MetadataKey &metadata_key() const; /// Category of the property. Category category() const; /// Origins of this property. std::set origins() const; /// Checks if the property is undefined (null). bool is_null() const { return d == 0; } /// Checks if the property permits full text searches. bool supports_full_text_search() const; /// How to resolve merge conflicts for this property. MergeStrategy merge_strategy() const; public: /// Extracts property values from discoverer stream information. /// /// @param[in] info - the discoverer info to investigate /// @param[in] stream - the stream info to investigate /// @param[in,out] properties - the item to update /// @returns true if the property value got updated bool MergeStreamInfo(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, ValueMap *properties) const; /// Turns a property value into Lucene++ fields. /// /// @param[in] value - the property values to convert LuceneFields MakeFields(const Value &value) const; /// Extracts property values of the Lucene++ fields. /// /// @param[in] fields - the Lucene++ fields to convert Value TransformFields(const LuceneFields &fields) const; Value TransformSingleField(Lucene::FieldablePtr field) const; /// Constructs a Lucene term query for this property and value. /// /// @param[in] value - the value to search for Lucene::QueryPtr MakeTermQuery(const Value &value) const; /// Constructs a Lucene term query for this property and value. /// /// @param[in] lower_value - the lower value to search for /// @param[in] lower_boundary - boundery to use for lower_value /// @param[in] upper_value - the upper value to search for /// @param[in] upper_boundary - boundery to use for upper_value Lucene::QueryPtr MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const; /// Constructs a Lucene term query for this property and value. /// /// The constructed query searchs for lower_value <= x < upper_value. /// /// @param[in] lower_value - the lower value to search for /// @param[in] upper_value - the upper value to search for Lucene::QueryPtr MakeRangeQuery(const Value &lower_value, const Value &upper_value) { return MakeRangeQuery(lower_value, Inclusive, upper_value, Exclusive); } /// Converts a tag value into a canonical property value. /// /// @param[in] input - the tag value to transform /// @param[in/out] output - location for the transformed value /// @returns true on succcess template bool TransformTagValue(const GValue *input, Value *output) const; /// Converts a tag value into a canonical property value. /// /// @param[in] input - the tag value to transform /// @param[in/out] output - location for the transformed value /// @returns true on succcess bool TransformGriloValue(const GValue *input, Value *output) const; bool TransformDBusVariant(GVariant *input, Value *output) const; /// Turns a property value into a Grilo value. /// /// @param[in] value - the property values to convert GValue* MakeGriloValue(const Value &value) const; /// Finds a property by its Lucene++ field name. /// /// @param[in] name - the field name by which to search static Property FromFieldName(const String &name); /// Finds a property by its Grilo metadata key. /// /// @param[in] key - the metadata key ID to search for. static Property FromMetadataKey(GrlKeyID key); /// Visits all property declarations known to the media scanner. /// /// @param[in] visit - a function that is called for each property static void VisitAll(const PropertyVisitor &visit); public: /// Checks if two properties are the same. bool operator==(const Property &other) const { return d == other.d; } /// Checks if two properties are the different. bool operator!=(const Property &other) const { return d != other.d; } /// Safe boolean cast. The more natural operator bool() /// is problematic because it leads to unwanted implicit casts. operator const void*() const { return d ? reinterpret_cast(1) : 0; } /// Define sort order for usage in std::map. bool operator<(const Property &other) const { return d < other.d; } protected: static MetadataKey define_boolean(const char *name, const char *nick, const char *blurb, bool default_value); static MetadataKey define_datetime(const char *name, const char *nick, const char *blurb); static MetadataKey define_string(const char *name, const char *nick, const char *blurb, const char *default_value = 0); static bool merge_nothing(const GstDiscovererInfo *, const GstDiscovererStreamInfo *, ValueMap *) { return false; } static bool MergeAny(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, const StreamInfoFunction &merge_first, const StreamInfoFunction &merge_second, ValueMap *item); StreamInfoFunction bind_any(const StreamInfoFunction &first, const StreamInfoFunction &second) const; template bool MergeAttribute(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, ValueType (*get_attribute)(const GstDiscovererInfo *), ValueMap *item) const; template bool MergeAttribute(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, ValueType (*get_attribute)(const InfoType *), ValueMap *item) const; template bool MergeTag(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, const char *tag_name, ValueMap *item) const; template StreamInfoFunction bind_attr(ValueType (*)(const GstDiscovererInfo *)); template StreamInfoFunction bind_attr(ValueType (*)(const InfoType *)); template StreamInfoFunction bind_tag(const char *tag_name) const; private: typedef std::map FieldNameCache; typedef std::map MetadataKeyCache; /// Private fields of a property. const PrivatePtr d; /// Properties by their field name. static FieldNameCache field_name_cache_; /// Properties by their metadata key. static MetadataKeyCache metadata_key_cache_; }; /// This class adds some utilities for property implementations. template class GenericProperty : public Property { public: typedef ValueType value_type; BoundValue bind_value(const ValueType &value) const { return BoundValue(*this, Value(value)); } protected: class Private; typedef value_type (*MediaInfoGetter)(const GstDiscovererInfo *); typedef value_type (*StreamInfoGetter)(const GstDiscovererStreamInfo *); typedef value_type (*AudioInfoGetter)(const GstDiscovererAudioInfo *); typedef value_type (*VideoInfoGetter)(const GstDiscovererVideoInfo *); explicit GenericProperty(Private *impl) : Property(PrivatePtr(impl)) { } StreamInfoFunction bind_attr(MediaInfoGetter get_attribute) { return Property::bind_attr(get_attribute); } StreamInfoFunction bind_attr(StreamInfoGetter get_attribute) { return Property::bind_attr(get_attribute); } StreamInfoFunction bind_attr(AudioInfoGetter get_attribute) { return Property::bind_attr(get_attribute); } StreamInfoFunction bind_attr(VideoInfoGetter get_attribute) { return Property::bind_attr(get_attribute); } StreamInfoFunction bind_tag(const char *tag_name) const { return Property::bind_tag(tag_name); } }; class DateTimeProperty : public GenericProperty { protected: typedef GenericProperty inherited; friend class Private; class Private; // TODO(M3): merge with numeric property? DateTimeProperty(const String &field_name, const MetadataKey &metadata_key, Property::Category category, MergeStrategy merge_strategy, const StreamInfoFunction &stream_info); explicit DateTimeProperty(Private *impl); static MetadataKey define(const char *name, const char *nick, const char *blurb) { return define_datetime(name, nick, blurb); } }; template class NumericProperty : public GenericProperty, T> { protected: typedef GenericProperty, T> inherited; // TODO(M3): Add explicit typedefs for all the numeric properties? // TODO(M5): Introduce Private class to avoid C&P in impl. NumericProperty(const String &field_name, const Property::MetadataKey &metadata_key, Property::Category category, Property::MergeStrategy merge_strategy, const Property::StreamInfoFunction &stream_info); explicit NumericProperty(typename inherited::Private *impl); static Property::MetadataKey define(const char *name, const char *nick, const char *blurb, T default_value = T()); static Property::MetadataKey define(const char *name, const char *nick, const char *blurb, T minimum, T maximum, T default_value = T()); }; template class GenericStringProperty : public GenericProperty, String> { protected: typedef GenericProperty, String> inherited; GenericStringProperty(const String &field_name, const Property::MetadataKey &metadata_key, Property::Category category, Property::MergeStrategy merge_strategy, const Property::StreamInfoFunction &stream_info); explicit GenericStringProperty(typename inherited::Private *impl); static Property::MetadataKey define(const char *name, const char *nick, const char *blurb, const char *default_value = 0) { return Property::define_string(name, nick, blurb, default_value); } }; typedef GenericStringProperty StringProperty; typedef GenericStringProperty TextProperty; } // namespace mediascanner #endif // MEDIASCANNER_PROPERTY_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/taskfacades.cpp0000644000015700001700000000727312232220161025123 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/taskfacades.h" // Boost C++ #include #include // Standard Library #include // Media Scanner Library #include "mediascanner/logging.h" #include "mediascanner/writablemediaindex.h" namespace mediascanner { // Boost C++ using boost::locale::format; // Context specific logging domains static const logging::Domain kInfo("info/tasks", logging::info()); template class MediaIndexFacade::Private { public: Private(MediaRootManagerPtr root_manager) : media_index_(root_manager) { g_mutex_init(&mutex_); } FileSystemPath media_index_path_; T media_index_; mutable GMutex mutex_; }; template MediaIndexFacade::MediaIndexFacade(MediaRootManagerPtr root_manager, const FileSystemPath &path) : d(new Private(root_manager)) { set_media_index_path(path); } template MediaIndexFacade::MediaIndexFacade(MediaRootManagerPtr root_manager) : d(new Private(root_manager)) { FileSystemPath f; set_media_index_path(f); } template MediaIndexFacade::~MediaIndexFacade() { delete d; } template void MediaIndexFacade::set_media_index_path(const FileSystemPath &path) { g_mutex_lock(&d->mutex_); d->media_index_path_ = path.empty() ? MediaIndex::default_path() : path; g_mutex_unlock(&d->mutex_); } template FileSystemPath MediaIndexFacade::media_index_path() const { g_mutex_lock(&d->mutex_); const FileSystemPath path = d->media_index_path_; g_mutex_unlock(&d->mutex_); return path; } template MediaRootManagerPtr MediaIndexFacade::root_manager() const { return d->media_index_.root_manager(); } template void MediaIndexFacade::Run(const TaskFunction &run_task, const ErrorFunction &report_error) { std::string error_message; g_mutex_lock(&d->mutex_); if (d->media_index_.is_open() && d->media_index_path_ != d->media_index_.path()) d->media_index_.Close(); if (not d->media_index_.is_open()) { if (not d->media_index_.Open(d->media_index_path_)) { error_message = d->media_index_.error_message(); if (error_message.empty()) { const FileSystemPath index_path = d->media_index_.path(); error_message = (format("Opening media index at \"{1}\" " "failed without error message.") % index_path).str(); } } } g_mutex_unlock(&d->mutex_); if (error_message.empty()) { run_task(&d->media_index_); } else { report_error(error_message); } } template class MediaIndexFacade; template class MediaIndexFacade; } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/glibutils.cpp0000644000015700001700000000266412232220161024647 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/glibutils.h" // C++ Standard Library #include #include namespace mediascanner { std::string to_string(const GError *error) { std::ostringstream oss; if (error) { oss << g_quark_to_string(error->domain) << "(" << error->code << "): " << error->message; } else { oss << "Unknown error."; } return oss.str(); } std::string to_string(const GstCaps *caps) { if (caps) { const std::string text = take(gst_caps_to_string(caps)).get(); return "(caps: " + text + ")"; } return "(caps: null)"; } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaartcache.h0000644000015700001700000000364012232220161025063 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Jussi Pakkanen */ #ifndef MEDIAARTCACHE_H #define MEDIAARTCACHE_H #include namespace mediascanner { /* * A class to store thumbnails for files according to * https://wiki.gnome.org/MediaArtStorageSpec * * As this class deals mostly with the filesystem, all * errors are reported with runtime_error exceptions. */ class MediaArtCache { private: std::string root_dir; std::string compute_base_name(const std::string &artist, const std::string &album) const; std::string get_full_filename(const std::string &artist, const std::string & album) const; public: static const unsigned int MAX_SIZE = 200; MediaArtCache(); bool has_art(const std::string &artist, const std::string &album) const; void add_art(const std::string &artist, const std::string &album, const char *data, unsigned int datalen); std::string get_art_file(const std::string &artist, const std::string &album) const; std::string get_art_uri(const std::string &artist, const std::string &album) const; void clear() const; void prune(); std::string get_cache_dir() const { return root_dir; } }; } #endif mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/utilities.h0000644000015700001700000001554112232220161024327 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_UTILITIES_H #define MEDIASCANNER_UTILITIES_H // Boost C++ #include #include // C++ Standard Libary #include #include #include #include #include namespace mediascanner { template struct flag_operations { static T combine(T a, T b) { return static_cast(static_cast(a) | static_cast(b)); } static T intersect(T a, T b) { return static_cast(static_cast(a) & static_cast(b)); } static T invert(T a) { return static_cast(~static_cast(a)); } }; template<> struct flag_operations { }; template inline T operator|(T a, T b) { typedef boost::enable_if, T> enabled; return flag_operations::combine(a, b); } template inline T operator&(T a, T b) { typedef boost::enable_if, T> enabled; return flag_operations::intersect(a, b); } template inline T operator~(T a) { typedef boost::enable_if, T> enabled; return flag_operations::invert(a); } /** * @brief abort_with_backtrace * Prints a backtrace and aborts then. Useful for std::set_terminate(). */ void abort_with_backtrace(); /** * @brief Safely constructs a std::string from potential null pointers. * @param str A nul terminated string, or @c null. * @return A new std::string instance. */ inline std::string safe_string(const char *str) { return str ? std::string(str) : std::string(); } /** * @brief Safely constructs a std::string from potential null pointers * and then frees the pointer. * @param str A nul terminated string, or @c null. * @return A new std::string instance. */ inline std::string take_string(char *str) { std::string result; if (str) { result.assign(str); ::free(str); } return result; } /** * @brief Safely constructs a std::wstring from potential null pointers. * @param str A nul terminated string, or @c null. * @return A new std::wstring instance. */ std::wstring safe_wstring(const char *str); /** * @brief Conveniently fills the std::map @p target with elements. * @param target The map to fill. * @param element The element to fill in. * @return @p map */ template inline std::map &operator<< (std::map &target, // NOLINT: runtime/reference const typename std::map::value_type &element) { target.insert(element); return target; } /** * @brief Conveniently fills the std::vector @p target with elements. * @param vector The vector to fill. * @param element The element to fill in. * @return @p vec */ template inline std::vector &operator<<(std::vector &target, const T &element) { target.push_back(element); return target; } /** * @brief Conveniently creates a copy of the std::vector @p source, * and then pushes @p element to its end. * @param source The vector to fill. * @param element The element to fill in. * @return A new vector */ template inline std::vector operator<<(const std::vector &source, const T &element) { std::vector target(source.begin(), source.end()); target.push_back(element); return target; } /** * @brief Prints a property to a regular I/O stream. * This is useful in the context of boost::locale::format and logging. * @param os The target stream * @param s The property to print * @return @p os * @group logging */ std::ostream &operator<<(std::ostream &os, const class Property &p); } // namespace mediascanner // Note: The following stream operators must be defined in the std namespace // to allow boost::locale::format find them when using Koenig lookup. namespace std { /** * @brief Prints a wide string to a regular I/O stream. * This is useful in the context of boost::locale::format and logging. * @param os The target stream * @param s The string to print * @return @p os * @group logging */ ostream &operator<<(ostream &os, const wstring &s); /** * @brief Prints a std::vector to a I/O stream. * This is useful in the context of boost::locale::format and logging. * @param os The target stream * @param v The vector to print * @return @p os * @group logging */ template inline basic_ostream &operator<<(basic_ostream &os, const vector &v) { os << "(vector: size=" << v.size() << ", elements=["; typename vector::const_iterator it = v.begin(); if (it != v.end()) { os << *it; while (++it != v.end()) os << ", " << *it; } return os << "])"; } /** * @brief Prints a std::pair to a I/O stream. * This is useful in the context of boost::locale::format and logging. * @param os The target stream * @param m The pair to print * @return @p os * @group logging */ template ostream& operator<<(ostream &os, const pair &p) { return os << "(" << p.first << ", " << p.second << ")"; } /** * @brief Prints a std::map to a I/O stream. * This is useful in the context of boost::locale::format and logging. * @param os The target stream * @param m The map to print * @return @p os * @group logging */ template inline basic_ostream& operator<<(basic_ostream &os, const map &m) { os << "(map: size=" << m.size() << ", elements={"; typename map::const_iterator it = m.begin(); if (it != m.end()) { os << *it; while (++it != m.end()) os << ", " << *it; } return os << "})"; } } // namespace std #endif // MEDIASCANNER_UTILITIES_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/taskfacades.h0000644000015700001700000000412712232220161024563 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_TASKFACADES_H #define MEDIASCANNER_TASKFACADES_H // GLib #include // C++ Standard Library #include // Media Scanner Library #include "mediascanner/declarations.h" #include "mediascanner/taskmanager.h" namespace mediascanner { template class MediaIndexFacade { NONCOPYABLE(MediaIndexFacade); class Private; public: explicit MediaIndexFacade(MediaRootManagerPtr root_manager); explicit MediaIndexFacade(MediaRootManagerPtr root_manager, const FileSystemPath &path); ~MediaIndexFacade(); void set_media_index_path(const FileSystemPath &path); FileSystemPath media_index_path() const; MediaRootManagerPtr root_manager() const; typedef std::function TaskFunction; typedef std::function ErrorFunction; TaskManager::TaskFunction bind(const TaskFunction &task, const ErrorFunction &report_error) { return std::bind(&MediaIndexFacade::Run, this, task, report_error); } protected: void Run(const TaskFunction &run_task, const ErrorFunction &report_error); private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_TASKFACADES_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filesystemwalker.h0000644000015700001700000000370712232220161025707 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_FILESYSTEMWALKER_H #define MEDIASCANNER_FILESYSTEMWALKER_H // Standard Library #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { class FileSystemWalker { NONCOPYABLE(FileSystemWalker); class Private; public: typedef MediaIndexFacade TaskFacade; typedef std::shared_ptr TaskFacadePtr; FileSystemWalker(const MediaRoot &media_root, MetadataResolverPtr resolver, MediaArtDownloaderPtr art_downloader, TaskManagerPtr file_task_manager, TaskManagerPtr index_task_manager, TaskFacadePtr index_task_facade); ~FileSystemWalker(); MediaRoot media_root() const; void set_file_monitor_enabled(bool enable); bool file_monitor_enabled() const; std::string error_message() const; bool is_cancelled() const; size_t task_group() const; bool start(); void cancel(); bool Join(); private: Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_FILESYSTEMWALKER_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediautils.h0000644000015700001700000000713512232220161024454 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_MEDIAUTILS_H #define MEDIASCANNER_MEDIAUTILS_H // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/property.h" typedef struct _GList GList; typedef struct _GrlMedia GrlMedia; namespace mediascanner { class MimeType { public: static const MimeType kApplicationOgg; static const MimeType kAudioPrefix; static const MimeType kImagePrefix; static const MimeType kVideoPrefix; explicit MimeType(const std::string &str) : str_(str.begin(), str.end()) { } explicit MimeType(const std::wstring &str) : str_(str) { } const std::wstring& str() const { return str_; } bool is_audio() const; bool is_image() const; bool is_video() const; GrlMedia* make_media() const; private: std::wstring str_; }; class MediaInfo { private: typedef std::vector PropertyVector; public: typedef PropertyVector::const_iterator const_iterator; typedef PropertyVector::value_type value_type; MediaInfo(); void add_related(const Property::ValueMap &properties); void add_single(const Property::BoundValue &value); Property::Value first(Property key) const; size_t count(Property key) const; template ValueType first(const GenericProperty &key) const; std::wstring first(StringProperty key) const { return first(key); } std::wstring first(TextProperty key) const { return first(key); } GrlMedia* make_media(GList *const requested_keys) const; GrlMedia* make_media(GList *const requested_keys, const std::string &url) const; void copy_to_media(GList *const requested_keys, GrlMedia *const media) const; bool fill_from_media(GrlMedia *media, const GList *const requested_keys, GList **failed_keys, std::string *error_message); const_iterator begin() const { return related_properties_.begin(); } const_iterator end() const { return related_properties_.end(); } bool empty() const; bool operator==(const MediaInfo &other) const { return related_properties_ == other.related_properties_; } bool operator!=(const MediaInfo &other) const { return not operator==(other); } private: std::vector related_properties_; }; template inline V MediaInfo::first(const GenericProperty &key) const { const Property::Value value = first(static_cast(key)); return value.which() ? boost::get(value) : V(); } } // namespace mediascanner #endif // MEDIASCANNER_MEDIAUTILS_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaindex.cpp0000644000015700001700000006620412232220161024760 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/mediaindex.h" // Lucene++ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Boost C++ #include #include #include // GLib #include // Standard Libary #include #include #include #include #include // Media Scanner #include "mediascanner/filter.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaartcache.h" #include "mediascanner/mediaroot.h" #include "mediascanner/mediautils.h" #include "mediascanner/glibutils.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" namespace mediascanner { // Boost C++ using boost::locale::format; // Lucene++ using Lucene::LuceneException; using Lucene::newLucene; const int32_t MediaIndex::kUnlimited = boost::numeric::bounds::highest(); static const Lucene::LuceneVersion::Version kLuceneVersion = Lucene::LuceneVersion::LUCENE_30; // Media index parameters const char MediaIndex::kMediaIndexFormat[] = "Ubuntu Media Scanner Media Index 1.0"; const char MediaIndex::kMetaIndexFormat[] = "Ubuntu Media Scanner Meta Index 1.0"; class MediaIndex::ParamId : public std::string { public: template explicit ParamId(const char (&id)[N]) : std::string(id, N - 1) { } }; const MediaIndex::ParamId MediaIndex::kParamFormat("format"); const MediaIndex::ParamId MediaIndex::kParamSegments("segments"); const MediaIndex::ParamId MediaIndex::kParamRelativePath("relative-path"); // Context specific logging domains static const logging::Domain kWarning("warning/index", logging::warning()); static const logging::Domain kInfo("info/index", logging::info()); static const logging::Domain kTrace("trace/index", logging::trace()); static const logging::Domain kDebug("debug/index", logging::debug()); static const logging::Domain kExplain("debug/index/explain", &kDebug); class MediaIndex::Private : public MediaRootManager::Listener { public: Private(MediaIndex *q, MediaRootManagerPtr root_manager) : q(q) , params_(take(g_key_file_new())) , params_timestamp_(0) , params_changed_(false) , root_manager_(root_manager) { if (root_manager_) root_manager_->add_listener(this); } ~Private() { if (root_manager_) root_manager_->remove_listener(this); } void set_index_reader(Lucene::IndexReaderPtr reader) { index_reader_ = reader; searcher_.reset(); } Lucene::IndexReaderPtr index_reader() const { return index_reader_; } void set_searcher(Lucene::IndexSearcherPtr searcher) { searcher_ = searcher; } Lucene::IndexSearcherPtr searcher() { if (not searcher_ && index_reader_) searcher_ = newLucene(index_reader_); return searcher_; } FileSystemPath params_path() { return path_ / "mediaindex"; } std::time_t params_last_write_time() { try { return boost::filesystem::last_write_time(params_path()); } catch(const boost::system::system_error &ex) { return 0; } } bool ReadParams(); void rebuild_index_reader(); void OnMediaRootAdded(const MediaRoot &root); void OnMediaRootRemoved(const MediaRoot &root); void report_error(const boost::locale::format &error_message); MediaIndex *const q; FileSystemPath path_; std::string error_message_; RefreshPolicyPtr refresh_policy_; Lucene::StandardAnalyzerPtr analyzer_; Lucene::QueryParserPtr query_parser_; Wrapper params_; std::time_t params_timestamp_; bool params_changed_; MediaRootManagerPtr root_manager_; // Multi-index readers. Actually should be a separate class. typedef std::map MediaReaderMap; MediaReaderMap media_readers_; MediaArtCache art_cache_; private: Lucene::IndexReaderPtr index_reader_; Lucene::IndexSearcherPtr searcher_; }; MediaIndex::MediaIndex(MediaRootManagerPtr root_manager) : d(new Private(this, root_manager)) { set_refresh_policy(RefreshPolicy::default_policy()); } MediaIndex::~MediaIndex() { delete d; } void MediaIndex::Private::report_error(const format &error_message) { const std::string previous_message = error_message_; error_message_ = error_message.str(); if (not previous_message.empty()) { error_message_.reserve(error_message_.length() + previous_message.length() + 3); error_message_ += " (" + previous_message + ')'; } } void MediaIndex::report_error(const format &error_message) { d->report_error(error_message); } // Returns the error message of the last failed operation. std::string MediaIndex::error_message() { // FIXME(M5): Really Clear error message after returning it? const std::string result = d->error_message_; d->error_message_.clear(); return result; } // Returns the default path of the underlaying Lucene index. FileSystemPath MediaIndex::default_path() { static const FileSystemPath cache_dir = g_get_user_cache_dir(); static const FileSystemPath default_path = (cache_dir / "mediascanner"); return default_path; } // Returns the file system path of the underlaying Lucene index. FileSystemPath MediaIndex::path() const { return d->path_; } // Returns true of the index got opened. bool MediaIndex::is_open() const { return d->index_reader() != nullptr; } bool MediaIndex::is_current() const { if (d->index_reader()) { if (not d->index_reader()->isCurrent() || d->params_last_write_time() > d->params_timestamp_) return false; } return true; } void MediaIndex::set_refresh_policy(RefreshPolicyPtr policy) { if (not is_current()) { kTrace("Changing refresh policy while the index is not up " "to date anymore. Reopening the index."); Reopen(); } d->refresh_policy_ = policy; } RefreshPolicyPtr MediaIndex::refresh_policy() const { return d->refresh_policy_; } MediaRootManagerPtr MediaIndex::root_manager() const { return d->root_manager_; } // Returns the Lucene term analyzer. Lucene::AnalyzerPtr MediaIndex::analyzer() const { return d->analyzer_; } // Builds a Lucene term for the given URL. Lucene::TermPtr MediaIndex::MakeLookupTerm(const std::wstring &url) { return newLucene(schema::kUrl.field_name(), url); } bool MediaIndex::Private::ReadParams() { Wrapper error; // Grab timestamp before actually reading the file to easily avoid // missing updates if the file gets updated between those two operations. const std::time_t current_params_timestamp = params_last_write_time(); if (not g_key_file_load_from_file(params_.get(), params_path().string().c_str(), G_KEY_FILE_NONE, error.out_param())) { const std::string message = to_string(error); report_error(format("Cannot open media index params at \"{1}\": {2}") % path_ % message); return false; } // Now that the params have been read we also can update the timestamp. params_timestamp_ = current_params_timestamp; return true; } bool MediaIndex::ReadParams() { return d->ReadParams(); } bool MediaIndex::FlushParams() { BOOST_ASSERT(is_open()); if (d->params_changed_) { if (not FlushParams(d->params_)) return false; d->params_timestamp_ = d->params_last_write_time(); d->params_changed_ = false; } return true; } bool MediaIndex::FlushParams(Wrapper /*params*/) { const FileSystemPath parent_path = path().parent_path(); report_error(format("Cannot modify read-only index at \"{1}\".") % parent_path); return false; } std::string MediaIndex::get_param(const std::string &group, const ParamId &key) const { return take_string(g_key_file_get_string(d->params_.get(), group.c_str(), key.c_str(), nullptr)); } void MediaIndex::set_param(const std::string &group, const ParamId &key, const std::string &value) { g_key_file_set_string(d->params_.get(), group.c_str(), key.c_str(), value.c_str()); d->params_changed_ = true; } FileSystemPath MediaIndex::params_path() const { return d->params_path(); } // Opens this media index. // // This method returns true on success. It fails when Lucene cannot // open the requested index. It also fails when index was already open. // // On success this method resets the last error message. bool MediaIndex::Open(const FileSystemPath &path) { d->error_message_.clear(); // Open default path if nothing passed. if (path.empty()) return Open(default_path()); // FIXME(M4): Find better strategy for non-existing media index if (is_open()) { report_error(format("Reader already open, " "call Close() before re-using")); return false; } kInfo("Opening media index index at \"{1}\".") % path; d->path_= path; if (not ReadParams()) { d->path_.clear(); return false; } try { d->analyzer_ = newLucene (kLuceneVersion, Lucene::HashSet()); } catch(const LuceneException &ex) { const std::string message = FromUnicode(ex.getError()); report_error(format("Cannot create standard analyzer: {1}") % message); d->path_.clear(); return false; } const std::string media_index_format = get_param("global", kParamFormat); kDebug("Media index format: {1}") % media_index_format; if (media_index_format == kMediaIndexFormat) { d->set_index_reader(OpenIndex()); } else if (media_index_format == kMetaIndexFormat) { d->set_index_reader(OpenMetaIndex()); } else { report_error(format("Cannot open media index at \"{1}\": " "Unsupported media index format") % path); d->path_.clear(); return false; } if (not d->index_reader()) { d->path_.clear(); return false; } try { d->query_parser_ = newLucene (kLuceneVersion, L"title", d->analyzer_); } catch(const LuceneException &ex) { const std::string message = FromUnicode(ex.getError()); report_error(format("Cannot create query parser: {1}") % message); d->path_.clear(); return false; } if (not FlushParams()) { d->path_.clear(); return false; } return true; } static Lucene::IndexReaderPtr newMultiReader (const Lucene::Collection &children) { const bool kSharedReaders = false; return newLucene(children, kSharedReaders); } void MediaIndex::Private::rebuild_index_reader() { Lucene::Collection children = Lucene::Collection::newInstance(); for (const auto &p: media_readers_) { children.add(p.second); } set_index_reader(newMultiReader(children)); } void MediaIndex::Private::OnMediaRootAdded(const MediaRoot &root) { const std::string root_path = root.path(); if (not root.is_valid()) { const std::string error_message = root.error_message(); kWarning("Ignoring addition of invalid media root \"{1}\": {2}") % root_path % error_message; return; } if (not q->is_open() || media_readers_.count(root) > 0) return; kDebug("New media root \"{1}\" added.") % root_path; const Lucene::IndexReaderPtr reader = q->OpenChildIndex(root); if (not reader || not q->FlushParams()) { // Call report error to merge possible cause. report_error(format("Ignoring media root \"{1}\" " "since no index could be created") % root_path); const std::string error_message = q->error_message(); kWarning("{1}.") % error_message; return; } media_readers_.insert(std::make_pair(root, reader)); rebuild_index_reader(); } void MediaIndex::AddMediaRoot(const MediaRoot &root) { d->OnMediaRootAdded(root); } void MediaIndex::Private::OnMediaRootRemoved(const MediaRoot &root) { const MediaReaderMap::iterator it = media_readers_.find(root); if (it != media_readers_.end()) { it->second->close(); media_readers_.erase(it); rebuild_index_reader(); } } void MediaIndex::RemoveMediaRoot(const MediaRoot &root) { d->OnMediaRootRemoved(root); } Lucene::IndexReaderPtr MediaIndex::OpenIndex() { return OpenIndexReader(path() / "index"); } Lucene::IndexReaderPtr MediaIndex::OpenIndexReader(const FileSystemPath &path) { try { const Lucene::FSDirectoryPtr directory = Lucene::FSDirectory::open(ToUnicode(path.string())); return Lucene::IndexReader::open(directory); } catch(const LuceneException &ex) { const std::string message = FromUnicode(ex.getError()); report_error (format("Cannot open media index at \"{1}\": {2}") % path % message); } return Lucene::IndexReaderPtr(); } Lucene::IndexReaderPtr MediaIndex::OpenMetaIndex() { Lucene::Collection children = Lucene::Collection::newInstance(); if (d->root_manager_) { // Check previously know media roots and try to restore them. const Wrapper group_ids = take(g_key_file_get_groups(d->params_.get(), nullptr)); for (const char *const *group = group_ids.get(); *group; ++group) { if (g_str_has_prefix(*group, "media:")) { const std::string path = get_param(*group, kParamRelativePath); if (not path.empty()) { kTrace("Restoring media root for \"{1}\"") % path; const MediaRoot root = d->root_manager_-> AddRelativeRoot(path); if (not root.is_valid()) { // Call report error to merge possible cause. std::string error = root.error_message(); report_error(format("Ignoring invalid media root " "\"{1}\": {2}") % path % error); error = error_message(); kWarning("{1}.") % error; continue; } const Lucene::IndexReaderPtr reader = OpenChildIndex(root); if (not reader) { // Call report error to merge possible cause. const std::string root_path = root.path(); report_error(format("Ignoring media root \"{1}\" " "since no index could be created") % root_path); const std::string error = error_message(); kWarning("{1}.") % error; continue; } d->media_readers_.insert(std::make_pair(root, reader)); children.add(reader); } } } // Create sub indexes for each known media root. for (const auto &root: d->root_manager_->media_roots()) { const Lucene::IndexReaderPtr reader = OpenChildIndex(root); if (not reader) { // Call report error to merge possible cause. const std::string root_path = root.path(); report_error(format("Ignoring media root \"{1}\" " "since no index could be created") % root_path); const std::string error = error_message(); kWarning("{1}.") % error; continue; } d->media_readers_.insert(std::make_pair(root, reader)); children.add(reader); } } return newMultiReader(children); } Lucene::IndexReaderPtr MediaIndex::OpenChildIndex(const MediaRoot &root) { const std::string dirname = get_param(root.group_id(), kParamSegments); if (dirname.empty()) { const std::string root_path = root.path(); d->report_error(format("No media index available for \"{1}\"") % root_path); return Lucene::IndexReaderPtr(); } return OpenIndexReader(path() / dirname); } // Closes this media index. void MediaIndex::Close() { if (d->index_reader()) d->index_reader()->close(); d->set_index_reader(Lucene::IndexReaderPtr()); d->analyzer_.reset(); d->query_parser_.reset(); d->path_.clear(); } bool MediaIndex::Reopen() { if (d->index_reader()) d->set_index_reader(d->index_reader()->reopen()); return true; } Property::Set MediaIndex::GetFields(Lucene::DocumentPtr document) { if (not document) return Property::Set(); Property::Set fields; // Collect distinct field names, so that we properly report multi-value // properties. // FIXME(M4): Find a way to avoid building this set. // Actually Lucene::FieldSelector should be the solution. for (const auto field: document->getFields()) { if (const Property &p = Property::FromFieldName(field->name())) fields.insert(p); } return fields; } MediaInfo MediaIndex::ExtractProperties(Lucene::DocumentPtr document) { return ExtractProperties(document, GetFields(document)); } MediaInfo MediaIndex::ExtractProperties(Lucene::DocumentPtr document, const Property::Set &fields) { MediaInfo metadata; if (not document) return metadata; for (const auto &property: fields) { if (property) { const Lucene::Collection fields = document->getFieldables(property.field_name()); // FIXME(M3): RESTORE RELATED KEYS!!! for (const Lucene::FieldablePtr field: fields) { const Property::Value value = property.TransformSingleField( field); metadata.add_single(std::make_pair(property, value)); } } } // If we have an artist and album name, check to see if there is // art in the cache. if (not metadata.first(schema::kArtist).empty() && not metadata.first(schema::kAlbum).empty()) { std::string artist = FromUnicode(metadata.first(schema::kArtist)); std::string album = FromUnicode(metadata.first(schema::kAlbum)); std::string cover = d->art_cache_.get_art_uri(artist, album); g_warning("Found cover art: %s", cover.c_str()); if (not cover.empty()) { metadata.add_single( std::make_pair(schema::kCover, ToUnicode(cover))); } } return metadata; } Lucene::DocumentPtr MediaIndex::FindDocument(const std::wstring &url) const { const Lucene::TermPtr term = MakeLookupTerm(url); const Lucene::TermDocsPtr matches = d->index_reader()->termDocs(term); if (matches->next()) return d->index_reader()->document(matches->doc()); return Lucene::DocumentPtr(); } bool MediaIndex::Exists(const std::wstring &url) { d->refresh_policy_->OnBeginReading(this); return d->index_reader()->termDocs(MakeLookupTerm(url))->next(); } MediaInfo MediaIndex::Lookup(const std::wstring &url) { d->refresh_policy_->OnBeginReading(this); return ExtractProperties(FindDocument(url)); } // FIXME(M4): Use Lucene::FieldSelector MediaInfo MediaIndex::Lookup(const std::wstring &url, const Property::Set &fields) { d->refresh_policy_->OnBeginReading(this); return ExtractProperties(FindDocument(url), fields); } // FIXME(M4): Permit selection of keys. Use Lucene::FieldSelector for that. void MediaIndex::VisitAll(const ItemVistor &visit_item, int32_t limit, int32_t offset) { if (limit < 0) limit = kUnlimited; d->refresh_policy_->OnBeginReading(this); int32_t remaining_items = std::min(limit, d->index_reader()->numDocs()); const Lucene::TermDocsPtr matches = d->index_reader()->termDocs(); while (remaining_items > 0 && matches->next()) { if (offset > 0) { --offset; continue; } const Lucene::DocumentPtr document = d->index_reader()->document(matches->doc()); if (document) visit_item(ExtractProperties(document), remaining_items); --remaining_items; } } class MediaIndex::Collector : public Lucene::Collector { public: Collector(MediaIndex *index, const ItemVistor &visit_item, Lucene::SearcherPtr searcher, Lucene::QueryPtr query, int32_t offset, int32_t limit) : index_(index) , visit_item_(visit_item) , searcher_(searcher) , query_(query) , offset_(offset) , limit_(limit) { } void setScorer(Lucene::ScorerPtr) { } void collect(int32_t doc) { if (limit_ == 0) return; if (offset_ > 0) { --offset_; return; } if (kExplain.enabled()) kExplain(searcher_->explain(query_, doc)->toString()); Lucene::DocumentPtr document = reader_->document(doc); if (document) visit_item_(index_->ExtractProperties(document), limit_); if (limit_ > 0) --limit_; } void setNextReader(Lucene::IndexReaderPtr reader, int32_t) { reader_ = reader; } bool acceptsDocsOutOfOrder() { return true; } private: MediaIndex *const index_; ItemVistor visit_item_; Lucene::IndexReaderPtr reader_; Lucene::SearcherPtr searcher_; Lucene::QueryPtr query_; int32_t offset_; int32_t limit_; }; static std::wstring to_wstring(Lucene::BooleanClause::Occur occur) { switch (occur) { case Lucene::BooleanClause::MUST: return L"must"; case Lucene::BooleanClause::SHOULD: return L"should"; case Lucene::BooleanClause::MUST_NOT: return L"must-not"; } std::wostringstream oss; oss << L"unknown(" << (unsigned) occur << ")"; return oss.str(); } // FIXME(M4): Permit selection of keys. Use Lucene::FieldSelector that. bool MediaIndex::Query(const ItemVistor &visit_item, const Filter &filter, int32_t limit, int32_t offset) { std::wstring error; const Lucene::QueryPtr query = filter.BuildQuery(d->query_parser_, &error); if (not query) { if (error.empty()) { VisitAll(visit_item, limit, offset); return true; } const std::string error_message = FromUnicode(error); report_error(format("Cannot build Lucene query from filter: {1}") % error_message); return false; } d->refresh_policy_->OnBeginReading(this); if (kDebug.enabled()) { const std::wstring query_class = query->getClassName(); const std::wstring query_string = query->toString(); kDebug(L"{1}(\"{2}\"), limit={3}, offset={4}") % query_class % query_string % limit % offset; } if (kExplain.enabled()) { const Lucene::WeightPtr weight = query->weight(d->searcher()); const std::wstring weight_class = weight->getClassName(); const double weight_value = weight->getValue(); const Lucene::ScorerPtr scorer = weight->scorer( d->index_reader(), false, false); kExplain(L"weight={1}({2}), scorer={3}") % weight_class % weight_value % scorer; Lucene::BooleanQueryPtr boolean_query = boost::dynamic_pointer_cast(query); if (boolean_query) { for (auto clause: boolean_query->getClauses()) { const std::wstring occur = to_wstring(clause->getOccur()); const std::wstring query_class = clause->getQuery()->getClassName(); const std::wstring query_string = clause->getQuery()->toString(); const Lucene::WeightPtr weight = clause->getQuery()->weight(d->searcher()); const std::wstring weight_class = weight->getClassName(); const double weight_value = weight->getValue(); const Lucene::ScorerPtr scorer = weight->scorer(d->index_reader(), false, false); kExplain(L"occur={1}, query={2}(\"{3}\"), " "weight={4}({5}), scorer={6}") % occur % query_class % query_string % weight_class % weight_value % scorer; } } } const auto searcher = d->searcher(); if (limit < 0) { /* If no limit is specified, return results unsorted */ const Lucene::CollectorPtr media_collector = newLucene(this, visit_item, searcher, query, offset, limit); searcher->search(query, media_collector); } else { const auto collector = Lucene::TopScoreDocCollector::create( offset + limit, false); searcher->search(query, collector); const auto docs = collector->topDocs(offset, limit)->scoreDocs; int32_t remaining = docs.size(); for (const auto &score_doc: docs) { const auto doc = searcher->doc(score_doc->doc); visit_item(ExtractProperties(doc), remaining); if (remaining > 0) remaining--; } } return true; } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/taskmanager.h0000644000015700001700000000600112232220161024600 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_TASKMANAGER_H #define MEDIASCANNER_TASKMANAGER_H // Standard Library #include #include typedef struct _GError GError; namespace mediascanner { // TODO(M5): Move this to the internal namespace? /** * @brief The TaskManager schedules tasks for running them in a background * thread. The tasks are ordered by an operation ID. This operation ID can * also be used to cancel tasks which are still queued, but not started yet. * * @see MediaIndexFacade for decorating tasks with MediaIndex management * routines. */ class TaskManager { explicit TaskManager(TaskManager const &) = delete; TaskManager& operator=(TaskManager const &) = delete; class Private; class TaskInfo; public: /** * @brief The signature of task functions. */ typedef std::function TaskFunction; public: static const unsigned kInstantly = 0; explicit TaskManager(const std::string &name); ~TaskManager(); unsigned AppendTask(const TaskFunction &task, unsigned priority = kInstantly) { return AppendGroupedTask(priority, task, priority); } unsigned PrependTask(const TaskFunction &task, unsigned priority = kInstantly) { return PrependGroupedTask(priority, task, priority); } void RunTask(const TaskFunction &task, unsigned priority = kInstantly) { RunGroupedTask(priority, task, priority); } unsigned AppendGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority = kInstantly); unsigned PrependGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority = kInstantly); void RunGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority = kInstantly); unsigned CancelByGroupId(unsigned group_id); bool CancelTaskByTaskId(unsigned task_id); void Shutdown(); private: Private *const d; }; typedef std::shared_ptr TaskManagerPtr; } // namespace mediascanner #endif // MEDIASCANNER_TASKMANAGER_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaroot.cpp0000644000015700001700000002622412232220161024632 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/mediaroot.h" // POSIX Library #include #include // Boost C++ #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/logging.h" #include "mediascanner/utilities.h" namespace mediascanner { // Boost C++ using boost::locale::format; // Context specific logging domains static const logging::Domain kWarning("warning/roots", logging::warning()); //////////////////////////////////////////////////////////////////////////////// class MediaRoot::Private { public: Private(const Wrapper &base, const Wrapper &root) : base_(base) , root_(root) { } explicit Private(const std::string &error_message) : error_message_(error_message) { } const Wrapper base_; const Wrapper root_; const std::string error_message_; }; //////////////////////////////////////////////////////////////////////////////// class MediaRootManager::Private { public: Private() : idle_id_(0) , enabled_(true) { } ~Private() { teardown(); } MediaRoot make_root(const MediaRoot &parent, const std::string &path); MediaRoot make_root(const std::string &path); void AddRoot(const MediaRoot &root); void RemoveRoot(const MediaRoot &root); void setup(); void teardown(); bool enabled() const { return enabled_; } void initialize() { if (enabled_ && not volume_monitor_) on_init(); } Wrapper udev_client() { if (not udev_client_) { static const char* const subsystems[] = { "block", nullptr }; udev_client_ = take(g_udev_client_new(subsystems)); } return udev_client_; } private: void on_init(); static void OnMountChanged(Private *d, GMount *mount); static void OnMountRemoved(Private *d, GMount *mount); public: std::vector media_roots_; std::vector manual_roots_; std::vector listeners_; typedef std::map > RelativeRootMap; RelativeRootMap relative_roots_; private: Wrapper volume_monitor_; Wrapper udev_client_; unsigned idle_id_; bool enabled_; }; //////////////////////////////////////////////////////////////////////////////// static Wrapper resolve_path(const Wrapper &parent, const std::string &path) { if (g_path_is_absolute(path.c_str())) return take(g_file_new_for_path(path.c_str())); if (parent) return take(g_file_resolve_relative_path(parent.get(), path.c_str())); return Wrapper(); } static std::string get_path(const Wrapper &file) { if (not file) return std::string(); return take_string(g_file_get_path(file.get())); } static std::string get_relative_path(const Wrapper &parent, const Wrapper &descendant) { return take_string(g_file_get_relative_path(parent.get(), descendant.get())); } //////////////////////////////////////////////////////////////////////////////// MediaRoot::MediaRoot(Private *d) : d(d) { } MediaRoot::~MediaRoot() { } bool MediaRoot::is_valid() const { return d->error_message_.empty() && d->root_; } std::string MediaRoot::error_message() const { return d->error_message_; } Wrapper MediaRoot::file() const { return d->root_; } bool MediaRoot::operator==(const MediaRoot &other) const { return g_file_equal(file().get(), other.file().get()); } bool MediaRoot::operator<(const MediaRoot &other) const { return path() < other.path(); } std::string MediaRoot::path() const { return get_path(d->root_); } std::string MediaRoot::base_path() const { return get_path(d->base_); } std::string MediaRoot::relative_path() const { return get_relative_path(d->base_, d->root_); } std::string MediaRoot::group_id() const { std::string id; if (is_valid()) { const std::string path = relative_path(); id += "media:" + path; } return id; } //////////////////////////////////////////////////////////////////////////////// MediaRoot MediaRootManager::Private::make_root(const MediaRoot &parent, const std::string &path) { BOOST_ASSERT_MSG(not g_path_is_absolute(path.c_str()), path.c_str()); return MediaRoot(new MediaRoot::Private(parent.d->base_, resolve_path(parent.d->base_, path))); } MediaRoot MediaRootManager::Private::make_root(const std::string &path) { BOOST_ASSERT_MSG(g_path_is_absolute(path.c_str()), path.c_str()); std::string error_message; const Wrapper root = take(g_file_new_for_path(path.c_str())); const Wrapper base = take(g_file_new_for_path("/")); return MediaRoot(new MediaRoot::Private(base, root)); } void MediaRootManager::Private::AddRoot(const MediaRoot &root) { for (const MediaRoot &r: media_roots_) { if (r.path() == root.path()) return; } media_roots_.push_back(root); for (Listener *const l: listeners_) { l->OnMediaRootAdded(root); } } void MediaRootManager::Private::RemoveRoot(const MediaRoot &root) { std::vector::iterator it = media_roots_.begin(); while (it != media_roots_.end()) { if (it->path() == root.path()) { // The media root passed to this function might not have // proper UUID and base path because the media got removed // already. Therefore use the stored variant for notification. const MediaRoot removed = *it; media_roots_.erase(it); for (Listener *const l: listeners_) { l->OnMediaRootRemoved(removed); } return; } ++it; } } void MediaRootManager::Private::setup() { enabled_ = true; if (not idle_id_ && not volume_monitor_) idle_id_ = Idle::AddOnce(std::bind(&Private::on_init, this)); } void MediaRootManager::Private::teardown() { enabled_ = false; if (volume_monitor_) g_signal_handlers_disconnect_by_data(volume_monitor_.get(), this); if (idle_id_) { Idle::Remove(idle_id_); idle_id_ = 0; } } void MediaRootManager::Private::on_init() { // Initialize in idle handler to ensure all stakeholders are in place. volume_monitor_ = take(g_volume_monitor_get()); idle_id_ = 0; g_signal_connect_swapped(volume_monitor_.get(), "mount-added", G_CALLBACK(&Private::OnMountChanged), this); g_signal_connect_swapped(volume_monitor_.get(), "mount-changed", G_CALLBACK(&Private::OnMountChanged), this); g_signal_connect_swapped(volume_monitor_.get(), "mount-pre-unmount", G_CALLBACK(&Private::OnMountRemoved), this); g_signal_connect_swapped(volume_monitor_.get(), "mount-removed", G_CALLBACK(&Private::OnMountRemoved), this); const ListWrapper mounts (g_volume_monitor_get_mounts(volume_monitor_.get())); for (GList *l = mounts.get(); l; l = l->next) { GMount *const mount = static_cast(l->data); OnMountChanged(this, mount); } } void MediaRootManager::Private::OnMountChanged(Private *d, GMount *mount) { // Ignore shadow mounts, they are of no interest to us. if (g_mount_is_shadowed(mount)) return; const Wrapper root = take(g_mount_get_root(mount)); d->AddRoot(d->make_root(get_path(root))); } void MediaRootManager::Private::OnMountRemoved(Private *d, GMount *mount) { // Ignore shadow mounts, they are of no interest to us. if (g_mount_is_shadowed(mount)) return; const Wrapper root = take(g_mount_get_root(mount)); d->RemoveRoot(d->make_root(get_path(root))); } //////////////////////////////////////////////////////////////////////////////// MediaRootManager::MediaRootManager() : d(new Private) { d->setup(); } MediaRootManager::~MediaRootManager() { delete d; } void MediaRootManager::initialize() { d->initialize(); } void MediaRootManager::add_listener(Listener *listener) { d->listeners_.push_back(listener); } void MediaRootManager::remove_listener(Listener *listener) { std::vector::iterator it = d->listeners_.begin(); while (it != d->listeners_.end()) { if (*it == listener) { it = d->listeners_.erase(it); continue; } ++it; } } std::vector MediaRootManager::media_roots() const { return d->media_roots_; } MediaRoot MediaRootManager::AddRelativeRoot(const std::string &relative_path) { // Check if this relative root's volume is already know, // and report this relative media root if that's the case. for (const MediaRoot &known: d->media_roots_) { if (known.relative_path().empty()) { // Reuse media root as parent const MediaRoot relative = d->make_root(known, relative_path); d->AddRoot(relative); return relative; if (known.relative_path() == relative_path) { // Reuse manual media root return known; } } } const MediaRoot parent = d->make_root(std::string("/")); if (relative_path.empty() || not parent.is_valid()) return parent; return d->make_root(parent, relative_path); } void MediaRootManager::AddManualRoot(const std::string &path) { d->manual_roots_.push_back(path); d->AddRoot(d->make_root(path)); } std::vector MediaRootManager::manual_roots() const { return d->manual_roots_; } void MediaRootManager::set_enabled(bool enabled) { if (enabled) { d->setup(); } else { d->teardown(); } } bool MediaRootManager::enabled() const { return d->enabled(); } MediaRoot MediaRootManager::make_root(const std::string &path) const { return d->make_root(path); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/refreshpolicy.cpp0000644000015700001700000000322012232220161025514 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/refreshpolicy.h" // Boost C++ #include // Media Scanner Library #include "mediascanner/logging.h" #include "mediascanner/writablemediaindex.h" namespace mediascanner { static const logging::Domain kTrace("trace/refresh", logging::trace()); RefreshPolicy::~RefreshPolicy() { } RefreshPolicyPtr RefreshPolicy::default_policy() { static const RefreshPolicyPtr default_policy(new InstantRefreshPolicy); return default_policy; } bool InstantRefreshPolicy::OnBeginReading(MediaIndex *index) { if (index->is_current()) return true; kTrace("Index is not up to date anymore. Reopening \"{1}\".") % index->path(); return index->Reopen(); } bool InstantRefreshPolicy::OnBeginWriting(WritableMediaIndex *index) { return OnBeginReading(index); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediautils.cpp0000644000015700001700000002536112232220161025010 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/mediautils.h" // Boost C++ #include #include #include // C++ Standard Library #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/property.h" #include "mediascanner/propertyschema.h" #include "mediascanner/utilities.h" namespace mediascanner { // Boost C++ using boost::algorithm::starts_with; // Context specific logging domains static const logging::Domain kWarning("warning/media", logging::warning()); static const logging::Domain kTrace("trace/media", logging::trace()); // MIME type constants const MimeType MimeType::kApplicationOgg(L"application/ogg"); const MimeType MimeType::kAudioPrefix(L"audio/"); const MimeType MimeType::kImagePrefix(L"image/"); const MimeType MimeType::kVideoPrefix(L"video/"); bool MimeType::is_audio() const { return starts_with(str(), kAudioPrefix.str()) || str() == kApplicationOgg.str(); } bool MimeType::is_image() const { return starts_with(str(), kImagePrefix.str()); } bool MimeType::is_video() const { return starts_with(str(), kVideoPrefix.str()); } GrlMedia* MimeType::make_media() const { if (is_video()) return grl_media_video_new(); if (is_image()) return grl_media_image_new(); if (is_audio()) return grl_media_audio_new(); return grl_media_new(); } MediaInfo::MediaInfo() { // Append one initial map for unrelated, single-value properties. related_properties_.push_back(Property::ValueMap()); } void MediaInfo::add_related(const Property::ValueMap &properties) { if (not properties.empty()) related_properties_.push_back(properties); } void MediaInfo::add_single(const Property::BoundValue &value) { const Property &property = value.first; Property::ValueMap &front = related_properties_.front(); Property::ValueMap::iterator it = front.find(property); if (it == front.end()) { front.insert(value); } else if (property.merge_strategy() == Property::MergeReplace) { it->second = value.second; } else if (property.merge_strategy() != Property::MergePreserve) { related_properties_.push_back(Property::ValueMap()); related_properties_.back().insert(value); } } Property::Value MediaInfo::first(Property key) const { for (const auto &properties: *this) { const Property::ValueMap::const_iterator it = properties.find(key); if (it != properties.end()) return it->second; } return Property::Value(); } size_t MediaInfo::count(Property key) const { size_t occurrences = 0; for (const auto &properties: *this) { if (properties.find(key) != properties.end()) ++occurrences; } return occurrences; } GrlMedia* MediaInfo::make_media(GList *const requested_keys) const { const MimeType mime_type(first(schema::kMimeType)); const std::wstring url = first(schema::kUrl); GrlMedia *const media = mime_type.make_media(); copy_to_media(requested_keys, media); if (not url.empty()) grl_media_set_id(media, FromUnicode(url).c_str()); return media; } GrlMedia* MediaInfo::make_media(GList *const requested_keys, const std::string &url) const { GrlMedia *const media = make_media(requested_keys); const std::string stored_url = safe_string(grl_media_get_url(media)); if (url != stored_url) grl_media_set_url(media, url.c_str()); return media; } void MediaInfo::copy_to_media(GList *const requested_keys, GrlMedia *const media) const { GrlData *const media_data = GRL_DATA(media); for (const auto &properties : *this) { kTrace(" - copying to media: {1}") % properties; typedef std::map > RelatedKeyMap; RelatedKeyMap related_keys; for (const auto &p : properties) { const Property::MetadataKey &key = p.first.metadata_key(); if (requested_keys && not g_list_find(requested_keys, GRLKEYID_TO_POINTER(key.id()))) continue; const GList *const relation = key.relation(); const GrlKeyID primary_key = GRLPOINTER_TO_KEYID(relation->data); RelatedKeyMap::iterator it = related_keys.find(primary_key); if (it == related_keys.end()) { Wrapper row = take(grl_related_keys_new()); it = related_keys.insert(std::make_pair(primary_key, row)).first; } grl_related_keys_set(it->second.get(), key.id(), take(p.first.MakeGriloValue(p.second)).get()); } for (RelatedKeyMap::iterator it = related_keys.begin(); it != related_keys.end(); ++it) { grl_data_add_related_keys(media_data, it->second.release()); } } } bool extract_property(GrlRelatedKeys *relation, GrlKeyID key, Property::ValueMap *property_map, GList **failed_keys) { const Property property = Property::FromMetadataKey(key); if (not property) { kWarning("Skipping unknown metadata key \"{1}\"") % grl_metadata_key_get_name(key); if (failed_keys) { *failed_keys = g_list_prepend (*failed_keys, GRLKEYID_TO_POINTER(key)); } return false; } if (const GValue *const grilo_value = grl_related_keys_get(relation, key)) { Property::Value property_value; if (not property.TransformGriloValue(grilo_value, &property_value)) { kWarning("Skipping unsupported value for \"{1}\"") % grl_metadata_key_get_name(key); if (failed_keys) { *failed_keys = g_list_prepend (*failed_keys, GRLKEYID_TO_POINTER(key)); } return false; } Property::ValueMap::iterator it; if (property.merge_strategy() == Property::MergeReplace) { it = property_map->find(property); } else { it = property_map->end(); } if (it == property_map->end()) { property_map->insert(std::make_pair(property, property_value)); } else if (property.merge_strategy() != Property::MergePreserve) { it->second = property_value; } } return true; } bool MediaInfo::fill_from_media(GrlMedia *media, const GList *const requested_keys, GList **failed_keys, std::string *error_message) { if (failed_keys) *failed_keys = nullptr; const Wrapper registry = wrap(grl_registry_get_default()); GrlData *const media_data = GRL_DATA(media); std::set remaining_keys; // Copy the requested keys into a more useful data structure. for (const GList *l = requested_keys; l; l = l->next) { const GrlKeyID key = GRLPOINTER_TO_KEYID(l->data); if (key != GRL_METADATA_KEY_ID && key != GRL_METADATA_KEY_SOURCE) remaining_keys.insert(GRLPOINTER_TO_KEYID(l->data)); } const std::set requested_key_set = remaining_keys; // Retreive all requested keys. while (not remaining_keys.empty()) { const GList *const keys = grl_registry_lookup_metadata_key_relation (registry.get(), *remaining_keys.begin()); if (not keys) { if (error_message) { *error_message = "Empty key relation found. This indicates a corrupted " "metadata registry. Aborting property extraction."; } return false; } // Remove identified related keys from requested key set. for (const GList *l = keys; l; l = l->next) remaining_keys.erase(GRLPOINTER_TO_KEYID(l->data)); const GrlKeyID primary_key = GRLPOINTER_TO_KEYID(keys->data); const unsigned num_relations = grl_data_length(media_data, primary_key); for (unsigned i = 0; i < num_relations; ++i) { GrlRelatedKeys *const relation = grl_data_get_related_keys(media_data, primary_key, i); Property::ValueMap property_map; for (const GList *l = keys; l; l = l->next) { const GrlKeyID key = GRLPOINTER_TO_KEYID(l->data); if (requested_key_set.count(key)) extract_property(relation, key, &property_map, failed_keys); } if (not property_map.empty()) { if (keys->next) { kTrace(" - related values {1}") % property_map; add_related(property_map); } else { Property::ValueMap::iterator iter = property_map.begin(); kTrace(" - single value {1}") % *iter; add_single(*iter); } } } } // Ensure we have a proper URL. const std::wstring id = safe_wstring(grl_media_get_id(media)); std::wstring url = first(schema::kUrl); if (url.empty()) add_single(schema::kUrl.bind_value(url = id)); if (url != id && not id.empty()) { if (error_message) *error_message = "Media URL doesn't match the media ID."; return false; } if (url.empty()) { // FIXME(M3): Generate URL from UUID if needed? if (error_message) *error_message = "No media URL provided."; return false; } return true; } bool MediaInfo::empty() const { switch (related_properties_.size()) { case 0: return true; case 1: return related_properties_.front().empty(); default: return false; } } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filesystemwalker.cpp0000644000015700001700000007726612232220161026255 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/filesystemwalker.h" // System Libraries #include #include // GStreamer #include // Boost C++ #include #include #include #include #include // Standard Library #include #include #include #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/mediaartdownloader.h" #include "mediascanner/mediaroot.h" #include "mediascanner/mediautils.h" #include "mediascanner/metadataresolver.h" #include "mediascanner/property.h" #include "mediascanner/propertyschema.h" #include "mediascanner/taskfacades.h" #include "mediascanner/utilities.h" #include "mediascanner/writablemediaindex.h" namespace mediascanner { // Boost C++ using boost::locale::format; using boost::posix_time::from_time_t; using boost::posix_time::microseconds; using boost::posix_time::ptime; // Context specific logging domains static const logging::Domain kError("error/fs-walker", logging::error()); static const logging::Domain kInfo("info/fs-walker", logging::info()); static const logging::Domain kWarning("warning/fs-walker", logging::warning()); static const logging::Domain kDebug("debug/fs-walker", logging::debug()); static const logging::Domain kTrace("trace/fs-walker", logging::trace()); class FileSystemWalker::Private { public: typedef TaskFacade::ErrorFunction ErrorFunction; typedef std::map > FileMonitorMap; enum ScanDetails { ScanAttributes = (1 << 0), ScanStreams = (1 << 1), ScanMetadata = (1 << 2), ScanAll = (ScanAttributes | ScanStreams | ScanMetadata) }; struct FileTaskInfo { FileTaskInfo(unsigned task_id, ScanDetails details) : task_id(task_id) , details(details) { } unsigned task_id; ScanDetails details; }; typedef std::map FileTaskInfoMap; Private(size_t group_id, const MediaRoot &media_root, MetadataResolverPtr resolver, MediaArtDownloaderPtr art_downloader, TaskManagerPtr file_task_manager, TaskManagerPtr index_task_manager, TaskFacadePtr index_task_facade, const ErrorFunction &report_fatal_error); FileSystemPath media_index_path() const { return index_task_facade_->media_index_path(); } MediaRoot media_root() const { return media_root_; } size_t group_id() const { return group_id_; } bool is_cancelled() const { return g_cancellable_is_cancelled(cancellable_.get()); } void set_file_monitor_enabled(bool enable) { if (not (file_monitor_enabled_ = enable)) file_monitors_.clear(); } bool file_monitor_enabled() const { return file_monitor_enabled_; } void ReportError(const format &error_message) { ReportErrorLiteral(error_message.str()); } ErrorFunction bind_report_error_literal() { return std::bind(&Private::ReportErrorLiteral, this, std::placeholders::_1); } void ReportErrorLiteral(const std::string &error_message); void cancel(); typedef void (Private::*FileTaskFunction)(Wrapper file, ScanDetails details); typedef std::function IndexTaskFunction; void RunIndexTask(const IndexTaskFunction &task); void push_index_task(const IndexTaskFunction &task); void StoreMediaInfo(const std::wstring &url, const MediaInfo &metadata); void store_media_info_async(const std::wstring &url, const MediaInfo &metadata); void push_file_task(FileTaskFunction task, Wrapper file, ScanDetails details); void cancel_file_task(const std::string &path); void take_file_task(const std::string &path); void CancelDiscoverer(); void ScanDirectory(Wrapper directory, ScanDetails details); void ScanFile(Wrapper file, ScanDetails details); bool start(ScanDetails details); bool MarkRunning(); void NotifyFinished(); bool Join(); const std::string& error_message() const { return error_message_; } private: static void on_file_changed(Private *d, GFile *file, GFile *other, GFileMonitorEvent event); void on_file_deleted(GFile *const file); void on_file_moved(GFile *file, GFile *other); MetadataResolver::StoreFunction store_function() { return std::bind(&Private::store_media_info_async, this, std::placeholders::_1, std::placeholders::_2); } // related objects const ErrorFunction report_fatal_error_; const Wrapper cancellable_; const MetadataResolverPtr metadata_resolver_; const MediaArtDownloaderPtr art_downloader_; const TaskManagerPtr file_task_manager_; const TaskManagerPtr index_task_manager_; const TaskFacadePtr index_task_facade_; FileMonitorMap file_monitors_; // attributes const size_t group_id_; MediaRoot media_root_; bool file_monitor_enabled_; std::string error_message_; // state attributes volatile bool walking_; FileTaskInfoMap file_tasks_; GMutex mutex_; GCond cond_; }; static std::string get_url(GFile *file) { return file ? take_string(g_file_get_uri(file)) : std::string(); } static std::string get_path(GFile *file) { return file ? take_string(g_file_get_path(file)) : std::string(); } FileSystemWalker::Private::Private(size_t group_id, const MediaRoot &media_root, MetadataResolverPtr resolver, MediaArtDownloaderPtr art_downloader, TaskManagerPtr file_task_manager, TaskManagerPtr index_task_manager, TaskFacadePtr index_task_facade, const ErrorFunction &report_fatal_error) : report_fatal_error_(report_fatal_error) , cancellable_(take(g_cancellable_new())) , metadata_resolver_(resolver) , art_downloader_(art_downloader) , file_task_manager_(file_task_manager) , index_task_manager_(index_task_manager) , index_task_facade_(index_task_facade) , group_id_(group_id) , media_root_(media_root) , file_monitor_enabled_(true) , walking_(false) { g_mutex_init(&mutex_); g_cond_init(&cond_); // Check if an error occured during initialization if (media_root_.is_valid()) { push_index_task(std::bind(&WritableMediaIndex::AddMediaRoot, std::placeholders::_1, media_root_)); } else { ReportErrorLiteral(media_root_.error_message()); } } FileSystemWalker::FileSystemWalker(const MediaRoot &media_root, MetadataResolverPtr resolver, MediaArtDownloaderPtr art_downloader, TaskManagerPtr file_task_manager, TaskManagerPtr index_task_manager, TaskFacadePtr index_task_facade) : d(new Private(reinterpret_cast(this), media_root, resolver, art_downloader, file_task_manager, index_task_manager, index_task_facade, d->bind_report_error_literal())) { } FileSystemWalker::~FileSystemWalker() { cancel(); Join(); delete d; } // Attributes ////////////////////////////////////////////////////////////////// bool FileSystemWalker::is_cancelled() const { return d->is_cancelled(); } void FileSystemWalker::set_file_monitor_enabled(bool enable) { d->set_file_monitor_enabled(enable); } bool FileSystemWalker::file_monitor_enabled() const { return d->file_monitor_enabled(); } MediaRoot FileSystemWalker::media_root() const { return d->media_root(); } std::string FileSystemWalker::error_message() const { return d->error_message(); } size_t FileSystemWalker::task_group() const { return d->group_id(); } // Actions ///////////////////////////////////////////////////////////////////// static std::string PrintStreams(GList *streams) { std::ostringstream text; text << ", streams=["; for (GList *l = streams; l; l = l->next) { if (l->prev) text << ", "; const Wrapper si = wrap(GST_DISCOVERER_STREAM_INFO(l->data)); text << gst_discoverer_stream_info_get_stream_type_nick(si.get()) << ", " << to_string(wrap(gst_discoverer_stream_info_get_caps(si.get()))); if (GstDiscovererContainerInfo *const ci = si.get()) text << PrintStreams(gst_discoverer_container_info_get_streams(ci)); if (GstDiscovererAudioInfo *const ai = si.get()) { text << ", channels=" << gst_discoverer_audio_info_get_channels(ai) << ", sample-rate=" << gst_discoverer_audio_info_get_sample_rate(ai) << ", depth=" << gst_discoverer_audio_info_get_depth(ai) << ", bitrate=" << gst_discoverer_audio_info_get_bitrate(ai) << ", max-bitrate=" << gst_discoverer_audio_info_get_max_bitrate(ai); } if (GstDiscovererVideoInfo *const vi = si.get()) { text << ", width=" << gst_discoverer_video_info_get_width(vi) << ", height=" << gst_discoverer_video_info_get_height(vi) << ", depth=" << gst_discoverer_video_info_get_depth(vi) << ", framerate=" << gst_discoverer_video_info_get_framerate_num(vi) << '/' << gst_discoverer_video_info_get_framerate_denom(vi) << ", par=" << gst_discoverer_video_info_get_par_num(vi) << '/' << gst_discoverer_video_info_get_par_denom(vi) << ", is-interlaced=" << gst_discoverer_video_info_is_interlaced(vi) << ", bitrate=" << gst_discoverer_video_info_get_bitrate(vi) << ", max_bitrate=" << gst_discoverer_video_info_get_max_bitrate(vi) << ", is-image=" << gst_discoverer_video_info_is_image(vi); } } text << "]"; return text.str(); } static bool merge_properties_visitor(const Property &property, GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, Property::ValueMap *properties) { property.MergeStreamInfo(media, stream, properties); return false; } static void CollectMediaProperties(GstDiscovererInfo *const media, MediaInfo *metadata) { Property::ValueMap media_properties; Property::VisitAll(std::bind(merge_properties_visitor, std::placeholders::_1, media, nullptr, &media_properties)); metadata->add_related(media_properties); std::unique_ptr stream_list(gst_discoverer_info_get_stream_list(media), gst_discoverer_stream_info_list_free); for (const GList *l = stream_list.get(); l; l = l->next) { GstDiscovererStreamInfo *const stream = GST_DISCOVERER_STREAM_INFO(l->data); Property::ValueMap stream_properties; Property::VisitAll(std::bind(merge_properties_visitor, std::placeholders::_1, media, stream, &stream_properties)); metadata->add_related(stream_properties); } } static ptime GetTimeStamp(GFileInfo *file, const char *attr_sec, const char *attr_usec) { BOOST_ASSERT(g_file_info_has_attribute(file, attr_sec)); ptime t = from_time_t(g_file_info_get_attribute_uint64(file, attr_sec)); if (g_file_info_has_attribute(file, attr_usec)) t += microseconds(g_file_info_get_attribute_uint32(file, attr_usec)); return t; } void FileSystemWalker::Private::ReportErrorLiteral (const std::string &error_message) { error_message_ = error_message; cancel(); } void FileSystemWalker::Private::cancel() { if (metadata_resolver_) metadata_resolver_->cancel(); g_cancellable_cancel(cancellable_.get()); BOOST_ASSERT(is_cancelled()); } void FileSystemWalker::cancel() { d->cancel(); } void FileSystemWalker::Private::RunIndexTask(const IndexTaskFunction &task) { index_task_manager_->RunTask (index_task_facade_->bind(task, report_fatal_error_)); } void FileSystemWalker::Private::push_index_task(const IndexTaskFunction &task) { index_task_manager_->AppendTask (index_task_facade_->bind(task, report_fatal_error_)); } bool FileSystemWalker::start() { return d->start(Private::ScanAll); } void FileSystemWalker::Private::push_file_task(FileTaskFunction task, Wrapper file, ScanDetails details) { g_mutex_lock(&mutex_); const std::string file_path = get_path(file.get()); const FileTaskInfoMap::iterator it = file_tasks_.find(file_path); unsigned task_id = 0; if (it != file_tasks_.end()) { if (it->second.details > details) // FIXME(M5): actually we should have a smart lock class goto unlock; kTrace("Updating scan request for \"{1}\".") % file_path; file_task_manager_->CancelTaskByTaskId(it->second.task_id); file_tasks_.erase(it); } else { kTrace("Adding new scan request for \"{1}\".") % file_path; } // FIXME(M5): avoid "crosses initialization of..." error task_id = file_task_manager_->AppendGroupedTask (group_id_, std::bind(task, this, file, details), group_id_); file_tasks_.insert(std::make_pair(file_path, FileTaskInfo(task_id, details))); unlock: g_mutex_unlock(&mutex_); } void FileSystemWalker::Private::cancel_file_task(const std::string &path) { g_mutex_lock(&mutex_); const FileTaskInfoMap::iterator it = file_tasks_.find(path); if (it != file_tasks_.end()) { file_task_manager_->CancelTaskByTaskId(it->second.task_id); file_tasks_.erase(it); } g_mutex_unlock(&mutex_); } void FileSystemWalker::Private::take_file_task(const std::string &path) { g_mutex_lock(&mutex_); const FileTaskInfoMap::iterator it = file_tasks_.find(path); if (it != file_tasks_.end()) file_tasks_.erase(it); g_mutex_unlock(&mutex_); } bool FileSystemWalker::Private::start(ScanDetails details) { if (not MarkRunning()) return false; const std::string root_path = media_root().path(); const FileSystemPath index_path = media_index_path(); kInfo("Scanning \"{1}\" using media index at \"{2}\"") % root_path % index_path; error_message_.clear(); push_file_task(&Private::ScanDirectory, media_root().file(), details); file_task_manager_->AppendGroupedTask (group_id_, std::bind(&Private::NotifyFinished, this), std::numeric_limits::max()); return true; } bool FileSystemWalker::Private::MarkRunning() { g_mutex_lock(&mutex_); if (is_cancelled()) return false; walking_ = true; g_mutex_unlock(&mutex_); return true; } void FileSystemWalker::Private::NotifyFinished() { const std::string root_path = media_root_.path(); kInfo("Finished scanning directories below \"{1}\".") % root_path; // Wakeup joiners g_mutex_lock(&mutex_); walking_ = false; g_cond_broadcast(&cond_); g_mutex_unlock(&mutex_); } bool FileSystemWalker::Private::Join() { bool was_walking = false; g_mutex_lock(&mutex_); while (walking_) { g_cond_wait(&cond_, &mutex_); was_walking = true; } g_mutex_unlock(&mutex_); return was_walking; } bool FileSystemWalker::Join() { return d->Join(); } void FileSystemWalker::Private::ScanDirectory(Wrapper directory, ScanDetails details) { const std::string path = get_path(directory.get()); take_file_task(path); kInfo("Scanning directory \"{1}\"") % path; Wrapper error; // Install file monitor if needed. if (file_monitor_enabled_ && not file_monitors_.count(path)) { const Wrapper monitor = take(g_file_monitor_directory(directory.get(), G_FILE_MONITOR_SEND_MOVED, cancellable_.get(), error.out_param())); if (not monitor) { const std::string error_message = to_string(error); kWarning("Cannot create file monitor for \"{1}\": {2}") % path % error_message; error.reset(); } else { g_signal_connect_swapped(monitor.get(), "changed", G_CALLBACK(&Private::on_file_changed), this); file_monitors_.insert(std::make_pair(path, monitor)); } } const Wrapper children = take(g_file_enumerate_children(directory.get(), G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, cancellable_.get(), error.out_param())); if (not children) { const std::string error_message = to_string(error); kWarning("Cannot list content of \"{1}\": {2}") % path % error_message; return; } while (const Wrapper child = take(g_file_enumerator_next_file(children.get(), cancellable_.get(), error.out_param()))) { if (is_cancelled()) return; if (error) { const std::string error_message = to_string(error); kWarning("Cannot list content of \"{1}\": {2}") % path % error_message; return; } // Skip hidden and backup files if (g_file_info_get_is_hidden(child.get()) || g_file_info_get_is_backup(child.get())) continue; const char *const name = g_file_info_get_name(child.get()); push_file_task(&Private::ScanFile, take(g_file_get_child(directory.get(), name)), details); } } static void RemoveMediaInfo(WritableMediaIndex *media_index, const std::wstring &url) { media_index->Delete(url); } void FileSystemWalker::Private::on_file_moved(GFile *file, GFile *other) { const std::string file_path = get_path(file); const std::string other_path = get_path(other); // Skip symlinks to avoid getting into infinite loops for things // like each process' "root" link in procfs. if (g_file_test(file_path.c_str(), G_FILE_TEST_IS_SYMLINK)) return; kTrace("File moved from \"{1}\" to \"{2}\".") % file_path % other_path; ScanDetails details = ScanAttributes; g_mutex_lock(&mutex_); const FileTaskInfoMap::iterator it = file_tasks_.find(file_path); if (it != file_tasks_.end()) { file_task_manager_->CancelTaskByTaskId(it->second.task_id); details = (details | it->second.details); } g_mutex_unlock(&mutex_); FileTaskFunction task = &Private::ScanFile; if (g_file_test(file_path.c_str(), G_FILE_TEST_IS_DIR)) task = &Private::ScanDirectory; push_file_task(task, wrap(other), details); } void FileSystemWalker::Private::on_file_deleted(GFile *const file) { const std::string path = get_path(file); kTrace("File \"{1}\" deleted.") % path; cancel_file_task(path); push_index_task(std::bind(&RemoveMediaInfo, std::placeholders::_1, ToUnicode(get_url(file)))); } void FileSystemWalker::Private::on_file_changed(Private *d, GFile *file, GFile *other, GFileMonitorEvent event) { switch (event) { case G_FILE_MONITOR_EVENT_CREATED: case G_FILE_MONITOR_EVENT_CHANGED: d->push_file_task(&Private::ScanFile, wrap(file), ScanAll); break; case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: d->push_file_task(&Private::ScanFile, wrap(file), ScanAttributes); break; case G_FILE_MONITOR_EVENT_MOVED: d->on_file_moved(file, other); break; case G_FILE_MONITOR_EVENT_DELETED: d->on_file_deleted(file); break; case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: case G_FILE_MONITOR_EVENT_UNMOUNTED: // Ignoring this events, they are handled by the volume monitor. break; case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: // TODO(M5): Figure out if we can do something with "changes-done". break; } } static void compare_etags(WritableMediaIndex *media_index, const std::wstring &uri, const std::wstring &etag, bool *etags_are_equal) { const std::wstring stored_etag = media_index->Lookup(uri, schema::kETag); *etags_are_equal = (stored_etag == etag); } static void InsertMediaInfo(WritableMediaIndex *media_index, const std::wstring &uri, const MediaInfo &metadata) { media_index->Insert(uri, metadata); } void FileSystemWalker::Private::StoreMediaInfo(const std::wstring &url, const MediaInfo &metadata) { RunIndexTask(std::bind(&InsertMediaInfo, std::placeholders::_1, url, metadata)); } void FileSystemWalker::Private::store_media_info_async (const std::wstring &url, const MediaInfo &metadata) { push_index_task(std::bind(&InsertMediaInfo, std::placeholders::_1, url, metadata)); } template static std::string enum_nick(T value) { static GEnumClass *const enum_class = static_cast (g_type_class_ref(internal::GetGType())); if (enum_class) { GEnumValue *const enum_value = g_enum_get_value(enum_class, value); if (enum_value) return enum_value->value_nick; } std::ostringstream oss; oss << g_type_name(internal::GetGType()) << "(" << value << ")"; return oss.str(); } void FileSystemWalker::Private::ScanFile(Wrapper file, ScanDetails details) { Wrapper error; const Wrapper discoverer = take(gst_discoverer_new(GST_SECOND * 25, error.out_param())); if (!discoverer) { const std::string error_message = to_string(error); ReportError(format("Cannot create discoverer: {1}") % error_message); return; } const std::string path = get_path(file.get()); take_file_task(path); kDebug(" Scanning \"{1}\"") % path; const Wrapper file_info = take(g_file_query_info(file.get(), "standard::*,etag::*,time::*", G_FILE_QUERY_INFO_NONE, cancellable_.get(), error.out_param())); if (not file_info) { const std::string error_message = to_string(error); kWarning("Failed to query information about file \"{1}\": {2}") % path % error_message; return; } const GFileType file_type = g_file_info_get_file_type(file_info.get()); if (file_type == G_FILE_TYPE_DIRECTORY) { if (g_file_info_get_is_symlink(file_info.get())) { // FIXME(future): Maybe only skip if the symlink creates a cycle // which could be detected by tracking G_FILE_ATTRIBUTE_ID_FILE. // But actually not sure if we really want to have duplicates // in the index, and such. kDebug(" Skipping symbolic link at \"{1}\"") % path; } else { // Queue the directory for scanning it later. push_file_task(&Private::ScanDirectory, file, details); } return; } // Skip anything that isn't a regular file. if (file_type != G_FILE_TYPE_REGULAR) { const std::string file_type_name = enum_nick(file_type); kWarning("Unexpected file type \"{1}\" for \"{2}\".") % file_type_name % path; return; } // Resolve the file's URI. const std::string url = get_url(file.get()); const std::wstring w_url = ToUnicode(url); // Skip if ETag indicates the file has not changed. const std::wstring etag = ToUnicode(g_file_info_get_etag(file_info.get())); bool etags_are_equal = false; RunIndexTask(std::bind(&compare_etags, std::placeholders::_1, w_url, etag, &etags_are_equal)); if (etags_are_equal) { kDebug(" Not changed, ignoring \"{1}\"") % path; return; } // Skip files according to their MIME type. const MimeType mime_type(g_file_info_get_content_type(file_info.get())); if (not mime_type.is_audio() && not mime_type.is_image() && not mime_type.is_video()) { kDebug(" Unknown MIME type, ignoring \"{1}\"") % path; return; } // FIXME(M5): Obey ScanStreams flag // FIXME(M5): Discover in background const Wrapper media = take(gst_discoverer_discover_uri(discoverer.get(), url.c_str(), error.out_param())); if (error) { const std::string error_message = to_string(error); kError("Content discovery failed for \"{1}\": {2}") % path % error_message; return; } if (media) { GstDiscovererResult res = gst_discoverer_info_get_result (media.get()); if (res != GST_DISCOVERER_OK) { kWarning(" Unable to discover \"{1}\", error code: {2}") % path % res; return; } std::unique_ptr streams(gst_discoverer_info_get_stream_list(media.get()), gst_discoverer_stream_info_list_free); // Skip any file that doesn't provide GStreamer compatible streams. if (not streams) { kDebug(" No media streams found in \"{1}\"") % path; return; } // File must be an image if duration is zero. Check that. if (gst_discoverer_info_get_duration(media) == 0) { // No streams at all? That's suspecious, but well. if (not GST_IS_DISCOVERER_VIDEO_INFO(streams.get()->data)) return; GstDiscovererVideoInfo *const video_info = static_cast(streams.get()->data); // Duration is zero, but it's also no image? Let's skip! if (not gst_discoverer_video_info_is_image(video_info)) return; } } if (kDebug.enabled()) { std::ostringstream text; text << " Found " << url << ": type=" << FromUnicode(mime_type.str()) << ", etag=" << g_file_info_get_etag(file_info.get()); if (media) { text << ", gstinfo={result=" << gst_discoverer_info_get_result(media); std::unique_ptr streams(gst_discoverer_info_get_stream_list(media.get()), gst_discoverer_stream_info_list_free); text << PrintStreams(streams.get()); text << "}"; } kDebug(text.str()); } // FIXME(M3): https://live.gnome.org/MediaArtStorageSpec // else if (tag_name == GST_TAG_IMAGE) // else if (tag_name == GST_TAG_PREVIEW_IMAGE) // -> attach as data: URL // Update media index with findings Property::ValueMap file_properties; file_properties.insert(schema::kMimeType.bind_value(mime_type.str())); file_properties.insert(schema::kETag.bind_value(etag)); const uint64_t file_size = g_file_info_get_size(file_info.get()); file_properties.insert(schema::kFileSize.bind_value(file_size)); const ptime last_modified = GetTimeStamp (file_info.get(), G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC); file_properties.insert(schema::kLastModified.bind_value(last_modified)); const ptime last_accessed = GetTimeStamp (file_info.get(), G_FILE_ATTRIBUTE_TIME_ACCESS, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC); file_properties.insert(schema::kLastAccessed.bind_value(last_accessed)); MediaInfo metadata; metadata.add_related(file_properties); if (media) CollectMediaProperties(media.get(), &metadata); // Generate title property if not available in media properties. if (metadata.first(schema::kTitle).empty()) { static const boost::wregex::flag_type rx_flags = boost::regex_constants::extended | boost::regex_constants::optimize | boost::regex_constants::icase; static const boost::wregex rx_extensions(L"(\\.\\w+)+$", rx_flags); static const boost::wregex rx_format_info(L"[-_ ]\\d+([ip][-_a-z0-9]*|x\\d+)$", rx_flags); static const boost::wregex rx_separators(L"(\\S)[-_]+(\\S)", rx_flags); // Strip extensions from filename. std::string title = g_file_info_get_edit_name(file_info.get()); std::wstring w_title = ToUnicode(title); w_title = boost::regex_replace(w_title, rx_extensions, std::wstring()); w_title = boost::regex_replace(w_title, rx_format_info, std::wstring()); w_title = boost::regex_replace(w_title, rx_separators, "$1 $2"); metadata.add_single(schema::kTitle.bind_value(w_title)); } // If the media has artist and album, then resolve artwork if (not metadata.first(schema::kArtist).empty() && not metadata.first(schema::kAlbum).empty()) { std::string artist = FromUnicode(metadata.first(schema::kArtist)); std::string album = FromUnicode(metadata.first(schema::kAlbum)); art_downloader_->resolve(artist, album); } StoreMediaInfo(w_url, metadata); if ((details & ScanMetadata) && metadata_resolver_) metadata_resolver_->push(url, metadata, store_function()); } } // namespace mediascanner // TODO(M5): Maybe rescan when new plugins get added? mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/filter.cpp0000644000015700001700000003353712232220161024141 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/filter.h" // Lucene++ #include #include #include #include #include #include #include #include #include #include #include #include #include // C++ Standard Library #include // Media Scanner Library #include "mediascanner/utilities.h" namespace mediascanner { // Lucene++ using Lucene::LuceneException; using Lucene::StringReader; using Lucene::Term; using Lucene::newLucene; // Standard Libary using std::wstring; static const bool kDisableCoord = true; //////////////////////////////////////////////////////////////////////////////// static Lucene::TokenStreamPtr make_token_stream(Lucene::AnalyzerPtr analyzer, const wstring &field_name, Lucene::StringReaderPtr text) { Lucene::TokenStreamPtr token_stream; try { token_stream = analyzer->reusableTokenStream(field_name, text); token_stream->reset(); } catch(Lucene::IOException &) { token_stream = analyzer->tokenStream(field_name, text); } return token_stream; } //////////////////////////////////////////////////////////////////////////////// class Filter::Private { public: virtual ~Private() { } virtual Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, wstring *error_message) = 0; }; Filter::Filter(Private *d) : d(d) { } Filter::~Filter() { } Filter::Filter() : d() { } Filter::Filter(const Filter &other) : d(other.d) { } Filter &Filter::operator=(const Filter &other) { d = other.d; return *this; } Lucene::QueryPtr Filter::BuildQuery(Lucene::QueryParserPtr parser, wstring *error_message) const { return d ? d->BuildQuery(parser, error_message) : Lucene::QueryPtr(); } //////////////////////////////////////////////////////////////////////////////// class QueryStringFilter::Private : public Filter::Private { public: explicit Private(const wstring &text) : text_(text) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, wstring *error_message) { if (text_.empty()) return Lucene::QueryPtr(); try { return parser->parse(text_); } catch(const LuceneException &ex) { if (error_message) *error_message = ex.getError(); return Lucene::QueryPtr(); } } private: wstring text_; }; QueryStringFilter::QueryStringFilter(const wstring &text) : Filter(new Private(text)) { } //////////////////////////////////////////////////////////////////////////////// // Create Lucene queries from wildcard search string. To match the stored // terms the string must be pass through the token analyzer. Lucene doesn't // support spaces in wildcard queries. Therefore queries that contain multiple // terms are transformed into a phrase query. static Lucene::QueryPtr wildcard_field_query (const wstring &field_name, const Lucene::AnalyzerPtr analyzer, const Lucene::StringReaderPtr text) { text->reset(); const Lucene::TokenStreamPtr stream = make_token_stream(analyzer, field_name, text); Lucene::PhraseQueryPtr phrase_query; Lucene::TermPtr term; if (const Lucene::TermAttributePtr term_attr = stream->getAttribute()) { while (stream->incrementToken()) { if (term) { if (not phrase_query) phrase_query = newLucene(); phrase_query->add(term); } term = newLucene(field_name, term_attr->term()); } } if (not term) return Lucene::QueryPtr(); if (phrase_query) { phrase_query->add(term); return phrase_query; } term->set(term->field(), L"*" + term->text() + L"*"); return newLucene(term); } static bool wildcard_search_visitor(const Property &property, const Lucene::AnalyzerPtr analyzer, const Lucene::StringReaderPtr text, const Lucene::BooleanQueryPtr query) { if (property.supports_full_text_search()) { if (const Lucene::QueryPtr field_query = wildcard_field_query(property.field_name(), analyzer, text)) query->add(field_query, Lucene::BooleanClause::SHOULD); } return false; } class SubStringFilter::Private : public Filter::Private { public: explicit Private(const wstring &text) : text_(text) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, wstring *) { if (text_.empty()) return Lucene::QueryPtr(); const Lucene::BooleanQueryPtr query = newLucene(kDisableCoord); query->setMinimumNumberShouldMatch(1); Property::VisitAll(std::bind(wildcard_search_visitor, std::placeholders::_1, parser->getAnalyzer(), newLucene(text_), query)); return query; } private: wstring text_; }; SubStringFilter::SubStringFilter(const wstring &text) : Filter(new Private(text)) { } //////////////////////////////////////////////////////////////////////////////// // Create Lucene queries from fulltext search string. To match the // stored terms the string must be pass through the token analyzer. static Lucene::QueryPtr fulltext_field_query (const wstring &field_name, const Lucene::AnalyzerPtr analyzer, const Lucene::StringReaderPtr text) { text->reset(); const Lucene::TokenStreamPtr stream = make_token_stream(analyzer, field_name, text); Lucene::PhraseQueryPtr phrase_query; Lucene::TermPtr term; if (const Lucene::TermAttributePtr term_attr = stream->getAttribute()) { while (stream->incrementToken()) { if (term) { if (not phrase_query) phrase_query = newLucene(); phrase_query->add(term); } term = newLucene(field_name, term_attr->term()); } } if (not term) return Lucene::QueryPtr(); if (phrase_query) { phrase_query->add(term); return phrase_query; } return newLucene(term); } static bool fulltext_search_visitor(const Property &property, const Lucene::AnalyzerPtr analyzer, const Lucene::StringReaderPtr text, const Lucene::BooleanQueryPtr query) { if (property.supports_full_text_search()) { if (const Lucene::QueryPtr field_query = fulltext_field_query(property.field_name(), analyzer, text)) query->add(field_query, Lucene::BooleanClause::SHOULD); } return false; } class FullTextFilter::Private : public Filter::Private { public: explicit Private(const wstring &text) : text_(text) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, wstring *) { if (text_.empty()) return Lucene::QueryPtr(); const Lucene::BooleanQueryPtr query = newLucene(kDisableCoord); query->setMinimumNumberShouldMatch(1); Property::VisitAll(std::bind(fulltext_search_visitor, std::placeholders::_1, parser->getAnalyzer(), newLucene(text_), query)); return query; } private: wstring text_; }; FullTextFilter::FullTextFilter(const wstring &text) : Filter(new Private(text)) { } //////////////////////////////////////////////////////////////////////////////// class PrefixFilter::Private : public Filter::Private { public: explicit Private(const wstring &key, const wstring &value) : key_(key) , value_(value) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr, wstring *) { // TODO(M5): Support prefixes that contain whitespace return newLucene(newLucene(key_, value_)); } private: wstring key_; wstring value_; }; PrefixFilter::PrefixFilter(const StringProperty &key, const wstring &value) : Filter(new Private(key.field_name(), value)) { } //////////////////////////////////////////////////////////////////////////////// class ValueFilter::Private : public Filter::Private { public: explicit Private(const Property &property, const Property::Value &value) : property_(property) , value_(value) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr, wstring *) { return property_.MakeTermQuery(value_); } private: Property property_; Property::Value value_; }; ValueFilter::ValueFilter(const Property::BoundValue &value) : Filter(new Private(value.first, value.second)) { } ValueFilter::ValueFilter(const Property &property, const Property::Value &value) : Filter(new Private(property, value)) { } //////////////////////////////////////////////////////////////////////////////// class RangeFilter::Private : public Filter::Private { public: Private(const Property &property, const Property::Value &lower_value, const Property::Boundary &lower_boundary, const Property::Value &upper_value, const Property::Boundary &upper_boundary) : property_(property) , lower_value_(lower_value) , lower_boundary_(lower_boundary) , upper_value_(upper_value) , upper_boundary_(upper_boundary) { } Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr, wstring *) { return property_.MakeRangeQuery(lower_value_, lower_boundary_, upper_value_, upper_boundary_); } private: Property property_; Property::Value lower_value_; Property::Boundary lower_boundary_; Property::Value upper_value_; Property::Boundary upper_boundary_; }; RangeFilter::RangeFilter(const Property &property, const Property::Value &lower_value, const Property::Value &upper_value) : Filter(new Private(property, lower_value, Property::Inclusive, upper_value, Property::Exclusive)) { } RangeFilter::RangeFilter(const Property &property, const Property::Value &lower_value, const Property::Boundary &lower_boundary, const Property::Value &upper_value, const Property::Boundary &upper_boundary) : Filter(new Private(property, lower_value, lower_boundary, upper_value, upper_boundary)) { } //////////////////////////////////////////////////////////////////////////////// static Lucene::BooleanClause::Occur to_lucene(BooleanFilter::Occur occur) { switch (occur) { case BooleanFilter::MUST: return Lucene::BooleanClause::MUST; case BooleanFilter::MUST_NOT: return Lucene::BooleanClause::MUST_NOT; case BooleanFilter::SHOULD: return Lucene::BooleanClause::SHOULD; } throw std::domain_error("Unknown BooleanFilter::Occur instance"); } class BooleanFilter::Private : public Filter::Private { public: Lucene::QueryPtr BuildQuery(Lucene::QueryParserPtr parser, wstring *error_message) { if (clauses_.empty()) return Lucene::QueryPtr(); if (clauses_.size() == 1) return clauses_.front().filter().BuildQuery(parser, error_message); Lucene::BooleanQueryPtr query = newLucene(); for (const auto &c: clauses_) { wstring child_error; const Lucene::QueryPtr child_query = c.filter().BuildQuery(parser, &child_error); if (not child_query) { if (child_error.empty()) continue; if (error_message) *error_message = child_error; return Lucene::QueryPtr(); } query->add(child_query, to_lucene(c.occur())); } if (query->getClauses().empty()) return Lucene::QueryPtr(); return query; } std::vector clauses_; }; BooleanFilter::BooleanFilter() : Filter(new Private) { } void BooleanFilter::add_clause(const Clause &clause) { data()->clauses_.push_back(clause); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/writablemediaindex.h0000644000015700001700000000651112232220161026152 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_WRITABLEMEDIAINDEX_H #define MEDIASCANNER_WRITABLEMEDIAINDEX_H // C++ Standard Library #include // Media Scanner Library #include "mediascanner/declarations.h" #include "mediascanner/mediaindex.h" namespace mediascanner { /** * @brief A writable instance of the media index. * * Use set_commit_policy() to control when changes * are written back to the disk. * * @see MediaIndex for a read-only instance. */ class WritableMediaIndex : public MediaIndex { friend class CommitPolicy; class Private; public: explicit WritableMediaIndex(MediaRootManagerPtr root_manager); ~WritableMediaIndex(); /** * @brief Changes the commit_policy() of this media index. */ void set_commit_policy(CommitPolicyPtr policy); /** * @brief The commit policy of this media index. */ CommitPolicyPtr commit_policy() const; /** * @brief Changes the time to wait for a write lock in milliseconds. */ void set_write_lock_timeout(int64_t timeout); /** * @brief The time to wait for a write lock in milliseconds. */ int64_t write_lock_timeout() const; bool Open(const FileSystemPath &path); void Close(); /** * @brief Inserts new properties for the media referenced by @p url. * @param url The URL of the media to modify. * @param metadata The new property values to attach. */ bool Insert(const std::wstring &url, const MediaInfo &metadata); /** * @brief Removes a media information from the index. * @param url The URL of the media to remove. */ bool Delete(const std::wstring &url); /** * @brief Commits all pending changes so that other readers will see the * changes, and so that the updates will survive a system crash or power * loss. * * This call can be very expensive, therefore we should only call it if * when really necessary. It's usually more efficient to just rely on the * configured commit_policy(). */ void CommitPendingChanges(); protected: bool ReadParams(); bool FlushParams(Wrapper params); using MediaIndex::FlushParams; Lucene::IndexReaderPtr OpenIndex(); Lucene::IndexReaderPtr OpenChildIndex(const MediaRoot &root); Lucene::IndexWriterPtr find_index_writer(const std::wstring &url); private: bool ReadParamsUnlocked(); // TODO(M5): figure out how to allocate in one go with parent class pimpl Private *const d; }; } // namespace mediascanner #endif // MEDIASCANNER_WRITABLEMEDIAINDEX_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/declarations.h0000644000015700001700000000772512232220161024771 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_DECLARATIONS_H #define MEDIASCANNER_DECLARATIONS_H // C Standard Library #include // C++ Standard Library #include #define NONCOPYABLE(classname) \ classname(classname const &) = delete; \ classname& operator=(classname const &) = delete // Forward declarations of some types to avoid hard dependency on GLib, // Grilo or Lucene++ to avoid hard dependencies for API users that don't // use that features. Especially Lucene++ doesn't guarantee source // compatibility. typedef struct _GFile GFile; typedef struct _GKeyFile GKeyFile; typedef struct _GList GList; typedef struct _GParamSpec GParamSpec; typedef struct _GValue GValue; typedef struct _GVariant GVariant; typedef struct _GstDiscovererInfo GstDiscovererInfo; typedef struct _GstDiscovererStreamInfo GstDiscovererStreamInfo; typedef struct _GstDiscovererAudioInfo GstDiscovererAudioInfo; typedef struct _GstDiscovererVideoInfo GstDiscovererVideoInfo; typedef uint32_t GQuark; typedef unsigned long GType; // NOLINT: runtime/int typedef uint32_t GrlKeyID; /* * This is horrible, you should never forward declare things * in the std namespace (or boost for that matter). Unfortunately * old code did this so the only short-to-medium term solution is * to do this. */ namespace std { template class shared_ptr; } namespace boost { template class shared_ptr; namespace filesystem { // template class basic_path; class path; } // namespace filesystem namespace locale { template class basic_format; typedef basic_format format; typedef basic_format wformat; } // namespace locale } // namespace boost namespace Lucene { template class Collection; /* * These must be boost::shared_ptrs instead of std::shared_ptr * because they come from Lucene's headers. */ typedef boost::shared_ptr AnalyzerPtr; typedef boost::shared_ptr DirectoryPtr; typedef boost::shared_ptr DocumentPtr; typedef boost::shared_ptr FieldablePtr; typedef boost::shared_ptr IndexReaderPtr; typedef boost::shared_ptr IndexWriterPtr; typedef boost::shared_ptr QueryPtr; typedef boost::shared_ptr TermPtr; } // namespace Lucene namespace mediascanner { namespace internal { template struct CopyHelper; } // namespace internal class CommitPolicy; class Filter; class MediaArtDownloader; class MediaIndex; class MediaInfo; class MediaRoot; class MediaRootManager; class MetadataResolver; class RefreshPolicy; class TaskManager; class WritableMediaIndex; template class MediaIndexFacade; template > class Wrapper; typedef std::shared_ptr CommitPolicyPtr; typedef std::shared_ptr MediaArtDownloaderPtr; typedef std::shared_ptr MediaRootManagerPtr; typedef std::shared_ptr MetadataResolverPtr; typedef std::shared_ptr TaskManagerPtr; typedef boost::filesystem::path FileSystemPath; } // namespace mediascanner #endif // MEDIASCANNER_DECLARATIONS_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/taskmanager.cpp0000644000015700001700000002440712232220161025145 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/taskmanager.h" // Standard Library #include #include // Media Scanner #include "mediascanner/glibutils.h" #include "mediascanner/logging.h" namespace mediascanner { // Context specific logging domains static const logging::Domain kDebug("debug/tasks", logging::debug()); static const logging::Domain kTrace("trace/tasks", logging::trace()); // Last id assigned to a task static unsigned last_task_id = 0; /** * @brief Description of a queued task. */ class TaskManager::TaskInfo { public: /** * @brief Constructs a new task description. * @param priority a number used to sort tasks * @param group a number used identify groups of tasks * @param task the function to execute */ TaskInfo(unsigned priority, unsigned group_id, const TaskFunction &task) : task_(task) , priority_(priority) , group_id_(group_id) , task_id_(++last_task_id) { } /** * @brief This number is used to sort tasks. */ unsigned priority() const { return priority_; } /** * @brief This number is used to identify groups of tasks. */ size_t group_id() const { return group_id_; } /** * @brief This number is used to identify tasks. */ size_t task_id() const { return task_id_; } /** * @brief Runs the function associated with this task. */ void RunTask() const { task_(); } bool less_by_priority(const TaskManager::TaskInfo &other) const { return priority_ < other.priority_; } private: TaskFunction task_; unsigned priority_; unsigned group_id_; unsigned task_id_; }; class TaskManager::Private { friend class TaskManager; typedef std::list TaskQueue; explicit Private(const std::string &name); ~Private(); TaskQueue::iterator find_predecessor(const TaskInfo &task) { if (task.priority() == 0) return tasks_.begin(); return std::find_if (tasks_.begin(), tasks_.end(), std::bind(&TaskInfo::less_by_priority, task, std::placeholders::_1)); } TaskQueue::iterator find_successor(const TaskInfo &task) { if (task.priority() == 0) return tasks_.end(); return std::find_if (tasks_.begin(), tasks_.end(), [&](const TaskInfo &t) {return ! t.less_by_priority(task); }); } static void *BackgroundThread(void *data); void Shutdown(); GThread *const foreground_thread_; GThread *background_thread_; std::string name_; TaskQueue tasks_; GMutex mutex_; GCond cond_; volatile bool destructing_; volatile bool background_thread_started; }; TaskManager::Private::Private(const std::string &name) : foreground_thread_(g_thread_self()) , background_thread_(nullptr) , name_(name) , destructing_(false) , background_thread_started(false) { // ensure the GThread primitives are in usable state g_mutex_init(&mutex_); g_cond_init(&cond_); // acquire mutex to prepare waiting for the background thread g_mutex_lock(&mutex_); // create the background thread background_thread_ = g_thread_new(name_.c_str(), &Private::BackgroundThread, this); // wait for background thread getting ready while(!background_thread_started) { g_cond_wait(&cond_, &mutex_); } g_mutex_unlock(&mutex_); kDebug("Task manager for {1} ready, " "with foreground thread {2} and background thread: {3}") % name_ % foreground_thread_ % background_thread_; } TaskManager::Private::~Private() { kDebug("Destroying the task manager for {1}.") % name_; Shutdown(); } void TaskManager::Private::Shutdown() { if (background_thread_) { // Signal the background thread we are tearing down. g_mutex_lock(&mutex_); destructing_ = true; tasks_.clear(); g_cond_signal(&cond_); g_mutex_unlock(&mutex_); // Wait for the background thread to follow our advice. kDebug("Waiting for the background thread of {1} to finish.") % name_; g_thread_join(background_thread_); // Destroy the thread object. g_thread_unref(background_thread_); background_thread_ = nullptr; } } TaskManager::TaskManager(const std::string &name) : d(new Private(name)) { } TaskManager::~TaskManager() { delete d; } unsigned TaskManager::PrependGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority) { g_mutex_lock(&d->mutex_); const TaskInfo task_info(priority, group_id, task); d->tasks_.insert(d->find_predecessor(task_info), task_info); const unsigned task_id = task_info.task_id(); const size_t num_tasks = d->tasks_.size(); kTrace("Prepending task {1}, now queuing {2} tasks for {3}.") % priority % num_tasks % d->name_; g_cond_signal(&d->cond_); g_mutex_unlock(&d->mutex_); return task_id; } unsigned TaskManager::AppendGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority) { g_mutex_lock(&d->mutex_); const TaskInfo task_info(priority, group_id, task); d->tasks_.insert(d->find_successor(task_info), task_info); const unsigned task_id = task_info.task_id(); const size_t num_tasks = d->tasks_.size(); kTrace("Appending task {1}, now queuing {2} tasks for {3}") % priority % num_tasks % d->name_; g_cond_signal(&d->cond_); g_mutex_unlock(&d->mutex_); return task_id; } static void RunAndNotifyOnExit(const TaskManager::TaskFunction &task, GCond *cond, GMutex *mutex, volatile bool *finished) { kTrace("Starting sync task..."); task(); kTrace("Synchronius task finished."); g_mutex_lock(mutex); *finished = true; g_cond_signal(cond); g_mutex_unlock(mutex); kTrace("Synchronius task signaled."); } void TaskManager::RunGroupedTask(unsigned group_id, const TaskFunction &task, unsigned priority) { BOOST_ASSERT_MSG(g_thread_self() != d->background_thread_, "Function must not be called from task thread"); GCond cond; g_cond_init(&cond); GMutex mutex; g_mutex_init(&mutex); g_mutex_lock(&mutex); volatile bool task_finished = false; AppendGroupedTask(group_id, std::bind(&RunAndNotifyOnExit, task, &cond, &mutex, &task_finished), priority); kTrace("Waiting for queued task to finish..."); while(!task_finished) { g_cond_wait(&cond, &mutex); } g_mutex_unlock(&mutex); } unsigned TaskManager::CancelByGroupId(unsigned group_id) { g_mutex_lock(&d->mutex_); Private::TaskQueue::iterator it = d->tasks_.begin(); unsigned task_count = 0; while (it != d->tasks_.end()) { if (group_id == it->group_id()) { const size_t task_id = it->task_id(); kDebug("Cancelling task {1} for group {2} of {3}") % task_id % group_id % d->name_; it = d->tasks_.erase(it); ++task_count; continue; } ++it; } const size_t num_tasks = d->tasks_.size(); kTrace("Number of queued tasks: {1}") % num_tasks; g_mutex_unlock(&d->mutex_); return task_count; } bool TaskManager::CancelTaskByTaskId(unsigned task_id) { g_mutex_lock(&d->mutex_); Private::TaskQueue::iterator it = d->tasks_.begin(); bool task_cancelled = false; while (it != d->tasks_.end()) { if (task_id == it->task_id()) { kDebug("Cancelling task {1} of {2}") % task_id % d->name_; it = d->tasks_.erase(it); task_cancelled = true; break; } ++it; } const size_t num_tasks = d->tasks_.size(); kTrace("Number of queued tasks: {1}") % num_tasks; g_mutex_unlock(&d->mutex_); return task_cancelled; } // Main routine of the task manager's background thread. void *TaskManager::Private::BackgroundThread(void *data) { Private *const d = static_cast(data); // Signal that the backround thread got ready for work g_mutex_lock(&d->mutex_); d->background_thread_started = true; g_cond_signal(&d->cond_); for (;;) { // Abort on destructor invokation. if (d->destructing_) break; // Wait for the next event to happen. g_cond_wait(&d->cond_, &d->mutex_); // Process pending tasks. while (not d->tasks_.empty()) { if (d->destructing_) { break; } const TaskInfo task = d->tasks_.front(); d->tasks_.pop_front(); const unsigned task_priority = task.priority(); const size_t num_tasks = d->tasks_.size(); kTrace("Running task {1}, now queuing {2} tasks for {3}.") % task_priority % num_tasks % d->name_; // Run the next task. g_mutex_unlock(&d->mutex_); task.RunTask(); g_mutex_lock(&d->mutex_); } } g_mutex_unlock(&d->mutex_); return 0; } void TaskManager::Shutdown() { d->Shutdown(); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/commitpolicy.h0000644000015700001700000001424212232220161025021 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_COMMITPOLICY_H #define MEDIASCANNER_COMMITPOLICY_H // Boost C++ #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/declarations.h" namespace mediascanner { /** * @brief A commit policy decides when changes to the media index are written * back to the disk. Choosing the proper policy has great impact on reliability * and performance, but also on hardware wear (for instance, flash disk wear). */ class CommitPolicy { public: virtual ~CommitPolicy(); /** * @brief The default policy - currently an instance of InstantCommitPolicy. */ static CommitPolicyPtr default_policy(); /** * @brief This method is called when data has been inserted into the * specified @p media_index. Policy implementations can now decide when * to call CommitPendingChanges(). * @param media_urls The URLs of the affected media. * @param media_index The affected media index. * @return If the change got committed. */ virtual bool OnCreate(const std::vector &media_urls, WritableMediaIndex *media_index) = 0; /** * @brief This method is called when data has been updated in the * specified @p media_index. Policy implementations can now decide when * to call CommitPendingChanges(). * @param media_urls The URLs of the affected media. * @param media_index The affected media index. * @return If the change got committed. */ virtual bool OnUpdate(const std::vector &media_urls, WritableMediaIndex *media_index) = 0; /** * @brief This method is called when data has been deleted from the * specified @p media_index. Policy implementations can now decide when * to call CommitPendingChanges(). * @param media_urls The URLs of the affected media. * @param media_index The affected media index. * @return If the change got committed. */ virtual bool OnRemove(const std::vector &media_urls, WritableMediaIndex *media_index) = 0; }; /** * @brief A CommitPolicy which askes for instant commit of any change. * This policy gives the most reliable behavior, at the cost of reduced * performance and increased hardware wear. */ class InstantCommitPolicy : public CommitPolicy { public: bool OnCreate(const std::vector &media_urls, WritableMediaIndex *media_index); bool OnUpdate(const std::vector &media_urls, WritableMediaIndex *media_index); bool OnRemove(const std::vector &media_urls, WritableMediaIndex *media_index); }; /** * @brief A CommitPolicy which delays commits slightly, trying to batch them. * This policy permits weighting reliability versus performance and wear. */ class DelayedCommitPolicy : public CommitPolicy { public: typedef boost::posix_time::microsec_clock Clock; typedef boost::posix_time::time_duration Duration; typedef boost::posix_time::ptime TimeStamp; DelayedCommitPolicy(); ~DelayedCommitPolicy(); /** * @brief The default value of maximum_batch_size() - currently 10. */ static unsigned default_maximum_batch_size(); /** * @brief The default value of maximum_delay() - currently 5 seconds. */ static Duration default_maximum_delay(); /** * @brief Changes the value of maximum_batch_size(). */ void set_maximum_batch_size(unsigned batch_size); /** * @brief The current maximium batch size after which commits @b must * be performed. */ unsigned maximum_batch_size() const; /** * @brief Changes the value of maximium_delay(). */ void set_maximum_delay(Duration delay); /** * @brief The current maximium timely delay after which commits @b must * be performed. */ Duration maximium_delay() const; bool OnCreate(const std::vector &media_urls, WritableMediaIndex *media_index); bool OnUpdate(const std::vector &media_urls, WritableMediaIndex *media_index); bool OnRemove(const std::vector &media_urls, WritableMediaIndex *media_index); protected: void UpdateDelayedCommits(); bool PushDelayedCommit(const std::vector &media_urls, WritableMediaIndex *media_index); private: class DelayedCommit { public: DelayedCommit(DelayedCommitPolicy *policy, WritableMediaIndex *media_index); ~DelayedCommit(); bool is_due(DelayedCommitPolicy *policy, TimeStamp t) const; void reset_timeout(DelayedCommitPolicy *policy); void cancel_timeout(); void increase_pressure(size_t amount); private: WritableMediaIndex *const media_index_; TimeStamp due_time_; size_t num_commits_; unsigned timeout_id_; }; typedef std::shared_ptr DelayedCommitPtr; typedef std::map DelayedCommitMap; DelayedCommitMap delayed_commits_; unsigned maximum_batch_size_; Duration maximum_delay_; }; } // namespace mediascanner #endif // MEDIASCANNER_COMMITPOLICY_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/logging.h0000644000015700001700000001336512232220161023744 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_LOGGING_H #define MEDIASCANNER_LOGGING_H // Boost C++ #include // C++ Standard Libary #include #include #include #include // Media Scanner Library #include "mediascanner/utilities.h" namespace mediascanner { namespace logging { class Domain; class MessageSink; typedef std::shared_ptr MessageSinkPtr; class MessageSink { public: virtual ~MessageSink(); virtual void Report(const std::string &domain_name, const std::string &message) = 0; virtual void Report(const std::string &domain_name, const std::wstring &message); static void set_default_instance(MessageSinkPtr instance); static MessageSinkPtr default_instance() { return default_instance_; } private: static MessageSinkPtr default_instance_; }; class DefaultMessageSink : public MessageSink { public: enum Color { Default, Black, Red, Green, Brown, Blue, Magenta, Cyan, White, Bold }; explicit DefaultMessageSink(std::ostream *stream = &std::clog, const std::string &prefix = std::string(), Color color = Default); void Report(const std::string &domain_name, const std::string &message); private: std::ostream *stream_; std::string prefix_; }; class Domain { public: enum Flags { Explicit = (1 << 0), Enabled = (1 << 1), Disabled = 0 }; Domain(const std::string &name, Flags flags, const Domain *parent = nullptr, MessageSinkPtr sink = MessageSinkPtr()); explicit Domain(const std::string &name, const Domain *parent = nullptr, MessageSinkPtr sink = MessageSinkPtr()); const std::string& name() const { return name_; } Domain* parent() const { return parent_; } void set_enabled(bool enabled = true) { if (enabled) flags_ = (flags_ | Enabled) | Explicit; else flags_ = (flags_ & ~Enabled) | Explicit; } bool enabled() const { if (not(flags_ & Enabled)) return false; if (not(flags_ & Explicit) && parent_) return parent_->enabled(); return flags_ & Enabled; } void reset() { flags_ = initial_flags_; } void set_message_sink(MessageSinkPtr sink) { message_sink_ = sink; } MessageSinkPtr message_sink() const { if (message_sink_) return message_sink_; if (parent_) return parent_->message_sink(); return MessageSink::default_instance(); } MessageSinkPtr default_message_sink() const { return default_message_sink_; } template class Message { friend class Domain; private: typedef boost::locale::basic_format format_type; Message() : format_(nullptr) { } Message(const Domain &domain, const std::basic_string &text) : format_(new format_type(text)) , domain_name_(domain.name()) , message_sink_(domain.message_sink()) { } public: ~Message() { if (format_ && message_sink_) message_sink_->Report(domain_name_, format_->str()); delete format_; } template Message& operator% (Formattible const &object) { if (format_) *format_ % object; return *this; } private: format_type *format_; const std::string domain_name_; const MessageSinkPtr message_sink_; }; Message print(const std::string &text) const { if (enabled()) return Message(*this, text); return Message(); } Message operator()(const std::string &text) const { return print(text); } Message print(const std::wstring &text) const { if (enabled()) return Message(*this, text); return Message(); } Message operator()(const std::wstring &text) const { return print(text); } private: typedef std::map SettingsMap; static const SettingsMap& ReadSettings(); static Flags LookupFlags(const std::string &name, Flags preset); void VerifyGraph() const; void VerifyPrefix() const; MessageSinkPtr message_sink_; Domain *const parent_; const std::string name_; const Flags initial_flags_; Flags flags_; const MessageSinkPtr default_message_sink_; }; Domain* error(); Domain* critical(); Domain* warning(); Domain* message(); Domain* info(); Domain* debug(); Domain* trace(); void capture_glib_messages(); bool is_capturing_glib_messages(); } // namespace logging } // namespace mediascanner #endif // MEDIASCANNER_LOGGING_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaindex.h0000644000015700001700000002024312232220161024416 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_MEDIAINDEX_H #define MEDIASCANNER_MEDIAINDEX_H // C++ Standard Library #include #include // Media Scanner Library #include "mediascanner/declarations.h" #include "mediascanner/mediautils.h" #include "mediascanner/property.h" #include "mediascanner/refreshpolicy.h" namespace mediascanner { /** * @brief A read-only instance of the media index. * * Use set_refresh_policy() to control when changes are * read from the disk. * * @see WritableMediaIndex for a writable instance. */ class MediaIndex { NONCOPYABLE(MediaIndex); class Collector; class Private; public: /** * @brief Pass this constant as @p limit argument to methods such as Query(), * or VisitAll() to request all available results for those methods. */ static const int32_t kUnlimited; static const char kMediaIndexFormat[]; static const char kMetaIndexFormat[]; /** * @brief Signature of the visitor function passed to methods like * Query(), or VisitAll(). * @param metadata The metadata of the current item. * @param remaining_items The number of remaining items. */ typedef std::function ItemVistor; explicit MediaIndex(MediaRootManagerPtr root_manager); virtual ~MediaIndex(); /** * @brief The error message of the last failed operation. */ std::string error_message(); /** * @brief The default that is used when path() is empty. * @see Open() */ static FileSystemPath default_path(); /** * @brief The file system path of the index, or an empty string to use. * the default_path(). * @see Open() */ FileSystemPath path() const; /** * @brief Predicate indicating weither the index was opened successfully. * @see Open() */ bool is_open() const; /** * @brief Indicator if the index still reads current data, or if Refresh() * should be called. */ bool is_current() const; /** * @brief Changes the current RefreshPolicy of the media index. * @see refresh_policy() */ void set_refresh_policy(RefreshPolicyPtr policy); /** * @brief The current RefreshPolicy of the media index. * @see set_refresh_policy() */ RefreshPolicyPtr refresh_policy() const; /** * @brief The MediaRootManager of the media index. */ MediaRootManagerPtr root_manager() const; /** * @brief Opens the media index at @p path. This method must be called * before performing any read operations. * @param path The file system path of the index, or an empty string to use * the default_path(). * @return @c true on success. * @see Close(), is_open), path(), default_path() */ virtual bool Open(const FileSystemPath &path); /** * @brief Closes the media index. After calling this method no other * queries are permitted. * @see Open() */ virtual void Close(); /** * @brief Refreshs the media index if it doesn't reflect recent changes * anymore. Such a method is needed because, to optimize performance, * Lucene++ doesn't automatically reload its index readers upon changes. * @see RefreshPolicy, is_current() */ virtual bool Reopen(); void AddMediaRoot(const MediaRoot &root); void RemoveMediaRoot(const MediaRoot &root); /** * @brief Checks if the media index contains information about the media * referenced by @p url. * @param url The URL of the media to check * @return @c true if the index contains information about @p url */ bool Exists(const std::wstring &url); /** * @brief Retrieves all information stored about the referenced media. * @param url The URL of the media to lookup. * @return A key-value map of the stored information. */ MediaInfo Lookup(const std::wstring &url); /** * @brief Retrieves the information stored in the index for the media * referenced by @p url, as requested in @p field_names. * @param url The URL of the media to lookup. * @param field_names The fields to lookup. * @return A key-value map of the stored information. */ MediaInfo Lookup(const std::wstring &url, const Property::Set &fields); /** * @brief Looks up a single detail about the referenced media. * @param url The URL of the media to lookup. * @param key The field to lookup. * @return The stored information, or a default constructed value. */ template ValueType Lookup(const std::wstring &url, const GenericProperty &key); /** * @brief Visits all items stored in the media index. * @param visit_item The function to call for each item. * @param limit The maximum number of items to return, or kUnlimited * @param offset Number of items to skip. * @see Query */ void VisitAll(const ItemVistor &visit_item, int32_t limit = kUnlimited, int32_t offset = 0); /** * @brief Visits all items matching the @p filter. * @param visit_item The function to call for each item. * @param filter The filter to apply to all items. * @param limit The maximum number of items to return, or kUnlimited * @param offset Number of items to skip. * @param @p true on success, check error_message() on error. * @see VisitAll, error_message() */ bool Query(const ItemVistor &visit_item, const Filter &filter, int32_t limit = kUnlimited, int32_t offset = 0); protected: Lucene::AnalyzerPtr analyzer() const; void report_error(const boost::locale::format &error_message); static Lucene::TermPtr MakeLookupTerm(const std::wstring &url); Lucene::DocumentPtr FindDocument(const std::wstring &url) const; static Property::Set GetFields(Lucene::DocumentPtr document); MediaInfo ExtractProperties(Lucene::DocumentPtr document); MediaInfo ExtractProperties(Lucene::DocumentPtr document, const Property::Set &fields); bool Lookup(const std::wstring &url, const Property &key, Property::Value *value); virtual bool ReadParams(); virtual bool FlushParams(Wrapper params); bool FlushParams(); virtual Lucene::IndexReaderPtr OpenIndex(); virtual Lucene::IndexReaderPtr OpenChildIndex(const MediaRoot &root); class ParamId; static const ParamId kParamFormat; static const ParamId kParamSegments; static const ParamId kParamVolumeUUID; static const ParamId kParamRelativePath; std::string get_param(const std::string &group, const ParamId &key) const; void set_param(const std::string &group, const ParamId &key, const std::string &value); FileSystemPath params_path() const; private: Lucene::IndexReaderPtr OpenIndexReader(const FileSystemPath &path); Lucene::IndexReaderPtr OpenMetaIndex(); Private *const d; }; template inline V MediaIndex::Lookup(const std::wstring &url, const GenericProperty &key) { Property::Set fields; fields.insert(key); return Lookup(url, fields).first(key); } } // namespace mediascanner #endif // MEDIASCANNER_MEDIAINDEX_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/dbustypes.h0000644000015700001700000002667512232220161024350 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #ifndef MEDIASCANNER_DBUSTYPES_H #define MEDIASCANNER_DBUSTYPES_H // Boost C++ #include // C++ Standard Library #include #include #include #include #include #include // Media Scanner Library #include "mediascanner/glibutils.h" #include "mediascanner/mediautils.h" #include "mediascanner/property.h" #include "mediascanner/utilities.h" namespace mediascanner { namespace dbus { /** * @brief This calls describes the signatures of D-Bus interface members. * It is useful to avoid type-cast issues between C++ types such as std::string * and C types such as GVariantType. */ class Signature { public: /** * @brief Implicit constructor that converts a C++ string. * @param signature The string representation of the signature. */ Signature(const std::string &signature); // NOLINT:runtime/explicit /** * @brief Implicit constructor that converts a GVariantType. * @param signature The GVariantType representation of the signature. */ Signature(const GVariantType *type); // NOLINT:runtime/explicit operator const char*() const; operator const GVariantType*() const; const std::string& str() const; static Signature array(const Signature &element_type); static Signature dictionary(const Signature &key_type, const Signature &value_type); static Signature tuple(const Signature &element); std::string signature_; }; template struct Type { typedef T value_type; static const Signature& signature(); static GVariant* make_variant(T value); static T make_value(GVariant *variant); }; //////////////////////////////////////////////////////////////////////////////// // Boxed variant types //////////////////////////////////////////////////////////////////////////////// template struct BoxedTypeTrait; template struct BoxedType { typedef T value_type; typedef typename BoxedTypeTrait::boxed_type boxed_type; static const Signature& signature() { return Type::signature(); } static GVariant* make_variant(value_type value) { return Type::make_variant(make_boxed(value)); } static T make_value(GVariant *variant) { if (variant == nullptr) return value_type(); return make_unboxed(Type::make_value(variant)); } static boxed_type make_boxed(value_type value); static value_type make_unboxed(boxed_type value); }; template<> struct BoxedTypeTrait { typedef std::pair boxed_type; }; template<> struct BoxedTypeTrait { typedef uint64_t boxed_type; }; template<> class Type : public BoxedType { }; template<> class Type : public BoxedType { }; //////////////////////////////////////////////////////////////////////////////// // Container types //////////////////////////////////////////////////////////////////////////////// template struct Type< std::pair > { typedef std::pair value_type; static const Signature& signature() { static const Signature signature = Signature::tuple(Type::signature().str() + Type::signature().str()); return signature; } static GVariant* make_variant(const value_type &value) { GVariant *elements[2] = { Type::make_variant(value.first), Type::make_variant(value.second) }; return g_variant_new_tuple(elements, G_N_ELEMENTS(elements)); } static value_type make_value(GVariant *variant) { if (variant == nullptr) return value_type(); Wrapper v1 = take(g_variant_get_child_value(variant, 0)); Wrapper v2 = take(g_variant_get_child_value(variant, 1)); return value_type(Type::make_value(v1.get()), Type::make_value(v2.get())); } }; template<> struct Type { static const Signature& signature(); static GVariant* make_variant(const Property::Value &value); static Property::Value make_value(GVariant *variant); }; template<> struct Type { static const Signature& signature(); static GVariant* make_variant(const Property::ValueMap &value); static Property::ValueMap make_value(GVariant *variant) { return make_value(variant, nullptr); } static Property::ValueMap make_value(GVariant *variant, std::set *bad_keys); }; template<> struct Type { static const Signature& signature(); static GVariant* make_variant(const MediaInfo &value); static MediaInfo make_value(GVariant *variant) { return make_value(variant, nullptr); } static MediaInfo make_value(GVariant *variant, std::set *bad_keys); }; namespace internal { template struct SequenceType { typedef typename Container::value_type value_type; static const Signature& signature() { static const Signature signature = Signature::array(Type::signature()); return signature; } static GVariant* make_variant(const Container &value) { GVariantBuilder builder; g_variant_builder_init(&builder, signature()); for (typename Container::const_iterator it = value.begin(), end = value.end(); it != end; ++it) { GVariant *const element = Type::make_variant(*it); g_variant_builder_add_value(&builder, element); } return g_variant_builder_end(&builder); } static Container make_value(GVariant *variant) { Container container; if (variant) { for (size_t i = 0, l = g_variant_n_children(variant); i < l; ++i) { GVariant *const element = g_variant_get_child_value(variant, i); add(Type::make_value(element), &container); } } return container; } private: static void add(const value_type &element, std::list *container) { container->push_back(element); } static void add(const value_type &element, std::set *container) { container->insert(element); } static void add(const value_type &element, std::vector *container) { container->push_back(element); } }; } // namespace internal template struct Type > : public internal::SequenceType< std::list > { }; template struct Type > : public internal::SequenceType< std::set > { }; template struct Type > : public internal::SequenceType< std::vector > { }; template struct Type > { static const Signature& signature() { static const Signature signature = Signature::dictionary(Type::signature(), Type::signature()); return signature; } static std::map make_value(GVariant *variant) { std::map map; if (variant) { for (size_t i = 0, l = g_variant_n_children(variant); i < l; ++i) { GVariant *const element = g_variant_get_child_value(variant, i); map.insert(Type::value_type>:: make_value(element)); } } return map; } }; namespace internal { template struct TupleTail { typedef typename boost::tuples::element::type value_type; typedef TupleTail predecessor; static const Signature& signature() { static const Signature signature = predecessor::signature().str() + Type::signature().str(); return signature; } static void make_value(GVariant *variant, T *value) { predecessor::make_value(variant, value); value_type &element_target = boost::tuples::get(*value); if (variant == nullptr) { element_target = value_type(); return; } GVariant *const element = g_variant_get_child_value(variant, N - 1); element_target = Type::make_value(element); } static void make_variant(const T &value, GVariantBuilder *builder) { predecessor::make_variant(value, builder); const value_type &element_value = boost::tuples::get(value); GVariant *const element = Type::make_variant(element_value); g_variant_builder_add_value(builder, element); } }; template struct TupleTail<0, T> { static const Signature &signature() { static const Signature signature = std::string(); return signature; } static void make_value(GVariant *, T *) { } static void make_variant(const T &, GVariantBuilder *) { } }; } // namespace internal template struct TupleType { typedef boost::tuples::tuple tuple_type; typedef boost::tuples::length length; typedef internal::TupleTail tail; static const Signature& signature() { static const Signature signature = Signature::tuple(tail::signature()); return signature; } static GVariant* make_variant(const tuple_type &value) { GVariantBuilder builder; g_variant_builder_init(&builder, signature()); tail::make_variant(value, &builder); return g_variant_builder_end(&builder); } static tuple_type make_value(GVariant *variant) { tuple_type value; if (variant) tail::make_value(variant, &value); return value; } }; template struct Type< boost::tuples::tuple > : public TupleType { }; } // namespace dbus } // namespace mediascanner #endif // MEDIASCANNER_DBUSTYPES_H mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/property.cpp0000644000015700001700000013614212232220161024534 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Mathias Hasselmann */ #include "mediascanner/property.h" // Grilo #include // Lucene++ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Boost C++ #include #include #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/dbustypes.h" #include "mediascanner/glibutils.h" #include "mediascanner/locale.h" #include "mediascanner/logging.h" #include "mediascanner/propertyprivate.h" #include "mediascanner/settings.h" #include "mediascanner/utilities.h" namespace mediascanner { // Lucene++ using Lucene::newLucene; // Boost C++ using boost::algorithm::ends_with; using boost::gregorian::date; using boost::posix_time::seconds; using boost::posix_time::microsec; using boost::posix_time::time_duration; //////////////////////////////////////////////////////////////////////////////// Property::MetadataKeyCache Property::metadata_key_cache_; Property::FieldNameCache Property::field_name_cache_; const GrlKeyID Property::MetadataKey::kPending = boost::numeric::bounds::highest(); const GrlKeyID Property::MetadataKey::kInvalid = GRL_METADATA_KEY_INVALID; static const logging::Domain kError("error/property", logging::error()); const Property::Category Property::Category::Generic(1); const Property::Category Property::Category::File(Generic.id_ | 2); const Property::Category Property::Category::Media(File.id_ | 4); const Property::Category Property::Category::Music(Media.id_ | 8); const Property::Category Property::Category::Image(Media.id_ | 16); const Property::Category Property::Category::Photo(Image.id_ | 32); const Property::Category Property::Category::Movie(Image.id_ | 64); //////////////////////////////////////////////////////////////////////////////// std::string Property::MetadataKey::name() const { return safe_string(grl_metadata_key_get_name(id())); } std::string Property::MetadataKey::description() const { return grl_metadata_key_get_desc(id()); } GType Property::MetadataKey::gtype() const { return grl_metadata_key_get_type(id()); } const GList* Property::MetadataKey::relation() const { GrlRegistry *const registry = grl_registry_get_default(); return grl_registry_lookup_metadata_key_relation(registry, id()); } bool Property::MetadataKey::RegisterCustomKey() { g_return_val_if_fail(id_ == kPending, false); // Lookup and reuse existing key. const Wrapper key_spec = take(make_spec_()); const char *const key_name = g_param_spec_get_name(key_spec.get()); if (key_spec == nullptr || key_name == nullptr) { id_ = kInvalid; return false; } GrlRegistry *const registry = grl_registry_get_default(); id_ = grl_registry_lookup_metadata_key(registry, key_name); if (id_ != kInvalid) { const GType type = grl_registry_lookup_metadata_key_type(registry, id_); if (type != G_PARAM_SPEC_VALUE_TYPE(key_spec.get())) { kError("Conflicting type for existing metadata key \"{1}\". " "The existing key is of type {2} instead of {3}.") % key_name % g_type_name(type) % g_type_name(G_PARAM_SPEC_VALUE_TYPE(key_spec.get())); return false; } return true; } // Register custom key. Wrapper error; id_ = grl_registry_register_metadata_key(registry, key_spec.get(), error.out_param()); if (error) { const std::string error_message = to_string(error); kError("Cannot register user metadata key \"{1}\": {2}") % key_name % error_message; return false; } BOOST_ASSERT(key_spec->ref_count > 1); return true; } //////////////////////////////////////////////////////////////////////////////// Property::Property(PrivatePtr impl) : d(impl) { // Register this property by its field name to make FromFieldName() work. field_name_cache_.insert(std::make_pair(d->field_name_, d)); // Register properties with known Grilo metadata key. if (d->metadata_key_.id() != MetadataKey::kInvalid && d->metadata_key_.id() != MetadataKey::kPending) { metadata_key_cache_.insert(std::make_pair(d->metadata_key_.id(), d)); } } Property::~Property() { } //////////////////////////////////////////////////////////////////////////////// String Property::field_name() const { return d->field_name_; } const Property::MetadataKey &Property::metadata_key() const { // Register pending custom metadata keys. // See documentation of kPendingMetadataKey for reasoning. if (d->metadata_key_.id() == MetadataKey::kPending && d->metadata_key_.RegisterCustomKey()) { metadata_key_cache_.insert(std::make_pair(d->metadata_key_.id(), d)); } return d->metadata_key_; } Property::Category Property::category() const { return d->category_; } std::set Property::origins() const { std::set origins; if (category() == Category::File) origins.insert("file system"); if (d->merge_stream_info_ && *(d->merge_stream_info_).target() != merge_nothing) origins.insert("GStreamer"); const Wrapper registry = wrap(grl_registry_get_default()); const Wrapper sources = take(grl_registry_get_sources(registry.get(), false)); for (const GList *l = sources; l; l = l->next) { GrlSource *const source = static_cast(l->data); const GList *const keys = grl_source_supported_keys(source); if (g_list_find(const_cast(keys), GRLKEYID_TO_POINTER(metadata_key().id()))) origins.insert(grl_source_get_name(source)); } return origins; } bool Property::supports_full_text_search() const { return d->supports_full_text_search(); } Property::MergeStrategy Property::merge_strategy() const { return d->merge_strategy_; } //////////////////////////////////////////////////////////////////////////////// Property::MetadataKey Property::define_boolean(const char *name, const char *nick, const char *blurb, bool default_value) { return MetadataKey(std::bind(&g_param_spec_boolean, name, nick, blurb, default_value, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } Property::MetadataKey Property::define_datetime(const char *name, const char *nick, const char *blurb) { // Defer invokation of G_TYPE_DATE_TIME to avoid warnings // about missing g_type_init() calls. return MetadataKey(std::bind(&MakeParamSpecBoxed, name, nick, blurb, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } Property::MetadataKey Property::define_string(const char *name, const char *nick, const char *blurb, const char *default_value) { return MetadataKey(std::bind(&g_param_spec_string, name, nick, blurb, default_value, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } template<> Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, bool default_value) { return define_boolean(name, nick, blurb, default_value); } template<> Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, Fraction default_value); template Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, T default_value) { return define(name, nick, blurb, boost::numeric::bounds::lowest(), boost::numeric::bounds::highest(), default_value); } template<> Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, double minimum, double maximum, double default_value) { return MetadataKey(std::bind(&g_param_spec_float, name, nick, blurb, minimum, maximum, default_value, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } template<> Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, int minimum, int maximum, int default_value) { return MetadataKey(std::bind(&g_param_spec_int, name, nick, blurb, minimum, maximum, default_value, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } template<> Property::MetadataKey NumericProperty:: define(const char *name, const char *nick, const char *blurb, uint64_t minimum, uint64_t maximum, uint64_t default_value) { return MetadataKey(std::bind(&g_param_spec_uint64, name, nick, blurb, minimum, maximum, default_value, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } //////////////////////////////////////////////////////////////////////////////// bool Property::MergeAny(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, const StreamInfoFunction &merge_first, const StreamInfoFunction &merge_second, ValueMap *item) { return merge_first(media, stream, item) || merge_second(media, stream, item); } template bool Property::MergeAttribute (GstDiscovererInfo *media, GstDiscovererStreamInfo *, ValueType (*get_attribute)(const GstDiscovererInfo *), Property::ValueMap *item) const { item->insert(std::make_pair(*this, get_attribute(media))); return true; } template bool Property::MergeAttribute(GstDiscovererInfo *, GstDiscovererStreamInfo *stream, ValueType (*get_attribute)(const InfoType *), Property::ValueMap *item) const { if (not G_TYPE_CHECK_INSTANCE_TYPE(stream, internal::GetGType())) return false; const Value value = get_attribute (reinterpret_cast(stream)); item->insert(std::make_pair(*this, value)); return true; } bool Property::MergeStreamInfo(GstDiscovererInfo *media, GstDiscovererStreamInfo *stream, Property::ValueMap *properties) const { return d->merge_stream_info_(media, stream, properties); } template bool Property::MergeTag(GstDiscovererInfo *, GstDiscovererStreamInfo *stream, const char *tag_id, ValueMap *item) const { if (stream == nullptr) return false; const GstTagList *const tags = gst_discoverer_stream_info_get_tags(stream); if (tags == nullptr) return false; const GValue *const tag_value = gst_tag_list_get_value_index(tags, tag_id, 0); if (tag_value == nullptr) return false; Value property_value; if (not TransformTagValue(tag_value, &property_value)) return false; item->insert(std::make_pair(*this, property_value)); return true; } //////////////////////////////////////////////////////////////////////////////// template static GType get_tag_type(); template<> GType get_tag_type() { return G_TYPE_BOOLEAN; } template<> GType get_tag_type() { return GST_TYPE_DATE_TIME; } template<> GType get_tag_type() { return G_TYPE_DOUBLE; } template<> GType get_tag_type() { return G_TYPE_INT; } template<> GType get_tag_type() { return GST_TYPE_FRACTION; } template<> GType get_tag_type() { return G_TYPE_UINT64; } template<> GType get_tag_type() { return G_TYPE_STRING; } template static bool supports_tag_value(const GValue *const value); template<> bool supports_tag_value(const GValue *const value) { return G_VALUE_HOLDS(value, GST_TYPE_DATE_TIME) || G_VALUE_HOLDS(value, G_TYPE_DATE); } template bool supports_tag_value(const GValue *const value) { return G_VALUE_HOLDS(value, get_tag_type()); } template static T transform_tag_value(const GValue *const value); template<> bool transform_tag_value(const GValue *const value) { return g_value_get_boolean(value); } template<> double transform_tag_value(const GValue *const value) { return g_value_get_double(value); } template<> Fraction transform_tag_value(const GValue *const value) { return Fraction(gst_value_get_fraction_numerator(value), gst_value_get_fraction_denominator(value)); } template<> int transform_tag_value(const GValue *const value) { return g_value_get_int(value); } template<> DateTime transform_tag_value(const GValue *const value) { if (G_VALUE_HOLDS(value, GST_TYPE_DATE_TIME)) { const GstDateTime *const dt = static_cast(g_value_get_boxed(value)); if (dt == nullptr) return DateTime(); // note: this creates not_a_date_time time_duration time; if (gst_date_time_has_time(dt)) time += time_duration(gst_date_time_get_hour(dt), gst_date_time_get_minute(dt), 0); if (gst_date_time_has_second(dt)) time += seconds(gst_date_time_get_second(dt)) + microsec(gst_date_time_get_microsecond(dt)); try { return DateTime(date(gst_date_time_has_year(dt) ? gst_date_time_get_year(dt) : 1, gst_date_time_has_month(dt) ? gst_date_time_get_month(dt) : 1, gst_date_time_has_day(dt) ? gst_date_time_get_day(dt) : 1), time); } catch(const std::out_of_range &ex) { const std::string error_message = ex.what(); kError("Bad date value: {1}") % error_message; return DateTime(); // note: this creates not_a_date_time } } else if (G_VALUE_HOLDS(value, G_TYPE_DATE)) { const GDate *const dt = static_cast(g_value_get_boxed(value)); if (dt == nullptr || g_date_valid(dt) == false) return DateTime(); try { return DateTime(date(g_date_get_year(dt), g_date_get_month(dt), g_date_get_day(dt))); } catch(const std::out_of_range &ex) { const std::string error_message = ex.what(); kError("Bad date value: {1}") % error_message; return DateTime(); // note: this creates not_a_date_time } } else { // Unknown value type kError("Unkown date value type: {1}") % G_VALUE_TYPE_NAME(value); return DateTime(); // note: this creates not_a_date_time } } template<> unsigned transform_tag_value(const GValue *const value) { return g_value_get_uint(value); } template<> uint64_t transform_tag_value(const GValue *const value) { return g_value_get_uint64(value); } template<> String transform_tag_value(const GValue *const value) { const char *const str = g_value_get_string(value); return ToUnicode(str ? str : ""); } template bool Property::TransformTagValue(const GValue *input, Value *output) const { if (supports_tag_value(input)) { *output = transform_tag_value(input); return true; } GValue transformed_value = G_VALUE_INIT; g_value_init(&transformed_value, get_tag_type()); if (g_value_transform(input, &transformed_value)) { *output = transform_tag_value(&transformed_value); return true; } const String name = field_name(); kError(L"Unexpected value type {1} in GStreamer tag for \"{2}\" field") % G_VALUE_TYPE_NAME(input) % name; return false; } //////////////////////////////////////////////////////////////////////////////// template static GType get_grilo_type(); template<> GType get_grilo_type() { return G_TYPE_BOOLEAN; } template<> GType get_grilo_type() { return G_TYPE_DATE_TIME; } template<> GType get_grilo_type() { return G_TYPE_FLOAT; } template<> GType get_grilo_type() { return G_TYPE_INT; } template<> GType get_grilo_type() { return G_TYPE_FLOAT; } template<> GType get_grilo_type() { return G_TYPE_UINT64; } template<> GType get_grilo_type() { return G_TYPE_STRING; } template static T transform_grilo_value(const GValue *const value); template<> bool transform_grilo_value(const GValue *const value) { return g_value_get_boolean(value); } template<> Fraction transform_grilo_value(const GValue *const value) { // FIXME(M5): Is there a better way to get fractions from Grilo values? // Maybe we should exponse two metadata key: One with a fraction, // and another one with the Grilo compatible double. return Fraction(g_value_get_float(value), 1); } template<> int transform_grilo_value(const GValue *const value) { return g_value_get_int(value); } template<> DateTime transform_grilo_value(const GValue *const value) { GDateTime *const dt = static_cast(g_value_get_boxed(value)); if (dt == nullptr) return DateTime(); // note: this creates not_a_date_time return DateTime(date(g_date_time_get_year(dt), g_date_time_get_month(dt), g_date_time_get_day_of_month(dt)), time_duration(g_date_time_get_hour(dt), g_date_time_get_minute(dt), g_date_time_get_second(dt)) + microsec(g_date_time_get_microsecond(dt))); } template<> double transform_grilo_value(const GValue *const value) { return g_value_get_float(value); } template<> uint64_t transform_grilo_value(const GValue *const value) { return g_value_get_uint64(value); } template<> String transform_grilo_value(const GValue *const value) { const char *const str = g_value_get_string(value); return ToUnicode(str ? str : ""); } template bool GenericProperty:: Private::TransformSafeGriloValue(const GValue *input, Value *output) const { *output = transform_grilo_value(input); return true; } template bool GenericProperty:: Private::TransformGriloValue(const GValue *input, Value *output) const { if (G_TYPE_CHECK_VALUE_TYPE(input, get_grilo_type())) return TransformSafeGriloValue(input, output); GValue safe_value = G_VALUE_INIT; g_value_init(&safe_value, get_grilo_type()); if (g_value_transform(input, &safe_value)) return TransformSafeGriloValue(&safe_value, output); kError(L"Unexpected value type {1} in metadata value for \"{2}\" field") % G_VALUE_TYPE_NAME(input) % field_name_; return false; } bool Property::TransformGriloValue(const GValue *input, Value *output) const { return d ? d->TransformGriloValue(input, output) : false; } template bool GenericProperty:: Private::TransformDBusVariant(GVariant *input, Value *output) const { const Wrapper unboxed_value = take(g_variant_get_variant(input)); if (not unboxed_value) return false; if (not g_variant_is_of_type(unboxed_value.get(), dbus::Type::signature())) { kError(L"Unexpected variant type {1} in D-Bus value for \"{2}\" field") % g_variant_get_type_string(input) % field_name_; return false; } *output = dbus::Type::make_value(unboxed_value.get()); return true; } bool Property::TransformDBusVariant(GVariant *input, Value *output) const { return d ? d->TransformDBusVariant(input, output) : false; } //////////////////////////////////////////////////////////////////////////////// Property::StreamInfoFunction Property::bind_any(const StreamInfoFunction &first, const StreamInfoFunction &second) const { return bind(&Property::MergeAny, std::placeholders::_1, std::placeholders::_2, first, second, std::placeholders::_3); } template Property::StreamInfoFunction Property:: bind_attr(ValueType (*get_attribute)(const GstDiscovererInfo *info)) { return bind(&Property::MergeAttribute, this, std::placeholders::_1, std::placeholders::_2, get_attribute, std::placeholders::_3); } template Property::StreamInfoFunction Property:: bind_attr(ValueType (*get_attribute)(const InfoType *info)) { return bind(&Property::MergeAttribute, this, std::placeholders::_1, std::placeholders::_2, get_attribute, std::placeholders::_3); } template Property::StreamInfoFunction Property::bind_attr(bool (*)(const GstDiscovererInfo *)); template Property::StreamInfoFunction Property::bind_attr(int32_t (*)(const GstDiscovererInfo *)); template Property::StreamInfoFunction Property::bind_attr(int32_t (*)(const GstDiscovererAudioInfo *)); template Property::StreamInfoFunction Property::bind_attr(Fraction (*)(const GstDiscovererVideoInfo *)); template Property::StreamInfoFunction Property::bind_attr(int32_t (*)(const GstDiscovererVideoInfo *)); template Property::StreamInfoFunction Property::bind_tag(const char *tag_name) const { return bind(&Property::MergeTag, this, std::placeholders::_1, std::placeholders::_2, tag_name, std::placeholders::_3); } template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; template Property::StreamInfoFunction Property::bind_tag(const char *) const; //////////////////////////////////////////////////////////////////////////////// Property::LuceneFields Property::MakeFields(const Value &value) const { return d->MakeFields(value); } Property::Value Property::TransformFields(const LuceneFields &fields) const { return d->TransformFields(fields); } Property::Value Property::TransformSingleField (Lucene::FieldablePtr field) const { return d->TransformSingleField(field); } namespace internal { class MakeGriloValueVistor : public boost::static_visitor< Wrapper > { private: static GArray *make_value_array(unsigned size) { GArray *va = g_array_sized_new(false, false, sizeof(GValue), size); g_array_set_clear_func(va, (GDestroyNotify) &g_value_unset); return va; } public: MakeGriloValueVistor() { // clang++ requires a user-provided default constructor } result_type operator()(const boost::blank &) const { return result_type(); } result_type operator()(const String &value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_STRING); g_value_set_string(&result, FromUnicode(value).c_str()); return wrap(&result); } result_type operator()(const DateTime &value) const { const boost::gregorian::date d = value.date(); const time_duration t = value.time_of_day(); typedef boost::rational FSeconds; const FSeconds fsecs(t.fractional_seconds(), t.ticks_per_second()); GDateTime *const dt = g_date_time_new_utc (d.year(), d.month(), d.day(), t.hours(), t.minutes(), t.seconds() + boost::rational_cast(fsecs)); GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_DATE_TIME); g_value_take_boxed(&result, dt); return wrap(&result); } result_type operator()(const Fraction &value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_FLOAT); g_value_set_float(&result, boost::rational_cast(value)); return wrap(&result); } result_type operator()(bool value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_BOOLEAN); g_value_set_boolean(&result, value); return wrap(&result); } result_type operator()(int value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_INT); g_value_set_int(&result, value); return wrap(&result); } result_type operator()(unsigned value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_UINT); g_value_set_uint(&result, value); return wrap(&result); } result_type operator()(uint64_t value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_UINT64); g_value_set_uint64(&result, value); return wrap(&result); } result_type operator()(double value) const { GValue result = G_VALUE_INIT; g_value_init(&result, G_TYPE_FLOAT); g_value_set_float(&result, value); return wrap(&result); } }; } // namespace internal GValue* Property::MakeGriloValue(const Value &value) const { return d->MakeGriloValue(value).release(); } Wrapper Property::Private::MakeGriloValue(const Value &value) const { static const internal::MakeGriloValueVistor make_grilo_value_visitor; return boost::apply_visitor(make_grilo_value_visitor, value); } bool DateTimeProperty::Private:: TransformGriloValue(const GValue *input, Value *output) const { if (metadata_key_.gtype() != G_TYPE_STRING) return inherited::TransformGriloValue(input, output); // https://bugzilla.gnome.org/show_bug.cgi?id=686175 GTimeVal tv; if (not g_time_val_from_iso8601(g_value_get_string(input), &tv)) return false; *output = DateTime(boost::gregorian::date(1970, 1, 1)) + boost::posix_time::seconds(tv.tv_sec) + boost::posix_time::microseconds(tv.tv_usec); return true; } Wrapper DateTimeProperty::Private:: MakeGriloValue(const Value &value) const { // https://bugzilla.gnome.org/show_bug.cgi?id=686175 if (metadata_key_.gtype() != G_TYPE_STRING) return inherited::MakeGriloValue(value); const DateTime dt = boost::get(value); const std::wstring str = boost::posix_time::to_iso_extended_wstring(dt); return inherited::MakeGriloValue(str + L"Z"); } //////////////////////////////////////////////////////////////////////////////// Property Property::FromFieldName(const String &name) { const FieldNameCache::const_iterator it = field_name_cache_.find(name); if (it != field_name_cache_.end()) return Property(it->second); return Property(); } static bool register_metadata_key(const Property &p) { // Querying the metadata key registers it lazily. p.metadata_key(); return false; } Property Property::FromMetadataKey(GrlKeyID key) { // Metadata keys are registered lazily. // So we better make sure we know them all before telling crap. if (metadata_key_cache_.size() != field_name_cache_.size()) VisitAll(std::bind(®ister_metadata_key, std::placeholders::_1)); const MetadataKeyCache::const_iterator it = metadata_key_cache_.find(key); if (it != metadata_key_cache_.end()) return Property(it->second); return Property(); } void Property::VisitAll(const PropertyVisitor &visit) { for (const auto &p: field_name_cache_) { if (visit(Property(p.second))) break; } } //////////////////////////////////////////////////////////////////////////////// static String date_time_string(const Property::Value &value) { auto date_value = boost::get(value); // make sure only valid date is passed to Lucene, otherwise it crashes. return date_value.is_not_a_date_time() ? String() : Lucene::DateTools::dateToString (date_value, Lucene::DateTools::RESOLUTION_MILLISECOND); } template<> Lucene::FieldablePtr DateTimeProperty::inherited:: Private::MakeSingleField(const Value &value) const { return newLucene (field_name_, date_time_string(value), Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS); } template<> Lucene::FieldablePtr NumericProperty:: Private::MakeSingleField(const Value &value) const { const Fraction fraction = boost::get(value); const double double_value = boost::rational_cast(fraction); std::wostringstream string_value; // Boost C++ only defines a << operator for ostream, but not for wostream. // Sadly the stream operator still works, apparently by applying an unsafe // typecast. See . string_value << Lucene::NumericUtils::doubleToPrefixCoded(double_value) << L' ' << fraction.numerator() << L'/' << fraction.denominator(); return newLucene (field_name_, string_value.str(), Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS); } template<> Lucene::FieldablePtr NumericProperty:: Private::MakeSingleField(const Value &value) const { return newLucene (field_name_, Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS)-> setIntValue(boost::get(value) ? 1 : 0); } template<> Lucene::FieldablePtr NumericProperty:: Private::MakeSingleField(const Value &value) const { return newLucene (field_name_, Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS)-> setDoubleValue(boost::get(value)); } template<> Lucene::FieldablePtr NumericProperty:: Private::MakeSingleField(const Value &value) const { // FIXME(M3): Add unsigned integer support to Lucene++ return newLucene (field_name_, Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS)-> setLongValue(boost::get(value)); } template<> Lucene::FieldablePtr NumericProperty:: Private::MakeSingleField(const Value &value) const { return newLucene (field_name_, Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED_NO_NORMS)-> setIntValue(boost::get(value)); } template<> Lucene::FieldablePtr StringProperty:: Private::MakeSingleField(const Value &value) const { return newLucene (field_name_, boost::get(value), Lucene::Field::STORE_YES, Lucene::Field::INDEX_NOT_ANALYZED); } template<> Property::LuceneFields TextProperty:: Private::MakeFields(const Value &value) const { LuceneFields fields = LuceneFields::newInstance(); fields.add(newLucene(field_name_ + L"$", boost::get(value), Lucene::Field::STORE_NO, Lucene::Field::INDEX_NOT_ANALYZED)); fields.add(newLucene(field_name_, boost::get(value), Lucene::Field::STORE_YES, Lucene::Field::INDEX_ANALYZED)); return fields; } template Property::LuceneFields GenericProperty:: Private::MakeFields(const Value &value) const { return Lucene::newCollection(MakeSingleField(value)); } //////////////////////////////////////////////////////////////////////////////// template<> Property::Value DateTimeProperty::inherited:: Private::TransformSingleField(Lucene::FieldablePtr field) const { return Lucene::DateTools::stringToDate(field->stringValue()); } template<> Property::Value NumericProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { const String value = Lucene::StringUtils::toLower(field->stringValue()); return static_cast(value != L"0" && value != L"false"); } template<> Property::Value NumericProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { return Lucene::StringUtils::toDouble(field->stringValue()); } template<> Property::Value NumericProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { const String string_value = field->stringValue(); const String::size_type i = string_value.rfind(L' '); if (i == String::npos) return value_type(); std::wistringstream tokenizer(string_value); tokenizer.seekg(i + 1); // Boost C++ doesn't provide a >> operator for wide streams. // See . value_type::int_type numerator; tokenizer >> numerator; if (tokenizer.get() != L'/') tokenizer.clear(std::wistringstream::badbit); value_type::int_type denominator; tokenizer >> denominator; if (tokenizer.fail()) return value_type(); return value_type(numerator, denominator); } template<> Property::Value NumericProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { return static_cast (Lucene::StringUtils::toInt(field->stringValue())); } template<> Property::Value NumericProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { // FIXME(M3): Add unsigned integer support to Lucene++ return static_cast (Lucene::StringUtils::toLong(field->stringValue())); } template<> Property::Value StringProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { return field->stringValue(); } template<> Property::Value TextProperty:: Private::TransformSingleField(Lucene::FieldablePtr field) const { return field->stringValue(); } template Property::Value GenericProperty:: Private::TransformFields(const LuceneFields &fields) const { if (fields.empty() || *fields.begin() == 0) return typename GenericProperty::value_type(); return TransformSingleField(*fields.begin()); } //////////////////////////////////////////////////////////////////////////////// DateTimeProperty:: DateTimeProperty(const String &field_name, const MetadataKey &metadata_key, Category category, MergeStrategy merge_strategy, const StreamInfoFunction &stream_info) : inherited(new Private(field_name, metadata_key, category, merge_strategy, stream_info)) { } DateTimeProperty::DateTimeProperty(Private *impl) : inherited(impl) { } template NumericProperty:: NumericProperty(const String &field_name, const Property::MetadataKey &metadata_key, Property::Category category, Property::MergeStrategy merge_strategy, const Property::StreamInfoFunction &stream_info) : inherited(new typename inherited:: Private(field_name, metadata_key, category, merge_strategy, stream_info)) { } template NumericProperty:: NumericProperty(typename inherited::Private *impl) : inherited(impl) { } template class NumericProperty; template class NumericProperty; template class NumericProperty; template class NumericProperty; template class NumericProperty; template GenericStringProperty:: GenericStringProperty(const String &field_name, const Property::MetadataKey &metadata_key, Property::Category category, Property::MergeStrategy merge_strategy, const Property::StreamInfoFunction &stream_info) : inherited(new typename inherited:: Private(field_name, metadata_key, category, merge_strategy, stream_info)) { } template GenericStringProperty:: GenericStringProperty(typename inherited::Private *impl) : inherited(impl) { } template class GenericStringProperty; template class GenericStringProperty; //////////////////////////////////////////////////////////////////////////////// template<> bool TextProperty::Private::supports_full_text_search() const { return true; } template bool GenericProperty::Private::supports_full_text_search() const { return false; } //////////////////////////////////////////////////////////////////////////////// template<> Lucene::QueryPtr NumericProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return Lucene::NumericRangeQuery::newIntRange (field_name_, boost::get(lower_value) ? 1 : 0, boost::get(upper_value) ? 1 : 0, lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return Lucene::NumericRangeQuery::newDoubleRange (field_name_, boost::get(lower_value), boost::get(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return Lucene::NumericRangeQuery::newDoubleRange (field_name_, boost::rational_cast(boost::get(lower_value)), boost::rational_cast(boost::get(upper_value)), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return Lucene::NumericRangeQuery::newIntRange (field_name_, boost::get(lower_value), boost::get(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { // FIXME(M5): unsigned numbers in Lucene++ return Lucene::NumericRangeQuery::newLongRange (field_name_, boost::get(lower_value), boost::get(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr DateTimeProperty::inherited:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return newLucene (field_name_, date_time_string(lower_value), date_time_string(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr GenericStringProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return newLucene (field_name_, boost::get(lower_value), boost::get(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } template<> Lucene::QueryPtr GenericStringProperty:: Private::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return newLucene (field_name_, boost::get(lower_value), boost::get(upper_value), lower_boundary == Property::Inclusive, upper_boundary == Property::Inclusive); } Lucene::QueryPtr Property::MakeRangeQuery(const Value &lower_value, Boundary lower_boundary, const Value &upper_value, Boundary upper_boundary) const { return d ? d->MakeRangeQuery(lower_value, lower_boundary, upper_value, upper_boundary) : Lucene::QueryPtr(); } //////////////////////////////////////////////////////////////////////////////// static Lucene::QueryPtr MakePhraseQuery(Lucene::FieldablePtr field, Lucene::AnalyzerPtr analyzer) { Lucene::TokenStreamPtr stream = field->tokenStreamValue(); if (not stream) { const Lucene::StringReaderPtr text = newLucene(field->stringValue()); try { stream = analyzer->reusableTokenStream(field->name(), text); stream->reset(); } catch(Lucene::IOException &) { stream = analyzer->tokenStream(field->name(), text); } } const Lucene::PhraseQueryPtr query = newLucene(); if (const Lucene::TermAttributePtr term_attr = stream->getAttribute()) { while (stream->incrementToken()) { query->add(newLucene(field->name(), term_attr->term())); } } if (query->getTerms().empty()) return Lucene::QueryPtr(); return query; } static Lucene::QueryPtr MakeFieldQuery(Lucene::FieldablePtr field, Lucene::AnalyzerPtr analyzer) { Lucene::QueryPtr query; if (field->isTokenized()) query = MakePhraseQuery(field, analyzer); if (not query) { const Lucene::TermPtr term = newLucene(field->name(), field->stringValue()); query = newLucene(term); } return query; } template<> Lucene::QueryPtr NumericProperty:: Private::MakeTermQuery(const Property::Value &value) const { return MakeRangeQuery(value, Property::Inclusive, value, Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeTermQuery(const Property::Value &value) const { return MakeRangeQuery(value, Property::Inclusive, value, Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeTermQuery(const Property::Value &value) const { return MakeRangeQuery(value, Property::Inclusive, value, Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeTermQuery(const Property::Value &value) const { return MakeRangeQuery(value, Property::Inclusive, value, Property::Inclusive); } template<> Lucene::QueryPtr NumericProperty:: Private::MakeTermQuery(const Property::Value &value) const { return MakeRangeQuery(value, Property::Inclusive, value, Property::Inclusive); } template Lucene::QueryPtr GenericProperty:: Private::MakeTermQuery(const Value &value) const { const Property::LuceneFields fields = MakeFields(value); const Lucene::AnalyzerPtr analyzer = newLucene(); if (fields.empty()) return Lucene::QueryPtr(); if (fields.size() == 1 || ends_with((*fields.begin())->name(), L"$")) return MakeFieldQuery(*fields.begin(), analyzer); const Lucene::BooleanQueryPtr query = newLucene(); for (const Lucene::FieldablePtr &f: fields) query->add(MakeFieldQuery(f, analyzer), Lucene::BooleanClause::MUST); return query; } Lucene::QueryPtr Property::MakeTermQuery(const Value &value) const { return d ? d->MakeTermQuery(value) : Lucene::QueryPtr(); } } // namespace mediascanner mediascanner-0.3.93+14.04.20131024.1/src/mediascanner/mediaartcache.cpp0000644000015700001700000001415212232220161025416 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Jussi Pakkanen */ #include"mediaartcache.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; namespace mediascanner { static string md5(const string &str) { const unsigned char *buf = (const unsigned char *)str.c_str(); char *normalized = g_utf8_normalize((const gchar*)buf, str.size(), G_NORMALIZE_ALL); string final; gchar *result; if(normalized) { buf = (const unsigned char*)normalized; } gssize bytes = str.length(); result = g_compute_checksum_for_data(G_CHECKSUM_MD5, buf, bytes); final = result; g_free((gpointer)normalized); g_free(result); return final; } MediaArtCache::MediaArtCache() { string xdg_base = g_get_user_cache_dir(); if (xdg_base == "") { string s("Could not determine cache dir."); throw runtime_error(s); } int ec = mkdir(xdg_base.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); if (ec < 0 && errno != EEXIST) { string s("Could not create base dir."); throw runtime_error(s); } root_dir = xdg_base + "/media-art"; ec = mkdir(root_dir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); if (ec < 0 && errno != EEXIST) { string s("Could not create cache dir."); throw runtime_error(s); } } bool MediaArtCache::has_art(const std::string &artist, const std::string &album) const { string fname = get_art_file(artist, album); return access(fname.c_str(), R_OK) == 0; } void MediaArtCache::add_art(const std::string &artist, const std::string &album, const char *data, unsigned int datalen) { string abs_fname = get_full_filename(artist, album); GError *err = nullptr; if(!g_file_set_contents(abs_fname.c_str(), data, datalen, &err)) { string e("Could not write file "); e += abs_fname; e += ": "; e += err->message; g_error_free(err); throw runtime_error(e); } } string MediaArtCache::get_art_file(const std::string &artist, const std::string &album) const { string abs_fname = get_full_filename(artist, album); if (access(abs_fname.c_str(), R_OK) == 0) { utime(abs_fname.c_str(), nullptr); // update access times to current time return abs_fname; } return ""; } string MediaArtCache::get_art_uri(const string &artist, const string &album) const { std::string filename = get_art_file(artist, album); if (filename == "") { return ""; } GError *err = nullptr; char *c_uri = g_filename_to_uri(filename.c_str(), "", &err); if (!c_uri) { string e("Could not convert file name "); e += filename; e += " to URI: "; e += err->message; g_error_free(err); throw runtime_error(e); } std::string uri = c_uri; g_free(c_uri); return uri; } std::string MediaArtCache::get_full_filename(const std::string &artist, const std::string & album) const { return root_dir + "/" + compute_base_name(artist, album); } std::string MediaArtCache::compute_base_name(const std::string &artist, const std::string &album) const { string type = "album"; string h1 = md5(artist); string h2 = md5(album); return type + "-" + h1 + "-" + h2 + ".jpg"; } void MediaArtCache::clear() const { DIR *d = opendir(root_dir.c_str()); if(!d) { string s = "Something went wrong."; throw runtime_error(s); } struct dirent *entry, *de; entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX); while(readdir_r(d, entry, &de) == 0 && de) { string basename = entry->d_name; if (basename == "." || basename == "..") continue; string fname = root_dir + "/" + basename; if(remove(fname.c_str()) < 0) { // This is not really an error worth // halting everything for. fprintf(stderr, "Could not delete file %s: %s.\n", fname.c_str(), strerror(errno)); } } free(entry); closedir(d); } void MediaArtCache::prune() { vector> mtimes; DIR *d = opendir(root_dir.c_str()); if(!d) { string s = "Something went wrong."; throw runtime_error(s); } struct dirent *entry, *de; entry = (dirent*)malloc(sizeof(dirent) + NAME_MAX); while(readdir_r(d, entry, &de) == 0 && de) { string basename = entry->d_name; if (basename == "." || basename == "..") continue; string fname = root_dir + "/" + basename; struct stat sbuf; if(stat(fname.c_str(), &sbuf) != 0) { continue; } // Use mtime because atime is not guaranteed to work if, for example // the filesystem is mounted with noatime or relatime. mtimes.push_back(make_pair(sbuf.st_mtim.tv_sec + sbuf.st_mtim.tv_nsec/1000000000.0, fname)); } free(entry); closedir(d); if (mtimes.size() <= MAX_SIZE) return; sort(mtimes.begin(), mtimes.end()); for(size_t i=0; i < mtimes.size()-MAX_SIZE; i++) { if(remove(mtimes[i].second.c_str()) < 0) { fprintf(stderr, "Could not remove file %s: %s.\n", mtimes[i].second.c_str(), strerror(errno)); } } } } mediascanner-0.3.93+14.04.20131024.1/examples/0000755000015700001700000000000012232220310020527 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/examples/example-grilo-search.c0000644000015700001700000001012112232220161024702 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Murray Cumming */ #include #include #define EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" GMainLoop* loop = NULL; static void browse_cb (GrlSource *source G_GNUC_UNUSED, guint browse_id G_GNUC_UNUSED, GrlMedia *media, guint remaining, gpointer user_data G_GNUC_UNUSED, const GError *error) { /* First we check if the operation failed for some reason */ g_assert_no_error (error); if (media) { const gchar *title = grl_media_get_title (media); /** The grl-mediascanner plugin does not provide a hierarchy, * but let's check for a container anyway. */ if (GRL_IS_MEDIA_BOX (media)) { guint childcount = grl_media_box_get_childcount (GRL_MEDIA_BOX (media)); printf ("Container: title='%s', child count=%d\n", title, childcount); } else { guint seconds = grl_media_get_duration (media); const gchar *url = grl_media_get_url (media); printf ("Media: title='%s', length(seconds)=%d\n", title, seconds); printf (" URL=%s\n", url); } g_object_unref (media); } /** Stop the main loop when we are finished. */ if (remaining <= 0) { g_main_loop_quit (loop); } } int main(int argc, char *argv[]) { /* * These defines are set by the build system. * Uncomment this to use the installed grilo plugins, * instead of the locally-built grilo plugins. */ g_setenv (GRL_PLUGIN_PATH_VAR, EXAMPLE_GRILO_PLUGIN_DIR, TRUE); grl_init (&argc, &argv); /* * Load the Grilo plugin: */ GrlRegistry *registry = grl_registry_get_default(); GError *error = NULL; gboolean plugin_loaded = grl_registry_load_plugin_by_id ( registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID, &error); g_assert (plugin_loaded); g_assert_no_error (error); /* * Get the plugin: * GrlPlugin *plugin = grl_registry_lookup_plugin (registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (plugin); */ /** * Get the Grilo Source: */ GrlSource *source = grl_registry_lookup_source(registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (source); /* * List the content: */ g_assert (grl_source_supported_operations (source) & GRL_OP_SEARCH); GrlCaps *caps = grl_source_get_caps (source, GRL_OP_SEARCH); g_assert (caps); GrlOperationOptions *options = grl_operation_options_new (caps); grl_operation_options_set_count (options, 5); grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY); GList * keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_CHILDCOUNT, NULL); grl_source_search (source, "springsteen", keys, options, browse_cb, NULL); /* * Start the main loop so our callback can be called: */ loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); /* * Release objects: */ g_list_free (keys); g_object_unref (caps); g_object_unref (options); return 0; } mediascanner-0.3.93+14.04.20131024.1/examples/CMakeLists.txt0000644000015700001700000000251012232220161023271 0ustar pbuserpbgroup00000000000000include_directories(${DEPS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src) # ============================================================================= # Prepare a Grilo plugin directory for testing # ============================================================================= set(grilo_plugin_dir "${CMAKE_CURRENT_BINARY_DIR}/plugins") add_definitions(-DEXAMPLE_GRILO_PLUGIN_DIR="${grilo_plugin_dir}") get_target_property(grlmediascanner_LOCATION grlmediascanner LOCATION) add_custom_target(examplesgriloplugindir DEPENDS grlmediascanner COMMAND mkdir -p ${grilo_plugin_dir} COMMAND ln -sf ${grlmediascanner_LOCATION} ${grilo_plugin_dir}/libgrlmediascanner.so COMMAND ln -sf ${CMAKE_SOURCE_DIR}/src/grlmediascanner/*.xml ${grilo_plugin_dir}) # ============================================================================= # Setup rules for the examples # ============================================================================= foreach(example example-grilo-browse example-grilo-search example-grilo-search-range-filter example-grilo-query example-grilo-store) add_executable(${example} ${example}.c) target_link_libraries(${example} mediascanner) add_dependencies(${example} examplesgriloplugindir) endforeach(example) mediascanner-0.3.93+14.04.20131024.1/examples/example-grilo-browse.c0000644000015700001700000001011012232220161024734 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Murray Cumming */ #include #include #define EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" GMainLoop* loop = NULL; static void browse_cb (GrlSource *source G_GNUC_UNUSED, guint browse_id G_GNUC_UNUSED, GrlMedia *media, guint remaining, gpointer user_data G_GNUC_UNUSED, const GError *error) { /* First we check if the operation failed for some reason */ g_assert_no_error (error); if (media) { const gchar *title = grl_media_get_title (media); /** The grl-mediascanner plugin does not provide a hierarchy, * but let's check for a container anyway. */ if (GRL_IS_MEDIA_BOX (media)) { guint childcount = grl_media_box_get_childcount (GRL_MEDIA_BOX (media)); printf ("Container: title='%s', child count=%d\n", title, childcount); } else { guint seconds = grl_media_get_duration (media); const gchar *url = grl_media_get_url (media); printf ("Media: title='%s', length(seconds)=%d\n", title, seconds); printf (" URL=%s\n", url); } g_object_unref (media); } /** Stop the main loop when we are finished. */ if (remaining <= 0) { g_main_loop_quit (loop); } } int main(int argc, char *argv[]) { /* * These defines are set by the build system. * Uncomment this to use the installed grilo plugins, * instead of the locally-built grilo plugins. */ g_setenv (GRL_PLUGIN_PATH_VAR, EXAMPLE_GRILO_PLUGIN_DIR, TRUE); grl_init (&argc, &argv); /* * Load the Grilo plugin: */ GrlRegistry *registry = grl_registry_get_default(); GError *error = NULL; gboolean plugin_loaded = grl_registry_load_plugin_by_id ( registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID, &error); g_assert (plugin_loaded); g_assert_no_error (error); /* * Get the plugin: * GrlPlugin *plugin = grl_registry_lookup_plugin (registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (plugin); */ /** * Get the Grilo Source: */ GrlSource *source = grl_registry_lookup_source(registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (source); /* * List the content: */ g_assert (grl_source_supported_operations (source) & GRL_OP_BROWSE); GrlCaps *caps = grl_source_get_caps (source, GRL_OP_BROWSE); g_assert (caps); GrlOperationOptions *options = grl_operation_options_new (caps); grl_operation_options_set_count (options, 5); grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY); GList * keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_CHILDCOUNT, NULL); grl_source_browse (source, NULL, keys, options, browse_cb, NULL); /* * Start the main loop so our callback can be called: */ loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); /* * Release objects: */ g_list_free (keys); g_object_unref (caps); g_object_unref (options); return 0; } mediascanner-0.3.93+14.04.20131024.1/examples/example-grilo-search-range-filter.c0000644000015700001700000001114712232220161027270 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Murray Cumming */ #include #include #define EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" GMainLoop* loop = NULL; static void browse_cb (GrlSource *source G_GNUC_UNUSED, guint browse_id G_GNUC_UNUSED, GrlMedia *media, guint remaining, gpointer user_data G_GNUC_UNUSED, const GError *error) { /* First we check if the operation failed for some reason */ g_assert_no_error (error); if (media) { const gchar *title = grl_media_get_title (media); /** The grl-mediascanner plugin does not provide a hierarchy, * but let's check for a container anyway. */ if (GRL_IS_MEDIA_BOX (media)) { guint childcount = grl_media_box_get_childcount (GRL_MEDIA_BOX (media)); printf ("Container: title='%s', child count=%d\n", title, childcount); } else { guint seconds = grl_media_get_duration (media); const gchar *url = grl_media_get_url (media); printf ("Media: title='%s', length(seconds)=%d\n", title, seconds); printf (" URL=%s\n", url); } g_object_unref (media); } /** Stop the main loop when we are finished. */ if (remaining <= 0) { g_main_loop_quit (loop); } } int main(int argc, char *argv[]) { /* * These defines are set by the build system. * Uncomment this to use the installed grilo plugins, * instead of the locally-built grilo plugins. */ g_setenv (GRL_PLUGIN_PATH_VAR, EXAMPLE_GRILO_PLUGIN_DIR, TRUE); grl_init (&argc, &argv); /* * Load the Grilo plugin: */ GrlRegistry *registry = grl_registry_get_default(); GError *error = NULL; gboolean plugin_loaded = grl_registry_load_plugin_by_id ( registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID, &error); g_assert (plugin_loaded); g_assert_no_error (error); /* * Get the plugin: * GrlPlugin *plugin = grl_registry_lookup_plugin (registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (plugin); */ /** * Get the Grilo Source: */ GrlSource *source = grl_registry_lookup_source(registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (source); /* * List the content: */ g_assert (grl_source_supported_operations (source) & GRL_OP_SEARCH); GrlCaps *caps = grl_source_get_caps (source, GRL_OP_SEARCH); g_assert (caps); GrlOperationOptions *options = grl_operation_options_new (caps); grl_operation_options_set_count (options, 5); grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY); /* * Return only the items that are within this range: */ GValue min_duration = G_VALUE_INIT; g_value_init(&min_duration, G_TYPE_DOUBLE); g_value_set_double(&min_duration, 0); GValue max_duration = G_VALUE_INIT; g_value_init(&max_duration, G_TYPE_DOUBLE); g_value_set_double(&max_duration, 400); /* (seconds) */ grl_operation_options_set_key_range_filter_value (options, grl_registry_lookup_metadata_key(registry, "duration"), &min_duration, &max_duration); GList * keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_CHILDCOUNT, NULL); grl_source_search (source, "legend", keys, options, browse_cb, NULL); /* * Start the main loop so our callback can be called: */ loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); /* * Release objects: */ g_list_free (keys); g_object_unref (caps); g_object_unref (options); return 0; } mediascanner-0.3.93+14.04.20131024.1/examples/example-grilo-store.c0000644000015700001700000000734212232220161024604 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Murray Cumming */ #include #include #define EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" GMainLoop* loop = NULL; guint remaining = 1; static void store_cb (GrlSource *source G_GNUC_UNUSED, GrlMedia *media G_GNUC_UNUSED, GList *failed_keys G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED, const GError *error) { if (error) { if (error->domain == GRL_CORE_ERROR) { g_warning ("Grilo Error while storing media " "(Maybe the mediscanner-service is not running):\n" " %s", error->message); } else { g_warning ("Error while storing media:\n" " %s", error->message); } } else { g_debug ("Media stored"); } /** Stop the main loop when we are finished. */ g_main_loop_quit (loop); } int main(int argc, char *argv[]) { /* * These defines are set by the build system. * Uncomment this to use the installed grilo plugins, * instead of the locally-built grilo plugins. */ g_setenv (GRL_PLUGIN_PATH_VAR, EXAMPLE_GRILO_PLUGIN_DIR, TRUE); grl_init (&argc, &argv); /* * Load the Grilo plugin: */ GrlRegistry *registry = grl_registry_get_default(); GError *error = NULL; gboolean plugin_loaded = grl_registry_load_plugin_by_id ( registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID, &error); g_assert (plugin_loaded); g_assert_no_error (error); /** * Get the Grilo Source: */ GrlSource *source = grl_registry_lookup_source(registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (source); /* * Store some content: */ g_assert (grl_source_supported_operations (source) & GRL_OP_STORE); GrlCaps *caps = grl_source_get_caps (source, GRL_OP_STORE); g_assert (caps); GrlOperationOptions *options = grl_operation_options_new (caps); grl_operation_options_set_count (options, 5); grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY); /* * Add a new item. * The grl-mediascanner plugin will discover and set other properties * such as duration. */ const char* url = "file:///home/murrayc/Music/John%20Legend/John%20Legend%20-%20Once%20Again%20%5B2006%5D/04%20-%20Show%20Me.mp3"; GrlMedia *media = grl_media_new (); grl_media_set_id (media, url); /* Optional, but must match the URL. */ grl_media_set_url (media, url); grl_media_set_title (media, "Test Media"); grl_source_store (source, NULL, media, GRL_WRITE_NORMAL, store_cb, NULL); /* * Start the main loop so our callback can be called: */ loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); /* * Release objects: */ g_object_unref (media); g_object_unref (caps); g_object_unref (options); g_main_loop_unref (loop); return 0; } mediascanner-0.3.93+14.04.20131024.1/examples/example-grilo-query.c0000644000015700001700000001050012232220161024603 0ustar pbuserpbgroup00000000000000/* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp * Authored by: Murray Cumming */ #include #include #define EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID "grl-mediascanner" GMainLoop* loop = NULL; static void browse_cb (GrlSource *source G_GNUC_UNUSED, guint browse_id G_GNUC_UNUSED, GrlMedia *media, guint remaining, gpointer user_data G_GNUC_UNUSED, const GError *error) { /* First we check if the operation failed for some reason */ g_assert_no_error (error); if (media) { const gchar *title = grl_media_get_title (media); /** The grl-mediascanner plugin does not provide a hierarchy, * but let's check for a container anyway. */ if (GRL_IS_MEDIA_BOX (media)) { guint childcount = grl_media_box_get_childcount (GRL_MEDIA_BOX (media)); printf ("Container: title='%s', child count=%d\n", title, childcount); } else { guint seconds = grl_media_get_duration (media); const gchar *url = grl_media_get_url (media); printf ("Media: title='%s', length(seconds)=%d\n", title, seconds); printf (" URL=%s\n", url); } g_object_unref (media); } /** Stop the main loop when we are finished. */ if (remaining <= 0) { g_main_loop_quit (loop); } } int main(int argc, char *argv[]) { /* * These defines are set by the build system. * Uncomment this to use the installed grilo plugins, * instead of the locally-built grilo plugins. */ g_setenv (GRL_PLUGIN_PATH_VAR, EXAMPLE_GRILO_PLUGIN_DIR, TRUE); grl_init (&argc, &argv); /* * Load the Grilo plugin: */ GrlRegistry *registry = grl_registry_get_default(); GError *error = NULL; gboolean plugin_loaded = grl_registry_load_plugin_by_id ( registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID, &error); g_assert (plugin_loaded); g_assert_no_error (error); /* * Get the plugin: * GrlPlugin *plugin = grl_registry_lookup_plugin (registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (plugin); */ /** * Get the Grilo Source: */ GrlSource *source = grl_registry_lookup_source(registry, EXAMPLE_GRL_MEDIA_SCANNER_PLUGIN_ID); g_assert (source); /* * List the content: */ g_assert (grl_source_supported_operations (source) & GRL_OP_QUERY); GrlCaps *caps = grl_source_get_caps (source, GRL_OP_QUERY); g_assert (caps); GrlOperationOptions *options = grl_operation_options_new (caps); grl_operation_options_set_count (options, 5); grl_operation_options_set_flags (options, GRL_RESOLVE_IDLE_RELAY); GList * keys = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, GRL_METADATA_KEY_DURATION, GRL_METADATA_KEY_URL, GRL_METADATA_KEY_CHILDCOUNT, NULL); /* * The query string syntax is plugin-specific. * TODO(M4): This uses the lucene query syntax: https://lucene.apache.org/core/3_6_0/queryparsersyntax.html * TODO(M4): Link to our gtk-doc docs for our grilo plugin. */ grl_source_query (source, "title:Soul*", keys, options, browse_cb, NULL); /* * Start the main loop so our callback can be called: */ loop = g_main_loop_new (NULL, FALSE); g_main_loop_run (loop); /* * Release objects: */ g_list_free (keys); g_object_unref (caps); g_object_unref (options); return 0; } mediascanner-0.3.93+14.04.20131024.1/tools/0000755000015700001700000000000012232220310020051 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/tools/download-media.py0000755000015700001700000001405112232220161023317 0ustar pbuserpbgroup00000000000000#!/usr/bin/python from hashlib import sha384 from os import makedirs, unlink from os.path import basename, getmtime, getsize, isdir, isfile, join from urllib2 import Request, urlopen from sys import argv, stdout # This are the URLs of the media to fetch. Separated by a comma is the # content-length for media that needs peeking to the end. Use a command like # this to retreive this information: # # curl --referer=http://www.bigbuckbunny.org/index.php/download/ --head \ # http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov # # That size argument might have to be changed into a range list, like this: # # http://somehost/somefile,0-5000,32000000-33000000,66000000- # # to deal with non-streamable media. # mediaurls = """ http://mrstoast.fs.uni-bayreuth.de/mango/ToS/tears_of_steel_720p.mkv http://mrstoast.fs.uni-bayreuth.de/mango/ToS/tears_of_steel_720p.mov,372178639 http://mrstoast.fs.uni-bayreuth.de/mango/ToS/tears_of_steel_1080p.mkv http://mrstoast.fs.uni-bayreuth.de/mango/ToS/tears_of_steel_1080p.mov,583774083 http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_1080p_surround.avi http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_1080p_stereo.ogg http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_1080p_stereo.avi http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_720p_surround.avi http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_720p_stereo.ogg http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_720p_stereo.avi http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround.avi http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi http://ftp.halifax.rwth-aachen.de/blender/demo/movies/Sintel.2010.1080p.mkv http://ftp.halifax.rwth-aachen.de/blender/demo/movies/Sintel.2010.720p.mkv """ # Seems peeking at the end is not good enough for those files. Have to do look # in detail with "strace -f -e open,lseek gst-discoverer-0.10" what ranges # we must fetch to discover those files. """ http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov,725106140 http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_720p_h264.mov,416751190 http://mirror.bigbuckbunny.de/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov,249229883 http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v,121283919 http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4,64657027 """ # Parse command line. # First argument is the directory where to store files. # Second argument is the sha384 sum file. mediadir = len(argv) > 1 and argv[1] or 'media' checksums = dict(len(argv) > 2 and [reversed(l.split()) for l in file(argv[2]).read().split('\n') if l] or []) failed = False # Create mediadir if needed. if not isdir(mediadir): makedirs(mediadir) # Download the media for url in mediaurls.split(): # Parse URL and extract range list url, fullsize = (url + ',0').split(',')[:2] fullsize = int(fullsize) headsize = 128 * 1024 tailsize = fullsize and 1024 * 1024 or 0 fullsize = fullsize or headsize title = basename(url) filename = join(mediadir, title) # Check if a download is needed. if not isfile(filename) or getsize(filename) < fullsize: stdout.write('Downloading "%s" - ' % title) stdout.flush() output = file(filename, 'w') datasize = headsize + tailsize remaining = datasize # Fetches a byte range of the current media. def fetch_range(offset, size, remaining): request = Request(url) request.add_header('Range', 'bytes=%d-%d' % (offset, offset + size)) # Some download sites check the referer header. Patch it in here. if 'bigbuckbunny' in url: request.add_header('Referer', 'http://www.bigbuckbunny.org/index.php/download/') response = urlopen(request) # Fetch the bytes and print progress. while size > 0: progress = '%.1f%%' % (100 * float(datasize - remaining) / datasize) stdout.write('%s\033[%dD' % (progress, len(progress))) stdout.flush() data = response.read(min(4096, remaining)) if not data: break output.write(data) remaining -= len(data) size -= len(data) return remaining # Fetch the required ranges. if headsize: remaining = fetch_range(0, headsize, remaining) if tailsize: offset = fullsize - tailsize output.seek(offset) remaining = fetch_range(offset, tailsize, remaining) output.close() # Complain if not all expected data could be fetched. if remaining: stdout.write('failed\n') unlink(filename) else: stdout.write('succeeded\n') # Verify checksums filehash = checksums.get(title) if not filehash: print 'No checksum found, not checking "%s"' % title failed = True continue hashfile = join(mediadir, title + '.sha348') if (isfile(hashfile) and getmtime(hashfile) >= getmtime(filename) and open(hashfile).read().rstrip() == filehash): continue with open(filename, 'rb') as f: hash = sha384() while True: data = f.read(8192) if not data: break hash.update(data) digest = hash.hexdigest() open(hashfile, 'w').write('%s\n' % digest) if filehash != digest: print 'Checksum does not match for "%s"' % title failed = True # Report failure to the caller if needed. if failed: print 'Failed to setup media folder.' raise SystemExit, 1 mediascanner-0.3.93+14.04.20131024.1/tools/licensecheck.py0000755000015700001700000000454412232220161023061 0ustar pbuserpbgroup00000000000000#!/usr/bin/python import os import sys gpl_header = """\ /* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 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 . * * Contact: Jim Hodapp """ lgpl_header = """\ /* * This file is part of the Ubuntu TV Media Scanner * Copyright (C) 2012-2013 Canonical Ltd. * * 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 . * * Contact: Jim Hodapp """ def check_license_headers(rootdirs, copyright_header): failed = False for rootdir in rootdirs: for dirpath, dirnames, filenames in os.walk(rootdir): for filename in filenames: if filename.endswith('.cpp') or filename.endswith('.h'): filepath = os.path.join(dirpath, filename) header = open(filepath).read(len(copyright_header)) if header != copyright_header: print '%s: Bad copyright header' % filepath failed = True return failed failed = False failed |= check_license_headers( ['src/mediascanner', 'src/grlmediascanner'], lgpl_header) failed |= check_license_headers( ['src/mediascanner-service', 'tests', 'examples'], gpl_header) if failed: sys.exit(1) mediascanner-0.3.93+14.04.20131024.1/tools/lcov-cleanup.py0000755000015700001700000000145412232220161023026 0ustar pbuserpbgroup00000000000000#!/usr/bin/python from re import MULTILINE, escape, finditer, findall, search, sub from sys import argv if len(argv) not in (2, 3): raise SystemExit("Usage: %s INPUT [OUTPUT]" % argv[0]) # Read lcov report in one go. report = file(argv[1]).read() # Ignore deleting destructor if the base or the complete object destructor # was called. There should be switch in lcov to filter them automatically. for match in finditer(r'^FNDA:0,(\w+)D0Ev$', report, MULTILINE): dtor_name = match.group(1) pattern = r'^FNDA:[1-9][0-9]*,%sD[12]Ev$' % escape(dtor_name) if search(pattern, report, MULTILINE): pattern = r'^FN(?:DA)?:[0-9]+,%sD0Ev\n' % escape(dtor_name) report = sub(pattern, '', report, flags=MULTILINE) # Write patched lcov report in one go. file(argv[-1], 'w').write(report) mediascanner-0.3.93+14.04.20131024.1/COPYING.GPL0000644000015700001700000010451312232220161020375 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 . mediascanner-0.3.93+14.04.20131024.1/mediascanner.conf0000644000015700001700000000022112232220161022210 0ustar pbuserpbgroup00000000000000description "Media Scanner" author "James Henstridge " start on started dbus respawn exec mediascanner-service mediascanner-0.3.93+14.04.20131024.1/COPYING.LGPL0000644000015700001700000001674312232220161020520 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. mediascanner-0.3.93+14.04.20131024.1/CMakeLists.txt0000644000015700001700000002410712232220161021461 0ustar pbuserpbgroup00000000000000cmake_minimum_required(VERSION 2.8) project(mediascanner) set(MEDIASCANNER_VERSION "0.3.93") set(MEDIASCANNER_API_VERSION "1.0") set(MEDIASCANNER_ABI "1") set(MEDIASCANNER_GETTEXT_DOMAIN "mediascanner") set(MEDIASCANNER_SETTINGS_ID "com.canonical.mediascanner") set(MEDIASCANNER_SETTINGS_PATH "/com/canonical/mediascanner/") set(MEDIASCANNER_SETTINGS_FILE "${CMAKE_BINARY_DIR}/mediascanner.gschema.xml") set(MEDIASCANNER_PKGCONFIG_FILE "${CMAKE_BINARY_DIR}/mediascanner-${MEDIASCANNER_API_VERSION}.pc") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") add_definitions(-DGETTEXT_DOMAIN="${MEDIASCANNER_GETTEXT_DOMAIN}" -DMEDIASCANNER_SETTINGS_ID="${MEDIASCANNER_SETTINGS_ID}") # ============================================================================= # Create and install pkg-config file # ============================================================================= configure_file(mediascanner.pc.in ${MEDIASCANNER_PKGCONFIG_FILE}) install(FILES ${MEDIASCANNER_PKGCONFIG_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) # ============================================================================= # Coverage build type # ============================================================================= set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} --coverage" ) set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage" ) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if(CMAKE_BUILD_TYPE_LOWER STREQUAL "coverage") message(STATUS "Building for coverage testing") include(TestCXXAcceptsFlag) unset(coverage_flags_accepted) check_cxx_accepts_flag(--coverage coverage_flags_accepted) if(NOT coverage_flags_accepted) message(FATAL_ERROR "Compiler doesn't accept all required coverage flags") endif(NOT coverage_flags_accepted) find_program(LCOV lcov DOC "Path to lcov, the graphical GCOV front-end") find_program(GCOV gcov DOC "Path to gcov, the GNU coverage testing tool") find_program(GENHTML genhtml DOC "Path to genhtml, which generates a HTML view from LCOV coverage data files") if(${LCOV} STREQUAL LCOV-NOTFOUND OR ${GCOV} STREQUAL GCOV-NOTFOUND OR ${GENHTML} STREQUAL GENHTML-NOTFOUND) set(LCOV_FOUND OFF) else(${LCOV} STREQUAL LCOV-NOTFOUND OR ${GCOV} STREQUAL GCOV-NOTFOUND OR ${GENHTML} STREQUAL GENHTML-NOTFOUND) set(LCOV_FOUND ON) endif(${LCOV} STREQUAL LCOV-NOTFOUND OR ${GCOV} STREQUAL GCOV-NOTFOUND OR ${GENHTML} STREQUAL GENHTML-NOTFOUND) set(LCOV_FLAGS --gcov-tool=${GCOV} CACHE STRING "Flags which get passed to lcov") find_program(GCOVR gcovr DOC "Path to gcovr, which generates Cobertura reports from GCOV") if(${GCOVR} STREQUAL GCOVR-NOTFOUND) set(GCOVR_FOUND OFF) else(${GCOVR} STREQUAL GCOVR-NOTFOUND) set(GCOVR_FOUND ON) endif(${GCOVR} STREQUAL GCOVR-NOTFOUND) endif(CMAKE_BUILD_TYPE_LOWER STREQUAL "coverage") # ============================================================================= # Check supported compiler warnings and enable them # ============================================================================= include(EnableCompilerWarnings) enable_compiler_warnings() # ============================================================================= # Pull GNU standard installation directories # ============================================================================= include(GNUInstallDirs) # ============================================================================= # Permit persistent extension of pkg-config's search path # ============================================================================= set(PKG_CONFIG_PATH "" CACHE STRING "Additional pkg-config search path") if(PKG_CONFIG_PATH) set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}") endif(PKG_CONFIG_PATH) # ============================================================================= # Search required packages via pkg-config # ============================================================================= find_package(PkgConfig REQUIRED) pkg_check_modules(DEPS REQUIRED gio-2.0>=2.18 gio-unix-2.0>=2.18 grilo-0.2>=0.2.1 gstreamer-pbutils-1.0>=0.11.93 gudev-1.0>=175 liblucene++>=3.0.3.4 libsoup-2.4 ) # http://public.kitware.com/Bug/view.php?id=14381 if(NOT DEPS_FOUND) message(FATAL_ERROR "Required dependencies not found.") endif() add_definitions(-DGST_USE_UNSTABLE_API=1) execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} glib-2.0 --variable=glib_mkenums OUTPUT_VARIABLE GLIB_MKENUMS OUTPUT_STRIP_TRAILING_WHITESPACE) set(GLIB_MKENUMS "${GLIB_MKENUMS}" CACHE PATH "Path of the glib-mkenums tool") execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable=glib_compile_schemas OUTPUT_VARIABLE GLIB_COMPILE_SCHEMAS OUTPUT_STRIP_TRAILING_WHITESPACE) set(GLIB_COMPILE_SCHEMAS "${GLIB_COMPILE_SCHEMAS}" CACHE PATH "Path of the glib-compile-schemas tool") execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable=prefix OUTPUT_VARIABLE GLIB_SCHEMAS_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE) set(GLIB_SCHEMAS_PREFIX "${GLIB_SCHEMAS_PREFIX}" CACHE PATH "Installation location of glib schemas library") execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} grilo-0.2 --variable=plugindir OUTPUT_VARIABLE GRILO_PLUGIN_DIR_DEF OUTPUT_STRIP_TRAILING_WHITESPACE) set(GRILO_PLUGIN_DIR "${GRILO_PLUGIN_DIR_DEF}" CACHE PATH "Installation location of Grilo plugins") execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} gudev-1.0 --modversion OUTPUT_VARIABLE GUDEV_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) # ============================================================================= # Configure API key for "The Movie Database" API Key # ============================================================================= set(TMDB_APIKEY "" CACHE STRING "API key for \"The Movie Database\"") if(TMDB_APIKEY) set(TMDB_PLUGIN_CONFIG "(\"grl-tmdb\", \"grl-tmdb\", {\"api-key\": \"${TMDB_APIKEY}\"})") else(TMDB_APIKEY) set(TMDB_PLUGIN_CONFIG "") endif(TMDB_APIKEY) # ============================================================================= # Validate and install gsettings schema # ============================================================================= option(GLIB_SCHEMAS_COMPILE "Compile GSettings Schemas after installation" ON) configure_file(mediascanner.gschema.xml.in ${MEDIASCANNER_SETTINGS_FILE}) set(GLIB_SCHEMAS_DIR "${GLIB_SCHEMAS_PREFIX}/share/glib-2.0/schemas/" CACHE PATH "Installation location of glib schemas") if(${GLIB_COMPILE_SCHEMAS} STREQUAL GLIB_COMPILE_SCHEMAS-NOTFOUND) message(FATAL_ERROR "Cannot find glib-compile-schemas in path") endif(${GLIB_COMPILE_SCHEMAS} STREQUAL GLIB_COMPILE_SCHEMAS-NOTFOUND) add_custom_command(OUTPUT mediascanner.gschema.valid DEPENDS ${MEDIASCANNER_SETTINGS_FILE} COMMAND ${GLIB_COMPILE_SCHEMAS} --dry-run --strict --schema-file=mediascanner.gschema.xml COMMAND touch mediascanner.gschema.valid) add_custom_target(checksettings ALL DEPENDS mediascanner.gschema.valid) install(FILES ${MEDIASCANNER_SETTINGS_FILE} DESTINATION ${GLIB_SCHEMAS_DIR}) if(GLIB_SCHEMAS_COMPILE) install(CODE "message(STATUS \"Compiling GSettings schemas at \$ENV{DESTDIR}${GLIB_SCHEMAS_DIR}\")") install(CODE "execute_process(COMMAND ${GLIB_COMPILE_SCHEMAS} --strict \$ENV{DESTDIR}${GLIB_SCHEMAS_DIR})") endif(GLIB_SCHEMAS_COMPILE) message(STATUS "Using ${GLIB_COMPILE_SCHEMAS} to compile GSettings schemas") message(STATUS "GSettings schemas will be installed into ${GLIB_SCHEMAS_DIR}") # ============================================================================= # Install Upstart session job # ============================================================================= install( FILES mediascanner.conf DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions ) # ============================================================================= # Checking for required Boost libraries # ============================================================================= find_package(Boost 1.50 COMPONENTS date_time filesystem locale regex system REQUIRED) # ============================================================================= # Check that Lucene++ has the correct build configuration # ============================================================================= include(CheckLucenePlusPlus) check_lucene_plusplus() # ============================================================================= # Check for Google Tests # ============================================================================= include(FindGoogleTests) find_google_tests() # ============================================================================= install(FILES README.md DESTINATION ${CMAKE_INSTALL_DOCDIR}) # ============================================================================= # Include subdirectories # ============================================================================= add_subdirectory(src/mediascanner) add_subdirectory(src/mediascanner-service) add_subdirectory(src/grlmediascanner) add_subdirectory(${GTEST_ROOT_DIR} tests/gtest) add_subdirectory(tests) add_subdirectory(examples) add_subdirectory(docs) # ============================================================================= # Enable quality measures # ============================================================================= enable_testing() add_custom_target(check COMMAND ${CMAKE_BUILD_TOOL} test) add_custom_target(check-headless COMMAND ${CMAKE_BUILD_TOOL} test) add_test(licensecheck ${CMAKE_CURRENT_SOURCE_DIR}/tools/licensecheck.py) set_property(TEST licensecheck PROPERTY WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) mediascanner-0.3.93+14.04.20131024.1/mediascanner.pc.in0000644000015700001700000000062512232220161022302 0ustar pbuserpbgroup00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_BINDIR@ libdir=@CMAKE_INSTALL_LIBDIR@ includedir=@CMAKE_INSTALL_INCLUDEDIR@/mediascanner lib=mediascanner-@MEDIASCANNER_API_VERSION@ Name: mediascanner Description: Access library for the media scanner's index Version: @MEDIASCANNER_VERSION@ Libs: -L@CMAKE_INSTALL_LIBDIR@ -l${lib} Cflags: -I${includedir}/mediascanner-@MEDIASCANNER_API_VERSION@ mediascanner-0.3.93+14.04.20131024.1/README.md0000644000015700001700000000276112232220161020202 0ustar pbuserpbgroup00000000000000# Summary TODO: Describe the module. ## Build Dependencies * Lucene++ (TODO: Mention our package) TODO: Later this must maybe be built with this line commented out in its include/Config.h: #define LPP_USE_ALLOCATOR * Various parts of boost. * Google C++ Testing Framework (googletest / gtest). Ubuntu has this in the libgtest-dev package. # Building cd mediascanner mkdir builddir cd builddir cmake .. (or cmake -DCMAKE_INSTALL_PREFIX:PATH=yourprefix ..) make make install Developers might want to configure cmake like this: cmake -DENABLE_WARNINGS=ON -DFATAL_WARNINGS=ON -DTMDB_APIKEY="TDB:REPLACE-DUMMY-KEY" . For real deployment you most get a real API key from http://themoviedb.org/. # Coverage Reports This project provides coverage reports for its unit tests. To enable them configure the project with the "coverage" build type: cmake -DCMAKE_BUILD_TYPE=coverage At this point we support [lcov] and [gcovr] reports. They can be generated by running `make coverage` from the build directory. The HTML documents from [lcov] provide an easy to read overview on code coverage of the test suite. They are written to `docs/coverage/index.html`. The [gcovr] tool is used to generate coverage reports in Coverage's XML format. They can be used for coverage reports and coverage tracking in Jenkins. The [gcovr] reports are written to `gcovr.xml`. [lcov]: http://ltp.sourceforge.net/coverage/lcov.php [gcovr]: https://software.sandia.gov/trac/fast/wiki/gcovr mediascanner-0.3.93+14.04.20131024.1/docs/0000755000015700001700000000000012232220310017641 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/docs/namespaces.dox0000644000015700001700000000066412232220161022506 0ustar pbuserpbgroup00000000000000/** @namespace mediascanner @brief Public components of the Ubuntu TV Media Scanner. @namespace mediascanner::logging @brief The logging system used by the Ubuntu TV Media Scanner. @namespace mediascanner::internal @brief Internal components of the Ubuntu TV Media Scanner. @namespace mediascanner::schema @brief %Property definitions of the Ubuntu TV Media Scanner. @namespace Lucene @brief Forward declarations for Lucene++ */ mediascanner-0.3.93+14.04.20131024.1/docs/gschema_xml_to_dox.xsl0000644000015700001700000000305512232220161024243 0ustar pbuserpbgroup00000000000000 /** @page gsettings-schema GSettings Schema These settings may be used to control the mediascanner-service. This list is also available on the command-line via: <pre>gsettings list-keys com.canonical.mediascanner</pre> @note Although it might seem natural to use Grilo's configuration infrastructure to share settings between the scanner and the media store plugin, GrlConfig is based on plain key-value files, doesn't provide any change notifications and does not yet store settings between sessions. As of this writing, integration of GrlConfig with GSettings or DConf is not planned until a later version of Grilo. Therefore, we use GSettings to maintain settings. */ - @anchor gsettings-schema- <b></b> - Type: - Summary: - Description: - Default: mediascanner-0.3.93+14.04.20131024.1/docs/developer/0000755000015700001700000000000012232220310021626 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/docs/developer/Doxyfile.in0000644000015700001700000023022412232220161023750 0ustar pbuserpbgroup00000000000000# Doxyfile 1.8.1.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = "Ubuntu TV Media Scanner" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "A centralized index for removable media content." # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = @CMAKE_BINARY_DIR@/docs/developer # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@/src # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = YES # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you # can mix doxygen, HTML, and XML commands with Markdown formatting. # Disable only in case of backward compatibilities issues. MARKDOWN_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data fields will be shown inline in the documentation # of the scope in which they are defined (i.e. file, namespace, or group # documentation), provided this scope is documented. If set to NO (the default), # structs, classes, and unions are shown on a separate page (for HTML and Man # pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = YES # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 # Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be # set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given # their name and scope. Since this can be an expensive process and often the # same symbol appear multiple times in the code, doxygen keeps a cache of # pre-resolved symbols. If the cache is too small doxygen will become slower. # If the cache is too large, memory is wasted. The cache size is given by this # formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = YES # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this # feature you need bibtex and perl available in the search path. CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = doxygen-developer.log #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = @CMAKE_SOURCE_DIR@/src \ @CMAKE_SOURCE_DIR@/docs/ \ @CMAKE_BINARY_DIR@/docs/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = *.c \ *.cpp \ *.h \ *.dox # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = YES # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = Lucene* # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 25 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 220 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. HTML_DYNAMIC_SECTIONS = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of # entries shown in the various tree structured indices initially; the user # can expand and collapse entries dynamically later on. Doxygen will expand # the tree to such a level that at most the specified number of entries are # visible (unless a fully collapsed tree already exceeds this amount). # So setting the number of entries 1 will produce a full collapsed tree by # default. 0 is a special value representing an infinite number of entries # and will result in a full expanded tree by default. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = YES # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = net.launchpad.MediaScanner # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = @QHELPGENERATOR_PATH@ # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you may also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to # the MathJax Content Delivery Network so you can quickly see the result without # installing MathJax. However, it is strongly recommended to install a local # copy of MathJax from http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. For each # tag file the location of the external documentation should be added. The # format of a tag file without this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths # or URLs. Note that each tag file must have a unique name (where the name does # NOT include the path). If a tag file is not located in the directory in which # doxygen is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. You need to make sure dot is able to find # the font, which can be done by putting it in a standard location or by setting # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If the UML_LOOK tag is enabled, the fields and methods are shown inside # the class node. If there are many fields or methods and many nodes the # graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS # threshold limits the number of items for each type to make the size more # managable. Set this to 0 for no limit. Note that the threshold may be # exceeded by 50% before the limit is enforced. UML_LIMIT_NUM_FIELDS = 10 # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. If you choose svg you need to set # HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # Note that this requires a modern browser other than Internet Explorer. # Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you # need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible. Older versions of IE do not have SVG support. INTERACTIVE_SVG = NO # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = @DOXYGEN_DOT_PATH@ # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES mediascanner-0.3.93+14.04.20131024.1/docs/images/0000755000015700001700000000000012232220310021106 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/docs/images/media-scanner.png0000644000015700001700000046722012232220161024341 0ustar pbuserpbgroup00000000000000‰PNG  IHDR°Q ûâ§ pHYsÄÄ•+nBIDATxÚì] uHw9*H:*’Pé.¢›J]¤ûß}ê¾oEÊ«(Q9×ÿyûÖ³ÖZ‹uÕ|?zÍ›73oö}ßw®7ó>Ÿj3Ð#@" …Ո͛7›˜˜8CCÃzõꕸwïÞˆB™R· É*22²Q£FE¼øHþ ÌÌ̇ ‹Å*žÂµk×àñÑ£G ,@Ê?"¼Tß¾}{ñâEcccèþ|x¾GL*dÈÖÆæ`D°-ÎVÈëÜ,6ïIà ¿p–×Tìöo~°£-½aÇüoOÄèìÔ©Ra‰ P(­[·~=w372°…y—™IŠë•óŸLÊc¨-¶+Oñ^ÿ¬™iJj]r¶«qË”žàÕ«W¡ƒà¢ÉøÍ¬ñ¸ã €‚ç6J@pBú@˜K")G9ª 5H bhØ ŒþÉì„a×˜Ì¶Ž¬ÇÞ½ëwò~òȯcI1„}\Á}!ðèÑŸ ÛNp[F°}€e íl`!•?2A¢,ŽgÙ }µMoŸµ³'»Û´‰íâG Ò*Š0}qþ^½zÕ¶m[èÈËË4hÐ?¤B {$8~&NIþš4iòùóg‰Q4òîòÕOz²g= ÉÛ”î(½jzñâ©BQ¬qÝÌÚSµC 8(«©ÖC¡ÃÎû‚e±À:t077GÍðñãÇfÍšáuR üAtµ€Eý=ÿp•¥§F5Ã=JkUh@1W/@íê’L†7›G½ô‡ï¬ ¹d®SÂÜØÎ§i!#òÅ¢WY¡„(” S:0‘9Uà¶Æ¸øæäÀ㈺`„@‹Øâ\¼-9‚W<:ÉŸh‰Jbx3æÎX×!ØeGf›yØë DaÍ…ÄõÔG .;ãÇþ`W`¥¯oé)**û,q/SŽ;†a˜²²ò·oß&NœøåË—’Bþúõ«nÝ"Ý*ØŠÖ××ÿ×[¤¢X¾|ùÊ•+‹ûûßÇ´¡>²$ó.®[¾dq©!aï9«_¿><544ìÓ§ØÈËçøM Z@Ç™3g†*–ÂgäÞØ©Å’üyzz®Y#l¾L²R™Ò¡X\*y¤;“—9Ën–µíT###üœóÕf²+"$hç}=ÅÈ=;6º7jÒò÷2dl™¬¿h¿ÛÌͬ°ÂîÇ­[·j…² ãX™<@òÓñ 9Hí‚Rò3ޱ_±èF>Ü-ÕÔÀãà·ñNðb–Õ!,v“ïƒIOyàÀ–––;wv †Ð²9| q ¨uç:„n«þ…ÜÁN©ç~<|X‘îc=jÏ[•ƒxˆ‚”¢1†a>oLíÀyj[0Ú# s$.\¸$ °Aþý¬tÜc5‹uq…ÓËTp¿Ù–wW³×XR(`^ä‡9ÖÎÜ!155uܸqµ¸.är¹4­¤G/7\Û z»ÊÜ«kéýôU«V™ššÂ QÎñ‹ÝæmÆâ*´šEŽx²ñ‘5[ëà/~þzõê ‰ÚÝ"ÍÊÊRUU-s¬›ŽæN>Ű¼g¾ŒÖÌ6òâ<Û–BÉ"W¹g~éÒ¥¢§îIIPšŠŠŠvvv°…Âf³E¯9r¸’†C+‘BNÒ.=óŸß\¢ª·ö8—šUGÕTLn[„°âüÍ;7(¨”nœªÅö§Øvè`´Ç[˜qž5¢Äž,$²³³ÉFYñôéSxlß¾}­iΔ‰? ˜Ù°hÑ¢õë×WüÖ»¯¼ý¦ÖŠÚËY–ÀA¯dj”BhkkÃã?Ê—+&“Y£ RѺ=99¹|qÅøƒ¯m‡Ê‘ä¯Ì’]³a¹§›Œyéq)êf³G[ÅÆÆZYYEÇÆŽ…Ç˜0«qcŒó>³ÝâÒÏäŸÓà;ïév ÜäÜ‚k…eåÏÇwyÌÎ=Oþ;’µsOʵ#‰‰m]]…]:ø(á-kš¢£3ÅA Êç¹U6*Åù#Ó|øðaáÅ—˜6ºÖ³½Û³ÀÏypN”™ëq"6häx7¨Éýsy€ªúòÎI(¾õþ,ê¢^Ó),8”;Çcø“õ(ê ùóòòò÷÷/G’°b+‰E‰PysR«ëÙÃZñ'v'îºú‘ÿ—ã_Ö˜¼WkÞk®•ЃE.lŽ¯ŠŠTàq!íÄ‘}0|ßÓS@—ã5— ŽPKP²·eiž¢,üAÄî´'¼Ž fõêâÍ"‘ªÎX0ë*p pf~gŸJäOí´*äÏÇÇÇW–¹•s÷ôôt‹ëÅò¥pÿþýQ£FéèèT…Ï®y·äRK¡M}2|YZRÜc—hcìý*žË{÷îÁžu•ñרQ£¯_¿ŠyjjjŠÎF,ÇKP *Ìÿ´”®Ú”?õ1ý¹B‚uWU$—????oo瑱°85Ò(Ü©¸ÈŽ]ñ{¼~÷­nEãïöíÛUù5€À÷ïß÷îÝÛ¥K—–-[ÊëãÇ7nܘ2eJ“&MªšÂEC÷Ÿ‚ñŽë63¬ËÜfYÆX:2:ÚÔ—¾«˜¿—/_ª©á}»‰'–5n3x<^RRR~~~™è—[sfF/Áe™°t¤üeå5€¥#W€âþþüÍœ¬¬,È ›ÍVTT¤Óé***°â¬S§Nõ¤5†††Uy»† ~ûö<åeÊVjªFÅÝ+'X=Ȱ箮.Þm·²ÂÇÞÎ-´²1öߥÐÿú/ ,am›}ÄÛһ휑vÜf{!sïóAKzuSx"Fñƒ'{S2P1 Ùšš.H Ùcç9?ñ×U]›RþJE›…q R|ÜË’(”; ðx(ö é3iNÝNüèleË ¶Çùƒ§ÍA쮩À?ÀŎИøÄÃÊà¯Ì¶¥ƒ¶ë Júý¾ÈRÀžÎÜ›ˆŠfSÊ 59ÿ“tL_11vùsP@[[»yóæ5bt¦Ä"E`1xÄø™¢è%m9¢²|C700ˆ—p¡k•ÎTl.@ `«áˆŒŒµ‹B==½¤¤$ò´òÆþ…•‚¿1cÆ;v 1'…ƉéwÛkªþ©èMŒ+Ã0Ñ|ˆ¿²©P³Û3Ò}2Ê»®RZ¯g¥Gy•fyû…úŒ9rÉ_zz:ÁŸ[AˆBY1b‚„ïïÅ?´…ò[Ī©©I8•U0 2²EN}ÐÕÕ•b’Aë`årêâ¯êZ¤qqq¤MÕrƒÃá((àY>|ø©S§=UJaÅù»ÿ~׮‘M1þú3™ohõvÌï3|áV|²­À² ±WIýÂÜÜ\%%¥rD„üÍ;·˜6“ÇÞLò8úÑ/êlf ì}ÔñY“FˆGäÉ“Â7ëÌ[/¾Í4™‹Ý² úd²Ð@²Y‰èÞ½û;w·þð<6vjœÞã–ûâ ò§p‹Ù¡°þ_x½yôÌůvÉòWîuhr£0Ô¢pñ&ÂiÍX:5«.,ÒÍ/¿êšKˆ(”ÕOa>;—¾[§Js*³E7Daé!RfÈ0T”[åÍÌWÚš@Á–dí}F›Ö‘㚎E¨5oÃö¥Ð£ÚeŸŽR%¬:GìUó×Ìq¨C ¹£8ºOÞ"³|&µ„t‰-‘r 0DCúulY¶àÿ,bá.ä·ê­ÛDàÓ¦ ¬rÛMËL¾ä 6”Á`wi;» 峫Ը>Åì3ó(€Ê'ŽñyÀ „ÉÖÎ3²%•O¡ó¯µ~KÝëm+ézCÞ†57§l@á%ñÀÑíjÒÁ¹e87ãv¨qÊ:þ›â¡Í'Ç P]Ô‰G«Ç ½ ¸Bœ7Ì^¦SbK'(cÖÜ¥ëgrP¡»÷*ð%Ÿ/NøMþØ•w^ƒö›¼ê¯ i†zTàæœINFƒüÁ#äìy€-ÊÎÕ/H'½Q:·nœÂ(ÃÍZÂ÷™‹è’{Aj<Ò˸¼q‡ vúôiéa4Š(ª™ýBˆRùC¨é" …ˆBÙµk4…°8¾Nl3º´‚ümuÊ·£h·îœNm&lÑÓÓCTÉ›ÂJÜëÉC8xÆ¥2רȸíí×µ+à´Ð’îem¿~ã\g¥ðÈ:‡qªå± § Ë΀N)‰Úï»7ñFc¤¥°|üA$9 IQù2J¡Ò R(‚Ò*6æN:6…—GãQøÍvÒßô§-ˆVxmš=ú…Š›Sö U*¿Í³ïô…372εW0êŸC¥ð˜ë•°E¹RRæÍüJE¤É¡.eœmõ(‰×±YøN ŒBwþîE‚“^¸þþ+ªBlaÞ¢ûœõœQ)ü¥tÓé4ñ'ϩœ4CîÚµkæÌ™RœiÛv}Ø+é‰è ®ª±_8kÖ,é¾~ý1Q³(Ýa«k×®RúõõêÕKKK#X[[ÇÄÄ Jª™BbŸ Ѷîß—f3 òG8´µµ¿nîS¨ö½¬þ] áÓ/ßæ$b›EH´±Œ O Eçkoß¾ÝÑѱ‚{Гü7îÈ‘#$°°­¤-ÿ]  pñâEÑùÚ?9æ òG8–,Y²zõjQþ4hššŠÈ«(…?‰;äÈ?±{÷î3fÁޏäÕoß¾5lØQ(+Ä>U¢€üÿnõóWf «ý³ŸDÙˆÂ"ع³’z÷²†Œ~"ÿ6K]¥öýZ?E*D@"Ô }}™>>˜˜ûÆU³>è]¨F†¶€Gä,öì1QŒ]yÁe˜îæÓ_IJøi·NÓ)„ûæ<ÝÈ´ìàˆ ÜŸ ²ò@uDaɘè$Ü®  }wEàçtxP6Ø"÷ÌQšeΟ¯¼9+﹦Ûöüà @宦­›¢mt;ïþ‚w÷sñæÏùÙºM6~¡Ñ?›1w²JðÑtD¡¼Ô?ö–îáó–û=˜¦íB§#uAÇ™˜»ü—?m®¶a´K.XJ|7éeí6é!𼃴ó•|4©P2ZNL V}’üùÌ´@¸¿§BEßÔku‹ñ‡:r¢ðë×JÚlÕGÆpÏN¬¨ŒÛ÷óùg(ôñ©æßZí@)¢Qˆ(D@"T1…hý-R!¢Qˆ(D(?"##5j™™™]»v]¢%¶2äåË—ÎÎΈB™´ÅHž\†²-Þ¿v횘ÏW øƒPSS[b'¶²ÇÈȈH¡wïÞˆBixsÄ­u™ø`³ÿâù^Òld®Y³¦{÷î åÙÆ©k° š)€{Xn“mX¬0x‡7¯ï2(4±q?Å ì5oöÜ™¦ê{.§§´™Ïò6M-**j„ ÿ …ß.7ìO|^–iƒ|®÷¶:ä)3@ (r°EyÌU¸éô šñ.:¶8ú'-ÉÒ[-4.=_k'ë$¦6vìXr/£ö ÃYEðXBñó…;²ÖŽ€Ç-;÷Ãã¦2)÷éç•Ia¹Ì‰–o®9“ ØëÁl|dMû”±gÕ¶÷4\pC4”ººúŸ?Â}Na7ÈÁ\­½Ä)iZƒphjp±Å\èè×:_AÄð¡-ñs5Ñc~ñâEZZ±á¹X›éÛ,Û¾oa6.8xû8Û…7²4¿Åx»º¯êÞQÚâÈ-ZüCéáá¸a"¨¾Û>áªá׃´þÈ¡HþÌõÚBöJOöÀ˜"“k¨ÓßK lll ë°âüá‚Û3‡p˜››Ãã(\ŒûKJGîU`­¬ ûMÜyíš-á–¶³—ó¯Ä{1ûÏ–>¿+ºÅÄ¥¥}ú°äÝXøöíÛUFX­¤ðÊ•+}ûö•Ø&, ÍM­}L­+#3666ÕEUM¡ð FmÍÄM̬¹ ä9P¦eJÕG!a7oI¶óiZÛÆ`õÆõ™¹-pî¾Th•ÌPÑâ¹¥1ÂŒ²¿\Tá6ä,§(ØM7ïõæÍ›Ö­[#ªªBhû…ŒÀÅçb’OxGÏ'©Êǧ¢v+Ñ0Á_~~>^â&5œÄ…æÎˆÂ ’?ÂTøÏ(ÊŸÿöCÜ”w¥¦ih1bRÿNeÊÆªU«ttt~ÿþ­§§§¢¢"ÚRMHHÐ××—±YôÏQ(Ú-#ùKMMmРAñÀþ÷èáiÇbEØLÛÂÚ;O40Ñ{÷ît[X'…¿ÏŸ?7iÒDÔ“àïçÏŸZZZEø+#üïä{u/±ˆÞ´iS§Nþ>|5!φw¡Ó¶3"XkGø„±’VØL[4£Î ´ØÞÆfÝH§àý¹÷Åž×ÓÕ1ïÒQl,¢ö¨P®¶_!7oÞ´°°ó'ù£P(ššš Ïÿ$N¿æ%°Ãšéƒ „Z3ÕÕíöß=¡øðÛx§“ÿ°‚ÚZ20AOOÏ!C†>ã!ðÿí9Py‚¸´ZÍDtðýF(w ŽÂÑ*²\ÿ{TØ´iÓOŸ>ÉòGX'%‡‘*l$èúüu\‹È»´t]TR‚QQQ⣚¼Ÿ¿–柛”º“mæígá«ò¸ßÏÏ;gl¯Ïûqá™Ê@õã®-ì¶ GüͱÄÄÄ©S§þmÊŸ“yÃšŽ‹ýtÄqÆŒ»wï.)$¬ØÊT–ªÓÁ<ÏERL˜0áÎ;ÉÉÉPîÂm©á×ÅË‚?\ÉÚƒƒíë÷ÂËÍþäåå 4¨’Z45º.²ïåóÍû°Ç+Ž}Æ5ôéˆ277¿}û¶å‹îÝ»‹žÞ¸qãáÇþüÑÖÖ†EB1™2ž?^A+fUM!³…öÊ‚Æd2{¶Ò¾ñö»ã ¶ÞaÈ¥î«=wîÜ   âþç¦)k¡C§è§ÈŸìůÜÑSQ™VMç¡)lÑ[§)³é'ücB§ìL†OLÓ,2-…¿×¯_·iÓF"¥¿°–’’‚Fgä@áõ½k€ÃNŠú»ººnݺUz\Èßüùó7oÞ\ŽûBþ`ôª±(Û¡C‡ &òþýûþýûצº°Tþˆñ÷âÅ ccYwE„ü1âäÉ“bþÓOï/ýîÔº ½çÏ.5˜¯¯ïðáÃ#""&Mš$lÑ”°q £[ZZ¤ä¾úNþVÐ,äoÑ¢Eëׯ—1<äÏËËËßß¿hï^“6T¦'OÓÿ­[¯Ném«!CÒÓËoHòWs R1T„¿§OŸÂòJvþ„4øû?~|ôèѸb7Ñú-(Sô½ u¼dÛ¶èuDžߢ>ûOÙ”¾‰¸sKò/r­¬¬ˆ«Í( ðD¬•Utll® ôéÓçêÕ«e,_Ò’¸õFê1§yx¸º:D|Q±ëÐÁÓÓsÍš5e½;ÁŸÁV|ñ«¡ö 5F8)±úŒŠ~(ZeJ™B¡@•ñëÁš÷ëÊC ÙwUl_`5ÆägY·ÿ”Ãt¢â„,6Õ oÚâË`xçÊÝ3Ln–™?Øi<Ãâî$cÖs2­µÁ; ãïñãÇ&&&2&óÙ‡«yâó‹œlŒ•bœ Ã0ÑId5+0aãknDì\pÕÝj#·³Ç©Ÿ_ß1ÞoÐhxl0>Þ¼ÿ<¼ú²ø:dM¯Ô§ûÕh £££GU¾¸?xT¢€sÁö€Û(ÈŸXUWÊ8Nû×bžð™eÏ9Pœ?‰ÐÒVQâַͼjoÉÀçe ¾#@ °^d-è¨jÛKsû+Sº•ÕB¾ÅƲj4…ááá妰T<|ø°sçβóG`™uÇJ}díGÆÿn©AQhÔP?AK·žã>¼ò‹UŠRxâĉÊËä6ÁÉr¬jêää$êCtvîÜ ëÚ:uê”#Íììì—/_:::Â_T îÝî3Í*¿I_¾¯ÐoäÊ æOŒ¿{÷šV*…bü‘˜%»ecI“«B ¿às,§Y•3é~]9D Ͼöjßyˆ\² ùóóóóöö®TKšZÛØ>»šš|*Íݰg,¯'[Ùü‚ÙÙÅqøðá?þXXXH›ˆ\ùùù7oÞ¤Óé¶¶¶Õ¡ÂŸ‡þÑYáæÇ‘½º(–)Ý÷)ò>4Õx‹Ÿ¨t®Ô'[Ù€ºŸe”ûÐt‰«›_äÌ­ñ»öˆY£§5ÿb½åxˆ¤,7ÈåãštÔäǽ2U0Dã?ŒÂÉŒƒÍ ÅòåÆªk³Ð7ÒÈ#æêYî]{GzR­ äû¤pºr_uò²µ•‰Uã‚®Eó¡JG4ÄU¸],zú Íž^ÕM¡€¿[Ùà¨' x~¼¶ VÄKÑŠ€Ë4 V6s=ueÀÉ Éã&þ„3BJûì@¥ðûu2”×3òòòzòä N—RÛr'BðçááQýiüxg»ûâB©å'séi?9}úôë×/b½¸ŠŠŠ–––®®®èÚ"åïÀ6¦É7[^æ¥ðתU«·oßJ|àUÙø‚]@ÙgúüC Y@Z A¨•.^¼xݺuˆ¼Ò(¤È±Dªè6$“&M:tèyŠø“ÂÆ›^|Ò5®RÁ¬?5i‘çª &Bð×µk×û÷ï#ÎÊP›/†…–pÈ#;eË–­ÎƒÏ©)Jð¦(î½fÕ·o¿æm„Ó&ɯ²GüU¨.d¨è¸/  ÔÓŒä“3r>¿¶¶ö÷ïßU•Òœ¹r劧§çÝ»w+#gä|~Ä_%RØ·o_Èßž={¦OŸ.¯ ‘³:vìøäÉÄPUt*äÈ™}„ø«†~¡ÄÙÐå®I\þ :¾Þ¥Õm&LœV°vd€' "Oþ–›¿nݺ‘jñ%m×V9ÎIÔz}àõ–'Ÿçul2~xO%µºˆ9yRÈÔšpÚòCêRU‚5?|k’—e¨…/Êœ°^ [–É PÁ–ä0W«†ŒÎÛÍ8³4Ó5 ‹P[mÉ^rVÑÕŒíÑ'Ÿ—­Ðd['aô@ƒBXY<U—¯ˆ³ Qølc÷öRgD›?@¼¸1á1›tlƒ/uÛª¶ N'wbá¿§‰ü‘P¥f‡­ïà¾ÑV~ ¦¬6å»Óã<`Â@G¿ôUŒˆ¿ŠR¨¬¡‹[•Z1wÒ1PàŸgó¦íÃݯ1ö˾]›—# ³•ÍÍo¶›¨@Ÿ05û —wî*cþ€¼Rn/W§ÿvs¦àQ®ó[2\ãfÚ³"eé,¡˜3„nÛ‚R´€?Ü튳õqfž`È´V­%ñ÷ ˜^úÙn®×FÄSeu*{¯®x×P Ú þjb¿ØÜE¨Å" …ˆBÙѳgÏ7n g])¼q*œú&¢«òEŠ´^ùIåÜQôUžáeö`Ä“œ)Üàê6Üi¢­Bnøé覵]J€dvlQó™’w‘?c£2ñg6Kª*4²ðø¢ê»Ïë)YyTþ +ÔÏ·U°¥Ù—Ù¼~ é7§ þä@¡›W ÖαW‹ çž²ß<\g–ߘüA˜ Èz¦Æ ò!eŒ'ÊÄG¨~ŠÁèp]Ѯ׌mˆ-y¤öîðJ~¦²mæ9¡ Ø5Â÷BDUo¿Púˆ6¼š˜˜Ø¬Y3DF ¢pÇŽ³gná!}D›¸gff†ø¨)BþJ¶H!¢ü™ššÞ»wqS¥N˜0!**Špggg«¨¨”‰?QEÂr•äH ‘T¹–z!™™™jjøôµŠ…¾Ë籂S‘ƒY«wsÌhާí] y£ŸHq©]\±ß·Ù •2õû¡.19Á‡»ŒÐÔ4ǦB ¡{ÞÑ/;Çê6VóŽ}A-HwDúÌðr_÷£¼­ ÙßÇs3`ôfçÓ}}™>>‰¹@±K ª¸¢úk…·ñHÂ!€p¯;ý©P’ g;àc%˜`°dŒÐÓi&3ÐØ?j‘" …8¾~­Œs}d G£ç=;±Bî·Õñ_¢Øz¸ºàµtR*H…ˆBD!¢ð_£Yž@@@å(!!BIxþü9†aÊÊÊæææt:]ôRFFÆ£Grrr6lXÒ~ÙH„rÑÍsÇ)î—ošëÒœ+bT öíÛÇf³ %^§P(-[¶”15¨@ XBWüFSSSËÌ̼víéùäÉ>ŸïèèX+–N!Ö<ÙhÞQéå8Eù§¼¸^ìÔ,'sL¤š5k–óxÍË&Ø»± lllÇûº4maù>Oë¿=~ÏÕ»7 /ä5¿ï³ªcÖòvýDm±Íw”ÍdOë í~k² T"‹µgædË…[GµÎ±æÍb…iÏFÇŽø~r7X‘Úà) "” ‰[Œ;*}&OglQ;› VÌÉš©k3·0ÕévÓŠ˜Ò"ö Y픵d[\ÔD—z•Í“`¯¼fÑV¯^ [‰/{’œ óÉâeàËîÿâ±öͳ]P0±'üÒk^ƒñ¸ŠnúM» ÃMO“~ð(¸BOþ/|ëíÉî'éáVݳ8àèZ×;‚sN*.øû™ kÉë’‰õçL&sÕªUK—.E"”BªuïɆÝÀèÿðwÙÃÞˆ^ÉŠsU5ÛZî„—/_®¯¯?uêTè;v¬­­í¨Q£šÓ?‹† Ä–eö¼I]qM7—ÅÆíð³i/¹CÕÈ=^Çeï.µi33Ǽ¡Ü9¦º¦oÒà*®Òwv†^n¹é9räúõë ÜÝÝ£££µµµ‹‡±ÝŠ««± …¨¬ ª¬fýš-³³Idôúðþª:Y¡‰9¼íµ±ü¤¯KG5Ñ«a„ëÒÓÓa×ÔÔT,d¡§§‡j¿/’ß´˜|fÑï937¹+4wò¸ý':|lÖØAýè/÷¿º@Qi›€aËIØûCÅㆇ‡'$$¬\¹RÔSôôèÑ£BWÑ=]uݰà ÁJ];‘]– K„{‚kÍžŒ"û1H¶8Ú¸,¿ýĉ¢]²²>ºÙá,ÏÙÛ®yÛôöÛx¿ ]Ã︘k}=°¢·+… ØûÕ4ú¼¾ø_Ë©ÿ9ƒŽÒúÚšš¤ïÝ»Çáp:uê4xð`40ó—C“>ì ŒÄi ¹z y/þ#¨B¼?týúõ]»v8p@4.QÝÉ ç_ŸŸi7©2~Ŷ_³æ,Y׸) 2Dô4%%åÛ·o©©©ŠŠ%öb›*Öž9̼ƒøÍ·Ní†~xE§CƒUœ7ÒpîÓѽô©×¯_öƒººzïÞ½k׋„D(O$''Oœ8ñæÍ›¢ž½¨`ÊMM†“J1y5GÞ êPÖX6lhÒ¤Iñˆùùù<°²²jÛ¶”-ŸjöþžM¢ZêU}šÆ‹)AF¸¹¹•tiÀ€÷oÿKDþÖý›âº3´Ý®9ßÜ.«ÕÌ(àèÒL×”ìÁp‡ú‰ÊkJ§/(ϺGKvË£pÌÜ™j€Ý´Ì‘/(Î2[!y¡0¿øíH+ãÆ;|ø0’!˜ªÍÚ9{ÚáîF"#ñc¸Ü ¼¦Ÿas°¸Ã¼èP>Ø`ÌÆÂqŽF@Â艒f½7lØv‡Ê”óÏç×õóºñ0l²ñØàOo®ê3™ñÖßÄÐýäË¡éèE"D(Ä8iÒ¤¦M›J‰²çwuìñÎvÓÃï_e2[@Ÿìk³®>~wâÇ?±º…Ëå;vìË—/™™™êêêL&v툆EEE™˜˜P©T2üÏŸ?\]]×®]Û©S'1£Ó?~d0°[ŽDø¾O4MÔÇÃÃCô4''ÇÝÝ=((HÔsÅOx{iŸàµû@ú¬O æwî#¥^³ÊÈ6ý1Ëc–°iüS¿‘VÅS#HL(ãÿ¹—Ä0]:ÕÆ„ï~g·ñ<>íc± óþûü¾Íǘ#{^.žŸ®bêbgÓ† ¼#X!ç±<¿9ø§yvB*M¿ óÎXÁ/ó´"ŽÝ}¹ÕókÖŽ·™OϱBæ•”Ÿ.]ºÀ,¡ikÿÂâ¼Ô!uB±¢§ .\¹r¥šš…BÑ_¾|é»zmÀ⢑ØÛlr܄ױ¡NBçÝ›Ýôa °õð<¸Æ) µfÂãÞ±ÌöKîuéT7tT«A¶cŸ`íÇÌŸN\º¶zd|½rïŠ)Ú³+òÊ•s°0ü˜·÷Þ…3½-‡-XzöÀBpçCæÝb/^kjÞ?x†Í°a[PHo† 35°£Ÿ’’"{ø§OŸ–ã.~/ò¶ÖÇ>^ãf|àjŽIáyûÜz»½çéÏìaM?s@w½‘v‚Pàˆ#Nž%¨½¼ÿÖzyºW0‡’*v±‘gK#`)¬ôð&©eá¥é3'Â?áøÊðaxû3¿ÜC0dc5 ?<îf±þüù3Õyþùóçau+½üü|Xû5iÒÄÞÞ^t‰Pn`öWYÂCÊ>|Ó¥+¾Ï[IžÒámÌðÆðZ…¦Ý’Àxêøíxç*›(¨@20¡ÀrÜEXÅQ©nÃÚÉÿwr¯<ú`·±0 ËË˃ÕZI‹å‰å‚ jЦò";7 ìØÈæôæwùVúôyzÌfu@Ðk,€·ùnÌ{_RÕgŸW:é$¾X{öìÙ;vì€B¥‚,DEm%Hô”;ˆ»”µ¢þ§ /À?õ“kŠÇ6 ŽýŒ<œëŽù†¸ Nb®‹2n­ O]5pÒ©HW„h ,+H±‰îò-ÑSî Ø´iÓOŸ>ýݯי3g’““ÓÒÒ7nÌd24hPÒ°M9•••ššúåËx 555˜>lóׯFiÍjŽž_úì{ØëСƒ”®HYAˆÃá(((H÷”/š››K4RëLnªLÂH€Jº£ªªjsHŸÏŸ?‹…½$Bùà÷ïßuêÔ‚‘ÌJybkÙ²åû÷ï¥xÊ„g̘±{÷n)Án&üºšVYöwä0è}`u÷ãÇRƒÁÎÞŸ?233!¡ðk3裢¢B£Ñˆõ‡ðH|.b³Ùø¸› Ý0$ôWj¾ °F•ň̪ + ¾°A(° @ˆMlšDO9‚P`hh¨““Sñ«þ¸¥•J´è±î þxu•s²ŸO®™³ëöÎ#'2 ?Ü1¨_¿¾ä8¼´§yõ:(ƒs ­†lŒ-ôÏŒ»‘ßÍ\“jeå»]$øyõM°š»ÔZÔ\þüùSKK‹"@ÕÜ1‰ôò‪³ëØ×÷Ž¢oѢŇÂ)¤E=åB7nÜèÙ³'üÕáááS¦L(P&bøl·fŽÓ”=êVΞ. \©ËVnøßáxu/óû󩬬œ]üRæÇ³ûÜùÎ9ã<æ—²²Š‰{Úã‹ çËÓXÍ|Õ¤/\Ó½Ìüb&¬¹âéÚ×g¢ŒƒïùleeåÜä'EØ Lb÷_š,y´l^‡± i„ØÄê@‰žòT ™’Âÿ‘@©/Óha:®C ›«Ð]ßþV¨u\Øód½¯Æçk {ÿ²æ677Öl/µ™A8¬ µQøÉáÐBcbpyöÕ¬IwT”pî„ùªXA˜abþĸ]l”G­îN×’-1BU¨@xÔŒ¥ ;(´ƒa•W™BBŠ­y󿉉‰R<åoK}¿È›”¥†s¤`*ÌxÜQ¦¹iôG]fÛŠyúùùy{{KX’Ea5nõÂæq}ÖÆZ¹]¢cc¶=òÓÔa•:³Œ|üÂÕÑ}ô¯Ç}\$=bê§Ë uö^×Ðmßüû«ëÁë\ ÞÐ9ÀXK”•·wŒ´kÒ€ÞÉ똵•u{:ÏÀzAÝ{›¬‚b‘˃+VìÙ³²{\ûB5„Øx<žèP¸DOyêp"®À£g/¿×îÃáËóM•r[²ãÍ;·]m%Ü×Û[__?!!¡‚w ì÷¦“ *Û0ÜV”yuôÀ>·ÄÓ”0ÄcÝX#®´‡ÿl6½`-06V´|ð1¯Ì…z‹‰Ö´Os¬PMXnúúúÖÆ½ÙH±ééé%%%Iñ”#ÆZö«„Ÿ¢$°–X" ß¼yÓºuëR²··/iYV§}åÎT`©abÖ-éRñ¯&ÿ„ó3ßœ=uðù{³vÜŽ­¸Zu¤iŒ-_¤ÙEæpÁó÷´¸ç4U%~·NMZw™Q£!6XŽˆŽIô¬½E@0ÓEƳ²²>þœ’’E›‘‘A|´‚¯>7:žŸŸs' `馬¬¬¢¢¢¡¡¡©©Y¯^=ØþWUU Ê/Âk'—viKQU×özÁ?®|òG&­¸ð(—%*ví~†»§O5>DRl¢“`$z"ÁGöÖ GQ~~¼¿¸“Þèª-I÷ÉyPœ—îrû ¬Þœb«%zÖR?~|ôèÑH6Õ/Âf óÀOÜHû¸‰cú¥ê|·6¤/´ÄÍ3÷ïPS¶§#ÅÖ¸qãääd)žµPÚÚÚß¿—xuõêÕ5ÊŠDttôüùóÿò>¡½Y$È »„j!£Õã¤6/5 hõ™¹Íòé(÷±ÔdÓ3þ|N¡~Àï1•FõrÛ5Mê {§¤À -kîÕ(¶Ú«@P111ÖÖÖbþ¾¾¾Ã‡'f®}ýúõÅ‹°ÞªU«'ÇȰ‡ùöíÛøøø¶mÛÛ¿ôìÙæÊÇÇço¡t4Õx ÿFŠÎôü)SDMø§ ÚkPËWÁÚoäÈ‘ð•ýËšOÅ(†FnY&”Ê -ø‡fþY¤dƒ»)€'Cw8ú(¦¼ŸÙh`ÞÔSª•OÉÊjZlì^à 4ê¬v&ä=Ø;s”•`§œ¬#Aãæ†éMß™´g1‘ †¾áè)·±ŒŽ¦¤_¥dîwسô'æÐI ÙKÎSƒ~zÖöá{jll á߆Øð ЪÁöÓ"µߣ3 SkÖè¬íaÛÖ?Äl;Q¡³ <ðÙÛ*ÀmìDŸ3P0sO|(箽Q±±#—ÞÌd­mã¾W™rYŠÏ4èu4²•ZÚÂ4w+²´Hª•ÊÄÃÉœC:ì»ùEÐàÂÃ,\ã­ †/B5a!˜#8ØIæ`6v¾ˆ‘ ^ö ލ§Xn7§M­f< áüiZM°HO Xûr‡Š€œk{_^Ù€¢¢ÌìŽW_ÙéÏcv,‰ÙŒŒÍ*œ)J]eQ€aĤPb hð~ádfíD«¥”}ÐÏŠ`gd,±Šb|cœý¬œHø±ð_ØaA²Ü@ÓA"ı?WàÀþÈ5^Àƒx½lþí±Í;ɧ*6LÀŒpaÛ+ö?Þì`bÑeª©PÞFӋ˸ʰrÓvª…c9" àqô`íãçñ±ÄQ~aÑ+÷ü«ÓÜ»3ÌÁKÓcŠåš‚¶rõÚåKÜkì+•°o®þÔ=„û 0Áõa?Ão\ð‹Ío¥LÉà‚—þcL}^ jîq°š†‹‡Ÿq•¢ÑG´ â ý󭯬ˆ=¶Fÿ–ƒo°eµðDìÆ‘³­ìvDÃÒúä ¾NP°ýM'ƒ4(H„ôW¤xÞE®6ˆ÷¢Šp"ƒ] ÀhÜ­&âY] „X¾ÀÑïFÑ`–5"1yZx,˜ëtdX€r@“–7§+‚T DGXq k¶†4ÐPW“˜>O*Þß%ˆWôE $ èPÂèjTr1¡ÝŽÂÛ­”Â"M£V}‘­¢>a\0SÌżc 8·_ƒ…{™S¸ËU¸t0a“">ÊA§¼ã­÷\z*àã°®xÆí«ùyĸ2p#µßÂj¦*n‡§ël5óMýðõë׳gÏfddÀ¾¢ŽŽNå™íšÂ›¾{÷N]]}àÀäõ%ÙkûçDh¾‡{:®+l^v'ê·}4¢-!Àæ¥ua…™z•Ó¾OM7Zî!OæååɲH]g×–¢½Q£FÓ¦M«úû6 ÔBTÅ[N(°¬hÐçï¹%¦ÎÔÆµ#µ»9úâùóçíÚ‰¯Ú·o’a¥ƒhmŠ*ÜhÊ”)èù ÈC„M¶¦%_¯§þ«Fý’ç¿æ´«Ö ¬_¿~Ñ"üK1Ùß‹700µp‹„ZPÖ3{Bºß?9píÖ3‡^Ǩ”ªkhÝM›ð•1É¡p·£êR`ƒ RSS¡ƒP (°„*½9Ú²£}K|¦õºrÄ%×P—ÝŒA·j}pžžžkÖ¬‚Ý¡ r‹Ê¶‡€D(Op8œ1cÆ;v¬¶<¯?þ») $…‚‚B¥î$ƒ€DXYàóùáááµâI}üø±Y³f¢û™–ù«àc4a%bêÔ©„cñâÅëÖ­«iÈÚÚšXHø$&&›T™e~$Â*©@KK˳gÏVofHC÷ä’ܸ¸8333 0õ+K y/üÆ^è C¨M"$A*ÐÐÐðÝ»wUyë“'O–*É­&"##'NÄ·q&X~Ãz±™Ýùü+s0ì9È|Ô,Œ·XdIÚšO@iNkæ„îc\ÑK‡PóEH‚T ù ²AÚŠ]¹råòåË¡ƒP`iÀÛ¥#ó€`u·n0;~Wú!¶½ÁÐÛ†P£E˜;]Ì}~Ä-ç*ÜŒ»gafhÎô¸1‡F|,P ÜM•öD;¨Ä@¡À²‚Š^(„Ú+B:¾ËSò*¡á;S¸©Àîݻ߹s§Ü·#·X اOŸ«W¯BGmªE@"¬~ ´··?pà€Œ±F}üøq ØbA´ÏI(‰°<]Æ ;sæŒÄ>'¿(ÂÄ„÷'ï5 ¾Ö£'7RHѤUútðÓàë€ùì;ý“«ñ…Ó0!_/E¡ÕðIΤ™M„*ÂÈ.•ŠTYÍp•á[wŸµÿ–er0ÆbNÞ6½Bÿ¡jg<³¯²y}” =]“ÀV‘0Ì5lYfñ4‰`ðj`#`7-³|¿H‹–ÿŒo8¢·Š]½Á\×sä,ôz!Ô  jž‰åµ`ßñHUƒqY1AŒ˜,:!*æn×›ÂW ‚w¨¯/©bc®SªÃVxµ ÷OÇ®V;º¿´ï€êÒO‰šdnfAÊP–õ€ë¬¬›ù| ®ÒÄe™åû¬×[ Bã.ç_è%C¨N~Ò׮ئî¸lxøØ?6?€<¡ç<¡Jåbwè.”¬ß¶.Î%Ü݈¸˜bŸE.§%jKò*™2écÁ( "8»ÃÍröôž!T›µç&üL¼¯uv@EI†ŠÚIŸ–ÃðsÌŠ‚%‹Þå<ЂÌwø3¸>(©7ÏT>/“NUË'š£ð—D(ŠKÍKUøÓ]jš‚Û¨l ºF—2Á!3OePxÝ.Q)TÊU~÷÷d®WÂå–?ß*oÆKº2zɪ¹9ªÕ¼+Ù$K¸¾;íiLWÚ2¥sO›•#øž_ˆ~De5+ŸôWo! ¡@ UÇhÄÄZ‰M\–† •X ·?ៅ¬åSÊ¡ÀóÔ)ÝúÒ0ì+Ì z¿jÎÀ ý^3à_Yc8Ë9¢{ÊOkâƒÑ …PÃEXðwì#€DX‹," " " VRO55« +W®tssCt" –Ž›‡üô¿Gè*|“o²Þõ__íO“õkÔD½T*ú@ˆPÃDx5djM¸-*ùžü,…{Šœn  Ì•“n9Ùþ„õjûÛòß2Ù»Òx-¹•ww›ºÑ`»ð—fó”U\¾¡7 ¡ˆ0¤nŸªìªrHËÜØ²ì>u¦?ÎU˜jüí»ÿL›á± ½gÕ)ÂuiN‹ë…V0³«Ô¸>¼âþ°®#•Vfð(€*m ' …²L§¢#=HÕ/ÂÅÞkÀ-Æ?Ú…v«|‰ô7ä1wÒrn3sëw‹îÕ#¿q0>7ú·ûÎ°ïÆ³íyšÁz¢PP`‹óúÎÑ4Þ S ð¾½T:“Å[wAqv·üùò¢ÂÔ¾4ä.°ÌMå³½J¹¿ù 9ôVtðŸ{&à ºsÞù!.sÏí¼}ýo¿ÿ›tõîuÞ4â¾k¤J§”¸3Œ¿.ð™«íÝw`.9×…þk†åÁ¿ÂZ¨¥ÐÑÐ(wÓLÙÄéáÄîTða1K» EXßf€Á=xR~N·ÎWŠ>Fo÷$Uݲƒš‘Õ2Ñ« Л…PEX¬óÖh°Ó6yÍx¦P(uëÖÍÈÈ(_ôüü|EEÅ2}fÔüµ`z‰j©Ëzõꥥ¥‰yŽ?>**JÆŠ/¥Óé­[·þþý»¶¶6z'ÅáââLž ¼}û6¬úŒŒŒOÙDŒ—VkkÑÁƒ¯_¿¾øÖó¹¿}û56zôhâ”Tà¬Y³vîÜI¸ÍÍÍåxÇâ lÞ¼ybbâùóç‹vpp7nÜСCÑÛƒðW‰°C‡OŸ -p7lØP ¦¦fzz:†T`*Pô4//oذa/^„î°°°âá—.]ª¯¯?cÆ ôJ!Ô’–ç  œ6mÚÞ½{IOQV/ ¡@†ùúú’åªU«ŠÇÚ¾}{jjjù¶µ@@"”?®]»¦««k``@œ |òä ›Í655%>g‚¯Ç§]ããy4‰bìþì® :Ïü´÷ÙÏÏÑ}|0__¦Ç¸îJmáqò'òLš3`\¦ÛØžJF‘¨‚F@",? áÑ¥—è…ï8¯}úBmW§µ×í1A0¬0ݤ¹0.V+ž?C9Q9GŸ\†5€êèûc»OýVê`nä—a ŸL‰8ªd7> Ãtá1ø4¼Ô>px)ê„ÒÝu'¶[¾¸ŒÕ9Êã¦ÿùý \<ª¾%æûå{´~fùos¹Aãt{ñ¬×~sD4¤ËésÐ[‹DX |M'´úñÜä޻Ȗ3ÛOÇ|wE-êyeSÔ%/ŸxÀO”zx…¹+pr\ûü¸˜Þ¿;;tïw>ž·`M¸Ü}E¹¦Oˤhäè Á§…Vd Æ _ ¡@ÑK@9?øh*àÑ:Κ0ªàêÑt„ƒF8üÇ~‚Õ’­TDÓLEo*a` k3ƨVõ×€+З ë7Ö˾^>ÛÙ(RêÙÝ%CÕ²=çÈ3à«‘÷ Tøxâs5d}8 füwÎè þb.B%Špüa“ÒÓç-Ù´1‚ *@ìz;nÆÁÿ~£ˆ`m ãzÖ’æ(B!!Â_$Âʘ V‹àããè-A@5!!!!!Â_)B´»-ª Š ;;ûÑ£G¿~ýjÚ´iñ=ê{öŒ8ýøñã”)S*ç×Z V»&Ç“óiÍ|¨¢¢*÷¬feemÚ´ ÖN„BŠWn>477—¸‰¾>n¾‡Ü8¨8DXÍš5ƒZ½~ý:¼ûðáÑäƒg‘níî¬&çdÓ¿‚pÝui΋½WË+Í­[·vèÐ:zöìYR•ðÐRˆƒ÷óež–‘r²Ñ«W/ Ø 5jdhhˆDˆP~ä}y Xyé/®²{gÆâuO*00P´ iccÁbÙÙØ`±.ï xD7ïû%ÌÔs?ô'°X,Ò!8´±±ÝÏbMÆÃ4ë —ù‰Úb›Ÿý~ças7tLq\ó‹¶h–Ñ´ÍÌ—¡ÏÕ»7•ž«¯_¿""Tô˜¾•}‹ª;¨ÃÃÿÿ®®®^ÔÛÐÎÆuçÞ?¹ *o´ã·€.Ëöoxœiê¾ëÞÚ™ R´ñ»ÇòWÑd÷“ðÈb­:¼ÙûÙ½»ØOö ˜n¶q>ù߸àK&ϯ[ýé[_ðŒg•òŠ+Ôô—‰°&"??ŸN§ œ|*(2Þ€·J±e™ð¸+\mæÔLæ:%lqnñ0€Ý´L±”á%"®n^l;¿<:¼|ù2Fk.€ø5M}/Û¾jŒñ¹ û¦ßF¨+@}¸n^éAÑož=yþŽ LÉZ±¯Ëfþ¯ûÄi]ÜgÂßÛ¶l3™öÜ ‡²dïÏŸ?°h€íRr{L$¿ ±ÇŠYä.š6}ýÞ=OYQ‘° .MéO-ô·&%Ä£,ˆSèÌ+”Öä&|¯ÉYÄË»ÌãÂ$s+ü¡cK²úŸUxó„AÄõ¸¯qQ[–E¦¬²dÏ…‚oöF¥RÌÌÌJ ÆÚ6™p˜(¢Ù)¨ßX"ÇÞ¢ž4(p(’”ÐÓ¶È©+«Ô¬’•sXX˜ƒƒ¡œR·š3àüKðŸ¸ñ”ž+Ê*Ç«_¿>¹£¨††Fzzú§«ÁàU4fi'þ“<0l½Õ˜.ùß DÅÁ%±jBî60Š$/Ë<¸W Ã,ˆ¹T= þ›‚tFt̸XäÃÀ×ì2$ÖÔÔÔÓÓ{üß~qýúõ]»v-sU²é@xÇÑïgjYb—~¢ÖÑYz‚/_¾lÔ¨‘––VIlmmQMø·áç)[­á»ù@½1³ Ú†ïI¬³µ;pÀodyµš½c¾bëÅËç˜(IIgÓ¦Mð¸`ÁX± $y6íã^-#›Zf‘mQ °´6±%ÂÝ)È*i«žàµ+zu:ý<àa˲‰¸bRû)ÓËðÛEö^´hQhhhÛ¶m¡›ÍfÃ_Qj,áÀ}{¼ésæÛèÛ8{kýK—/Zö}¶å£Ÿ4tìÝ6œº„ûܾ}»¯¹shHÝjÕÈȈpdgg«¨)PnݺµtéRÔ'ü 1eöÍè› Í쎼:ºýF¨uóY¡î^…—–™©uß.ºÿæÈ(^€…GI•IZZ|e¡ü¤ß(4}Š“æ¾Šgxbû|ø'ñ»NÛºÌ6åN™P h+Z:Ò“ËCÿ³÷m+~?oؤE& ·²|ó耽Û:nªÐ§«¹9 ™Át™2U¯µµ5¬¡OMî "V'1¡Åþ@sõ@ó¡$’—š( ;×rÚåýD£œ8qââÅ‹AAAø‹˜ž.㜖mÍÛÉ äUÞoQ´‹«Hô¼¼<(?¢£( :/Ù…wBaÃ{wÞ„§aáxŒŽÞÓ£5°$|mé¯ÍèdL<''§†×{H„U‡÷{… lß¾ýÕ«Wawe¤åHŠ1'5v½•ÊI¹gò—–E݉§+˜È AƒH7—ËMJJŠŒŒìÑ£GÉ1”ÊvZ¡1 c2™ÅƒÀ6Å“'OæÎ[_$ÂJÁóçÏ—,YrêÔ) 2­±"°ZÇCÜŸ—Ï,3=ÕßS­¯ (òýá4M__Ÿ¨‹~üø±cÇŽÎ;++KžíB§ÓSRR¤Œ¦ˆÉ 62¥´-Ë0‰ð/ì‡øøø˜˜˜´k׎P |1ÚyíèÚó4êׯ¿lÙ2YBÂ^ÜÓ§O¡zÅä ›¸_¾|ùýû÷¬¿z?$Š"##ÃÂÂâŋЃH9ÐC€öçÿÍ"ÌϥЕ*Ý £††¡@„W„íÔ‚eþŽQÝm”}¸Pu#7¨ž›¢Å¦õ‹UøÍà<ÁEÜ# Ê iجWT.È¥1O(%/ÈÉQáì¤ã+jx”›ù| FÁœIEîÛ·ÊÝ-8…S(9ÔOTžÙ%ÀV &[Ž|AypBU˜H±i™ `Ò‡C£ÑÐk„€š£àm¾r³<½õøØ÷þ—´)]¹à;ãkÁU“z¹Ô¹ì ›xï?ì®âЋЗgæRëãS0ëÎÌ¢&" Åm–3™L C}#üÃ"$g`a‹r‹{ÂjðñÈ|0R0Y„Æóì͆Ä%‡nB‡huw©tË,î/->þüáÇS§N-Sæõ™Ìx 35h|/>ùÒ¶EN{^ß ÑaÜz {ÞN$B„Ò¡««ûåËÂÝN øP-Û ~Ë1Þú̇ ȼ=hÎz•ÄûÇÇ>Ý@ωA&@êëë'$$ˆz’* ß½{'%ºSçNÙ?Rí‡k.œ5ncïÊeΗ‡lîtûìd3˃ÿ…øôéÓ¸¸¸ß¿s¹Ü –) BBB ¶¶¶høgÏž=úÊ•+l6»E‹¢—Ó5^^^H„ÿ MLLˆu=b _²äääqãÆúPk à¸< wÛ46¸ëÜ£/.€ü\ùßÐpÙìI•úd’’’NŸ>••¥¦¦¦¤¤EÝÊÊÊPEûöí#? ¶iÓF´¿}íÚ5G¥RÉ…¢hß¾ýû÷ï›4iRü’ŠŠJÏž=aô”””ñãÇ#þ[€ ìׯßåË—%^%—½8ðÂ… åHß/ä ¥«-¨×‰6°“¼òÌÀÿ>à§}ôÒL¾OCÔÒŒD!½ÿΠ›´Oó¼œ4ªªvIW³S>¨è´]GG‡0ñôëׯ¼¼<‰Åá_¨À &DEEI C*°K—.<1eßÕëhWR¶)õšA)zu•Oj°®»ÿ¾@y!¯ùÎmJ˜¨ÍËøž¯Õ@8ÚØ8ï>˜ìp®Ç–= ×j¯˜9ÏŸUdÕü»]³ß3GRë´6Òá¿ ÷îÀ’%?uëâi^½zµOŸ>H„ÿ ]]]·nÝZjHB/^„›¡C‡J yé]Få)„ÿÍL/ 9Tܲe‹¹`ù ›ƒ{–½¢,Üà Á× ˜ccL\ý Àš¶€Úd¿²ÙtpÇ|¶º©ƒ{,nx´ÀÌ!i‘-""Â2 ÜcÙÔé[6¶>WÚ%pKAê˜ìk¬ÿ@ÂÆ˜‡‡‡, @8à‹{ûöm‰aâ~(zå盡–ö;»^•rD=vìÑå#~éÿáJôáçø4†ðu›&,ß 2. S>yáÔàúPÃ÷Ÿ[7{¼º{YO†‘“ýt¼äºxûûÀÁ$G’ëö·]"$¤xuê³ùüø]­¬úSÒØ¡Î6sCYR*gØ urrB"üW¸ÿþÉ“'Ë…T`ïÞ½agfÅŠ¾¾¾¿ÿÆÑÅ[t¡ÖL§,÷wnìäãc0§׮OÏÝ[5v{¬qÃ!¹ŽCï¶OÂ}ÜbÞŸ÷˜œVŸýõ:Œò"t,¼4͍u ÇL¬vÙ¾ÃÛ}¬X E[[;55µd3Œ Îø¨9@BHh’ƒÐâ(¡ÀBëO{\p ‰÷'•AÈœþþ`8s‡ØUÛPÒÔE˜ºhÛ¶m@@€ŒK:e€ÐÎ’LïJñÙ-ñýûw±¡ðŠ*ðܹsC† )kDb8€½½ýÀé‰!Ã&·€º:9[fhÍ0Ò¦]Ïí?ÑX=Ô+VŸBwÀþÅ /^Ò@'4ùÄ__Îã—À¤V‡°âí3¥zÌ2e>±–-[¾}ûöæÍ›2Fáåý¢2$éâ”é-äå*CÌXuêÔ)e ŠÏ¯±o4ª q4hÐàÖ­[bý™Š*ðþýûå0C†K ì­Ý¿:Õw¬ú ã 5ö©zµÔJú¹û¿'Eú®¾£Ð â(€WÚ{@-CÇIô…†µwVVVpp°ŽŽNýú°­ Š™Æ±vŠMG]0hµ„vãoÐ"Ì:r’BãRîM•`ƆT lSH$±†?D"ÂÁÁ–ërL*ðÝ»w4Àν(>4ªÜa©SL*F‚F)8„7ØœbŠLa%OGš×n¦¤eî Ê?ŽÿàÁQë÷ñDwjGÎý~ž¦=Øå@Âf{}\Ì€49vïÓ˜N¸ý¼q`¥Ž1 hàÍ ›yšVnó¹ëšÆÓìÿ$®¦Jv… ÌÌÌ$w¡«£££––V ·õ„DXdŒD¾€ üöí[Æ ˂ϒÅÅ+C¹CõY$è:±ÜÑÙlv©vÖ”’bteд>uÚÛf˜ƒÛ†óÚ~K®ÓüGäù—ΖF:yñ|…^çÎÆä?ìtl?ˆÏ[˜ì¹ø?c=w2ÀjÛ‘'þ³i)}—Ë%%Wóí¬ýå"„­#Ù»åû†^¹6Œøç(xŸ*P–‹xu…R>•5ÎN¹ºyÁâùIï»°mÛ6%%%&“  e™ššª§§G†ÙËÚJ¼l㇃ÇÜà±ESAßÿ oÑ€±´´†d¬ýkáq‘ãdýQ°1I³iy+daç%åäÎ;2M#V:8NJJJ¥ÞâM yk÷³qÙuÇvqÙô×Û…’®]_&ó †¹YLßøôa& *P–ºBª)þ«×‚îòý õÓ_ÌЖÚu¾\R›3§ÈÒÑ£GE[†ß4ØŽíÒ¥K©)¯ß¾_ؤR—D>8tèЇàSUQQÝQXõÁV(1-©¶T}¹›7ož˜˜Xñ4·Ì4ZæሽÉ s€ÕmÐ7v¹sñJ5JÅiõ«]ŒvˉÀP°Z®ÈWc¯%î•ð#Œ+ïùŒ;–tÃñÆß¿‡‚ÜÜ\:Î`0š5kææ†×‡=zôþpâããííí‰=}ÉÕþÔ\š2™÷ʸRöëׯPU“=F{ì•ÐMÇ—^$Üqž¸9jìžøX\ÍŸ·Q©]QMŠAôó]FFƧOŸ>þ +:Xž6nܸvõîj³y©€ÚÀhÁÝái+) ¾°ÓuLOã<»X~vBcƒžö–Él…aŸ™Ì¦–,1áÇŸ:uŠ0{.Scò֭͛[Ëâ‰P•Р}ûöÿÔ¯®"¼èÐ}À¾„[»-¼r(ðBï9­½ÁþȆ<õéÛ]6 k,ydúÚµke5òÙZ¨ºR=þ þÏÞ™ÄØüq|ön·SéÜ¥R)ÊÂß‘óŪœ%gÎ !„È•#7/YDdñzyݼμr*¬ ÝçÖÿyž'kK*µÕÆ|¬§yfgæyžÙù>¿™yž™IÍrìÙ¼ØÍo¬º<ø“›-‡;FÞû0dÆ’xjwnôUÿ‡ËÀ»Ý©V&å"®X±béÒ¥5««@±Íš5kóæÍUz"¿¾=O¿ó 'f\@àlÿFiSð⎠™PËÊÅZ»v-T`mŽ Å& ËõâT艨?†7µ>(++ÀÆ‚ŽŽ™L–WúŸ?NIIµ¤ÌÌLØz„µ˜š½¢„Dø 5çÕ yéÒ¥Þ½{Ëå‰!¶çÏŸK×÷ú‘'¢J‚ƒƒ‡JôaB455åø&à÷hãÈ6 “““ ‡D"9uêÔŒ3¾®:ŽD(W† %ß4¡Ø,,,ªô”;µ|t¡8\¾|YqVÃ…Yêææçàà€D(OÞ¿odd$w@±Í™3'44´JO¹—ê?Ê_óçYAÒC¹U=,;øº÷“WjMš4ùòå ¼§<}ú´¨¨ÈÎή™`ê¶s!5],ÛÛÛ7¢I™ÍÍÍ_¿®Û©8¡Ø¾o Vè)_ sss+| eÅÖ?ÉÆ››ò:n>>JCüÏÆ¥ÕIX{Š´~5ù}€Ü¯äååÁ-¼AOXƒ…aÕQöf¿‚r‚™/D"“ÉTQQÍK555è€Ûïkðgj¦¯ñ‰°¤¤þ~@µº7Ò#œŠ]nê+—’(&N¿ØrZ4Ÿ˜¦š‡®Y?-Œrùòegg犾,v¿•ÇëÉ„%­Ç;qô™;ÃmêXBEsZ’ÁÔ?ßE;ß4Ö¹íça^ô«YØ4¨4Ø„ Wl}pJÛén^Ôæä’Rÿ¬äÃÇx¼¸ÅY¥Ñ§luC"”oß¾… ¬ë£°ÙvdðùI^þÁ/ {e10WrèÙIë–Î~ŽoXÿE šâb‹µ··ÿfš*ò”#}ûöøh¬^½z ˜èšWK„×N$»y’Ã]̦EãÏTÔz¨Á‹äO6Ítkp .´²²š5kÖ÷]N¼“›çŸO›ÒÏöЃ ’¥_ìÂínåDkï’5”AÉšÎNÍ ìá^+#÷‡žaÞ[·sµë˜ÅÊD„¦f¼Ýä&fINÄü5LFÍífbÓß%kt"¢£ê¨ðôôŒˆˆ033«‡c­ú÷ñÕn&Îìf1|þ±4ñ‰G[*綪a/ .Ø{ešuÏðDH(¶ïœ¨ÐSn¹ÂBi- š#ëÝ¢ßsÏ ¹á.\3ïkÕ?¨¬ÃÂÂ|||ªi? ¬H—ûŠÇ;‚Õúc Û_=Gƒ‘x ók=“âß^åÛ®G8lÃ(ÜóTiR»GÝ:ݱ)/Zþ[”9V#n*a>BÖ×Ñã F‹’¸}'=¾°›Ífÿ=…m·ôúmXú'5ß2©œq^´hQppp•žr¡L=\"¤ê¼êE‘ÖÝÃ2´¸v?£GW¬^‘~úx” Çåà\lå¹ÍQûò…À–‰™àS—®ºõö̓&d$ÂZ°`Á‚5kÖ(Hv üa".¶GµiÓ¦rOyZE:£žÄËnVwW=Í"Gf©âoT¹´[5E‡i‰©$•¤ês¿°”UmÁæ^‡Àv ï:¾}Uœ96€÷'—|Õà"$3ÿ¥&15ubóä“ÈZ}ÌÉPŠ™"$šB¼ÖôþýûÆ•ePlÍš5KJJªÒS^´²lÖ 6b÷œ´*ç¤ÿ=°xƸ ¾.íVkTîåˆ;©A•”¾ÌÝž˜nßÄók3„[¦8’›ü‰7ðÆ‚W€Ñ¢Íb¢}ˆ½<¨gŠMìm‹=‰ÐÂ’6ý}4£ êh ùðáC#Í5(¶¥K—®X±¢JO90q¨üm?®òï.\¸zõêZÄQ­¦vŠQ÷x([ ,ÂW¯^)ò2•Åö}k°BÏF T`5×PÀÉA˽ù€DXA“ãÔ©S·€b+7Ý}…žbrû*‘Žcºzõê7¬­­as£>ÇF“H¤ää丸¸Ž;S¡6 Ö‰_ ¼û”ÄR’th)²jVÅX[$¯²?“¼ Ä½¡´³)éãºRsŠÍØØøÝ»wUz6R`áîÔ©Ó;wª¾;N%RSSß¿Ÿžžž™™™——WXXXŒð—õ‰¬Pãqé8 CMM­I“&šššÒYؾo®»ººþŽÕÑ=áí­Å­-K§'w°Ä>òÂ@[ÂÑrþ'ÄYºCV.içIúÈ¡ÿ3±è¡™Ŷ|ùòÀÀÀ*=)ÕW`uÐÈڋ0|˲iC°»×ÄÁõ}Òªÿ±.—ž¾&34»[ÚöjØ|„bûþÝË =)“&MÚ½{7Œ¢ˆðàîÀ1J¦ Qˆ °3‡5Þ+·ÿ¾æÔ/¨Á«m_Av‰¢ =#Pe§µG4¨¡í2œZ‰äL ØŒŒŒÊ=ü¬Ð³ÑQŸƒZ«"ãˆHâHýð—ogõª('ÅöýâÌz6²²B¥3W'pLLÌÛ·o³³³›áÈq!äò…1#6¿étº©©iÿþý—6!%ëˆPL?ë6¬WC6xÞ¦§gwlvµ—‚Í^Åö}k°BÏÆE% ¼t鼺-J¯›ãÔÃ)iâH‡•Ig=üðáŸÏ>|ø/+B,&¹xX›£ s?þÐùN¼ÙðžM5éu{¾’bÞ5’H$vuÀ†±Xh`Å„„SnÄ =®®®>Ý}üøñˆ#ç< _¼xñ+[Âr8Þ‚¼rPê#Sï¿ëò_2»¨˜ÜÜ ÐŒ-4Ô!«¨0ªLª¤Xú¥ä Ÿúö#+3b×,½­I¬ëÛÀÎïµXˆÂHL½UÎßÙÙ™˜^ ~ûüùóÜÜ\bmú«%$ÄÇÇS( {?µ±Ì¼VW/4ÉÂÍ®vh&ãUüM¢•[#%`ÔtoÞ¸ +Qílìv¯âeE"üö Òh­[·&ÜÎzHPTTTˆS"Àg:ø3z")(*ØURRb0L&:~ôV`“&M:tèðÛtÌ ªAVV–††Æ¯§@´3ãÆ;pà@mQÂÊAE‰°®PWWÿ…¯®JúŒËÔh¶¢üçäÑÅG>9ŒnVÅ*~ÎlLS‡ƒu®H'°Î†8|͓ȭ‘GþM)ùÕ ý¶ºžßc¤Úê(n>899ݾ}»b‰zq–ï-3m!1m/z?V攚·Ò¥•úD…%Hšù å"öвá zóµeÊ΀Xôúðð9Ç”œ‚dÓ¸~×Ùy“Oóx$$Â_±­ ¥ôœóS±‚îIâÒ«åY!0Øy¼¿]|sÇâÙSÉ V¸ ae¶ÐtuuSSSe¿Ê/.uà áþýE ˜• ±®» cPEØõSZû+=Y [Í¢¯vBKÄ4‡¬®+Ù7Ê,9þ-)|惡%”N‹ÓŸÜBï,Ô't"þz,_½žÒkÞÏ*PŽ»L ~Ä1–.ðS¨œ!:Ÿìíí?þ,ë?ýoæØa%ºmO\íâ1/š¾ý»¸§‚:»@kG3 äp¬ûŒ¿1σ¥Óz(ÿS¥Ìó}iR—˜÷6Ãä:Øw׸©ß¦E¥ÝPH„U³bO4T "œ ÙÙ/èÔã%®ŠÕí.íy*7nkËŸÇKëÞ\lCéœö­ñ™)ð-ˆÆë–ÇJ{Þ˜yÔ(ÎãÄñ.íÁñµÄWDR_gC$béá‰ôy¼®¨Møý‹§‘dã’¯Ì*×ô-þëumÙßô›§Ï’°Vß*[ì¾Åü tEÈ•{N‘[+Ðø4’¡CкMKæÏVä"ÅñÜÌ‹(xÌö— “ÎíÙÁ‹ŽdlÒä#S8î;«;Ghê?‹ôzʹý½Z'0ëo³+!¸)ì‰"ð^¤Ã¡%Ý*9‘ ® ºð[©³.ÀÝ$þšTxp+ #Ê» o£wPÜ…Ži MÖP1qëØÁŒ—µ°ë+¸›Hyçk6üøåOë¨áì©Gbv¸˜]“µeH=f+x‘Ú²–¸¸ˆLÇÞö>èmu;E7fÏöËó\?ç÷¥òƒ­8wM@Ë”·epBð›}ÌqÒ ÅmYe2ñɨÀ ¸ÒuKÓy®I`â~(I´fï„  D²`„§:(ú“W~½Ê@íqìß‹lCéëEP*,RªÝhf×dG`ȼe~í<æónM¡rª³¹fgóŸS`…ã˜~zpSûÿ_ƒ¡‹~oê\„² ü)Ö¶"‰!_rrràvôèчB¹@"l`+ngÏž²DXçxxx|ßöÛ³gÏ/9ÖD¨pæŽÁ`p¹\YOb°ÜÂ… Qþ ë– cš8q"´(sH„uÎÓ§OË)ðñãÇH9‹ðÑGç61 uŸÈ^ ;˱cÇFŒagg'ëéììƒ Bþ"lÓ»¯oZ¿pvÿ†]=¥Ä%_ÉÕܺ!ŸhwïÞýêÕ«²>|>ŸÍf#"ê¶::{ÞjàìÛ6·­EZkƒ›õvÒÅT»]Y¹¸¸š9é7hökK”S µµuc™€ñ‹´ 'L‘Ý}þàЕqZª%Ž–ï›k>¨eâéÛ‡o-î½TíÔZ©Ç@böe:3`4½½}ll¬¬OAA‹ÅB DÔ·ËaÓn´MµÇ‘U¹î¤}Ú‚>Š—q—/_.§@]]Ý´´4T¤ /ÂêsôèÑÀÀÀW¯^5®,»yóf—.]dG ˆÅbX)E D4>º»»7ºüú¾½§££óéÓ'T’R„Õr¦P@‹WNEEEHˆF,ÂK—.ÁíÚµkýýý<›úõë÷÷ßË.Êennþúõk´Œ;¢q‹*PÁu¸ÿ~¨@YØüƒ D¥ñ‹ˆÐ!Üž;wnÀ€ •;ñññ–––ãÇ—útíÚõÆh08âW!T Bve³Ù|~™ ãââ Q¡Aü²"„ |þü¹MÞIAA¬‰»ƒ­­mõSpZÿòö<+TÂL„P:tø÷ßäè“&MÚ½{7‹õmYØ ¬Ü>GrØÃyüzµ4YõMgmª ›ý’Ï¿¸mî´½/8¾y†üòRP§Ù×ã.†¡2‡h"„ ÌÌ̬ç5Í—/_(Ý „>Òy,„ïÃvþD³šºý,™Íî Z¸¿¸œw»ÏôVâý‘Gfóù·~ú’ùÌ$BD#!T ¡¡á‡êáXŸ?ÖÖÖ†ª“úlß¾*°z±c [Oÿ/üpÀ¾¤÷üûFl£¶BþÛK+¼C—nw$óQ!;v^ï½OPC42BŠD" ¥gã†ò“]k6""ÂÓÓÓÛÛ»šÑù|lUëælö¼IìB–®ÚŽï>‡Û¸óûQiC(®EŸ/ˆ´û®œ9ófKúŸyTã1¼ É´a{Îu ·_yŸL¡ÔÝÛa0YYúúúnܸ±é¼áóQyB4V¶±Ÿð„Ÿ`˘ikcͦêYxú¹;G“fú×$;±}o¶ñ%>J¥¨¨H¾o¨-Y²Ê›Ø%ÞÌ®™ˆÆ-Â'|~Êáú£Î©a•·ÇR;¦¤_újaZZZÆÇÇ×þ å&_C”Ž2HØ}}á§^ßÍ$ù­üK'•4£½‡ŸÎ¬ûà Û±Q¿é¢&÷›úösŸ…JBEx;ÄÙ‰õÀƒùÓÙ«TøyÄVÖß"ŠRð’)ëY.Œ(—V¨Z¢òƒˆ|µX˜oÙ­<ßAäõG ”ô‡ª3çÒB9%©(I*V+)˜&æûÞ¡nì\Â{Nó{!†ÚöéXv—¾XL›œWƒ“vS=+º{ò'Oœ·•0D‹pðä¹ZÇj_ v(4\¯²°)RÖõ,±ì]2|µJ䨒àDlP…ù6¥ä‰b úu4Ïç±]……ðŽ€?©þ-L¢wd•|W­ua90¤]IN<³uaÁM:Üõw.D¡õ¾EšV‹kŸ¨¼k}uÞ’Õ¨!R„ã5ŽÕ*>YÒ…¤•LK|¹ÛM" ÔßßJƒÅtƒ˜š=BØ·‹òl0˜2Hë¢Ð ,1Íò'CûO› µ¼üyZÛ‰5sE阩k’3I&Mäóü}`{*DãaB±™ýmmRPH,Q5;¢ð';¯R¹…›»›ñ”.–"I1™Dÿéç·JÀ²¶}6Ç„“F "†hXZÌ~|jýWåšPàeŽ_Hâ\:ž¡d¥`æéœ íÕGòˆqßüŸ §•>‚ød…U,‹?ÓG§{OÌß¾‡µÌÈ7•¬,¬Ù%¿ÕcêâCa â…P”6¡²®%˜]X´ï«ø TÍŠ^ 4!—®°^JTMK¬º•õ(€Û®½ ºâ_][ˆuÆ „@Âm€f»;௠p˜{ˆÅ=—tƶЧS ¸Ãù¹Ë“»3ݧ,Ül†ŠBa;fðþz¬·PÀpþÄÞOEÊm•_è‰_ëS?UòÚšU]ž£ ÞûBÈR–;œÊÌ`±„–*6L¡·¼›al©žÓwôB²ª!Li *P¡»Ë¬vrLD"Us\RõCV¬õ㟨!©–û÷ï·oßýö$Âc̘1åÖúD ë4šDˆ@ $BDˆ@ "$B‰P>K><22ý¢$Â@$‘H$¤@aCÒºukô["Ž´´´3›µ…¯ÍèÉljª&%«6©Ý'©ñL¾5@ Èü½B½{‚¶VÖ¶½]Ç¡’„h4" [é룹:t˜È”[²,:©>¯‚ ÄF´ðÓù¤°›ð7O¬|™5ÓeÂBTªŠ(Â×W÷š=÷#‰fݨð3Í|Ú*>>À—˜Vôf 6·Z]£BÎw)Z¶¯9›×gàüã¨l!H„o6µ6§'ÕÏõ@“FìS‘Ø}›³P: p=œÃ@•‹°n¼;oÒ¤ù!¨„!^„’íêÍéõ}UPrkõÁèö% .b›øI£xXq½žÇ$•Ýëƒhhò_D‹0l¥_]×?ËAØ:©Å#½€?½tVßúMþ‹hxfŠÔöÊ+Ÿ)8WTÑÛDˆ_@„`{hm+´?3ÿoõ!f þÑ·hò_įÓ&ÞÙ’í$PóñìåæÿUU‘„‹˜sÿ%ÅÀª¦„ ýa˜à4Ò"]IèK²…ªQÒîùJo±u(¨ˆù“K`5”œE#&öíÏ,ðèmØ®âç'ʦQÔK®Àè õåœ3¡Ù÷-¨åuoÍž2cá:T BHÞYGBg¹+¨Yôróÿfßf]ëP9ì¿Oßûªtæ_š•PÈ%kïÒ£œ‹ö(¯Ê·Ø¡tÖ«(ú_lb_¸[:áo Ýk„pÏ¿ô‰J¶>¢Ù´,ÁBÜEøäµ™ü÷ƒÎHNè º2*^E!Ä}Îf6¥=ÿ›êÂø¹ghåçÿu-ítÿ5Àì6¥³†ºÙaÿŽXÌ›‰ß*“ÉS1·]çÒ`Ä„¿`»Í1ŸÕç|<‘¯–tâQvò_x·Ùœ9iVÀzCT¬ (B%]ß=Àx{iÓ_÷’LÕ¦’gú © %»’ˆòÿ×ä»å–¾~Xùä¿b@Nê¤Ò[=È1Ócäü1b2MÏú£•²Š.BYÌzÏžÑ[n©ijjfddÔ8z»ví{öL¡Ó=xð á0`¬š2™e樢R©R®_¿îúúú¢²…PhBÓ±nݺE‹•³&LØ·oT Âæ×¹s燓“Ó7ˆÉ¿e‘}X¢¡¡‘žžþ}¢!Eˆm…ìãò;wzxx¨ªªB6–Œ»}û6á°¶¶þÑâÛYY¥“©>|øáǰB‹ ¢!ET_¾|Y¾|ù÷ÕQéóƈTúúú)))†…C¸MMMa+W]]>D=‰077722ÒËË *PêYPP°wïÞ™3gþJR6iÒ¤’Ê Ç•+WvíÚuôèQT ‘ëXÉüþɋł üUóTª@55µœœœë‰C¸»uëvíÚ5T‘å ‰D‚…ì·}í«mÛ¶°Àãñ8œª‡è#"Ö§NúmsöáÇð6T"ë ƒëc¿sæöéÓ•0D­D¸kW­’ +ªe Õ'%e96Ç©Ü-yl­{šº¶¶)TW{ *ÍÈ"$B‰@ "H„BAD˜+ªd”ÃDýŠðánvÛIüåËÙK|)ÑWý~¥xÑ‹k™ÃÞ¸våÊÚž=ýß__ëÀç®bÃ-úaH„òª±kÐÓ‡àyvòéù ølàâËR)î^Å äwíêE· ë¤w“Ó„èGA Ö»þÂÎ'Ü/Ÿ`ã,Ì´î?>@·üOaGŸ÷<îÈ‘µßÅf¦]p½àƒâçš IÙoº:¼—lÎx')1¦R^‰Z°*çÂJµÉkrZi£7c«bÍŽSçVHüHJÅ´¼¿$´KÞíýl%Kôì‹ }ï48ÿA„úå›`–A[hЀ „afOW~ÔR—œ£#5 {Ò–/¯a¤b¬ÛJ’ÃðÝ©ü@Ë®kºàˆ ô±m‡­¯(É¥O[’½Ö[i´³kvð)êÖÝ93Ü5¶ËD¥‰°  þ3 ml.–T!Ðq€j»ãÆóÉ]mÈX+±w`à%,Y™h4Bë·j9Û{1ÿÉvö5‰O»Á>=¬<׈º"±µÁ®«‹ÊV ¥n©ãæ,Ìln=š5c °ŠÍ¡›—N|eÿwÀÜ[án󤱌d¢/rÅÚÊHH„Õ@œ Èz:]Î-™8wV»XéÉúðˆl°=ýÑÒ°ÂÔ`¸}˜#ò^ ÚMúø/_; ôØüëet—Íåe‰@ÔÉzDïfà\lR£”¿‡^ƒÝØ+¸Ÿ]ΞÈœä­ßÒ@~Jñ'½E°ÍA’-Æ¢t›t6dÏânã×£_DX;J€ à«Ù‚vO´Ãü0ë7‡èt!©½/t¸]Œ»›A` 6Ïü܉è'A ÖZ„$”«DƒŠ@ "H„‰øD˜’²ü7ÏÿN/k,§êj ó¯(º˜¿¬qrª£!@"D !Dˆ@ $BDˆ@ "$Bâ¡D‚æ±D ª" !@ È" !@ È" !@ È"įˆH$Š‹‹{÷î]~~~aa¡@  R©jjjšššúúúÍ›7WRR*E,Ÿ9sÆÒÖÖÖÂ!‘Ê/Ç*‘Hrrr>þœ’’qww‡žwîܹ{÷.<¢žž^“&MTTT*<%xéééŸ>}"“É=zôhÕªú™!D %É/>ºr4)“’#d*ÎY‘Ic•skûÎý={¿xñ">>>555++«¨¨0:´4ОAFSUU…VÇÚÚÚÊʪ¸¸8,, š7CCÃrI©á”óü‚#kÛbcc [CC£K—.•œ4ê8æææÉÉÉ×®]#üÛ´iSåu1™LCèÎÌÌ$â‹õôôü‘íD Cˆ@( Ÿÿ;wù\¤»z4t›àÐD!O4€íÓàßO¦çD#Çû®Rä\…hïÞ½ÐÔµhÑš:Ù¯4pªL! :Ú·oO´)ÊÏž´m„üYLLLjŸ Ж?|øP6O’’’>~üèêê ¿BÒC†hxNùë|àjSrÜÕÓiëP¿Œ§nÛ·†¤O›»d¢Ù¿GGGè¶°°ýJ”ÂíwTÏumè0#¸;ÝÃ#0¹Ü½U¤(Lñã·`×áV*ZA‡ÇxéNÛ±küú—Â;1 VàšÑ¿‹-Îð𜰗۲^ÚÿÐ*›â|µ±XÜ¿¤Ddˆ†aCÐ|?­€Òˆ/a®VxîÖCâ¡×ÔõÌ䔤Vð{>ÞúñÌSþûZïÏ_;^yÀðÌs‘…¼Ú2qÝ÷Ñ#Ž:¦á¸Õ…ï9sÏöÃ܇K=ö~hËÝ‹Ù9 …‹Íô³%l·ãü]³í¿õ7vY²ÏÛZ €‰ët÷Ì·gE‰66Õܶƒê¸`ÿ ¬õù2öq6]ÙÁÞ²ÌW>XO湓g'Ÿý+.gÏa.ýó͘g $á ÷êû=\îFOgM·+4lKáNùÑ Ô %%¥ÏŸ?#%"Cˆ@4 ëƒ̃Vðç¹q™å—UrohIÄ>ÿt!^QõãV7Š„d¬|nA^ójiUrž ÊI0ö%CE³>3ðÙ³gÑÑÑfff²þÐ ƒñ}”øÇi€Ùqÿžå£Æ]WîÈç"ùpþ^Ð4¨ÿ`~ô±ëwcÈ^Þ³ízš£ìçúÁFýÌ¿Àhd§Ž=à§ÂSº·Ën=mYWGŸyzà0wˆþŽÈÉ€¤ ý lìY üW3„ÜZ©ÿ5=üc1X>{;{Ôº5fq¯ÎÍ€.M˜ð‚ÚÕº°Šø)cÆ ‡7>/_¾TWW‡7ô˜˜˜ÙÃÛ¶þa\ÿ[dÀ,é€?4b+›u+¸á$VðØÜ¼. ¬U'¸øÃt7-> O U ž‘·h+À¤ˆï)„£"T¢àšøùœ¢3û+îL¤1ÌöÐò$ÉJ–T1H›†Ð Âã:¤(Y†¨$äýHùÍÕ ê.3Åbñ‰'´´´ å{÷îÝÁƒá]Ûܼ¾:cE@aÔÙµ ¹V‰×ìžï)**d M¹Ìnᣠúó÷‘Áû“;.z;éÝÿ˜ðŸl(·{bX÷C×ù›™&N¾µ¹ûköÈ÷üs£2ϹOo؆Ý&O²+|ÏÍd¾©œJñÿý7sæL °°0é›~ÿûßÿ ?á†Õm¸+E"‘|ÞÚ\›’^a‚¼€¼?Ö¨°WÑ*?nwø‹:%7ÏM…f¹ ·pê%cÕÁØ€<Ù`D‹P6Êø7`çˆâ°té!º‚KÅ*­,¯Fª"R ”ê¨$$$|oóìììÇóçÏaK‘Édêêêcìäb¾ýR¹÷|ÏëmÞÔÃk?wïô2ß ?x¬Ná.i_ƒd£|=úoä²°æ&}¼‡Ï~nXϰÜõ¾yóF ØØØT÷VN¥ÂJ›¿¿·nÝÐ-B„¢Öwï"_˜¼ë;ùñŽ@’¾Ûp\¹]rgÝ·njÅŽ|Ão¡V´oåÆuš|ý)_ëÊóõ^yèS4.ìy_LUú`sÚ¼ÌÌÌ-[¶´n]Ú¦kÕª•t¼W…”³‚ïîKÒ› ýyý¢üµ ¯œÿ«yóœç‰;  ùÄkù³ò¶ü ~Y£ø- (æ˞Ѫ˜¨‹(â§ ©diDI©¶/~x{ó&Nö©£ß½’–ß¾}ûš7oÞ½{÷º+uÿ¬Ý´x€Òöîµ%V˸º”b1›¸ûJ‡TŠ>ý=çŠEÈãwÇü2\ ÖaÊ>ÒJ6ŒlÎ/ì!xQ¬åT‘€B8>~ü¨¯¯_nÌ>l[¿}ûöÓ§ONNNRË×¹sgt{A†Ñ8¶Ô~Ⱦ7MvOqb³?ÎÞs•Ï?ýewŸ9ú\¾éoM?:Ôä|ðë'wZ˜²Û¸¯´´u¦‚üÑ ÁãË“Öî»Á¯òpsæÌyôè´yÒ[†±±ñéÓ§år9í‡-ÎIžéjBû ¹+9ì•_³˜3§ú¬m“NOO—Þ÷ëˆNý-WÞ“P¨ºàÅ’óç[÷”ÏÉÍ"9¸¨€ë ‚ÀÞ²at:è¬ÞmI¦Ž?0òZî‚Iò?s «=zô@7dUû)oþ‹¸·ÉøËîÚ-¹Gt™<‘Œÿ˜Ì'lö<Ÿ_±ý+**Z°`ATTÔÖ­[Lxjjj8p N¯HMßRmÖ³ëGVZÞ§MMot¿È† o¿Å«}î¤/wdggŸ9s&11QOOOWW—Éd–A_c˜­f÷>·ì½É2¢_´[ñÒ •Ë]°÷YöÁöp¿½Gó5LùÀ²aLFn ±{æÎ<Äýþ~ ›nd2¹&·f*õÖ­['N„Ý4!D *Þ_V¬X:nÜ8ÂSIIiNƒœÒÿÜ?àðF?òû´ Šœ)ªÿ;’ÜÒkÂ8u+?…9+uuuOOÏJäççïÚµ«iÓ¦FFFßÏðY9ìËdwÊÓ¯ öËw)ÃÒøèÑ£®]»>þ<99ž­ŠŠ ƒÁ€þÐÀÃ%8yyy™™™°<:88¯Ñ ®Ndˆo@Û¸lÙ2_ßÒv ¬e/ÃQÀ³å»Añ³T€9°$(++KË€,>|xùò%ÜfeeÓh4hr¤0 É[@PTT¤¦¦Ö²eK///”:t¶D›7oÎb±jyz¹¹¹Ož<áp8ÄK@:t€[YÛ†@†¨šøùùMŸ>9iŸÒl”9ˆ!¥ºŒ=ºBÿ´´´øøøœœœ‚‚Ønƒ”˜ÈÚKMMMb8©,D?2„Du9}úôÌ™3¼~ýzéb7ãpPæ ]”È"ê«Un’E|ÿBlGLb¯Vž;9ßW[ò£ð¬RI°*LHb‡(ùLÊ÷ש8dZ­ÏQÆ1˜Ø§py;‘B]ò`ôÓ#d7Å€E¢°wÐùS‹¡$pðÉ·¯dV˜Ê‰\,³™àõ¨’‚ºÅ¾Ò÷è°4̶uV—d¿bÚœ äJ>äoHò©m3õ+ÜѶÔ.WÙO¸¸Á'¯ÿÇ2QïSñO)=™» ó®(³kDåÎ$âkø ãó‚ Ј@ !DÔ ÌríðÍ Qa¯¢{ŽÍS?­²í5µ¸ €¶G™ÂÈ%ýöÑUm _ºŠ°¡Lüyg0“¦Îd HIb`Š?\#) ‰aÔÿ^e¹Ý"?ž\ ­ >‰Æä²Q0èØ<ÎÖ«TÆÝ'/»{ÞŒZîL¤áksÙ/_¾tvv œ H†:"õ¯o•KJH“3ÏóÉLñ¡éù:’$Q÷•$dæè‚ £üºvq¹3‘ VVV²ƒE"‘»»;N?xð`ÍF_!2„š¨ÜZƒÆä "~!eÑ•Ùeæ ™Ø¿p¢Ìœ¥µKû—”ofÒ${Æ•™C™¦*º5Wz 2Q¤3~½ ìPv÷»3‘†¯1ºººÄºáR(Jdd¤¬ÏÆ7mÚtåÊ•º™y$ŸÍIÌkSô,¤¹W*ÿnéh°­Øú›ôTf³=ù|žoKö¬'üf$>Ûćÿ! XBBVÐÈÈèýû÷? ã‹#Ý}øða¿~ý i5j”#÷ÈKðÏ„o=´Ìvë®aK¼ö!Oñ% ¬ø÷á%û·ìÑ/Ö¸e&***11ÑÄÄD[[[:n§233¡©ËÈȰ³³{úô©££#•ZæÎ ­W¹("‘èÞ½{ÒÝ[·n988”°©pìc2Žt7''ÔÏÏïG«3"!Dü"V°cÇŽwîÜ©þÔYðÖpîÜ9YŸ   ãÇÿý÷ßå–;¯’³„ÂCɶƒHzÖ žá’Ô—â§§ýçú)ÑÍMàîÝ»7nÜ€¦ þ¸M›6ýÙèYYY>|€m¸æÍ›ÿh)à i‚C¸œœjpæµ™,MMM F‡×þêÕ«ââbh•µ´´ìííkv&dàNð¹9Nž{Œž5tÝA7ÃÞ{xðíŸRìáâ²æàpã BÆïž²ìŸÜÒýáÜ•ÆD`­cö¦tàî›U›ÜhÑ¢…Ô]RR"]2,))ISSsРAè‚ !â×áìÙ³pëååïzßOaõSüïÿËÈÈîfggs8xc •×ٮؼ—ìä…[ÁÆ ÙyîÊû€tkGÀ쩊sVÁÁÁq* >ÝãF&îÒéÍÝ4^’ûߨ)kð}s.wÅhbülŸÀ½ãÊO¯-~& q¬ÇÜhnHhÉ\¾œä6Î\š&£åøý½‰ÇÇœ*íÀþ7ƒ›–ÚÔü«'…>޵s·Ètb°¸Ü=RëKN•5ë¾§÷F½ñ ÷í¦ [a¬§‡/é¬^³Œ"(ž:uÊÕÕÝ=!DüRìÝ»«†/^.W ä{xLŠI{fæfç%{§cýÒ’ì{˜¤2°ðâÞËãÖ”3¨ØÛÔ³vp£&zxxD:/Û¯¼mbG¤ƒ ¯‹ ÞÕàUÕh9Lsá‚àÙëÎÃXl‚iî öðXESiÚ«Ý·—b¦N_¼#«PØÔØZ¹ª!¯Î+Âÿññ‰þ(ÜÈå^Ü»æ@ÌL-#ŸI­mX­ £‰©¯kMF=þÜÚÚšD"Á’YXX+ywïÞõ÷÷G÷ d– Óãë—Ô2þ´iÓàöäÉ“&&&mÛ¶­»ýã?233¥»Ÿ>}êׯßÀÉdòòåe •Íi‹ªüQ%ÙÑÐ ºåëË4l²žáæf7_0bl¢º¢Ää—µZ;¿[`xíY·‰#´zN/ ·ù놰÷¼å—ÖÏ3[ò¬7;æÙS zµýÎå—^Ñ|º8%Ü­Í„5Iûõ®c]oüy ãödÍ#v²±ú:Â`-<ƒUò ÇUÝóIfªÕQÞ–[üO[[{Ê”)å*UÁàÊ,Š‹·ñ­6í)ãÓò’þ_')=­ìº6ƒfp¥ÏÑ–å’êÈíˆ;(êVë–IÓkQ¯o»¥þ·ýÏÿ•;?ËI;¹²‹Ô“5¿E}å¶? ¿>^ ú|MÍrþêѵÈRXËùtìØñÏ?ÿ;v,ºé"C¨xŒKåD’H$òMÓØØ˜Éd¾zõJ1sÑÍÍ noܸQPPзoßz8¢ŽŽÎ£G cÙ²eƒ†‡ òöö&¾]¾juÕ?“zo¸å-Ù]Md›§&ÐîЧOÙDD©túðQƒÄÜ¿²±ã^`ôñ±4Ò\¶lqžèkQ³„@£FÍ]â51X‡ÐÀ»°Ø—†_›AŠd1<O¾iʽ£µn(b³{òù·‰ccch322‚‚‚6nÜXÿg£ÆRZ⬷bm(¹çœÆ[œÈ$ ŠÙ0Ê߯¡N€Ãá¼ÿþСC­ZµªÝ€åevWæë³2§ÓwãP¼¥¥ÞeÛhF—>¤èöß·ðsÃà¶4ðWTLœ¸\¬È]‚ÙJ¹D ÿÚþß'₹ÓSòté0Jÿ°qRkœ’;:¢¦¾°©giiˆnzÈ"J  …3gÎ\µªÏxùpýIf4§®wb÷¾Â·WÃLØÛùÚEoݺÝxõÙsïãµýXl66|ØõxüV'æJ¯?Âÿ~:tç£ÙÜ•Ø%lvów^ 4.}UESSZÁ‚‚//¯#GŽÔÿ-õǬà¾È3iÊæ"ëFô[°ó_ŠÓ^y Úù5ì™-Z´¨ò0aaa°1¤¥¥UIË Ûð*ÚÍÝ_¦†A&N§Gõ®ft …òåË—gÏžÁfZ³BD`ãïÖ­[k×®%^žlì˜8šìNÄF8é¸Û%H^x6s¿÷.¨%Û÷)ÿè•Ølö²~:OÛι’äׂ²/±¤ñÏý½‹*6d'࿵l¶ÍLþ«2ó‹²X,hE"Qß¾}/_¾\ÿ×5aø·©=n?ˆ}÷"=娸H¡2ŸÂ`jéµjiÕ¹½îa…å^¢¸ÿ>t4iÒ䧦""‘HÙÙÙOž_©Ö©'&&Ž?þêÕ«íÛ·—cGŸ±±qrrruæ³®~Ȣܹô­TýQ|~éjJþ·øø0à bwí“o/:ʾÂzßö>ø¶ÊƒVVöß¼yƒŠ,¢:@Ë7bÄ”ÈþÖâVv;ãvw¡Íý¹v§?žª{Ÿ=(Ž3)j¤iZp• Æ“ÏÞŸÕ1èö¢ÏŒÛmNLþëæó/«ã‡ˆ¸Ÿ%¬ › {Ü­$Ža›=†Ï?UÍ£gddtíÚõÙ³g¦¦¦?zѼ6¼{÷Žhež={¶ÜØð‡T|+h``ðñãGTÂ2„U0a@øÅäÅ]ŽPù!ÔÅCcþ…mñÒÅwM©m ''óùT zB¿™Úš=!‰Ï§‚Ñšñ÷‘§Yù_Ž5\Šž,{?"Ì0+IbhQ+XTT[-°Ñ£©© ­`]_c{è011MO2™\ûŠaµµµe×2D dËs8™è‚£òùAñ‹ýÏ•ØÓwò3¼kîIiƒÏ¿#³Ëâó±'Rá/K»ï”Z/»ßºêcI$OŸ>)))5ÈèubåχFGGÉ%¤‚CXA}}ý””TÚ`ª,??¿  €X ž G(BOè ‘H4 Öö¤³ÛH_½‘õ‹ÅÄjòŸ%\Önajt…Á`ÀXÐMl!L&SUUÖw-,,êð¹ByB·æÇ®­‹„áš( Eh´ÅøJ.oß¾­¤ÙWý aÍÌÌàU b®ø@ë“‚£®®®‡+‘Õœ T9 Õ—Ä£‡êÔ•á-"555-- ná%à éÕ«W¹eîÈ6&,--ïß¿•¬˜Í‘¤¤$€¿`Uù Åê‡Td+èààðøñcT8ŠK—.A »­Îï–°Õ¨ƒó}¯Avvö;w¦L™‚Ê 2„ŠŽ““Ó±cÇŒŒŒâããÿlÛà@‡©©éëׯ¥ï‚×&¤"CXÁž={›o#½„_Û·o8ðÞ½{°\µlÙÒÚÚú§þÚˆD¢7oÞ<{öLMM­U«V°¥ˆòBÅeÈ!K—.mݺ5Tuc<ÿÄÄDÂN?~<88X.!–+W®Àíˆ#öïß/—~§5Š…d; )v²P ~|¢E ËÎííÔÌp #''§Âùùù¹¹¹Ù8DhTTTTUU‰­²²rífk«CòqòpàUÀ-¼@ ¡¡ÏnáµÀó¯ð„¦¦&1ï<BeÖ¬YüñGß¾}£¢¢~ËqÀ!îJ •´™ªRa wâ „÷š¤~1î‹P‰¤mZômOP© rûQ ¬¼$ ׆u±¶2Öi,¿—2N%Ïÿ8 {þÐÈ©á Û&2„¿!!!Mš4ñòòÚ¼yó/yÄµØØØ£G–[Ò½Æ!â\·nÝÈ‘#«ëEZቻoH¶÷]@’E·i€tåܱÑ;_IKŒ£Ûj~­×¤&\º›6Ø¥‹œ’—$ÆÝ7¶uDòÈ6bà½>>>~éÒ¥sçÎý®×:ÌÍÍ_½zUI³¯ú!“ùóçÃmDD¼ [[ÛÊïü;ö³–=´‚¿ÀO,i9 øv§¹ ™nE>Ï[<ý/å æv-êdšãñ˜uqqú¤Y‹‚Žñ4¿¦~}ß^~çBÁËmÃæ_*ÝyÜ9@œÅq=Oóx¤&iÖ¢­ðä•J²Ò„jzL2@ CØX¸ví—Ëݹs'l4üž9ðú56Ïç“'O> Or ©€«þ^¸pÅbOeNŸ>]nM±|¢éÉ»%¹0ÞðmféNçÉ­ôŠÃ],,WÆ;Û²Î3LÊ*ýÊrö-çîÍä|p:ëä­'sÜ:ÕO&ÇÄÄ8;;×,näñ“ø_%]9±3O¯ëèÆyÝ}Íè¤ÛùyG§q{NÙ}ªå¥©>‘™<Þ‰¥0XÓ¡¼ÝcÂFs.—tãqÇp\¼Dœ>;fðëö‹#‰”÷ŒçðÔ&ó6~Í„‚8ÎÈE«#K>“äÞ7d2íÀnªO»Íw‰Øì«|>åÝÞBŸË° |<ÔoµyÔ–öél‡ü§ßnÓÄ|žÿý÷ßÁƒCBB*9tõC*Ä|jjjíÛ·g0Mš`‡·^¦ü“+çcuì«}!òSA¨‹îÂÖð¸ê>¼tÕõ΃(Þ‘”¼uú*ä·a˜ io­,÷‹e‚¢YÁ… ®^½ZŽÇjÖ¬™žžž……ÅîÝ»aÆBŸêLok=3‚7sLÿ“7÷é¶¹ñ8¯0.lÝU;ÇÞן,`¿¤¥H Ó>Š*­t¬‚zסú‚W.œy'yG@œßiÞ0»Þ½‰¯–Îí4|Ú*¯þ–Ö½{­.'×vçp&GíâåÞ寰¯:¬ZÚ±˜3ñ o_ioõ틽ÈM<yaýÑhìÆJR9µÍÃq=zšWa™CŸˆˆÉY>Ý-!¬_^½z%ÆwРA111¨(Ô)666„mkѢųgÏ*iöU?¤¢ªÌZâ[j³³³Ï\¾ùñCra–Â-‚¨¢¥ohlâÒ·;þ~ ´ö«A"¦¦X+jÆ ~~~ ~EÇx{Æ/ŠÚ<„ÇÃ&uíá‰÷6øâÃ;ÝFŒƒÛ6‹£ˆîH—ac¥½g`£ýº÷ÀÞVáñŽžD/¨ÖÿžúõáñvÁí`úö€QÆ“;K­ 4ñažßšÔ=ÇùQé¡nó†ÆõÎ!CXßÀ›lÓ¦M¿|ùÍá©S§P9¨Ï*ÀûÖÂÃÃÃÂÂäR1QWW÷2à—ÿA¡´··mè¡A+Xχ¤h¶–KWxÛéÛцáóçÏ$ÉÒÒM÷WÿX[[¶ VDž>}ZÉÚrÕ‰h( 422zÿþ½\R ÄE?yò$&&j³Y³fÄH¤   !!!##£[·níÛ·'–Eü0ëUâ˳Y9y’ÜBrv93‡$€¥$QS–(3*KB¥&CB&: 0h2 (á/*CÏ”ÿI$àã“€¢bR‰ˆD °{ýºŸª·P@‚ŽÜüäb˜¦º LY¬Â”¨«2t Z˜ÚGe¨6¼|ù’ØnÅ‘KHDý­à¬Y³ä8×®t†JøôéÓ»wïRSS¿|ù’™™™››Ëd2UTTˆ¥ [II‰N§Sp!—`Í[(åççC«–‹“——O[CCC[[[GGÇçG‰ ¥'~ CX~ëþí¿ÞS³óI6¦b«fb}1ù£ƒ˜ØP:7²¸‡•ÔÀ ê0øøÃ¡üBÒë÷äIä7|rs¶ÈÔH¹ƒ³?*p[{„mƒí¿ÿþû¯’f_õC"êhaÓ6àà ½~ŽøýºµD#3„‚¬;gÎü•žMù£³ÐH÷› cЭ-ü”4öœUfJZ[Šàç«G6øXæuóã(ÿ< ¶±–ôq]† "1:íÕ«WaaaÛ¶m“KHD½aoo/‹<8fÌ”d+æéõŰyäÒMÝ †bsíŠ~Û¬ï`+‚Ì…ÈBØrŒ1uÚt5õ¦¿y¡lÑ¢aÛlllžw9—&‚ R©8@¡P°;‹s(üp|py$NžwZ_èÃ÷|¾ô3‰D àx±X, q€/CÀEcyÑ ù())©¨¨Bo©+Bˆ™ÕëJC‰™N>è8iˆ@Y ÿ;Ï£Sï½Ô›Ú=Dˆ‡t„G¢KçåË—H6¿¿ÿÎ;+Ũ:° ^»v­sçÎø¸ÿk9DGG¿xñ"!!KŒzõêikÿxäk=µãÄW,ÆkW¾Mff&›Íþøñcjj*N|’צMè]µUåÔS};·ß[ÄCÒ?„DbÊÍ7}ÂßhwlÎídA¦Rþþo4'}J»¥–šAëÑ2¦¹ÞcydK]éP5j$×6|þŽ#•b T½{÷Æ^ÑÕ«WûôéSÁ$Gޱ±±ÁŽc·©ŒºÜ†ª2š5kVS ¿v Ô2!,™$ìmv±·|°³oÅ—~þVÿuJã¸OZŸÒh xõõxõtž"‰¨‰MÉårØÉ¢)´wŸ˜ißÈ ¸ u“šé=¥SŠ|~Ó€ŽFYAÇ«$I¥[•væ° Ž9òĉ±‹‹kÚ´)Þdïß¿óæ±±±©©©¾¾>´dZZZllì»wïpkà“<¸òÿ÷á.q¨Äã¿®¦EcÓ+¬L|µo¹j9é—Ky|é¸î9‘˜,Qx>6â)™$¦’… TAH¨ÒKÿŠTöh”\™¯Dã(Q²¨œŠ‡‹i¬&ýëÙú« ŽŽNҶÇaòÙ²ž»Áz™““Ãáp䯥Ë …!C),Æò˜‚{~‡Ýb.—›##;yŒ²²2®˜|\U.çõÿÂmÔ!üM´ üÛøï£I“&ØW.f̘mRóÁ*Ø»wïk×®Ubž¸È¥î‡oÖ‹Åbù0.ÕµúòÇsTUU¡'€@%ðúõkù=$uuuPÁZVÁÀÀ@{{û2æÙ/#ä$?ާvl¡QãìÏo¯=L<Äæ×’ „P:£Fºté4EíÂÁÁ!33óíÛ·–––?›Vü-|ÈØeˆ¤Ù«Wë§7ol=òƒÙJ2íû_Pˆ¥ìõ„˜·Æf*e-<\>Z/ÏW2žÝÈúýÆõdIoÚõ4˜Yô™¿;kÜö²;¢ÉBà/åÒ{Qøƒ{â÷–9¡nˆ”u†"U¢ ÚiÙ¥+dºü‘¤‚–E"‘p¿ Nª$3Iÿ˜dÔ¦k×®]Œ`»WhË©ªbœ9sf@@N‰‰‘Úð‡ÌÅ*¨;2d·ì[ƒ3gK¾…ÙõÛx*ÄD5xÔâÇC’¼ì¶±;…Ÿog7ÄlÞ¡5]¤oþQåÏÖ‰¿ÎZà¶üxÈ!{»·&“B6Ù¶³Óš¸kËžÝp×u§BôÃ9ø½ ¹o­c/ÓÎ=§âÓœ~­©/"žº¬ZòÑî+Ïm¡(×·bµâ(‘P΃ÈA±ä%óÿUc˜óL}¬ôv»±x·R„þBÔl¢¿ˆNÞC²™‚˜Fù*Xw!µ´Ã‡9쀊Ÿ; ìaª_»,ϯÆZسgÏcÇŽ¼)ïãSæø‚TÃá!!Þ˜+’~2^îoMš*ûé·>¤Ÿ,p,/¾ÀoMyø-;© Õƒô ÙŒâZ$DR/H>@6í²ô´ü“õ#úõ*T/…ï¥HõM¹X­¬Wç-.‘¼Xþß‹¦5tÚPf0ú a% àH®¿#ú›ÿn>¯ž‹·$Á‰C1|ü·»Î’ª PL[ úˆÐ¹‹§ÀG+ËÓŠ˜ÅžI¶0)ñ✽ãñƒ…cÎ/´o½ì¸aÙ£¯7r9âR 5ç>ñ;¢Š×™•wA2{ôFFÈA®Ý0ß3Ø…ÙÙ¹„„ìS”—.ÉYä2þOO,“ùä°C.~§:9ùy Ã=V¸ÊÕéA2#àÀ#E"ãÙññKŽj7¾×¯¼K)íç\:ÌÎëL|]„0¬‚¾;©Ãø7æSŸúÑÄ—ÏK$m%õþ†_¢ée‰ë¾½BߟÿÀÌÞ•ôKzÎ?K³‘qʳ \¤=:™É÷H$±ƒ)X-=¨Ám$õì…w/ÒŒó ðï4¦$n$9Øò£zP^¸‘ÞžvÛ#¾ëÃZ:Ôùî>E´ñ‡ m¼âMÏ „²¶1 Õ‹±m«\ª™ƒŒÑÛÿé5ü[5|ø¾5§³C†±Ø:cOî’kÔ×DëÏõ|‰*ËoÍêÿ-‚Ï2ÿ"aA¬¡ Þ~¨êð1`rPxêÎ3!ú‚çkH‹PB¯"#½cw×Gèº,‰‹ÝÐ!!-~,ë:6åÄw÷SÂ{ކ…„ –—ÝŒ3d^Hˆ¿:“‚:…Ÿ|*^4mp(½ªä­¶än†Å‰ñ»Î~¿:ê2Ø—ŽÑÎnªàýA¿Ó8òþÏË]ƒO;“/Zmoçz<„¦"}p´MÏ.?l„‘Ý”_óP x „PŠPÚ“›é1«Ifâ„+Ô#Óù—ß‹ ¦tEºÎzõ!÷¦`±Û=„ŸŠf`Ü—¯Ñ|j”줱ìrTðyhA¨HÄîÂ%òâÿin?úž ·FZ>^zî×g¸'õªŸ`Ê´§Kê:ªZºÕ5ZÁ»€xjݪ¸ Zlx×Á„þf±ñ‰Q-§–¾Y‘™-.xÙî ‰¹ç'‘GL;ºéOV˜®#Éþ:½}–LØÿvÿ¤qñÌÿö:È"Ç[r\nЬU‹¢ÞÛîÓ+f}”í}ô”ýzÿÑvvôF¶‡vwwö&MÜ¢×ô"ÆçNùΞô_|†íâÿ âÏ^}•AQSÏ£«sìÚm Ñ'#ªI‘¢s^lÎu;&?£Vj5·÷ÿ†¿q=¥G.\úY7ç;o¿µ²£š8îò$O÷;AÕ1ë£Iê‡+6ÓáQ cËÁs¸ÊËVïÇöYéßè³Ýì!Ce¼¾ðÙéU­¾O7Ú/¾®|Æúa^} öÙ%’Ööé¯Ó6š“lI^ż©F¯Aî¡©³ëh/œ6mš““Sûöíß¼ySŽÙðáÃ/^laaqãÆ Øu¨•BXD¨G­)ŸËMŒ;ö<&%›GiVŸÓ”õÑPåYmlY¥~Ü×–ÑñÔWñJõt„-›71ï0ŽThΦøÏ¦Nw¾Ë—/ß¿ùòåÛ·o/ÇlÒ¤IãÆëÚµëéÓ§aàï² 3êuì=¿cï s3RØÏR>¿OKKOËà¥gIÒ³•øÊІ‚X…!VR)Pñ_.vAi|éHª€LÒ(b:™+‘ˆI$‚'TBˆœÃ§ ‚Êã" /@|¡‚Q¹}úÔ××÷‡o €V&£e Ù•/^ÀÛ•IddäÆ<¸pá²l®]»vöìÙmÛ¶ý# ÂjC>d¥D"ÑÑÑIII‘½Åü <¯iÓ¦ïß¿·°°À*XªÍÿý‡5»ã½e@£BXSÀú'šY Ô«W†©ü)p‹aýSPPÀÓR ^½zåæævéÒ¥öíÛc„„°æB¥Rå*Èår[´hQÖ‘ÀŒ9réҥ͚5KLL,Õ !!aôèÑaaaØ« ´ „µ ƒ!WÁ”””ž={>þÚDÎÑ£G¿|ù2{öì'N”jPÐbÆÆÆX¡Å!¬ÝèèèÈU뢳³ó­[·êf;|üøÑÉÉéÆcÆŒ)Õ ++«uëÖqqq-VE\wlÀY7D«ÈÝ\Áëõí÷ˆ\Ñz, „U…‰‰‰\###W¬XQG^×H$úúúŸ?644,u„ÏÜÜ\Ü2IIIÊÊÊX§¬ö,Ö6›ŽrX,[6ûæìf¬yQlc"™e4ŠÍþwJsVƒí‘]˜Ž×MÙZÄLs–þÆp¯ k,›2¸Çº»ìy8á‡Cƃ³#·ÜplÀÝg§O?BXÙXXXÈUðæÍ›Çß¹sç_¹š-[¶¼wª*VÁ’KE"È””:^þ—"*JÖõ¤Nûè½ÛÚßéÂeÄ{zJÍu}¹¼TmÄòœð1Sïìì¢ójU×ëwðìóI·zè?vo¹ìA$âÞÖz|L„?u–)èyðôU@«–2ì­ð/^øøøü+åîînkkÛ½{÷R/obQWWK#™L.g˜˜_A¹×GÙ=Ç3nù"´ÚÚ6äÏjõÛþR:©ÍmKéÒf ÊÔ[#Ÿµòn%ýíÄÍË&œÍ†ž á¯#É~7zà°»¯SGøßßd¯ÄjäÁŽÛ/LØÝj£ùË í³^êAË!w{î\SIA¶,_æð±œI)t—»”Ä¢³UnPŽÏL¢¶ÛOWÑX¸¤èAîÛ‰·¯›0î?Å.×Éï¼8ìGН‘¼8”B¹m6– a^ÑÒÂøä¾LÁ]‡\!ÏSЇ_“•˜â7³³Çíaü›Lèé LàÑ -Ú¨KLŽCl/nöz£ÃT\½Š[˜Eù쮹=ÅÒÙý™£úš¥gÒv]@e¾QnÆáuîŽj'ÛáF5T«à¶¹Üé¶·ÐG3žÃ…À—d~#ŠÉ.štq.Éf ó¥g€/3Ó<‡=N„o %WR©Ð$™9„ÿqÆ…R‘Ì£žSEB¶ÏÌ—ÙSMxÉ„Úl?í-Öž,ªÑf©Yû2d–Wî+°sØcsØ–$,{ŒF<¶WÎkO½›Ï;{YÏ—Ä^ÈÃRwÜc#S6suñ¿ÉääOÒaO^¤àËüŒˆ¼@ëXå*XgÕ t)ü¿Ns_â;0uZ·¬˜;KcŸ£ZõÕ@(.cš„½0ûøK’}Ká‰ý(ð3…¦ÓW‘vE®D$®àí¹õ·*²¢dâW(ƒ™û?æþ¸é·e%M›ßKÖÈ÷ûQY¶yb´g¸HÄ¡mRx°³3€ÉZ'd»}¿ìyúS>°Í=î÷|ù–,·Óî9òBòN/¸”M1rǵâqX+™tSÞêzÕ¹õ¥nâ¶ítw>vcê¢ò3>b¬îƳ½8rí°o)½\9Ò™3R6¿o§°!UMÀöŽ‘'´q甚q^¶8²ÁNQt^)è [QûâÙ å }]9¾%s+Vt~æùH ÌH a©I0GO2¼!<Æp]ëKjB7Pʈ„}€:*„45Cÿ¯SÝ5wÔ„Êô_É ^TÚuB‰µ—žÌ/°—ÙÓ‰Óô‡Éˆ Ö2{‚°À-ô<¦xø©]sþÙ!üêZÓ1ÿpWù2fÖ Ä„§´‚€º)„÷%«ãX‘ùÔ§~¨Æj<»¦äáÆ¡KH¬uô]D‹¯Ñ†vÌE±t};®—®ÔÓB²EK; }®Óã¼8Ú$¤D Tšý\ õØgÉž‹ôýž3¢°ÿKf$±]² ß뺊q{aÞuNÁWêz±ÐC;OŸ¾F+®UÉñ3 X+Ù^ÙAû™Çg[¯§ûvõkÃßú/ÕˆD^úZÌžÊ?¾Ÿiʧ¦—bþá*yÛˆ|oÐÂrX$ÔÒ—9½îŠK´/îilÙˆ¯G¿X˜Ã:@e; ªwë‡R¦õ5o¢ŠìÃÔ]!Ä4ì8á?„¶øºW R%eýù:|N%É[QEèÜNN›Ð] ÞYlvä5$ßsRº´tæQo¾#XÂþþ+ÚÈá9‹mħ¿^Ú…·’¥â2*ª=šMs®ø2ÌôQ¬+î{R¨2ï !U¡“¥@˜N J$ê& °ÀbãiøÆ$þS_f.ÊÆUšÜA0©…¸Ý ÒC+Š’%ÏRGìÈËI³f-epª­ÛDëLŽI±Îö]@ 3ËË!ü‡rÓvnÛbÁ|×™zãÏÝ×ÃòWd»ç°'KoºM—ÝN£Šb†Jå‘ò©Y×léìón+–b?T6Ë–å¿_%‰Ù‹Š?ZàJSk <Š.ôÊ[ºAm0ù^Šºà­ôc‹(Ñ‹[Øþœ;©y¥1…á½¥~è…Þܳϫ6Ú¶›ù`:çmY1‰vA<ŽËŽž³‘(Mj {- „å@W7v•ŠâwRžž¹wûÆû,ÕWVäÛ$Tù÷·Øî9µ´¹ äM ‘§Íå3Ý¥ªTGÖ¸'é÷8U«­N–MÿÑJFÒÁe°8ÛÁn áï ÓzذÖÃ~l—›²“‘°š%б¤Tò“¶³–ΟjeѼœ§¨!EmD-ý•OÙ`½`§„°ÚŽÑtµQíÖ•›ßÅë÷Þ%~‰ŽŽ† BXç éã§111ÆÆÆ Ð „uˆ·oߪ©å¹¹ß¾}ãr¹ š„°®Ð¨Q£ôôthB!B!B!¬9Œ;VþÎ߸qã&NœØ­[7hÂ:D“&Mþ÷¿ÿá@TTT»ví A@ëK–,Yºt©X,ÆŠ¨¤¤ BXç‰DAœ8qš„°’‰~rëßk¡ê’”nŒú”ä[OÉV´MµnöEŽ˜q‹Ûñ• i×®]ÛÙô’ß1!ü>>»zôÜí¡Ê—šÐãä1Òºªüuë)!ÌV2B=9 ‚µŠqÜcC¯ÅkÃ$q*_ˆ®£ç[Ðó¼È/"#™Ãm:u²ì>vW@˃Ÿõ%`ãê9š{HHlˆ§Öß±ZË—AÒåœÄÇ3iÙHC ÅÇP¯Ðc9h˜ì,„> QCê÷/).Š w·Ê=ÒGð¬¿9mŽÆnÿœðì‘o#º˜É2µ€]Âïì[5ÍI5ˆ†Äó4ÿ:—KÆÓSŽRÌŒQlyšïŹFå Ù^<$&öµïèËÌh™Ãö]>ÊdùRÙ^ÙYƒŒU9…®â?´.mªÛâÕ°P×…0ûÝMnè¤ ª_ÿÖÍC0„Qó5ÌØ‘$xCÚû3>ÌÎ1ڤȒ]W<8'û/î¸n;жõ—v:vcê®8<]ýëß½…4MrÙ^¹E¼¢ œ±ùúÇöâȃ ù‚˜ºõKìÃÔi!ìiÝE¬!•é¿’¼ˆSñ§UX¾JlOk/…=™ÿG+*&XÈì Â[ŠH%«×—åÅ%ÕŒ6ñM§-ìÄÔe!lÒ}ê>a³¯»«úîê­É³kJnº„ÄZG_ÑE´ømhÇ\K×·ãzéIZú2§÷Ï]q‰–àÅMŽRìñD<ÓHön€TO$ø?ûíØgÉž‹ôýž3émA4ÚÙ¹/+ytZø2Gõä¾A ]È9µƒé2£‚ut==WÀò§ûvõkç½£[\'µ” ·Îí¾ò{qò ||?3Ò”OÿL9.ČȓÀuÛ e5/·/sP÷Ü//éƒÇszÈõ\^=ÙJ-í,ô¹Nóâ¼X˜Ã:@e;Uó“8—¨ÓzØÏuTÑ…}€:-„r\½üò^›ùþ¿žôkÕR‡Ï©$#y[ªÛ ÒÂiº Ô;‹Í.<º“•,y–:’`G^†9'?÷Ê¡!ä÷ðûð1#öÑFÏYl#>ý…ðÒ•Jã1ONF2¥y(Ý­5¡Ô9Û£ƒx~K±Õ ÒD Ê– •‚WìT…N–RMêyœëÅ¡"$Ì .N‹œg8­ߘÄêËÌEyB¨X(+aM±[ökñólZjÉÕSº´tæQo墾t1âT[·y§ëžHŒœ½¡?컀ÃrÈüïîÂÎyiY¹#OЉ?tÕ±¯‡å¯ÈvÏaOãY·é²uTQŒôå7Ñ}¿[^û~3Oú0‹Üþ¢<Ò,Û¶PžjºB¶—T´îw‘¹g á“Þø7?ù¼Ü‚ä˜ùÙRÔø…‹+Æ9™™üºèôÉE²ºÝIN#êt B,Ï_>5ëšm†Ð¶ÝÌÓÿÜmH "®?gŠçúT¥5€½ÂÒÊzÙïVù¬ ååý û$*Z¨~ìL¾Ê UÉã”XklƒØ;WTºúüåt—ªRA¢=¤ ¾—¬×L-£sïAjfR=Çþj?ØM!üM¨:Í»MX×í‡vüo('ñ³ª±ª„Ž…$%²ºJ„4–UV€DEL¢«•ºŒP'Ùa5AS‘þUãÆ H7ÇÆ£·æÌ™S= Þ3ž«V¿~}耔ݻw‹D"X¶lYµ¡¥¥¥<`kkûòåKØ"€”É“'«©©eddH$‚ ðôWàÉ“'¸8””4~üøC‡ÁF„øu.\øüùóóçÏÿðsB±±±:t¸~ýzÏž=«´Jÿþû¯½½=®ØÜ¹¥ KVXz…Bá¢E‹víÚåïïïää[BàÇ\¹reܸq=’ß`[µjUY–k^¿~ý°ØàYSSÓ/_¾üvïÞ=%%¥pÌ©S§&Nœ¸eËìéÊJÅ”ÒOÆÒ¥KK•RºEFF†¥¥%V…±c¥cöíÛ·˜ÌfþüùoÞ¼ Æ"•J½sçNMX…2 ÇìÞ½{Á‚‡4hP1c\ó…2ŠÅ`EÇ+èååE"‘ cBø73aÂ|¬ß³g«©©ÅÆÆ–e‰­9s愇‡ëééáÙµk×ÖŠt‘Q8fõêÕ›6m:yòd§N¥¿Ú0SF±È`itvv^±bFƒžamûFË–-Ãz&dß¾}eYÆÇÇ·oß @ÿþýKu¶j)2ŠÅ„„„œ>}ºY³fe¥r’Q,òܹsøä _¿~ø´€ÉdBï„°&‚õ¬]»vGíÝ[: Ùx¥ZŠD¢¾}ûb?ÉÇÇÏÖ¯_?99¹.4Ñj…ÛaÚ´i/_¾ŸPQùÁ=zôˆŠŠ*ùøñãÙ³g«««oÚ´ÉÔÔÂJ;§OŸ¾}û6FÃ’†…­,Ë)S¦àãr½zõ𬯯/t𢤤têÔ©Â1_¾|9r$nÃ={öTä–¡••UXXX±È˜˜¬Ž<oãÆÐ΀þááá}úô¹uë–¹¹9žu—Qª%›ÍÆGámÛ¶ 2ÏÚÉ€Žò›hiiݼy³pÌ»wï†Þµk× 6üðUK9fffW®\)™˜˜8gΜVÇ.]º@SB˜ö:uê4nÜ8ùpe–––iii¥Z»4Êb±’’’ gT5 4ˆŒŒ,v²2bÄgggooïŠçƒ½ÌbÞ'’½Ù2þ||Þ³iÓ¦@kPW„ûbccÏœ9ƒ= …'Ož”eéããsûöík×®‘Éäò/ |²_8o ‡%K–¸ººþTVjjj»wï.yn´hÑ¢cÇŽùûû3€¿ACCC'L˜!X±œwõŠ ûòS>P]ôîÝ;55µpLPPдiÓ~AÉð¹Ñz…#E">+ÂŽãêÕ«§N m „µ€Ë—/Ë_ÔC²;O>|PTTüaªò‡}j £d`_>šœß œL&/“m „µ†~ýúÑéôÜÜ\¶¶¶®ˆ <»w80|øð’7þr!”±OУGsçÎÁ&¬ƒà3¡6mÚDFF–5ÖÀŸBùp*ÕÅÒ¥K«½UDm¹Y½ogg):Õ#„úú°»V>Ÿ>Õ&i7¼´K?#¬mé_ÖṮÂŽµC„@„@„°Ry²›u!©•··t,P–f÷[3m˜>Ë-Ç{²MèËBü5øÄëNé˲Èòñ1+<¿à/Ö,k¡ÓûÞ4k<Ë}åàòö‚ÿ± è0 „5ƒC~²/¡S¬½½NÞÜ̺+œä=Ï+¥ñ?¯ZÞov!ÿë³—°Õ$)Ï^?x¶Û·ð^¼ÑgE//¶Æ›á«O>ôöfˬ”¥A¤ÏÊúê1fÐê£6éØêDVÁæcc@@«ºÔû& ¥Õ¥ç¹RÅ¢%û,kÅ•3)æIFè½pçjÖ±G Ó¬¤“¼Ø,¼®¢U`dJÓýõÏo^Þ¹ñ¬ÑPúJ•#&Ϥ;ëdR¥ª-çÞlƒ vNÀ©ô×{´|Î^¾˜[±ê‘ÜéÍNҥ¦Â?H‹ lç«|}XBªžÝ¤—,5$É25hˆ=BšŠù4Ï:t¹~àìï„ÝA•&-´ŠgAn:sèÄ_]ÝÆÃõø‚‰+6®dñͰñ¸ò‹n7™åÃJDm½]œ¡£TÄL[}TŸ°õ’gÖ%šdnYÍŸ9D[¶Tp^úÁÈ}Îú\¡xþÁdc¦4öN€æ‰KtsûôÉãspA+4ï= Ñ5þ‡S \ôÙ¶&…±wÉ[B?áÈçUwQJç£Ò'ŽËAb—§vÔ Š‚ßmÛýBcÓ¦>§¦³iq%ÙÔµS´>¤KÆïI÷Õyõެjšã³)" ®>ÛÊ ,„¢o4_­¯„ÐmOŠSZ➣JiYD“A³¦fKr(+œu’³$=R컈`Ãáï¢Ór¡WË……§Êc\îÓ:;ç‡ß¿"ïÂn+ûõÈ»ò‰4Í—y›ç}^@±žóBïÒ„j‘¥ô;“JÄ¿Œ$CzŽ2o½Ì¿§ˆ&š£½QØÏþŠçœM2¢I=Bž°ÿSý·ênö›B“–Û|‘¥:®>ó•$`¥«`1—1ÐÁ@¤Ë™ãùm×cƒYãUe~¸ÎH儦?˜k03ˆp>« 4•ö8 ÿê²0AyÎtåu¡Iò« )ú",„ßbçLlÎ-V=©}¼ÊœL™=i¦­ýþ$â:#M(-q¾­ÁJ½Üá5“³Å€?ØT@+ƒ÷g[ ÏEgMŸâ=ÍǬöiâáý:j/KÇ‘­‡×@øÂgÿKïI|–uûŸ÷á§Õ›wt³ë"Ožòpôö[_»vìùê¿sÓÝOøì½åí2V,A¤rïùÝÚÂê0‹Üá{Lœ?h’ «ü¶?¨–ã¿ì>Ì ?B´!4ÿ`1\3GJÎç'+¿µ—ì͘=Q-ïôÆ ûcÅŸwâ>ÐËDk¥â:ùdòÌAº{³¤>å(δ•º~£ö|•g·åY¹´\+5”ÖŸ¶(î|’Ñ\¾g5SRv³•˜»eDݧIe;ïôHP²z”ú߬4˜r{¤“ÝYÝ+"øÄ©ËÒz “èKÖ(¬õäÁ¦Âß$ãP¢“7ˆÅ¾Â$£«Xvexi„²·÷“í>¬“âÜìò.J²‚ö~œæí)Ån]=8Q›²|Ç9ïÉ6>[Ïx»z=ØÁ2ua³÷±Œ'²ÕQ‚ÏæÃÞsäj9ñ¥Ïï¹0üc% Ð˜Ê)¹*ô»‹ ·LÇÞ•4 —š]ÈVZÄŸctL ÍŸ!å-½w;þÒ[€yñb餰©ÑïK@?Y¨m’ìwúÉBÙöÈþAõr<œäXÈÆfS^>kåC“`[a%¢ˆ2£ fÆ,d§Üé)?ÇnСëñð؉L¿„/4i¿ðü| ç{³s^¹ìŒ÷˜ÒÉT&ŽöäWõR‰ÚTBv–O Qnþ™;)Joÿtœ*UÖ>>>&&KcRRŽéß¿¿™™ÎÿÔ©S Ú2TTTäö 1l6;11W×*99¹aÆööö$ 6!@¥=°sçνÿ^__ËÖ<2™¬¥¥UÌŒÏçcÁ{óæ Ö$ìðq8KKKÿêÕ+ssófÍš•_ vïôdà0.+88ØÂÂÂHFù ‚ÐѸqc<‹÷Ù³gvvvM›6…mBµ ‰Drï¸_lâ—\•U15jN}mZ».ú,ÃZÝÂYYY?~ÄB…µM,Óh4ìZá@nn.vìèt:V#¬(X{¬­­ñÒ‹/bkÕªKFEJÁ©+‡_¿~]4œüjÛ±cÇ_[MEEÅ:`ÏòòåËsçÎ…= „j™Ÿí>8NõtgJjg%„”j^ÛÊé%ÒigçäQ‹Úöþýû>Ä>“v¶pÀPƆ……a½d³ÙrÅŽà/”Þ¤I“_«¶üÚéo¦)ìP®\¹¯2^qì›¶lÙö5B¨‰ì\éj¯ì®™Yó«jË¼Ž²¯ï_ùnØÔeªº5¹ªQQQçÏŸoРÖ?ùõÉŸë‡üΜ|–Çãag±vu-¼ Ø­”ß/LKK ‰ÅŠîèèû!Ôü—{¸k¬]uvV Š9‘Úmµ©EX½èèh,VVV666¿ŸÛ‹£Kv%wÝ<¯WU¨àÍÅ{’Úî›]u­Qø©UUÕ¶mÛâ@ppð»wïæÍ›; !T3–»»kî®57£¿ù|w [é(«I»U±cÇŽ©©©µoß¾ä¢ùl„âãWîÛÎÞW­ï›Ùìòöñuü—/,„z•ºôÍî)KÿÍÊŸSÛwt›Qܿć`AÿÀ­ãJ&—Hª§•ÔÕÕ±"úûû»»»ÃnBÕÆ–sçjõ×£¤ÜýŸŽI0•VS.ŠÅb%¥.ý,›®¼õõÝ4½½¯âpBl:j¦/Êz³`Þ²ÂÀgÃêJ‡1S†øiD™åàÀïö¿‘"âåJ3Ïzã6wéWšñš +õè…åN)0p’pÆŒ™MSçÿ+2Wc×Ǩ^†æ=«¥t‘HtåÊ•ØØØÌÌLEEE.—Û¦M›Òlø¿Y÷§ÿ7µb;wÃÿØ”OÂ18~ƒç84캃ËÝ/â9.fíÞ—ù¸ B ­ÄM¹Š&^ÄÓ7—ö¿‘Írb–ôGEâ¼Z‰i¤r¤³ßD²(} ü›[Ê¢&½zöW¼{x냳h–9J~²ù|V§Ü¤Äނݵz¹í´U©ÀÏ/W€@õùh×”_s%„ÙJF¨'§!A°V1Ž»slèN+®h’»×•Ü2F*’+• ¾éùg…P"‘ìÝ»7''§I“& £ð[z’²n»‰³ñD…JumEÝüL°zf›È…xK$’Õ¥o|â£ú¢x¨Ç$ivsC»§ºmŠRë¹´À]"«J_i_s(°^Y?QÚš2…ƒ“æmdYØ—ä0^zNŠ J‘[[TÀíw©f?ñ ~wàhàEw‡RøéÉI·u ÛlrýA~†¯_¿&&&6nÜxÕªUžžžà‚@’››K§WžãןMV—•$'•jº« ‰˜í•]X̲úÈG + CCžæàè"ö<^ë“HbgþÄ]µ#ª¨õ8“YÜóyðàALLL£FJMRæ1]$}¤E„š»oÌ {oHAï°¦ÊílÔ‘—“ƒÔF«{SéíN•AÚè|*ZåÔø{¶*Ú27//3#·<¼¨PÖÙ²x’Á¡Õvø×B…]àp4oqÏq]‚üî`›v ÷[$…÷Ÿ<ù’uzî4F–[=$ú|aÜ<ùåSÄÒ*§?ÁëׯÉd²©©©üµHkkë#GŽXYY™™™ÁÞ BU‚““SPP>ôàSïùóç¸-e§ ° ’tù'I/œ¦ÉD0>†êx…ËAÆHç)EEêà%ÅEäîV¹Gú”ôá$l/®0j¼þ `¡ˆÄòSºèÁ©«Øì4™íÅÁq›CW?'++‹ç ÏQÂÍ—ÊIÔcºË]JbÑÙ*7¨O™b IX&;Z•Uù£y©¨¨`OYYùÆø`-Œ‹‹kРÁOçE­˜wON½kguüÓey`Ùüô­Ó‹ÚŽÞXðIOß@¹·ë¶+°d®]vºœ··¨¥ùÔÀÀ©yᢋôò3ϧ]~%ó X.µ?KÉüŒŒ¢¢¢°wÓ‚5áây¾+Ö{—o”±c«xœ§%£FU]$-!WÄ–TvY–.OO9æÝ>Ô=ÿx/ « Ø¯ ¹ž¤Øc¦7Î2½"¼zçú^£[ˆÄ‘ýEÅs£Ó¥£3k‘ˬ޾ýÌÕIh×èœÇ'ÎùñQÏ©‰9âÕ½„×é~­…ž:eê÷‡—÷Z²ªjXˬ¬¬ví¤/i`E¼v횆†ì •‹®®.>ÕèÝ»74aÍÂÇÇÇ[{}nÔyÞÞ…d/ëÎþ7MVü(Õ¾9Ëû\™ZÍŠ-.Z´èÀ[¶l)ߦMEEÅÈÈH‹Öºý–#õØâe¢‰ÇY ¸øu¿Å¯•–9ÓÛ‰¼¥OŒ G bP„Ô&Zž ¬UÒK‹Ãúä4*ûtWÒh•cÛ@TÊ;e4ñØö¹+®S¯Å#O2sr«ºU €›¯étzÛ¶mÿØm­l¡D‰RUeå$ŠÔê¿?‡»%Œ¾BÔ>žô²ðìÍvÄ¡ŠÕSÃÇϘ1ÃÄÄdÓ¦Mkd Ùrý322Ú»w/HÕz唲2!B<y„–àüãgJsªOBw>b_Ot9!q›þ7BÔh¶n~9ç1ïø­Xõ-ÇN~rd×Ö˜ÄNÊÂ"³ŒÔoŠ}IY,³É ìðéY>Ñl}²Ä˜eÏþpa¤ñ­ÑõS®áMýxÁ¥²*¶J–½©S§>zô¨ØÒcÇŽ•£ò›¸¼KD9Ìi,_Zy¥’$x'tòg|˜c´IQn|p¯"I°4¶&ÐÌßBY=›¿ƒ1.HzT'ˆŸì$A}ˆq˪Øè±±±¥Þì*ƒÏçŸ}úcÕ,l´¡MWG¤nfm-*²*b¿@¨K%¶ ö± >zô¨Xû`Ï/99ùõëר{þ矰dÂq„¨Uþ`ÆSŠYgé¡ ¡ RÌ~9ù–Z¢.iGÑÙ8A;÷ª^dm0C71¤^2vûFaün•a^ muó¿—Pñ¢ñ±ÃÕÕõýû÷SeTʵéÐMBBâšÐ¼Ûðž·ùÅ´SçNžðçëüõë×*/ƒDG¼Œ’Ñt-“@DþÆÃ'ñïßãs¯¥KÇ—e\¦àlC„”Ôª¢î:::Ø/ÌÎÎf2™zzzM›6mÕª¼ BÔbía}?5ý ´½úx³z,f‹q‰‘ØA,:›û+#4ãâc–Q¯ìÛmX&Û‚ö8GfïÜôíÌ·&ÿµ:ž`©õã|üøñY³f999­\¹‡+}štwö¿÷Ú]s{­Þ.1šNSìgVKÑxëœ9sF~o¬Ê`($ßBhd©ËÚZé¬ÞܘDñ˜a¹àÐÕ‘M8õ»7F¥?âTĆBEÑK.]JjÚcˆ1ï™~nUQõÔÔTø²!ð—AÚuûµô× Ï[ö÷—ð¨Efélv˜ô—lÀf߯¿ËÏF,—-À¾¤íéoÑl¶J¹ep8|ì¸qãÆÖ­[íeTé*¹/ñÛî›=Mý`-Ý$íέ¶Jš†ÕR:ƒÁ3fÌêÕ«±¯£®®^E¥xöBO²$mÝäÛH-pµô‘\Ã6J×yÔÆåùf{¾_eT’Û ¢¨q§y…lP`à~yà¼÷ßÀJ¯ö£G¾Þ€€œPv™ï°_½zuÆŒݺuÛ°aÃŽ;þd­¦.Ú´É—6[£ö}’0Q{Äg…V–ÝÇWçɉ$LéÁƒ÷ïßdz,KYY™N§S(•sŒjÖm­"”n~åÓÛ–Œ~úôiëÖ­+ž ™LþòåËË—/---‡ {7!”‡üw##£€€?5¡Vóû}‰¶ëØ©qj§µH_jnó䫤q™|šýìµ5|C3eà€©©)ž^»v-22²iÓ¦***¿“-›ÍÖ×ׯÒGN^¿~=|øp<Å'g©©©ØÁUPP É‹Å|2TUUñJá“ì2vpB(,x«V­Z½zu©ï¼×´¶›»¸D¼òv_;ýc¶Jªž*…k¬A²²îÞ§UßÚØzË@²!iðÉ’’‹ÅÂ*ò³ã°Ô«W/11199¹ŒOþ‰û¯nnnò‚zöì {.!ü±±±Øù‰DØùó”QóëL(]w…Weȇ¤‘‡qßxúôiBBBRRRNNv³°a¯«à#ž …8^ `/POO§íÒEúÎv×Nž<Ù¨Q#߬•J}ñâ™Lž0aB·nÝ`Àï²cǬy¿ùÎ;ð׃µ§­Œ_H‹%P>öiZZÚ¥K—âãã±§®®®¬¬üà §Ø ÍÌÌÄê‹Ó¶hÑbÀ€ÖÖÖ°9@à·HMM5kv+÷wø!…G)‹ÅؽÃ2++‹ËåbG{“ØËTPP`0ÚÚÚX/ãÉ„ŠrüøqWWWGGÇ•+WAƒÕvÍÍÍ¡@  ÉÎÎvss»vívþþÀ;ï „¯>P kïöSRRÚ¾};lG@< k¶ٜÁ²OË:À\ȳ½Ê|/[”Eí}˜þڋ㲊yG\è“­Å!<¼'Õ3žÏ3 CC€ÖHr¾H7Äô ƒÝxHB,”"F’È¿'^. ¸„e~mgîzƉ‰kþ³hª ¨ a%#•$÷ ߊ÷¦"$Xs¢r(F›ò>{ß“SŸŒz­dF¾£_…ìù}Ú¶m[óæÍ­¬¬*Ô3 B[†\PoÞ¼‰…sèС°í@¿|Þ=yòdOOÏŸ}ð80eÊ|ê}æÌ:^í«ó"‰zãNnj‚$+¥¦55¡¬ÃÐ1vÐY_C¹öv±X|çÎìra—.''»eXu°³…åM"‘È/6bä×*5557nÜ©S§Ý»wcƒ† ¶iÓ¦âeáêÉÀ.¥¥å/ÔVWW{¿¼²Ø¡ÄÓU«V-\¸Ž „Àß >ÿÅûùôéÓñ¹ó/$ß¹s'ž~øð¡GXS,XP-k±îÔ]î×O¤ÖÃÙ’Y mê„ö¾‰ŸždèMÔ¹¶ô¬j'Nœøøñ£ |Æc"£"iE"Ñ¿ÿþ«¨¨ˆ“cmР™üÓƒÐÿš "Ù7~õ­­­×¬Yƒ³ …¸þýúõ+y=!j=ªªªk×®uttR—é$ãÚÐÖ$2©Í?ŸZ¶aç¬IãÕ•kreO:…¬Y³fF2~QG¿Ä¿xž:À¶Ó¯ÖBò!:’Õ´Í/}ÄEõß-½¶=t~ïpÛ¾}û‚pDDvˆ“““œœ°Ç GBàïÁ`ìÚµkøðá§OŸþ|ÆÈH$ÎÎÎééé'Ož¤R©UWí£Þ%}Å*Xëœd3%àúÓžT­[˜ÔÀê;w.55µQ£FòfovOYú¯(0pÏ/dõ`ó’#ŸÚ°í7Ï}ìà²aÖ¶#Õ¤îÚuï±ûÞê®/SÊÒoy.ß½70pãX‡çbÝÀÀ ¥ª­ƒƒsÁŒÿ¡ÀƒN2ã#Kü6íñÚû»BX‚ äñ‹/öìÙ³hÑ"Bà/N§õïßÿÒ¥K¿°Ø¿?ÄÅÅõèÑÃÝݽ‚ϦþÁÏ’ßeQÃ6µ´Á‰z­o$VR`·6eՍдhÑBMM­*2Ïe?ÃÓ-ówtÜ=I2÷½#ô¹"_õ<°=UPÞ5I£žö]ô¥g]ªäãô*<‚S(666;wîœ2e h!!ð÷€½· .téÒåÎ;•’aÆ pàÀžžž×®]kÙ²e¥äœš-zý–hhS«œPÑ»ff¤«@«)„ììlì–dg<;=Ï3óÜuÔœIps¸úI:;yëQ«8?—õÏe 5n)––›/û¹ÇNçóB$%$þ?{gS÷Æñ3kÍTZ´O;ÑJ‘­¬á5"[)²V–Hö%$TôV–ˆ"¢ÈÖ;¯ýµû[³ERÒÞ´YÚ·Yÿgf’)J˜2å|?¹îòÜsÏ=sîùçÞ³T1a(fÐÑh>þþÜEEEòòò:u"‘HïÞ½³²²jæ”"6 wãõv雷½ý:ý‰×NJTçYÏu|\¤H¼"ŽóÕ¹%ùe§;{‹ÍÍ%ÈQ–ƒ;Ù  ¸©‡çÌ9$Ru5»Œ×…Tþ«Ê·ôñt÷`€'R áC7Z`øm góÜÐåóæÀ(Âd“Í7̰Ùâ³ð‡¢£%!õnݺÕÔ”Ÿ$„ˆvTÁîÝ»§¤¤´Fà‚~ô¡¡¡[·n½~ýº ÏV I¸|7ruªz`¯Ñb­LVµå5‹‹‹àå—@–Ç)”‘*.>Ë“Ï2˜Ô¿÷%køøé’U¹ý&™áq:œ[´)±WUƒH¤Ý€Ëu>>Oþ^qåûU ÞxD‘G/ï˜7ƨ˜M¢—œA$32ž³»®òÔô:ú®˜üTÓ]X]2dˆ¿¿ÿìÙ³­ŠH ¨‚:::™™™­w‰|Ølö”)SˆDâÑ£Gõý:}úô¤I“—o=¨-›{e–FúçÖögõPvÚuN™ê~Äcßäáö±te1½Ü{b+…|òäÉ©S§~½â½Z’žqqq¼ÿ5HÑ3H[ŽF06{ÝC¸ïèÞu+Vl<†¯;‿›×úý^÷€¢–¡X®Þ«çá‘ÎÀôx“åÐ)ã{€ÿ†ÞœbŵܫVFøEž8Bê¬i<Ž«&%ºaÍêíá'XšÝ-ê[“{­m¶zëÚ/Ý—ª÷½8K¯w°ò¹\m£öy¯Y½íð‘dž&³K î>x¾XP=~BÕÔÔê‡ÅéׯߣGp8ÜèÑ£Q¡„Ñ*=†ÜÜÜV½ ,DÞtCGŒáçççââ"8$¿,[¶ ®'åWbT¾æÇc  ÚËQ“Æn-ƒç„Cœzš®€6p’¡Ù™)LÜãþ¹½}jÒ£'³9‡.pËâcÜì*%Œ§î¿"+Á½àÙ?;#WªË_ÓÃp¼¿¸öJLLy9£ç¢c…±ó Še&~¢"å48 Sö`÷Å!ÅÅÕ¦¾ÉM¾3šL‚~î‡R EY‘§­ƒƒÃ´iÓ`õz-Ðä —K ¾l³ºÍ ‹ž×`í¼ ¶B{ðòÆAáÇ>o ="4W NÖwï—þ©_…$]§ˆ`äœÕ#ç'g°ÿhýæ°­ÑÃø+Î+ýGP¢>FÆFãEÄ `ß—ÐöîÿÓIjbbÒhŒŒLeeeuuµH†¤GBˆ5Òí"š\.h•ØïETAXÿÍÏÏoƒt055-((€+AAAÁÁÁ7nÜì‡.£¨ˆŽ VvsÞöÝ îœÎÒSx*ÁHÀÔÅ(CÏrãÉI­±[ÆÌ›-ø~eµòˆœ„àCìî´Mº¢Ã-Þ?ÃN•ºM?gGôTC·¸d%³éÝíÕ®ï\z8àöÜ•CÞ_?R^ÞmÀdí{{œ(ãÖ«\õ=ë4ÌýÜÙ°†gåžö+.Ö2wjg½Mqœð6KC±G+%,‡Ã çÑÍÍ Ö6PÙ Z¤¤¤nß¾mkk‹’ ¡xÁÿˆý;¿cc0Xûn‰%ƒE•È{#‰êæ¡ ª¨¨¶YÒyòÞSX‡•n›QÑ[R·àÿ‚õ}v9Ç[SôÝãèigV]Ú°ÿÂv×S4xHÍb8ïÕ(‡'óÝÇΤ;Ë®ÐÖðÏeÀ ¯üwî ‹™ó€!¼½¤n=¦ß;Õ•2j®Q×Ë‘AOØÙžÅ‡ddô×¢Þ2‘кe™L5jÔË—/çÌ™óúõk¸)âáaÙµ'QTX®¬ÒÒÁTÈøG©frI„=PåååÐ¥ûÅˉDKKKTê"!DüË–-ƒnØFª ªªªÀ]kK$%%­­­kjjbbb`àžåµû“¿bÿQJqWNU*“ëKC›‹íbï?Ï”xpå¡jÀû6Xàá7B¬ï£×ÔStþ‡)Æc‡§÷_Ì;G¹ˆò¨a=GàÊcùÖ謮3`¦ßZcÄM˜0ê_lllý˜×p%///22ÒØØXTç×ÍœµéÈ®¥»¢·k™p~ø¦ñ›K‘z£]~Y¢¹Kf΋>Ø%„*XQQ‘››[VVfhhø¢U0++ë'fbA !DÔ±j¯¹àéÓ§ÅYyîNAAÛkaIII#ÇEQF‚›ý£õ˜ÕÜŸ*ßÐ:ãøåÝ8;+ðÀ’ຠÒ)‚ € Ó)H¹Ÿ½ýE“d©º2î''óG{Á r‹pàÎÁ üMͦ•¬áYîgNüÐm*z®$gÞ*ŽQuõ7=?uuõµkוcÇŽ¥¥¥áp8EEEYYY‰íÙl¶´´tË_QpËWRýD2˜^ÒÕ«Ú£]~ýuÌß^ÝOd1´[)x³õífóô¶ð­t¯GŒT !â—àµ2/**âr¹b>VTAX@Ï ØÚ|³ç¼½ÓúîŒ8ûSâ¹Â=®A¿u—¸/îBë¶Q N>1 ¤ ï×<ã÷³êÍZBþÓ«`”y›%`zq¹Û¶m8p ¦¦æ/^%#îÈlû½Ð]l–=ßïË×õãùB«Ãk‡.tœÞŠ‹¡îÐ'çf:ùrD„×ÑhM¡œÎ)¹ç²#iþpuýAcáæ£û÷Id-#îy¡@†ÝÞàø®—³QmŒÑ—@ä“"Ü–;Ú¨GÌŒ>º²$~×)[¹—ɦk7XÏø×çÃæÁ?z/‚¶0÷ïßo¾3%“ÉŒ6l˜»»;*Ä"DƒÁó·£  jkk Nû]8¹Ì‰I¯DrÇøéå±Õ#gÌnûëîÞ½»%óܶ„̔⒂³<¶í¸ ˸DGÇê÷lâD';ÖšÙêr+>£Ñ™Ó„Ú±a°DVI‰ÙàyÒüô}­¬àO»ÚñK À{ƒúu´ ®A /ý®ŠŽ&fÔIÞƒãtÏ‘#xÀœ9+ [è‰ þ¹;‚*xïÞ½;555éééåååÃù ‚ !BOà'þoG@Ô××OMMý]è¢Ö™s$;Ì«cäÿíÕ_½¼í¯Ëb±D”¬®ŠÓÄÈÙ\6[w`½£ã५å²J¸ ›•`:õ‰ÞÛ+zËÜ»]ªØoŒG#Œp ¬†×„Bœ™‘µrÓ¦°ù—>***>|øPNNN^^^OOÏÜÜüÇD !D4ùÖ+¯]»—mßå§*hllüúõëß «–ù¦aL©í=H¿½à±rÙo¹´§§'t {öìùëAí}2“áÒU dü[¶fù yëöÓûa<‡¤l]~Ú9 ëŒ-eC¡ª,/Å×òG ÅÕËá+ÿÆÕ7Rg…ÜjVï>Êþqݰxá@ˆ Õ®>eÎú }#íôˇV-´Xyôê”Aº:6ú¥ jC­å¦8Žàû= !â CGG'//O„aÎàƒÁ`þûï?q¾÷{ÿçð`…Á— ª ……E||üoù® /ê>Ázÿ½d bÐ~s”nÍÛ¡¶D2‘úÏ¥¡‡‡GDDŒ€®®î¯üޤnW熺„/Žæ7Gêå~˜ß@U0^ÎÁúÞî3ÿâÿ7ˆ¿$ÔÃÒPmè5†ò÷[où¬p C¶Ô²µ.4¾±öæ°Ñ¼¶:Q.Ã-…ËÂ?¿ËüwõîU[£îv`‚ܽ{WИ„ñUUUѪ ÂLùÛ2jès_³Fûw)ÙÔx,²'Ož 4èÖ­[¿å•‘¢|'ç^µÇnßævÒ“Z>÷îËîjå~o4æÌáæR^^~ñâÅôôt)))yyyAóQ¨ð—…ŽQEEEee¥ººzÓÁHî˜P’Ú„6|Æ9ÿu§HʆþÞ ¼ŸË.»®å5îÇs%OHH044D*ˆ„Ñ.—+èNÞΧ߬¦PFÓé·#™Òïý™/ç}œŽ;;û´ýÕ½ËݶÅ`TÌÞ>»ð0(¬èß@ÊáNtú›²ÄÓ6–¾ÇuÍH¾v“ hRdûÍ{tz“p °âlkk{þüyX‚´ý-i«+­š<`ÛßÁX›¥íè—&€òÿBæ/]@ Ä$J222_I*àÕ«W/^¼¨ïƒØ”±›~ƒK=aUô„oìøøuð‚èÏÅ>%%…Á`Lœ8Q¸  !‚DZcDz³³‹ŠŠÚÿ­Àê1o ÿ›ž@5¯µ[è´…GÏå.êAQ ~’N÷¦Pœ Åö+Rètij^ï4œ[@Oå2Zõû&_ub¼Æ™÷ÞöJ ^å]¾|111"©¤ÅUxï•K¯Þyôøuš„ÅäZ@çß@“Ìʽkh¨7jÅ’ö’oLùäåå9rD“ϯ„–›› =Nèw¶^„=z´zõjXs…ú––öáÃè×Jò5&ŸÚÚZ¸TPP€n®………††† *î"¾|H222~KùÞ @…(…~¡Óu+PžXôíÖtìƒéŠëèCÕX¡  ü%ÁȘóÇŧÖuŒ{ÙäêH¸Âfr)¤o|Ð:{ö¬³³sXX™ü{º4ŒÜþUVVÒ®]§ggV~*·Ô'Ë+khéPG –2vl§Ïš5kx™áýûëׯ¿{÷NMMMUUUZZú‡ÂÑÒÒJNNÎÏÏïÓ§Ès‹`@AKl•Q£F¡B !âç¹qãFlllÇú"ˆQ7–õ‹zøÂݸoèXËØ¹ÕÉ«ºÚñ¦Æ}¾wßRù ò0[,°~S ?b9¼ó‘×ÍNp%8Ük¢Ü‹ŠŠrww÷óó“••ý]wý ‡ñhÔãÖEIIiÚ´iõ›,ëíÛ·éééeeeÕŸx]¼ÊÿÝ/\ÂÚd§Nôôôñüé‘‘‘p©­­ý£jÚˆŠŠŠ„„++«)S¦  !B4Þ¿èСì¾úÈçWsèr˜.€¬ò¶˜º¼?ʧpTX̧Ho<ÀJ€>ž®qÎÁ'¶ÙÉ›9®KÕ_§Â³ÀržW\š‡±lfò¾}ûö­ä£¨¨ˆrÑŸRáñF|~ø0o^ÝŒL?¾wïÔT¨² 222Í´¤…‡JKK øhjj0 k×®cÇŽE¿Â?‚ÚD_›K“î¯hÅæòIII°óæHBóññIIIéÞ½»-…Ðdz(åbýiFtzüß{«àˆžTwhob}‡ù·uö²ôÜsÍ_" À××wöìÙͶ0D `ɧ~“Ãá•𩪪ÌóG&“eee¡òý¢ûˆ@Bؾ¹»ãÐÊÀuv6ÕÜÿ'ûÆóGøGÚ67~#‹Å¢P(‚¾·mŒ……EÏž=»wïž’’"*K1gذa<€®!E”ÙÂo¦„Λåu³ß~¸ôŸ~ÿØõS½IÛö Ž G½7 ªo²¹¯¨Ô‰-¸Zpp0‰Dú-*(€@ @mÓÖÖÎÈÈøî'ÃZŠ9ýû÷þüyll,êïü‡P\\œ——÷ñãǪªªÚÚÚššƒÁüŒ`ÃáÀ"‘×1µ~ð& S?þªðˆN0Á <«~EÁN‡´‡¡„€{#«ÉÉÉihh())¡Ÿ aûà@XAj­À••• ÄAT²²²–,YâêêúÝžÈ-·gÌÍÍaý#""B0×¢RTTôæÍ¨pååå¥|¤¥¥edd:uê$ÇL&Ã=P¤ùˆOÌ¡$¿{÷®²²®”ó)++ÜŒ§ þòòò¿8˜* ¡H«ïë-RΜ9óìÙ3±;;$$Fiýúõ¾¾¾¢²g`ËÇ]»vyxx |.þ¼|ùòíÛ·°âÅC0¬¨ŠŠ t­tø´Ç;‚>¢Ÿfl Ç •¾à3\.Þx—.]úö틲Âö`œ_{{{q‹X¯^½zôèÑ­[7XâˆÊRœÑÕÕ4iÒÖ­[×­[‡²¥ë[< ‰¦¦¦Püúôéó&tjõøï|ñâȬ¬,¨ˆ¨åÂv|ªÃ²³³Å7àñPÛ B¤¥¥5ÿζå–⌺ºúüùóW®\€ò§X+(Ó§OGm𾉼¼¼¸‹˜‡Ûùì¤HÿÌÌÌ®^½)þQÍÈÈðôôœ;w®±±±¨,Å%%%è.X° 44åRñ‡Ã¡Dø.\.VCËÊÊ~ã,+H-"55uáÂ…/^¼hGq zþü9TX1•¥Ø ‘;v8;;GEE¡ì*&Œ1"''çÙ³gRRR°š…†MæÃ‡)))YYY½ø AB(îŒ9rïÞ½Ðlw1777755Õ×ׇB.*K±–¶áááööögΜa°.»ðøcN*ûc6`3Ä랉$Bg-C#ã MÅóG!“ɬCQÌÏÏ/((¨©©QUUUVV†Ëæ[št *** ?Ãb±`…@MM­S§NÝù  ¡¸STT4|øð—/_¶ãlÇCmÓÓÓƒËæ_UµÜRl‘8yò¤­­íåË—=´W…5g#vaÍì1*–@ÅRíBÄæÿålZ$£:dr`<}^É5—ÂüÏw!Üó,£bšÖ÷zz-wj#DBØî¸pá­[· þÀ{ïÙ³§‰‰IK>¶ÜR<*¨¦¦–ŸŸßŒÍ¥×ï±}gv¤ßÛ6‡ÃÿÁó Æ8¸šÞÞ¢¦Æÿã YÔ›yû%zºÚÜ—j7«‘ýÁs4¨7ªchæÛQít\U¤R' Úr½ÚÏq›`ø Åh‡xÿWߣRyíiÝÂÎŽQ©÷ ØÍ¬kv–¦VWˆrJă+NÑBÇeÙIÁ&Ïõæ|\¼ÒkËIZ—oá«£‘ÓŠ5zzzÐ×;v웂PáRRRZòɰ%–â TAè÷ jo¹ÇýW°ÇôÜ5–{ÜfÖáð%ë?Òœ÷Pôi*Õ94æŸEÓÇ·Aò>zôÈÒÒòçF˼¹vêMþŠÝjgnù}(o‡ãh ÷Ç;ùÕÞ PZ*¶MðOТÑvR©ö/«×tyÍ‚OÓtY/ÇO[ÿ²ÚÍM„FìãXYC{÷diŒ½ºTê$¯"6?tÒÚÉU»œ¨û—‡Ž‰š.¸ôæÙÛ1‹ÿ ±q"uyhÒqÁÄdLøofoØâõY7_¬±%þõÃt˜sôÙkß8 ´fÒöØ{Q©Âë¤û5¸º i ¡xòäÉ“;v¤§§w䛬Nè1õéKÚl¸ºØÒÿz¶£:¶EÃÂÅUBqd g¿zC¨p«V­rtt„ž_óA¶ÜR *(ÐÂõëן8qîe÷Ý»wëQBJÔd@Äèz¸­‚5e5B‡jy*¨½Ð=d-`åï›dqpå·Ñû£Ó_ÐB8mÚ´ÌÌL(„¶¶¶.\ø!E4š¾¼ðøÎ¸î³ ÉÌ,Þ#¹Àa*àwÀg¶QÖ” µðÞ:VT³YŸxfjD€!òºççUrF®]:+䯮W€}h4ÚÉ“';öm]ðîåQ7ðæî7¼/d¬Œ}¤µw÷,ÔûÚØßßÿåË—+V¬€õƒæƒm¹¥xj¡šššµµµ`óñãÇ‚Î×PÇ,ñÔŒÜÒ‹p9~û*Á¦d'ÉF‡&¬å?¸j#M°Wý "òû%Ȫ¶eòr¹ÜK—.a±X(„£GöññiÉY*Æ}¶ŸØ6~ÚZï}¬Ç Ô,fïú:­Éº Aª{¬Û¦ðþµ×áæ`E, “!»ž2§ìZÊóP+üÛ&)¹/Þ\wNMîŽíNÊvó–÷£8øïtsBàsæè)³HY?7Õa• ¶l±™¶á¦›¡äe2Ì:¼'¨‘}Z açÝõ§M=>4ò¿¾: ¡ø••åääß1nçß)ZŠºÅ·N•¹æ^˜sz~¿µéóÆÈ¼©MO ßùDgjä®Tu×A”nkéï"îû÷ÖgîÚKòßv–N¿Ä):­·D"3fœ ´=zøùùuéÒ%--­ùë¶ÜR ÉÏÏwpp¨ßœ0aÂ7ÜÝݳ¹¢®» X{ßþPÀC׸ú;Ü6R&ëël6ûÔ©SЇk+B œ8q"LR77·8‹l2S žÃwîèö%ÞûÜ72½éd ™»ÛšËXÊŽãÇÝìƒÌ ÍžÎo«½{©Û²`÷Û@YÇ:ˆ]† —Ï»¹;,«¿½×š™<n«Ëàu‘—‡ÐÎT„n ;r€¬¨ÝÞC!ð¿§²Êxþ({zÒh¼î˜²$ªyb7dsûÁ›Î”„nÝEVÓW¥>v®Ž@B(FŒ7nûöíwïÞí0wp}•îO•±”©Lî·äI.ý|îR4ŒÈ©žðÉf$ùi¸ðžç-ç+.Ð=ˆ€í¿ío¸9sIÌ©m)q8Ô¶~2l¡¥x"пàà`ÁŸ 0K‚œ-\Ò6„¸îh<Î Fv ‹ãVú»…¬¬¼«‰еUÆrcW–|ó·»|ù²È…ÐÞÞþæÍ›‘‘‘ðAìyúôé÷N’¤ñ>¦ñO¡ÕÍs-g¼çè—aÓë æGÒæ m»3>p‚&–²mͶn¤=ôÈ™¡õ‡:÷82¾Á`Ó‰Ëiy+cl» Q¤ ë¯"` màçõFöÔ[¨B›u'6¼: ¡XP\\leeõæÍ›Žu[•éÀVUAÙCÐ{++FaÚ~^í“û3ÀÊ©‘µ´||hÉæT%aúò¿†á AY5·dCò@ºô7Â}÷îÝš5k¦L™bnnÞ| Zn)VÄÄÄ|½+z9—rÙ¼0rcÀ>»¯g –œ³c]ÄŠ­ûìvñ7UçíœÕwJ1²øæ~(W£Fºr动.”žžŽæ•E !_,X0iÒ¤§‚€Sð/iôj¸’»}ÒšçhÕ{ÞÛ›},œN½w€Áð:ÇñBå‰8vÁ?òSV öx À­±ï³ÿQrSC×ùÕ«W^^^ÍG£å–bΧ©©l¨‡" “Ôs­{ÜZá=ª‹^¹ó»ìõ¸Ç-hÕ;RgæÎž0¼©£°SUU%ªÑ;E©‚ì€k­q˜¥yÜNêÄV“ì*&‡LÀBñ¡¶¶VOO¯%=©Û#Iÿžçõ?¸²w÷›%O¥ðx÷9,+ eÁÒð[þV2Œ²ÌìBÑÝx'åá   ·_ìÝá±ä±àÜá+ÏIXUNJ‹^ jR©³Ãh‘®Œ_]¸ Kåíœ÷¯Z8ä!´^€[5y<õpm ×bû {6[ñžµØ‚`'\\Cé/º‡Ý¿ÎèhÓE .ƒ„ð·?K\®²²rQQúhñ%Mc^fÁ,bæd€oÈœ›ššºnݺI“&}÷C`Ë-ÅmU\ìAvÿyíýç–!pj3Sq8ýæÍ‘#GÄJßÅtw< @ã6>ÝdÀÁk©‹†¾#.«¢¢$៣ZÃëúœpŠoOöyµh4Å`è¸yïβ”NMô"¾ÿÇ™Eµè4‡U«Ñ…oëNµÛG ˜L5s[VYÀuv¶ùFT0äð•¦Ûî—mh &sÁ » WÅžø•¯ñóúK«YÌ=»æ®Kj)ƒ„ð·ìüü|1|áó{Á:ÿüé[·n}ýúµ§§gPP¨,Å“5ó|¶ïÀ¶ãi˜ *oî[â¹°%Ʊ±±&L8wD>ýÍGóo}ôÔÕÕ333 … ÆÆÆººº¢²O¼×¬ðñÛ‰¶¼=F^Q‚SzëàÚ–© €¹sçÖÖÖÖÏêþ{‘“ÂU±¡¯*Šë^ìg~’üwÝ­hCÆR'nµ=· :÷ìiÿâNQÙÀ V¶?-Ò2r­Cñ”p5LÃ~š\6ûååóÜÁëv^?±aøš“´ŠŒ;TêºhÑß|wt7òò˜%sእ—ÝÆë*IYûhxa!`e5ê/íi†Rh2&$„¿‰7nœ>}://åƒÖ‹ÅBmëÖ­[RRRó[n)¦Z¸zùîCÑåš8rÚí(ÚÊŸÉ0?ºy¸þÐYcÇŽŸV3¦v}£2®ú$Íç }÷÷À¾~¡å@`¶Óâl âã”Fñ;\Vç\¾|!õþ?Ez‚Ö*¬¼+^IJ³«XvV*›öž2Àátø‡pªcË¢'„ãæâŠYš¨Z/_ž83"ɪöÆi#ce,`@Õ¼¼”Úm­‹àÓ^MöÅ ´gW¢¥OÆ •0ÐMZYh½¥îk`CáKsJ^RF GeÂ6¯+*’H¤ÜÜ\ îÝ»/Y²e…VåíÛ·ëׯŸ8qb¯^½De)†,žíÈd2ývH÷¶«”ï&ÎQ%ãdþ³âÄ[óVxþÜwñœœœ›7oÚØØüþ{éµä´¿]O¬vùG0ü@ÕÛÍûÏ~¢ÀŒvŽ×WÇÖvl}_¼ú¨ñêŒâß‚ÕÁu»ö¸è/äy{T—áõ¯4šÖ6¼NøŸ€M]2-Ž÷ }ÌXªpÄ‚¾„ÐØ@øÒg=vl ¢¡R a[S\\üñ#o:¨… è¥D[àëë ]=Xç •¥B 6¬áù7ïÇ¿IIý˜—Åa1Ä*†8 ’’º¶…™Iï1½À˜Ÿ¯mHJJîܹS„ÒÞ©Ÿ²˜@»mÛ]êMX!‚®‹œ²Kºë'¢¹&¶=³fÍŠˆˆàÕô&NtvvFù m022 ÒÖÖÎÊÊ•¥ØbcÕþuìôÂ… Ó§O?~üøo‰æ„€¶¿¨í„A"Ûé §%*þÂÃÃBxíÚ5” Ú, µ­{÷‰Ðy‰%â7bggÇb±Úo7PüÉe`9ËápöìÙƒ2AÛ“’’âííM¥R-,,De‰ø-Lž>% as0Jž„FœïgÂîgÊéeÀøcÓ]QŽë(; HJJòññ5jTß¾}Ee‰h  zyy¢¤@ !ü&œÀ>.1—:pQºùð`úhž"Þú×ûC v’ó&”&ooï·oß.X° 44TT–ˆ¶¡kWÔ”„ð›Xóæì©^Ó™(śº7 V‚vnò\Ž´G·nÝöîÝÛ’¾ƒ-·D´îîîèó- á7غ#vä‚߀§#ãßS;ÆM^Rð?Bm366~þüyóË´ÜÑÄÇÇ''' ¤@ !ü‚ZgJëò©¸%‚0¯_¿öõõ6lXÿþýEe‰hUTUUGŒñêÕ+”$„_ à1U5²$ú:øN\ædæcQ:4býúõ©©©nnnû÷ï•%¢U*èíííãÓ¦MÀØl6üõóóó+++kjj½æ#¹@‚"‘—‚MÁÐ3x<¾þEBý Üùõ¨4\.—Á¨kåW¿Âd29ƒ‹Å‚›µŸ{à¥á…Èd2\ úÚ“H$iiiEEEè4£fæcÒÒÞ×vÜ ¥yp™Ñ䥣¢}sœPb|¾¾þ¾}ûZòñ©å–ˆV¥S§N"óÙ³g¹¹¹%%%ÅÅÅeee rŸ‘‘‘W„£§§×^’j¶` 5xG¥¥¥Å| ^ÂÛ‘••UWWGï6:”BLÕð8úÒMî ^hö²/°j?º hÕ=Ëk4ê€Õ‚¾ƒ&&&OŸ>m~Ìå–["ZåË—CMJOOÿé®\¹e¯  @“ššZg>)•¤ùÀ[ûæQ˜zP,a"Àü 5^CCcôèÑȉlÇB(À±o,ïµIbß O F÷+ïÙýÏ-¤XŒÒ+%sÞ“ÄÏ·þ„òb ILLܶm›µµµ•••¨,­Ä­[·222tuu[~JmmmTT@0777àó'' ‡ÓàS?ËUvvvBBBff¦‹‹K«üABXçª?‚¼úNªñõD3 1«ÒÕ”ìðÉÍa–?~¹óRÁP«t„ѱ&¨]ÌϰvíÚwïÞ‰ÖÑhiiuëÖííÛ·-?ªàˆ#PÒ5¬"Xð΢„„ô&Qš´W!¬G¯ók½!¯ëY:O³z$¤)tÓ¬6Ö­í®K¶ÿV#¥%¥IøÄL Û»KN/͇ý´júi¡Œ÷«´¼×6êßý{*èçç·zõêÚÃÂz<={öDI× PŸ>} +():‚ £*“9ÖþÕm²? ÍÞj¤åu"KrôÔ«4•Yšªxi±|?Îe¼¯Ê)Äf’ÓòHòÒ¬.jŸº©¼Ó”Kí¯ úk£œ†øs©©©i¹±‘‘\B?>%%…Åbuåƒ>ô¤ÈÌÌLKK+++344TUUµ±±AY« a#pX¦‰Z<üÂcÙ3@ižRN±Na™bQ©LÁ'ÉFIŽ%/ÿ˜ 2yŽŒNŠŒ“Ñ@Ì\fU5³¬‚SVÉ-.Ç—Š+Ჯ«"W¥"W¬&[¨)÷NÏP¥€>PËXBˆM›6AyKJJjù)rrrõãÇ–——çææ¾ÿ¾¨¨Ê€ŠŠŠ ÉŒ’’™Lî`iÅ`0S Cà ‡SVV†w*hÛ»wo”þ,!l YÉ÷²jïMÔš‘.*£Œ\É©fJÕ°H,ÅÁW3yß k˜¼ªe5ƒÈæà˜lœ$׈Ddà°lŽ%g`1Ib5ÜI&TJËd$>ÁGŒª²ðÚ(à ?Ã?ÿü“ŸŸßTÛÈï?õ|½å®ªª*))©ä#˜êR]]----%%EB0[=܉Ƿu‰Çår³×ÖÖV5Æ꜌Œ Œ\ ¢-)) £ªÎe$„¿ WE$UÉ“ÐOÜÑ8q℃ƒƒ`ÐE@­ÉÅ}}}ÌÌL?æ|äåå¿kÉd2¡j²X,6›Íä#XÌM×Py 4½ðÎúœ†ÅbëûÚ vâù`0WK{ÔÎ !!¦M›V/„666HÛ PwíÚåááñ;Ë;>è‹# !¢Ýë×°"Wª««Qj´#Ðô $„„hðõõ]»v-…B9qâJvÄÎ;-,,žü°³45<ðOÍüöTr¤ñãÇ7«/FN¶ä…ýXé–Õ;óêy&^G· Aù !¢  ýw2+ÞÜÌjŒ†VÂH+I^¢ëÄå¹°Hýø›îQÉ­øÊ‹¸ù¯›)g<ØmXw"šG²e`0˜gÏžÁ•ÁƒC144´%g±?½Ëá:_|nš½¨L¢œ±Ë™ºq^-ÚîÜr’¦q×kÖž½\0НP<ñƒÿ_hÏÇ+Pœ£É¾ šºöfSׂô$¨ƒÅÿŒØ8‘º<4éØŒPÆöŸ£©–\µ›µ§á•PGáÔ¿Og¯øF¬x.iÉF§ož½]8üã‹Tê/½Ùž 䜉\EÀ ü‚„Ñ޸þ´°Ý†bûêtØû$Ja¤€‚6F»Ü*Àÿl<7óá˜is,4È(|(~PsrrîÞ½»oß>,;þ|¸ÒÌ)8¹.py3‹1«k]‡™62ä ãÔG{-áË»V²º©œo­­ä‰£p•* ›ñÍ+æ³7yÿÔ©á€@"WU³JyÓ[Ê»‰‹ô-kó›ˆÕ×§7 _8¸±GÖÌu±OÕš¸g†>Ê0HíƒÕ t¦Çxl?—?ÑËÑîÿ.gU]<þ·×2O)T“ÿAAA‚u‡³Ÿ\o²M©„œ[6éuŸÁBé­\ó É>·Öží:#2#7bvK.ŠSO‘Îã©õ{,zÊÆ^¹|ñÚ7Äfº9!ð9sô”Y¤Š¬Ž½ 8=NÎZ¹w–eU3—˜ß(VXž {àà¿{itz£ðøò~#68r´«·Bèæ»çÏ€«QnABˆh_{WV˜èú§'‘Œºìï /å¹¥‹& B£z÷î]?֌ݭ[·üüü\]]Ÿ>}Úd6ûçŸS»¼£®ÝyKè4ÞÅÎb܉y¤µÃö´uß²ÀpZò¢w†Fƒÿß\;3( z† ÏqÝ¿k·´¢Ö (Zµõ¦C6Ÿ©ÝväYQ»‡=‡"-îȶ%Þ{²´ú55–±Åʆ±`«Ëàu‘—ǸF§7_èUBYeæ±m7%;wÙµ e$„ˆv€OÄ?Øn6Ønh¶£ÏÞ!¥G ‡µ%øÀ†¥óQj4‰D’““è_ “v²Ç–ÉBCÓŒ[¼mÜâÏØÎ4¾ÈOÍþoû7ÍöóÁÏë *u’`’­ßmÇzø òs |Æ.Ø»°Î!Š&½ât3Åþâ}2+¸×Ò1£{  Ó€]—^aLÆ!lR ‡, Žúg©óx”¿Fí´%gN„86Ú[ùÔï˜Ì2×nBƒÍ²ò¦¬~»sbÄi*uæIÚ‘»^½êévšÒŠiZ¿\ÆrJ§û>;îfDBXO‡bÏ !ŽWàm]ÊXóC¿ÒäÔƒììËÙ)WˆóF3îpš´ŒbÚ]æÒ¯49|óx{fê |jÔ@^¨pbƒÒÒŒ"j$Ù ˜rU‹Z&[‚€†Óúy‚f¯Ý~ìëý{wÜ_rƒj·bÁü¡O.ý8 È¡KL@CdfS7ÒiÛú‹8NUÏ%lWÛQíÎÒâx…+›:ï?Úá9ÂWçÔ–‹¨>%ëT’ǶQG™ ¡€êl^ÔóXãÏà‡»†×³ˆËmKM#d3‘ã"JvFžÃô˜Ðf—+ØczîÚ§Ï[ÊsÏ=n˜Ùà¨ì_îGÂÄE å(AÁV.Eæ§ÙrSò´ç×oÕÿ#VÂÿ%µlÇŒ·3†J]¦úMXr‡¶{ü§g§ælŠR±œ»•HÜN¥‚]'öxøò11ÈãMº²*ü:Wó¯Ó{çÞ?:Ïïô¿UJNÎØp\ÉÈ>Âof3Qzê¿zщÂ\ð ”;@“G ¿.pŠWϺ¶ñ®‹f&²Fn<)Þìîü¼L9*j— |x3×'¦ÕÓg.ôðØ[9?Êáœ8aÐìxk¶Þ+]Od˜®2B%‚*8©,PÁ°2Æá5ó"|âh9°6÷.uð`¸-ãÐ Ç=™«r±ô8¼ö(†ÀÎ%°Õ¤áÓÅáRF×9!a†K÷ÇU¥²õ±{ŽÏGÆi~Û4’þ?v¿cË;LÖaœö ÖÓKDÕ?Écd3kÛüv;¹Ç½œ’ýÃÝüÜÃ5+×p{Ä) sÝÈ€E•+å)s®û¡Íûì(ê+“Æ[ɦš~ƒv‹¤ÑÆžÓmsR·¨ ù•·@ µauÞ>ÿrU û†®ZÀ›†›[ûéCÁÃ3Í&×{.¡euß™ÙÀd Ï#d6˜ø"ýöõÝçhªÐÇbfÎ;ÌÆqK¨ÙTÚ† ±ßüˆ(|iˆÿ\Ìr`¸jå䇼ûDfï € ë"ãÝâ³²0ŠûkÏpl¾½ã.ÚFBuÁIÚî~³÷WÁCy´åÁ¯ú¹#¹³Âi½¿ÿN#cVp=LŸƒrBôTžûç Ëýk;§æÜ¿–ÍcVqÁ"'ÆŸŒuËø+„ój€*x÷"Q»„­åÈ!‹ÿþs£##|/gë¾o¼k8yŽØå6Ë"˜Í¸l £ÀÅH…Ï\ªà(xå)¬*@tÛEÜ:ŠUpÌú?Ky§šñï¹0Vnl?òù‡ûh$„¹PóàD³MßxÇ]qª ÷iNæ‹6þ¡j”ŒQAóópj¤,üßÍÙ7$`$ÕÕ—dæ~Ògt˜ÿm×h/(ˆ€Už‘UØo¦ßI,ôÉx¢•{SÚWï*x•Þo ÆÌ½ ÏR©¼Æœ’¥œ}!nT*uÔâ…#tëm]T¿ÂXóz5`d­ˆO&rØ{ÒuçÔtaÒïéOWîí;EÐm±+3·F1õÎBÞ.]íª°Ú…¨|òøNÒw^WaAMÊ Hë(Ì…ò„õÛ‹™ý:ÌTU° €"ÈdîV÷ @ÄÈTc˜%¼º™ÊW2Ç)æhNc˜g¡ã'Ý´ßAáåJ¨Ÿy £ó¥Ød~â=HCìyïX¥ÉgiM7ªÃØŽFJñw]™Ëfó ¶ê}vü>&áx³P(ÚþtëÔ¦w%_Õ¥xnÙMŸI=—\mëèÉïK*•ä¤PqóSõžN ÷)c÷G­‡[ŸÝµê›œAž‚bïÞ=…Ï h HÝöηG¥üá]ð í8V‚'P¬@W9Úߟ4uA£XI¥2GÐêµ­á¥Ab˜ßJ·ã‚õmSC¶¯Y¹ù°ÐÕ­S7Å€á ›ý:Ûƒ4Š  ef "Vpãx¿1›à½-ð;ê^rËõxæ'úª R%Uõk„–ö'Œv/”~~¡QÌ0¼ V…xÀ—º,°¢7XôÌpÀJ•sÇÍÀqð»·'kC¿†^A O cƒ —73µàr+-ÀÂÛÌ==ñù‚M‹,PǬ‰y”Šé@öæjÌ0v #û ñú˜Í-x‘Uû fç_ˆ¯Úk ¹ÇÕ aU°g%¬Ð+wë¥Ü5ðÖ)óœ¼Eï_ 9²Ð¸äñŽ˜m#uúÓÕ%Û.¾²éJrz¨¬ùYýñOðéÚUxÐjÅ‚¦Ïèz`öƒ TªL×1Gÿvs¡\§Ro>~&d–ôöH ZAxÿ¥š¢`Ò£‰T*AÙ &<À×ÍñI^…•Ó»€ºämÏ7¾‚ûÝ`ûüÍWÇÑÿ¦—§œÐ[‚v¤§Š=•Ê’”_†µP3sÙ³p«txëÎ[ðŽqîÏD2·a“_ v˜Jzp÷÷;»Ï“"¢¢ü‡9r$\ÖÖÖ^ºt)11Q]]]SSúÐú“•Ö233sss `™››£¬‚„ð ÷Ïr&û6kÁäöÚU7ލ¿ cù~bK™Ë¦‡ãˆXúÜAwF"ƒxa~ƒC¥U¼~„¥@’ ˜Õ ¦Ô²¹¥•@V ƒé„Éú  á½ÚkgÔ¿ÕÒ?ýù”¯Î)}ucÍ7”+~ ;>‚M6›ýêÕ«wïÞååå•——C]ìܹ3\JIIIJJvŒ[†JÏb±***Š‹‹?ò·F¡Ptuu¡L$Q®@BØ$ÅUÀä[’Tï!Öfș×UÌÏåƒ]ã‚\@ÝÎAý@«Á!.û2•[Å/S9ZØOï8%…Ü÷%3ÈŒn5jÃÕ˜åÎãjØÜ€c FbÔMþÀ®È*{pÂ~‘§êà™(?ˆØÃÆáÌø|}ŠNÏÏχÊ%zNPK CIæC"‘àʪ$<¶•_Ys8(Þp £#S]] —555•|à&Œƒ´´t§Nø¨¨¨@ÇÆýÖH†=1ok€fÃQ;zPÀ£20²¿j©‰}áãx²g«œ»nšÓà”‡ :šcd°ìAæ¼§E»'–™Ë}ÌÂÒÔN¹$„ß@‡ñ^µŒÅ!'ÿ«-Ê`wµÆ(wëðw)Ζ̼ÍÀIÍ1QÚÍ'ÞæE¯ÍçGO„‚% ƒÁ€ë‚Á!(Tu¯“˜L¸_ø,¨¬V 'Gâµ .¡¼A?ý.HÛ㩸™Qœ§nPÅ¡Œâåãàâä-„¿¦3f}ýFâs>W{C^ûâq¶;óJwÖf‚¯e1Û$|¨ïPýBÀ6 W_½¿Ëö\Œk5¥°ÀËa¯ªÎ—rß¼N¬-}*Š85¿+J>|PTå€pX 2R"È*ëéw7´ ïŒ~÷v‡”ˆŽ „ÐqÓý—QåFl8› æ|ô—Míø¬+uë‡ö‘7zÏM4˜I4øêP3 ÜÎM¾‚rN‹qœ¹üû½Ñðõõ=zôhii)úEDÇBÈéómúùõ%z1ÚŽðööÖÑÑAé€@ :²‚¶Õ%‰R¼9êAq½=W,ü“Ó@EE¥  ƒùΔ"VVV111/^D¹@´!‚ç ¿Ã{«†>ÓS¦£tÿšä’I“eØü¥>ÿP¸ôÒæ})yÆ<ƒn2 >üðáÃáááÍ›=:00© h¯BÈó ñ sÁ•Ôçÿþ—jª[:´ûU–ó§¥{-qàÕ'*ÙEÄqÔ £§ôù“³`UU•©©iZZZófEEEÆ C_D»ÂzôÍç,3ç­0*sn] ”ÈéÕµ¤·ö%邎™Ò|vå¨go%Sr$õQí7Ô}\7Ê!!!ŠŠŠßUAggçE‹!D Jë!Ji¿yØøºÍ̤³/žÇ'eⵕ«Œ´Kô;?‘–(k‡²‡ûÀ´zKWz-¯"ëK™õ£el®e‰²Ü(JVVVóƒb—••™™™¡>‚¢# a#tŒ&Â?;¡=YwÒR¥e¾Ï)’ì,S£¡\«©X¦*“£$•ŽÅpolk¹j…•úùÅò¹E¸œ"›‹ÕRÅtÑÕîbh£¨¨¯h¬PûIII›6m¢Ó¿ÓlÊÃÃc̘1HÄŸ%„_£ª=þ øj?—Ã*¢?+¤¿þð¡ ¸¤¢¸”Y\g°p2’ 2[ŠÄ‘!Ã?6à2$ L<pµD€áÖˆÀ©’$²áÔ°dX “-YËÄp¹Äj`5 ƒE¨¬ÁVT²*lU-®´’$']­ Õ—•TPSTÖTÕ0ëÔ¹«ð¯/ÊJ-ÆÎÎ. 66¶›šš===4j(@BØ,^YÓþýÜél6Çs¹\” Ú &“©««›››Û¼ÙÆ ‘ "$„­Ë Aƒ455Qh3"##«ªªšWA‹E¡PÐ<‚ a[ðàÁÀŸÁM\×@GðÍ›7’’’ÍØKHH D HÛ6»îáüùó‘¶*™™™nnnÍ 6ËårUUUóóó±X,J1„°-4h`ENNe‚ÖcæÌ™K—.½|ùr36GŽ¡ÓéÈD HÛÁ{QHNNz;Úp8uuõ‚‚ï  ¦¦–••E$QŠ!$„mŠŸk×® >üÍ›7(ˆ–³gϦ¤¤4¯‚4íîÝ»ùùù(¹Â߀`L¯¸¸¸çÏŸoÞ¼åbddôèÑ£‰'6c£­­””D¥RQr!$„¿;>ÊÊÊÐ/ÁáÐÈŸ¿ tííí¡Â5csûöí¨¨¨¬¬,”\ ¡¸PTT´{÷n¸²xñb”?Í’%K&Ož|ïÞ½fl ¡ÁwçZB $„m ”@6› ]C(Š(5~.—«¢¢Ò|Ò%$$lÞ¼}ŽE HÅ‹ò7M:%H ¹Æ§yìÓ§O\\ÜÙ³gQr!$„âôZjjj444¾;&baaqåÊ•áÇ7e––6gΜøøx”V a»ARRª »»ûäÉ“‡Šä›[[['$$4c3lذýû÷ߺu %@BØþØ·o,ë»uëööí[”ذaƒ••U3*˜ŸŸ?vìØgÏž¡´B HÛ1òòòPíííׯ_onnŽD€²²raa!ƒiÊ`Ê”)kÖ¬A*ˆ@ vΜ9C§Óûôéƒ>t=zô(22²™v1ЇîÛ·okûЖÊÿèt4&@BØvP(¨‚ÖÖÖ‡ÖÕÕý3ÞþñãÇ÷íÛ×”««ëÔ©S[ÿMrÛÿkH¡\£Ó%QfE H[[·n%''=úÒ¥KÔWVVš››7£pUUUÝ»wÏÉÉù¥ËpŠ(ú«èi‡YY{÷xd±jõØõFcWýwÀƒw¼F·£Ó/œ™¦šþö¤ð¤'$ô"M{н÷ÞNïô,Ø vÑ=DEÅÞõT¬ØõÔSì½+öÞ±@@jBHÝo’ ¢"¢‡€òô6»³mv6ï¾ÙÍîà’Ã…ª-v7txéC+hcª²0ìåÍs}ŠÆV´ÑFl–´Þúh…þÑZ\œz!]›êäÔýOén4Z@ Ì~wîܹxñâèÑ£7nÜøk¯©££#p_º «Z­vvvήçž½fÌìÿÑŽÀmq«gs#·Lkê»…èmoñZÃx×éö‰”6Öé;7/äŽ(Õçì3ýi©‡Ö°ïúÍï?ò’~*²ó—/Q=Þ´C¥‰ÐVðGñ4pss»}û¶©©é¯·‚wïÞ ˆˆˆøRÀÀ@›l|šîÚSÊåvlB4uv,2göÍq—ÍÑõØ}i:¯V2Y¨oªÊ뤭î3IdžþmÊcRJDÈŠmQ“ýxô}ê³£ŽµËE¥ˆ~ÊB3Â-,,,$$äÔ©S³gÏþ•Ö«M›6óæÍÛ²eK†Cu:½½}æ¿ øÍÝ9­úŒ·¸N“ì„0¢‰h†ÏŠ•ÁÃvÝ3e‘? “F}}ín¯'Dÿ;–ß]IË“„Ön=úÖL@¼ÖHMí±©òv¯ÀГێ_@®¥ƒƒÃ«W¯x<ÞϾ:J¥²X±bt]¾T`åÊ•III?âÖäÝ÷¾ê®ÿ;Sÿ¢z µíîëHÿ‡Ž;+§ÿ`|ës^ê£ÿ;5-=½%}?©TŠf „9-222(((66ÖÇÇçç]‹Õ«WÓl/“(HÁððp.÷‡·™¤³Uþˆv„¹àÈÒ1§oa¨ððúÝmœl‚¥ ÌHC'§}R© Ñ¥¿.?öÜ’Vƒ¼ŽSâ!•îöêÕܩȘ9sli¶”½ßæ —Ç ‚ ‡îرãæÍ›QQQ9³0f ×íE[œ7´¼“«ϥӜjó™{œ1£½I ýuùÓ_—0`ƶ[R.}«¿1wÔ®¡W­kfi¼ŠÄËËëgYëgÏžy{{gòÈxggç'OžtîÜ-i² »m|¥ HÒq–Ç8B’ÂIš1±‡yõ}>»._·#A0__yB y©£aräåSRKúÞÇÇG­V;88DFFæý•þã?Æ’áУGîÞ½O¤Â|AñpO‘výiGئiø ÉçÙ5ëßn[ðÇèC©Ïf~t]¾ê!ãØË8bkòúñ2ußýïïûÅãñh;vl½zõZ¶l™7×W«ÕÒT/“h]´hÑk×®5nÜmóQ¹ÞÏÚ6òi¿rÍã˯D„)©;_Óo|óÕŸL<Çúèºüä‹óÜGM1Žè=²lõú³^J_|2ÁÙ³g'''»¹¹………嵕ݱcÇ‹/¾¯^½:wîܧOŸ¢Ua~"(%Õ3íÊ5õ·Rq–Jõ§Íüg®0 ¶O]¾iÝ•ï¿L\ºúîÒk/3¬GSSS{ôè1xð`OOÏ<²¢%J” ©žD"Ép¨»»ûáÇ¿ô#BÂ_â):$‹e¶*1ë±Õös/kÚeV7nŒŽŽ.W®Ü;wrwå"""ºuëöèÑ£ ‡>|øÐÛÛûæÍ›h€@˜‰*IOWÊbÙáû ÏZI;;;›7o>þü’%KæÊšÑ¬”榧OŸÎphíÚµ7lØpäÈ4@ „åàÁƒaaaõêÕ;uêTNΗaãMÑ2úúõëÎ;_ºt á‡sss£Q°jÕª{öìqrrÊ9:tèìÙ³_Š‚mÚ´™>}:¢ BŽ ½qㆷ·÷®]»~èŒ*V¬xüøñfÍš}>(66¶nݺ÷îÝÃæBÈ4DÑ(X¼xñË—/[ZZfûôß¾}Û¤In3Ú«W¯¾}û"  æ²Ç³X,†a²qš HII îÞ½»ñõó2ëÖ­Cå þš6nܨP(h‚wîÜÉ0á/kÿþý 6¤–––Û·oG… æ/ 40vÔ®]»xñâ¨Â|G$)ŠL± „¿²ýû÷wêÔiÛ¶m¨ Âü¨Aƒñññø^ðSIIIoœztûÊ‹xvaþ+gn„„-7c'KØ)æœd!+5ç‰YjF–™gûd“ç™þˆÉþG28Y'I6¼&hÍ^i ½ÑÚs—ðhIÃ6ŸÏÇ „ÙïuØãÓ{VÊSõL.–<«BH!6¿øZK¬¼¸Tl9ýïðI_zøqf:s†„**œVÔ(ælS·]_ 윀@øŸÜ9tàâ³¶¦‡K žv"À¶ÎÓhÜ®*ºEÿ“B6O½¤¨t.µÆï½::Dåá7H|ó|ÕÊ•uL.zˆn–ËciŸ.™Wp‘à‘¯ìÆ1“Ñ ê+Ôß7–’Ç%O:j¿cܳÆYwÐ8Ío#«•Wª‹®Óÿdï’C²úÜ’=‡p8쮀@˜™˜G§6nbµnŒµ*g樉ã¹,¸ÕRœ««I•$o#õ•©üû©= gKÙ_*"nw“de:YñÉÔ2ŸuÔLr’“QKwnLê2rü.—wáçA«ž=ý/o« QÖÊœœ¯"N_{açDÚº2&ŽoxÐKc¨ÓuE¾×9Mk¦®©§Ñç©<µ3[qDý«ÒŸÃë¾Z|ò ËÞAs±O*ÿ³p×'Xtø‡-ЖRÚºùl¿ÒËkLŽ´)œ:ËD<݉Á)nB뽟¨%c~“,Ê|Ô=eM°É°~òñvLÙ‰¨ZJh#]ÎTµ=7f¬ÕÒˆe;C”íû Ä~ „ÜÝ2<5âÆ8ë[9?ki(Œˆ°&†“„­üâÕU/ñS ¹¼[ì{Ÿµ¬ƒrð?ÂþÎò•…Iéµ·RªÎBά˜¦P®°ºK fØnA™]œO¿êdX4 vo¤|~NÐxŽ˜¦t›×K‚ÃÉ´æÊ€ƒcBóEU=”mbùµfIÂ}eO¨Üþ­”@“aü„caM oÄJIÓÿÉÎ] ¡N7³‰ºLÆL¡Ó,UΛ¶MÜ×7Ý {Ý]YÂßSOHP]]W¸#÷M?3ß´ðšbf[{/ ’]sú´…E©¹2÷7Ñ4êö×d5\/Ö‡äÚÚ²—H¼ŽLº¯O¹ÿ£W!—Ø*s}þv¤ƒJÌ›uã£]¬xΠ‹Z:å-íŸÁ9¿ÖîêŠ8Å7qi^¹$œX”Wô®¤ÝqPð„æu±ÿMç ù4þѵ£æðš\¼Ræ=M¿ýͱšœ3[$iØKV*}ŒÔ²‚¸Òþ_Ϩ6®7í#sxu¢_òl$ tËú(ZZ0¹µ²ña‰ŒriQ&/´»6ju}òi ´-Z}ÁÖ>#¬rÿZ‰Þ«ø|2lC½>;ÈÑe% ~BÉ÷8I^LÐOMcˆAŒŠý”Ñ{wƒ´s9µŠks`e-‹¤“/ßõ&Ç0Bk©Œ_{0äÛ@H˜8{ÎTÎëå¹»'X>!ÛÖJî•PÊò·Æë&×ÔN>ΓúÊiÏê=e—ÖKîWq#¸ÇlSÏÕ%N«8Òþê ËO:Î+ÄæL~¤“ü(XŽÜÄ?óá*M2çãc›–™ùîžžð!8¹ˆ_úÊwo”\o(›êÀvZÍ™ÇÜ(¦²çêú»°·D1«ÖŽ—•d³æ üjiŽó/L[/¹QT%ˆâncÔ»¨£ ë\b¼$ÜËn);+²œæêk›URùز¢b—«Ë0ænk¤ ½ÆMà ù:Rc&Í\>Cåe¶ÏRçÎèXÄ*-óª®vñÐîÜÁîW]z†Ÿþwvý/?µ¡ZǨ§_òQ |­$VéÎÌÕ¼Ôÿ,"LXµÛJýÝÙœ$ƒûÊ} èCã+}¬š¡=Áý_}ÎÍ•ä¨`P º †ù:.fœÅ±¬ÿÙb¦é_]Ý· ãy’=ŠA5õËy5@¢$ê?vrC}ä|¢*<[H**‰¹¦W}%»;1ÏÔÄ.÷(-Ðõn¢µ×¸éØ{PoЄya=BÏëb²#fÏþpëN=Ñ"²ïó[c³2¸uLºòêx^±Ùêi>é½ ‘݉¤Ç»çì^ð•©ßòæé4ÆŒPꫪ;Cœ>#ØV=.ŒÃ«¦*}Q¸&„åÛOyèÝ ’.¶oØLÉô‹ÁhuŸ¶ Úãn›vïùó£;¸&'»Üº^†+ZÛ³_çMm c×ÂÜ<»Òÿ‡VYqR"óöåìÌq<÷“î{±Ô*%c#h! 2ã*g‘‡>xvî.wš.¹ä¡*İ_Q./ò•kUDŽÊ$!>2 ‘®æYfHºHMÃ[B ›÷>ØÆð]a­9Êßê#»µ‘ôúGÐF̾f¯ÜVSÿϧœ.Á^U5Ý¥¢ËÃX›s¼Õ0<ñ†äî•J9ïï3Ö¬ß,úziëäaÉ]%;-9 93ßM•XGRÈo½çðtÒ?õ¹Õ¬‘ú·Æž.iƒ©¯þžžÒþº÷ƒ>)ÿ¢Ÿ¾Wû2úKat’5H—±}òûwžµÚ'ÝÛôé QZùj i5’nväPÚt ‘a[•·‡ÚÛCÈo²®ƒÒ…MFJ8”M}øáZq±‰Ž}É^™“?êxfÚb׋ÂëïY»'vW@ üºê]'W§×Ê¿þ]5ém éhò;ù‡Î±jó”ÊyMz¨óCs9qHx£WNÜeôµiïJz"µzŒ#,쨀@ømØó6Cés唫{çŸ{œâd’\‹wÒõæÆ\ûI£ ë]žG>Ê3Ñ ÅŒ‚OD ÏÆÖi”µ+)ÑdØ(I„ÿý£^`Q¥‹Úctêˆ+›Ÿ>¸ù$J£±*j–èÀ5Õ½‘h£MÙrSŽ\ÈJEƒø¡ä:“$DζIbÛ%sí_ËM_$‹ÝDÑŜ̋–õ(V¾C1Ô þÈ<‘çX½'ý_'ƒ¤NGäDûk ØUd¢oä…%‹Å&vØë0Æ 6‘8ëÿÿrNž<Éçó› ,δ æ?­ZµR©T>>>³fÍBm æ;))úË[6mÚ„@€@˜ïœâèèlìîØ±#*0±°°xß}üøqTa¾S´hѧOŸÒŽÕ«W£6óÐÐPKKËŽ¨ Â|Çøí(¾@ Ì¿Š-:sæLÔá¤Ñhß¿õèÊÁÇQJN¬+÷•'É”-3eËé«„-ÏÅe»ïMxoúe}²w²ÌR3²Ìê44 ¶5=ü«nž‰•˜ö×y7•JwAfÅô™¢†Å“¨ á=ײZý‰Ãâ¿îÙ4WÞËð«ÿ‹upµq«ˆ}òu œ0iœõá_xóx4—W¸%i9G’y±ºµä§O¤¶ƒ„4 46W{òå†ëÌÞ±²û¸å؇ _Âîæ»~ù-t`¼ì“>Rß´>ÝûȺ£BE…Ô˜ —-Î7m·»dó³K‹Tï€Ýòi |ºÅ‘û›0?»û*!äß@hãÞN{e‡•W~2Ÿ¬%¦ßr4•ŽðsãG€Z5KÇc¾óg–:BòÒm¬-±@þ „žèï„ßXnÌ £‰ã· 9VS—Åò‰Ek¬SyÿŠö’•ÊÙ[Œn\/nÚGæ…x¶$HòÉâŇ '2Ê¥E™¼Pç·­û{v‹}òo ¤:vý3&ä€-÷m®/IïUü>ªok¨—,/Wïç‹gY$õp€˜øÊs}Ù¡udа<ö`ÈçЦˆGhÁÁÜðE–œÄÜ]’, Ÿmk%÷J(åù[ãu“kj'çI}åÑ„u.1^îe·”•˜b’¾ U§ÎqšôÔ—¯ÞSæÂ&“Žó ±9“é¤õÑT+ãÝÆ™S™©\Ne&ð8ÅâÄZ¯yú›Öi [Ú]sé_1«¹üE°äF1•=W7¢º¦r€¤M=•Z­õvय़!h°æ üjiŽó/LxÛ´ìô“*þZè~„5É]ëwŠ-ŸvÏRãâ]Z/¹S\Åà³M=WOçcËŠfˆ+W£ i¡Õ‘Mú›Šò{ ¤ª¶vÙõÞ…µµçrm!t,b•vªÒ«ºÚÅC»s»_uMè~ !ìä†úÈùDUx¶P㦱¯›âS]7Ђ³&ݦ6Tëõô bBôÅ&*«Ay¥%›4ÞÆ{à+£Qöl€$•¤|2çA5T4ŽjxÂÚ)~†/fˆÞÏŽTR¤•3Óô¯®î[†ñ<É”ÑtÝÂ}ì+–ß) ï°*µ‘w7´Ÿ'‹jÕL©§pwbž©‰]îý6#ʾÓõXÛ~ãðc@ |Ç­Z'תífLnµŠÏRç°?¾ç'‹h KaÁ#Œáú’»QlZÑ{þTG«ùtlM<¯Ô¿ì õ4ïm¢‘×L G×ü8òÑ™¨3ÌÅX¬w½?šÝ§¥­.ãI©Ò]S½\ÚÆ^ýxÞ†XŸœLìrëz6iBï?ÛõnáX ». ~ü ÏæŽ4ãÍÝz›÷žj±šÇÒäìüq<טÌ}.¸­¦þžO9]‚½ªª….õ¼d"O)Ž` ë}X|µŠDÉXï/AQÇq×¾$3æ±’lé¢.$âȺíª²u–&3/¨V·ô[ÑBwFâÇVqåÏî}‰þ¢+¬5Gù[}d{ÈŒ«œEÚô“ZÓX[)Xàíüõo<—‡±6ç|«aó·©ûu°2Ð;- ~Q²MG–m÷¤åê­‡š™/Ï»™c³ÞT‰u$…üÖÛxN'ýSŸyÍ©+.›ú°ì‡’×Ó~oHÿŒå-U/úéÿ¶/“¿yVšþV„T4¾U>-¦ÿ3Òðæ®qôúÑ«õþp1Ë}ßÝég—ÆVåí¡ööЇê’d >›ñPÜñÐß¶{ò½?¬0®ŽKÚ\©¯‚èØ—ì•9ù„«pÓz[_–mYÕá·¦C±»a–X«3nRÚqcwÀ‘»),Žã<üÑ3­Ú<¥òF^“ꟴÒnŸ5?Í1µÐޤȤ؉C½Rr`yb%U¶GV)n­iôÇÌ1l<’¿KÅö¾õÏ'x~rùÙ+wRamqhiÎí37æZž‚,´ÿW~àX¾–BZëëSjÐâFÁ×ÂêgKǧZ¥­+´5˜g‚ý³Gáúƒ ×OëN¸·ÿÉísOÂÂv…ÄÉ.ÂX‰.ÆL-f%›±ebv ÚÄCE’µâd¶µŒe“ÌsŒHµxš(¶çÅ- *V¦bÁª¿ÿÎâ –ðDz(Óª*ýŸá0y$IÉþ›—V©RåêÕ«Y)é^ÿ·ý›9;Èæ„Ю"}#{K~¡ÀB(q²ÅW€@˜G‰ôÿ³ÛµWZbëž•’·î=î2ÈÿÂ… Ù¿jY[jî†ã£GF[BÈi*•þ’΄„„ÜZ€   ú:uêTB@ „\P­Z5úúàÁƒððpggçœ_€ÒW6›m„ nÞLûÕc—.]~È·£_£Ñè¶?wî\$…€@9Êø½¨Q®|;jü^Ôߎ!äB:haaaì~õê•N§Ëá¯(:D€Æ`ã+¶ BöèÖ­Û¬Y³¾ZÌÃÃcïÞ½íÛ·û6ûŸ­¸páBww÷]»v)RäKevìØA_—/_îççŒ „ðŸÐ¬®jÕª6lزeËW =úùóçÿüóψ‚”·V«8pàíÛ··oßþ¥‹qÐŽ3gÎŒ1¢xñâsçÎurrÂBȪ¿ÿþ{É’%—.]zóæ+¿ÍOHH¨R¥Ê¤I“h°Éãp8+V¬ †ó‘={ö|ýú5ˆ666®S§Îõë×i Ÿýõ×¢E‹fΜi¼¬2À0LëÖ­Ë”)8`À€Ì ÓD³À«W¯>}ú4ç•Ïç¯_¿žv(ŠÎ;§¤¤lÞ¼ÙÜÜüKásŠí¾qãM…BáÂ… K–,‰„ ÷øñcOOÏ={öìß¿ÿ«…»uëfii¹lÙ².]ºäú’‹D"ã©Á¤¤$Åi€Ü¸q£‰Éou]±bÅÓ§O»çÏŸ?yòä‰'Ž;mó©yóæmÛ¶íÌ™3_=·æá᜕S†9ÏÌÌlß¾}´ƒ®H›6mìí탂‚A&£Œ4 4©>|x||­VÛ¸qã Ð|hÔ¨Q™^°`M³Î;“÷WÍÚÚúرc´#22²sçÎ¥J•Z±b‡“Ù!Š-bì^½zõ¸qã†ú×_q¹hZ€@øË¹~ýz£FNd^R£Ñ4iÒ¤^½z4$Œ1â§[S¼¹l§NjÕª5þü¯þ´±¯íˆŠŠ¢k}ÿþ}z@Ðrð§7eÊ”S§N=z4...ó’—.]jÙ²åÉ“'¿, nnn×®]£4ªÑˆØºuëÀÀÀ¯Žeoo¿uëVc÷¶mÛhP¤ãÒ39 €@˜)•Jš uëÖí/ƒÌ O˜0áÖ­[û÷ïÿA?Ì]¥K—¦±Ð˜wìØ±gÏž“'OÎʈ¿ÐŽÄÄÄAƒ:tˆ¦‰mÛ¶EëÂ<íìÙ³íÛ·¿páBhhhæ%“’’ªU«6jÔ¨3f䇚©T©RXXí8þ|çν½½ÇŸ•ÍÍÍ—/_nì>pàÀðáÃéAÆÜ¹s­¬¬ÐÞ0ñññyøðáž={bcc3/¹{÷î!C†\»víÁƒù°¢jÖ¬A;Ž?Þµk׉'ÒØ–Åq[ïûÇ·nÝ:{ôèæ„¹F&“ÑÄnĈY9Ö«W/.—»zõjš5¢ê6lh¼&vß¾}={ö¤د_¿,ŽËçógÃ}ÝFŽéææ¶`Á‚\y"# æS¤-44ôÞ½{™— ¯\¹rPPÍ`PoŸkݺµñb¢íÛ·÷ïßñâÅß”äÕ©SÇxaŽN§›:uêìÙ³§OŸ>tèPT, þ(ƒŽß²eËWï º|ùòeË–]¼xñ«%ž-l¼uΆ † F:uê”õÑÙlö$Ú}ûömš&Òü÷uÂlCƒMì¦M›Fc[æ%ijÒ¬Y3ZØøXø&ýÈ'L˜@ãb«V­¾i åË—?~ü¸±{þüùýõ×ÿþ÷¿ñãdzX,T/ ~³;wÒôâúõëÏŸ?ϼ¤ñ·óG ÐJþ»ÄðpÄ€€š…7lØð['òþ¾n/^¼1bDttôܹs===Q½€@øu½zõâóù+W®üêwt“&Mº|ùòÁƒ¿úÛyøà hÇÌ™3—,Y²}ûö5j|ëD\]]÷ìÙcì^½z5Mû÷ï?eÊÜ× ?Y¹rååË—õò¹\N?‘éçéÔ©SÑ,rÀxÚñ×_ÿóÏ?îîîß1÷÷u‹‰‰¡ùâµk×hÒÙ¤IÔ0ä÷@¸víÚ3f„††ë–‰//¯+W®Üºu "ç½ÞáØ±c8°k×®ï»(ÆÖÖ–Tc7Ȉ#Úµk‡ûº@~ „;wvvvž?~ïÞ½3/Ió?…B±qãÆ¨¨(4…\7Û@§Ó 6ìÂ… 4˜¹ºº~ߤ:ÃÏC ´ÿþ ûü²ÐøÌ¿­[·6›‰ÈÈÈ*Uª,^¼xåÊ•hy ›Í¦›†wÕ·o߇Ò êààð}S“H$Ë h÷‘#G†^½zõY³fÑôU ¿N \ºtéêÕ«iñÕgþÑÁÐÐP©TŠmŸÇq8ºY‰áÖçÝ»wŽŽÞ²e‹µµõwO°I“&Æ›áïë¶fÍš9sæôêÕ U €@ø³Òét-Z´ ¹Ý´iÓ† ’yÉ6mÚ”)S&00ÐËË [ýç"Œ'ÿäryÇŽÕjõ¦M›LMM¿{‚éïëvñâEš&:::.X°à»¿‰Â\@?=zèС¬”_  6 ?Bš 6jÔß‹æë†Ëå)Rõ¹ýýýsw¹ëÕ«—Åe°´´Ìõ¥Í:??¿ŸeQs·V[·níêêšíËZËOÔò{Fèàðsì®3gþ4+ææ?SÓ)ßvr®Î¾ìÎöÉé'››ëu{ïd?K @ @ @ @ @ @ @ @ @ @ @ üÏmâ“ÇŠ—ªŽ¿z ÔEúO­ÒzÔ«J¦œÔûw\šè'½¹¦ôþØV~Y„//ù}n×÷÷!_*ðt³Ë¦'šï ­è¾ý©r’Ÿ”mˆ¼ÓüYŃ|»5Cs@ üù<»²Zªé›IëŠT<<¡-Ò A'Žeé’E–’Y}Cã5Õ,¹Œâ°–®í𢭠æªýþþNôo‹a/ªš?õŸÖ¨Ÿ¯Ôê±>eô›t•&‘iëæ6Ë÷Ï?ô]É«ýýW·ñ*i}¡³š¾~£üéè.ï÷*­î‘e©~µi‘+ªRµk{F-, äÐö ÕôyøÏ`BLKˆXh+„y¿•ß„¿÷Îv:°Ú§êè~Ÿÿs¼Ôú^ËùûÆ1ÄMõÁþ=‘Õ°zxX}e’ŽôÚõTiZý4ÂÜÀâëÓ¹T1å¤Ê“õ=Ò ,dInJŸûi™OGZ!äæ»ÞéëR³2g§¦»É•§Ÿ®×Ò»±;J¸0ÒqókÅ’ñ?v6:ÎÐÖ†íˆ(!dim?vOD^–Æ{´Ú68*eêD9¶aNBkSBN-+™Z½ã¥K÷ˆ¨#ÛØ_ýôÆ•YÿJ‰…çÂq¡=¶ï ¬aò:Ãi8Û Èã57n•0-ò[±ÂÖg¯‡ÞÈB`c»”’3›&Ò™Z²ÑN²ÇßÝï¼ s££ÿl ÙçåxX-_²!Q¡tßÙJ†¶t „D|[kÖ±é1Q\*É$¦_¼ˆû¼øh6"aNõ×Ãm+ë^º´É¾Dÿ~]ýhûr¿IÞîú÷àÃR5—tiTöç5mAÐÄ#„_°xϧP¼ë=×ÅåÿÝãS«W«†­nTŒ©yàß1B³em…™Ïºmÿi·MlÙZI¶_W€FÁá[#Š™’ó ͵kY7B?à­xhKñoë"ô©½B4´¥¨p“ÄQÃõ‰—.‰?ÕË&^ ö Š¡Ç31ç%+–›¾‰gÕÕ¹\ZØLÉ -{û¥›,üGÛwpˆY ‚TÅ‘1ä¸}È}–%}c’:plŠ“¹§¢ý­ŸbÛ*ó½­SÚº±¼»Ùn—X鵩‡Å!‘¶›½‰×tâ\äCò8߬Ul§Hë5ÓmHH4aØ4 Ö´ß< “ãüˆWÇÄñ2M×~©[WYú©5þ½>M<͵_w‚ÝÔ+Q§9‘–ƒãÕ%{&¨bˆÓ-žÞ½@ûmgØmû'í]iñ?µfú`-‚5û&Fo5ŸÑ¹À’­‰4 þ1å­ö9Qà“Úޥ¹iËz¼;q+RÕn—ôï*ɽÛì–lÚãùóç†!i©œH]«ìÃ$üXB&ôU9¦Æ“®V/UÄÅТݛÊK´MÚºÀÍ„wcµK✖lÚeNz}zrë ¶¨îÛÖ”´[!<±Öòm²~AeÚÏÏ`Ƕ¨Alã¶*»Ëf«š“Áq´g¥¦r×ÂüÑÿi%*úvÓ_Ö FÅ`ƒ fˆ ØuNØgÇ’†þíT¢ŸôÁ†…{<²ÍÂáú£]Úß0Ã9ÂìЩ½vÖn“§É EMÉÍù¶´OËÒÌu!ñm †Å±ÔßåÀwO„×OÌÝkM_g´s4¾]ºH0kÔ»{#¤ê'k)$Qï ‡½!Ä4m¨*ÝåSls_@ˆ>ŽígUÀ+zI2´­}+Èhñl8$öžNãv!’t·b0¶–nIHDôeÓ)SlËÕŒ(&Ä–@ ü˜äU×*ø5Hö÷/ÙwB¸Ÿ¥záý/3£“«Âár¿“h‘£ œ<‡K߬z¯ô‰®¦Küúþ%¦%Š™þEÝ*±ÐÜa<ÖZzUëydþ³@í#ÿàç~=›—hÿ¯ÿÊ~¢¡üw…ú¾)yÔqA×´ å>,šF”ZýSþd2´¥cÅi‘%>ÉÍåM-ŒAŽ£\òïÛϧt‘%ª÷vöX} »8ÖqÓI+fT4í^ÜÙ0_YÔ §éß®« ›¡ëï.KöE'2teâð¾æC[Jhk«`BnÙ J›~†‹7x}œww+Cy2pÍ/?Š”ÚÉð~fÆn>6;á–údXÓ£obÒaÍŒ ¾~·¿TÒ¥Åå­þ«µ©iÕþÚ»(Hîo*[gØËVi«jx½¹ÚÅÅKjÉVnZRáYŠóØ1ÇD¬äUKjEÄÅö\Ù£IK}¡}ýýÉÀ‰Ò¢±Sµd Nöd‹¡Û">écR)aIHBÚ›Šµ g‡¤ë¿õ£òµ¦xo~ȇž³#<‰þ×ôEÿë“tåDý*cÑ»±–¤”/ ùÂ"2Z<¶eê’ô ¦M{[.~IH¼a²2lkÂlvéD…?ú@ÈùŽœ4këÞQ_¼É({ì°‰SùùÙ¿ïuþiêŸV¯ã‰E…ë”þÁ ó_¶ÀoÈïÜ¡~~ý.­pŠÒHíiu]M3B}úXDðFCq.0W(”Zþ»ôŽmÖ¹±Ò홢%ÑŸwa]Úéí»ËW-žÖ±²ãþ°¤VnißM™óHªŽ2:Éw'žœádè,OÈïÆž…‹»ÄkIúó‘|•íäçÁÖ~œ®eGüLÐÑÕ)FE,Þ]qP¹çCÿÂeþ $, Nò: ñçUh2·)‹Èî÷e7>S¶†Û~ÿ‚Íý¤Æ/3ÛüÖiÁ–õãÿèI»FfPûnÊ%ÍHo©µ±“vº(íÜ!KBäÏŒ=^Kã+àWÔ„¹¥@­é›OßÕ´È»‚1]š4ùûúì]<Ó)ž¸ôõ cÍü§&ú­¦Fõé¹øðÍMõ·YØ'5p†¿“ZàÔòÏãîfÏX2¹µþ² Òtij­Wxô&Ö£ÛÍæÅ>É*œ[o÷÷ŸÝ}ܳ“1n5qÃmÂÜÂ5²uR5•ú™eì#.µº“qÐÝ{¼ô}ìšä÷ÔØÅ/8}DÁS°+ã3¡ŒÏ»w¿OÒ?^b²¡[Øuà­÷ÅüéoÞmWÿ‚ámßÎÔ Dv··{¯ƒh%„¹©§ï«Üšµ¤ìêVWŒ ærV˜‹¡Q ?BGéêË^‘‘ôÅïgYÚÛ{'c“@þ „'ú îò9???Täß@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@€@ðÃ!Ã0¨€ü˜¢ B@BH ! !$„€„B@BH !ü‚d2Ùƒ"##ããã“““SRR”J%Ï€Ãá°Ùl.—kì …5N§Ó¨ÕjcI‘H$‹­­­ÝÜÜJ—.Íçó3™\.úôéëׯccci·B¡ aF(ÒgG‹ÑÉËggœµÊ€v:G333:Ó‚ –,Y2ó™ !€<‡&B111>ŒŠŠJJJ¢ ÍÇhþCS,š Ñ$ǘ‰ÑWc²D;èXÆdŒ¢©‘úc7‹Å¢ãšššÒL©@… vttL?G:ý“'O>yò„vXZZÒbVVV4û27ø«“˜˜xþüyšUÆÅÅÑõ¢}è,è+í¦ëbccCßJ$º¦ÙR‡4¥3¥µ—ðöí[š^:;;תU‹f§h`€„¾?[£©M0 …ñ¼eÌÊhEš³™˜˜ÏheÍÙ._¾|ïÞ½ØØX@@³#šŒÑ4ŒN³€Aö.?]xšøÝ¾}›¦L4Y¢YÍ‹+Fç[±bÅTit]èÑê¢Ù©‹‹‹““ÍQéLè–¢3µ0puu5öyùò%­júJ sçδ¶Ñž !@6{ûêþíckoEI-Ƴ張à$YpMÙ26aP?Òv¢Ö4Ag– 5ÖØŽ<4`Àü{:šŽ»[0ÆÓ‚.­Œ96eäš't˜}§Y³:8ÿç3ÅõýæÜ¡Ó#Ú͜ݥÐ7MPýj×Ôi»ŸÊtïzpDÖNnÅËT­Û´^9;AV³]ÒƒÃ{OÝ¡­ÐT›‚Y\]Ü ¿¡Aa„«ù.-e)úe›M ¿VعsçÓ§OÝÝÝ›7oŽý @>{ñïµÇº™n‰ÚÈaμȱÖË_9¶àäÅ>‡›Y;¢N¾äèѣׯ_¯T©RÍš5³>–Rzëµ1ÅRÝ\víÚ5OOÏoͲäá/’ ].QÊÏÍüËqöÌvN\å³`ßE7U„mÂ֥м)úIŒº¦9_ŸO…þí;ÿlü'yCãñÓz•—„sÓ†.{@Ë»öZ8µ‰-‡h#ÿ0zk8Í=ÿ·xXéÔtçÙ†—e<5A™Þ3}øâa °g“楄úÎßûåÓu£þ:/»8o²CàìN†S~ò«3λ£ýh,ë&~³z–H—†ÆœØë ¡ËµÏ¢ilØY+õr€×ï†óJ÷ž5Þ¸¬™®K¸8oÔâë©é±K Y:±¦9ó=5ÓAùòåCBBŒ} @¾¡J^?wTYÁ‹Ê’Û¨Œ\GÈ»Jvœ‰¬»si\§!3Q!Äð,u©Tš˜˜(—Ëi·R©¤¯‰ä[¦¡Ž}kèpéÐýe@ÀɈí-)8¹ò¡Éã»ä€À6×|g]U‘„1J¦(Ov%pØ}Òc×fú¬®®|Â(î­p:úèÌá)¾K‡”©1¨÷aï gäEpÐ5OŸªÚ‹«·‡ë3ŠÊCz—1!LêG_·È®Ì¶à¶–°Šõ[ìWߊM˜ä+Cܾ·vRP¡ÅJdé! ‚‚5+™9žHÈ›û‘©¤ ~õÅU&lÜü~G~¾qäăño¬=Ó" ©Ýû-›O›ß£pºÛmf6ÖûŒð]*KRü=rêéäûtY .PRéèʰ³· ënQ­§÷Ÿ KXrYÙW9’^¾|¹uëÖØé !ä:Õº¹cª o”<ÉÅ¥HŽg_b?J"M«h\8?÷\²EÁéP³s©O§!ù¤%ÆÇÇ_½zõùóçoß¾åp8fb±ØøÐvc6›M{Ö«Wïþýû\.·xñâYO" '9vÖeNíõjøºgWæú\¡½ÌþåS×.*ÒœB´Dÿ"Úp ̤x{C*ŹV*È>}OGQr]¡MýQÏ[ñPsûï ó Õ*ýéB‘çÈÁ•$4úø9,Úøo Scž¬Ó/8-çâ‰D<ÂW(´Y[&åñÑˉ†N'÷‚úßö1‰WŽ^p%Åx,)óÔrEÚNýñ%¢LúÅÉòXïˆìKØ‘ÓÉ4/ŒMe˜ÔÐÌF7©8ní²GwìØwjý”Ëë CœÛLù«f6ÔÀÃ0LDDÄëׯSSS+W®œŸïÑ H ß¹°²¯ [þÝÙ NÉ>Ê[Ÿ{-†•Jšyº¢ºFeÔ½Ëk¾Ü”Ünó„gôŒw¿;†èX{w›øDÂ×”vÿîTõ༰ËNÜû£Z6ãl««QBíUES–?gÏ\2]‘lU•}æyŠÓ­Ã++4íÿ«6?z~àÀ¸¸8GGGãSã²xƒÐÒ¥KÛœ4É‘ÆdÊÄRÄ!Û&|Ÿ8+#¤H¯)½Š ˆVlebüÚ 2IC lѳúáY—)禎JjÞ¢‚yLè¿Gê7¶yýîžÖú_ð±-ëŒ×{ú9ù•eëÇ,ÐÎoPùŒîÂÂujÒ©üþ¥·5t9Hªª·hdñQá …:õu7ùâB§¾¼p(ä)‘¿}ý ôÒ£xc³i2É¿µƒ¾ù*ž¸fHÌ,jz iYÒŠsdö¬Ã±ïçjS̉G¢Õ$þðŠ5V-K›(âRê5-ú<Ó±>4ïˆÐ3§¢YÑwÿ{Éð›b›–z˜³×3]¶gÕ¡×’zWmËDY³áb" ?¼ÿE›^ßU?B¡xcÀår *äd`tüøñÇs8œ®]»ÚÚÚ"LBøù 2dÙ²eôغOŸ>455ÍøxóÅÙÓÑÇÛ,ýžT0…3z¥h»Üð†ÃT/©.iJÔ öíÜU·´½Ü¿ü¥¿@³yœüQKd£sÉÎ ÅYÛ˜ –ª¤êjMäɬÓwxÛÏp¶Ÿ!‚"©Wºd÷yˆµ"tíšuÙ®\C›ûÓ<_›Åb™˜˜´hÑbܸqU«VýR±7n:t¨D‰9ôˆmrt²¡Cd.2|`RfÐÊ̓>àÍŒ§!oã• Jܽƒ6ö|xtûŽC—n¾MØb‡2 »þö[ƒ¢’÷s•0Ãë͈ ':bRmìÔÎ_º›(󢾿 Õß¹çØÕÐ}[/îêèZÒ6YÉ8p?¹ë [dëlÎy¨Õ½>¾}“þ8QR Pù:u«7hR³¸Å‡Y˜TòYxvç–‹W‚Î(%6… ©RX¬_F“ŠcVÎ=¿-hçÉûg6¯<ÃÙ•n\¢n¹¯Œ%t­Y·<ûñ«Èˆ#ëW꛹•[Õ6-:µ©QÐp~¦£k㢒å¯.í<»_iØE,ݪ´iÝ­}5‹|C äD6( ] >*‹+V¬H;Ο?ÿàÁƒîÝ»ÿ\$„$..޾>|øpœAÚ/‡S¬X±ô)bXèÞÂü7,òí§¶4ìékŒÙ 3ÈK>ÑþKY#+xØç£[–êúôJ-q8íLÝ6ïÔZ¥RZwÔáÎäûZ¨7÷QÖýú}ð™Ò¥U} ÷^Sûû|áiQ¾âÞS–jÙ+L%bª>0XYKŠJïÒ§ Õ;Êw•ÔW‹"†ÿÇþeͧ£–l”r¸*³u½ø£É`} ‡Å®©§»i ²¿¹šy,•ƒ01üÎÑB[þD aJJÊNƒ´ü†Í¦GØM›6}Ÿ"ªÕê}ûöÕªU‹¶ÉZ,Añ~oî—Y Iu¿ÍÕ?îÅ1/٬ߤf™Æ±ièÜðó ЪaÀ憟M­‰×ø&^_ßìÛMZž¥‡°DëôW§Ç‡ jý9±ÖŸŸôÎt,“"-Œoñ½3µõì1ÎóKË“åøáD¢,=OÃÒÒ²F;vìhÞ¼y©R¥Y !ÀWøûû£ÀÏÏï‹Ã´‘›ÚT÷¸Å¦ë«ê™þÇù¤\QµÃû¿ÎàŠÐ4/_¾—Â龨p)¦…úðeY.a”œÑËDÛx¿/em÷V~ÅÛðšõ>½X«‡–×¥Ë+Y»‚%»>ždÍÆÊš<²õWS›Ìí´’“vÙ©.x©\ØDþRX%˜›ôÕ1ß­oäM“*!lå áÐûò=e™ï8bÆJv©Ôê§n™:.99Ù˜"š™™ …ÂèèèòåËÛÙÙá ò>Úhi‹E[$„YKlç¡ò/ûjþw2{&XêÝónïÑ5þk6Ht1‡w&ðÌëš_³Á¨¨(šõÑÜïØ±cE‹mÖ¬YëÖ­+W®<ЀèÖ­ÛÖ­ié‹ÅªX±¢››ëÞ½{Í›7ïܹ3E$ÝÙ1áɳ߷ĺ*rOFHwwŒª”}Ú9F’ý(‘¥?1!g1ä;S§–q_®ü4sÖ¸¶0K klG¶Ñ%U²ÃRÑÞ¢ëér+m¿BR¬œº5#é\ltκ´'ë>OÇXi½t,uF ˨Ù)ïÖWò]µ™YêÌ›&29Í`™ïh¯ –éÓ‹;‹Tïø³4N6›Í¼»‰‰••UõêÕ¹\nhhh:+VÌ8T.—oذA©TÒÆiaañ í Ê°í“®{LšÒÉ…ÿS-8“tmÉø¿ÝfŽ®m•{w]züøqLLŒässó÷wÊùÆüäɱX\·n]y@Bð3NÏ^ÅòXÔ»ø»$Mìåõ³ƒö]z™ÌpíÜ;™ãÿ{©÷GÝ™ U?Ù0ç2ãÐoL]sz$Ÿ|uN§ ïòÚ¾²§jg»Š#ï5Ùxcm3ÃT^®jZcrT—BçW3!Ú¸Ëk—î9{íîË áX—nÖwrÀàš¶yúC8..îèÑ£!!!4´··§‰\«V­jÔ¨ÑËàKc•-[–¾Òƒì-ZtêÔɘþ}^¬g·£7~WàÒNé¥|¾BpVÃZ$^*Òµ(¥)&&©oØß356±2æs*îÄ£<ÿZê©¥¸>`%ÝÕJQõ/¼~Ä_¯?IlÜ•m͈is¹´yf­­|嗯ͥ¶¦®¹¶³ð‹È¹ývóG”`R_´ü\+õºfœz‡8ª0aù鹱߈_Æšwsoò3íë C ‹/nLÿŠ-ú¥’´Ø AƒŒ£Ü¼yóòå˱±±¦¦¦4¤9€‰‰ Çû ?ë÷V ¸YÉwAG~Êí¹ƒfÞ÷˜´tH©Ì3Mø®±ãvY\=©jÎÝaåó™²Ì*öïâ?r¨oÒÜ€–¹”7HßG&“ÑOBÚpì²ââËÖN†rÓæ^#‡Ž­ifœ­âÆâEI‰IÞ•õ‡;)Wü{OÞé0ù­ZZûf×Õ»¸ïö={tpá<ñ9LzŽ?Ns¿ƒ …ÂæÍ›ÓܯN:¿|Ó¤| ¾ZÌܹ|YkÙ)yzâ ߺÌÕ[}Ô*gÛEÞÖÇœ#×ù ‡Ãf¦º2NÚ¶…™o9˜dZt“ï¸,˜v…sëï–§Æ»ƒü¥œ³þ?èÚsÂêjURùÔSWúOŸÎ¥.!UZÈïUàM=Îß÷„ÿ?ýVs3]ñÚ¶Öi£¸TV„Uþ0‰è;¢Êÿrt„Xš19p´"kT¿”˜+4ý‰vv†ùæ3ÃÆ“Ø=è?pàÀË—/íì윜œÔj5Ý)är9M …R©Ôh4tú:–N§ÓjµÆ“ydõå7—Î>+l9Ý«Œˆõy®øøŸeÎ= “¨¢oEÊ5èæõ›‡Ýûe0¿¯ñQðvífÎéRˆ«Ž¾²kݶc7#SØ’BUÛôöjQBG&õÅ¡uÁ§n?{ $„ïÔÔwZf&SþÂt2ž©mÃqÞW/ü§üœÎóHµO*TÈø–n÷ׯ_¿zõª@nnnÙõKT>Ÿ/•JŸ#èQø?ÿüãêêZÐà}“Ÿj=d÷ÜThWÇ9Ãs›Êțמó{Ìߨ¼Ma´Ñ}GŒ¸ÜjúÜß]Ÿb¥Ò¬Ó¼Ú>rü¦õ´ÀÕãè'š6*ÄwTÿ“g¶w椾¼pæ>y7b¸ã‹S~µ}ì¦C>›©1?—”m^žxæôëvÝÝòä9Z6›ý¾DDD¼|ùÒÝÝý›Î²X,.—OGŽŽ¶²²*V¬X¥J•<==° !Àtná®xníYÆŸü)Ÿ>Ÿ@\z5syw¨¡y¹qðä¬J3ÇV1QÞÏl(QÞ šzôôpݲ¶ÍÚ—<7òPȘrúä+kO)Ù57±3¾k¶-¸¨³ë5º®=ØJ½³t¶þ'ŒãjÏêbê±!Ú¢Ób¯b?öG£Ñ\¸pÁxÞ/**ªY³f4÷kܸqƒ… æúö©çmÛ¶U«VÏçÿÜ«¡SË:"4få§«²•ìÈžgo5Äõ³!µôÂù7ôノ½÷¥ë}õQR[gËo˜²Sô—§cöÅñ…¦’šœªû ªÜÑàÚµknnn4¯3ö4ž¦6ža¦’““é«R©455µ³³£™dáÂ…ß?u !À'p,mONŸõé7U;oBë‚¥þè]veÀ‚-žü^×>ùVÈ΋Q<÷ÑûŒ¬¨?Ù—éÐä³ó×¾&•æõ))0a<ú¸žýVÍÊßYq~mk;QÉæîœSçû·êÓ­¾]ÂCû®ÅÐy—pÖ?¶‹y{$`C41«•¸k†Ïë'BB#t¢ÊÃwÝSÝ‚}+ªÓéèqÉþýûiî÷ôéÓFµhÑ‚f€u óæöáðM†ùÎÛ8Ç;Z{×St 6—±ØG¸^²dNßqùg¥‹)òüùóz&áÖ¸•¶!‡nJ•Ml³rjó}ÞÈšÓòI‘‰Zbb8éÇsð¨bõïAóîsý[8|rœÈ|Ô3›ŽîÓ™¦ÑÆ=z&c,ïø“¤ç,+55ÕÚÚš¦y4åÃOþ !@LËN¼,øá}ÑÁ‡¥ƒß¿›òñ)2^fCMë¯~"ýðÖ¤¢ßeé»]°»ï}õáj½™+>”S=X=ó‚Ö¶{ðºé•³ïNwîÜ¡¹ß¡C‡nÞ¼Ió½–-[ÒܯªÁÏø–c…]Ù=÷ð¥Þ[­8 h¶¹"Ê¶ÍÆ‡{tjf_ªN¾ZñnݺÅÅÅÓƒ{šŠÅâŸs=x.mzW=6{Õ®gó{ù†|ŠmÓÐwjêŠe{lUŽy‘†ƒ|zöX¼®ièžÝz•¨!DdS¼v¯‘V2ÿ¶ï°ø…¿8ŒfZ^B¯¬ÛnßaV5sÖOPã\.÷ܹsMš4©R¥ >@ !d,élàÊ—¤ÊüQ¿7|úôé¾}ûhîwñâÅjÕªµ0(g0a„_¦¢Ü<Úöh(ˆóòMòf»$l9Oމ·müÄ¥ª}±“Æäϰ²²òöö&†+ý.]ºtëÖ­ÄÄDkSSS¡PÈbý KRÑ{Vßé£&ãN ìV´üèõ›? ³¨;usºÇp v^°¹ó»&EZšûñ'ùv]F{tùlŸLç+S&¬/L'ƒ™ê.νøuÝIs:8çé£S@ðúõkúÉ\£F |€BÈŒYÃuϤY-L2ŒÏx8qâD… hâ×¼ysww÷‘ù¡ºšyé°pçHÙ×ÃÚš-Æ{Š&ôñLî˜v=ôDØ¡båa;¡>ˆáŽÿµ Ò÷T©TáááR©4&&&))éÿì\NßÇﳟ§žÖÓNCI²I‘=³‹ìñ33ÿv?3{S¤(Ù+*’Ì!-¡½ŸöÓxÖÿ<÷’P)Šúù¾_½nç®sÎýÞóœ{>÷œ{¾<¯´´´¬¬ ©G …B8¨@Kb²¤$AåÓ{÷îýÛòOQê³ödo!F¦4=Û“å»/víJ¦Ô®²°°066¶S§N¿£­L¥–”” ú9--MSSéÀnݺÁAü™™™~~~HûèééS}vîܹ½ûߌq¿YÆý°òâv‡'ñ{ʆw¦=!c"(6õ‚@Î0¸´KHªtw}³ól°IÍÐéô8µ<žËå¢ßu||¼²²²ššÚïÎ.©)ªÁOš°6jɳ?"ó*(( ¥´´4‹ÅB÷èW:rÉdIÂHÉàäåå¡%ù¨~622’‘‘‚øIrssQëi?___UUU¤ýhnn^³{w€.­ÐÏa#1ËfzÔƒðÀÈär6µ´%;W‰š'OâÊ‹RÙâ2&[U‰ˆÊ. éäaŠùb… ¾B—.“ŒuØíº´hajj08θqã*V?|øŽ–<OEEÉBÃüwAlllQQ‘®®.«M˪Hì•••½|ùRKKkìØ±ß»äóùéé騾---Eat0 Âèt±XŒ4Ò~H=R(´D2O^^^QQÝ”ÆãO@4a÷îHûݸqƒÉdöïßðàÁ?áÞ¨ŒZ›^询.1Lÿ#ëý)tq¾Ù˜ŸŸ—’’’——‡´"ª1Øl6 … Ñm…” üœ\A'"$ ‹‹‹srr²²²:RSSC*¨¼¼üíÛ·h©­­äJ®ši6dd(‘HÔ¹sç>}úôí[ãQɨÝo}ú 4hÀ€„{÷­[·‚‰øKPÀ;B€ÿ,~8aaaæææC† ©pï¾~ýz° šGŽÙ°aCmޤÓéHû ô“”(±7)dŽˆøñyÏûôco(Ë0:[dÙ¾|m.íKœù©´-wèWIE˜XCYœó•¦³ˆ9ÃËIT‰»),ç=}aíe6)_(YUSØõ*û§¥˜þ½vÍ¥:jS4³¼õçÞE¾£‘?ëÍ2ŠÏcšK8%¼ˆ„ÒÒÑ,\>J^\S*b,<”±7šü&‹œ\BBÈ Qvüu|CÆö~¦f›|Œ¦¤>I¡¼+’¬J±EãF”ü«-&AùARJ*I ˆí{‰.> „Ü`­QämQ¢Ì?A°VÝÊÙOé/1,9•TŠal1éÎU©Io%B¦s—²åšXt}0óV06hl±KK1IDòö”^–,‰Q·MùÊæâ‚t꬚r Ë·à÷1)ÑI)ï©Û^Swž£6(y9Jø'&ª¼`­ mBãÒûl£c4¤»ø3ºò{^¨Å¤‡×¥ÆEHòFWÌï*TÅHïŠDeÅ?HEL  ÝJ“ÈI;ërcŒ|ííásFßçôù3x+•°šöªˆ±ZØäÑc†'ØÞüŽt,µ€ÔWÔ BøÃˆ32 a"êÞ©ÔAȲ &Ÿ:)Î&…•alƒÒË=Å[±—<¬$—œ+Òshkß~2ÏŸ1ž?û‘ïSr~K¡Tí ®åÛ• Ja˜°»õŠòºúDbR.Å5Œ–„÷¿±ór(Ù"¡ ùõ*¶ÄK„ ŠÉ7_Ó½^Qï†2ÐÚc5¢ØE™¶Wƒ2mK^ J×>•Šƒè¢!&|s6B•Ôé4µ#Ýþˆ-Uªq¯ &ÊþMä+Å?ÊTÃï'??ÿܹs öööPúA7"RR.fbEªØÀ¢ähºÔìxRX†)”_.P ‘uªãaX19KˆiJ‹4ÈX‚ÃTÊîNå·únJ±´H? ï=5¢LØõGâ§ –e|"À°6V¼ø®"¦ˆ¼ÿ˜”sNµÇóŠIT)1]Z4جt°VšÊ0s—Œk½BÍ#ÉÛGV˜L‰ã Mh?Ÿ Ò‹Ÿž¤O\ó^ÚlÒ““›‰C¬ …B???¤oß¾=hÐ ;;»¾}ûR(0à à/„xx€&f“%]pCÆ”(FQ’Db“VÉGzbL…ø˜¯„Ìa$)Á™9e³N0ne2,·UR{dáñE%X’NÏ)›îÆ,¢ÜIûaúìf|kåfuGJïÎ.Ϥ[£'|¿ƒ.t.P—{Ï.›âƸ›O´ýˈƹÖ%•ºòC›4BöˆS±%22ÒËËËÓÓSGGÇÁÁaìØ±HCÂï„ðßþ5ˆÖÎ+Z[y YdÖVTIîˆÆL)Si?Mž‘_C”耓Kª?€,¶›ZdW±Ê¸--ªefé*åW—×”´ßki•IלÊ×YÂ0iÒ¨Õ_VkÞûc›¿¿bdd´ ‡Xår¹ôððÈÊÊšŒ£§§?@4jÉd^½zÕÆÆ¦–1s8œy8Ä*ŸÏ¿víš»»ûƒ† 6eÊsss2™ÜÔ &.|²²·­Wº–…ÝðÎją̂—Ýœ]¦¶'\\0|áõLLµÓ >íT©yIÅÝœöØé ‡…¸$ÆkÁø•~ÇÈÚÊTGæ}1Œ4ÀëåqKLœs}l‡ÙÁr‡ l¯Ê(ˆ¾uþA2Édcðµ©:𜄠JÓ?þ\MMíèѣǯkB4mNÅ–/^xzzž={¶uëÖHÚÚÚ²ÙìFo0^ø©«é˜ÖŒ=‡×u—ÿ$gE9¾ö]fi, ˆ_Ö–ùÍñ/Ö˜u/éžÔWQr|é«ÕÝݰË–›K¦"â¿óÜ,T™|êÌ–øÐà¶s:Žñ¹üŒë £B†ò  ;wNOOŒŒlÖ¬™³³³Ý/ b턳wï^b533óرcîîî<bª­­Ýøl ÝãḢõOm­oû¶TÚb{à©Ñ¼›ƒJÕgœÿ­ÄÊcŽ,pÏPŸá·ƒPƒ˜(ëææS™4Ë­ú’Ïc‹Ÿï;ú3Ú<×øÓ‡¢Oa&õR5 FFF)))±±±:::«V­š5kV½D«¢¢²‡X-++»pႇ‡ÇÓ§Ommmºwïþ§/],ˆ)T2]ÍlúÞ€ÑCçtµ¿îý(k¬•¶†½xü8¹¼}Kb*"‘PD¦ñÙdIHå%e”ˆ1& †ØùDÄê?Ã\m¥_ßr¥5`å(Mâ™"H<¿õv©œíÿ©‚§°°0"""!!!//¯¸¸¸¤¤„N§KII1 F¡PÈd2ZR©T‹ÅB¡P$ ´äóù¥8è,t›ÍVVVÖÖÖ644”——¯¢Ž‹³²²PõžššÊår PUŒNd2™ ”"‘±$‘Hè ÿ(@R… ååå(ŒÎB”.:%ÊápÐó=;šÂ0AP;Zµj…(‰‰‰úúú3gÎ\¶lYýÆZ£q*¶Åg´öYJåùyYœ_§8C©€Z@è´$ô•P($ú¦èB É:’RH¶!ñÆ`0Øl¶¬¬l•=cÄÆÆ"1†ä:WAAé1¤ÙP$ºººuÊÑŸV˃QrïÞ½ËÎÎNHHPVVFâ°5NƒU¡Hß¶k×î·õÚ!(‹SaÌGÅÇǣۇ͛7‡R € hª¨©©½yó†ËåšššöìÙs÷îÝÄ«îÉ¿8Ä*jRœ>}éð°°qãÆ!‰Ø©S§¿öŽ$pK}C¢rÞGŠòÓHJ-H Ú˜”<Æ”#±ä0Jìà—ŠKó±ô—'ÎMgÅQåÔô ÛÙti#' Öû¤¤¤ÄÅÅ£ Ñ¢GŽ»HtÍUOôË! WõýÄA¬¬, ïô+ý ª7(*..FbÉH”J³fÍTUUÿÌÄÐÊ=zü¶Û´ióÇo4ºê¶mÛbŸçCadMMÍß<@Ô'44´   W¯^...¿Ó«j=LÄ!VQÛîÁƒîîîW®\Am,¤‡ZûöMRň°“÷¢S^dUÉmúcJHJ(P.“„d³Œd¼I§‹¤8aX,ú{‘/оå%w³´é×¹ Ø©^@’,$$äÝ»w™™™èª¨¨X1j±N½§ˆê%)œŽA‘‹ƒÓ××ÿ=¦¨¹ëò¿ ºéæææááá ©8H®â Ç ªá544b422ú /!ÀÏ#++ûðáC7pà@ôh÷ôô¤Rÿ@…‰Z ½p&$¶$&&žÄA-¼)S¦ 騢¢òß°y`47ø†7IEŸlЗÒÛ aƒ½u#w°EÿŸ‰E!§°Ì˜ ãÇë5SÃÔÔÜ LNNFU„¦¦&ª4ôqþX†DÜ;kç¹}À0f×Õ‡¶eaÒ8ZZZ¿'}qîÝuÿ‹Ç0F—U‡±Ä…‘ÞÛ÷ø~”6ü¿ÖêõXƒ~3ùÛ ÿƒ ¡nbbR±Jô#¡Xù$ÑýýýÓÒÒ222ÔÕÕ ôŸ©Æ„ÿ5èºyófyyùèÑ£…BáÅ‹«ÍõÛÐÖÖ^‹C¬yxx ¹3aÂ$›œÝîÇ%?ñ!›Œ¤X̃R÷ß7ImúamúyG&ˆN¹[ÞËD¬òC OŸ>Íãñ tp¾;¤,öØ‚ w Qb¾öàÜ6e€®0Ígå’³)xXm„óöÑZ­À²8Çõ¹ø ³5‡æþLžÅø_¥ôŠ’b>–`Â’wï²J­ÔÙµï«0àw¨Ùé<\û:f©¦WŠPo‰ƒÂaaaQQQH7:88ü·€ š*H^¹r Bô´NKKóññ©y˜Öï„Íf;à«"‘(00IÄëׯ[ZZ"}8pàÀÊ)56½/¸sÁlØŸbµJÚ†Šõ²ûÉ1÷öN›6UCQlRÞÞÞ999íÚµûUmVtþÂͧQiÅ" [ÓÈ|ÀèQ½[H‘Lb^ÒŸK¾Ã>ä–c$–Š™í¬Éæ*”‚ð³GκíºÖCcÏ”fч×x'b˜æ 1Lßóï0,û]V9¦Îú’OF«SÆèS“î¹zuï‹«R]–ï]hÂ&‰rîþ»ðX,T$µn#Ƕ•á}xt-©¦"V÷ÌómœúèË*gÀæÝöºôº¹ªÌ·|»eöÞ7"‰%fþc¦‚¦%dÊë+üÙo•I$R›6m^¼x‚AиÍ$á1bùòåß|ÒØÐÓÓû‡XÍÏÏwuuõððHHH°··GÑÀà|­'c[\ÏaräNc¡P5*ÄímÓ>>ÙyÌ{éŒ ¹)ŠŠŠ’’’233¹\nIIIÇËÉÉQTTüéhI~¸”2_»‡èü`Ýnó¬ÝoDywN>¶É²Ôß WƒRfk~;vS,ѱŒ~w²ŠF&zÇÊR „í¤~0$“Ÿ›HôÊö[ÚO¸lÅÕÌÀ Ë>ÈgÇ—`¬.K×bŸyˆ½+ÀÊ2ÓŠ„íh©~'ñ|Jpú»ÇúiÏ®¿-6éÂL º&Qƒ»×Ú­³ &Ê¥‡Å¿¯¾pÕ”ùªÕYÛjt/õÏcHl=ÅZ5\É5f~MÛa½Þåb™w\ïÐÔLúkÆùÃsWQ©Ô°°°3f@-€ hlß¾-‘Ð:sæÌ;wÚy`}!''7ç“0 ýüü>¼uë– Ò‡hI¡4lÃHŒa[‘TÔ%¦¾&lÞ]Æú{4aiié³gÏâââö£Ñhè7Bx™c±XÄ<•;ÕÛ´i“ššóÓîõ( ÍU)X¦ãÅ„¥•·Ñ¥câ’¡‰e…Ikj²Éz3E2–!Âx±/“Ë [Vú¦Œ÷bÛ¬]B Óž¸Ýc &]˜zuÅÒói_ø4o%¿´Lü]Â"^VN¹$ÀâÈÒhZ£|X°ÿuv|†©ŽØ0¯#›”¦*ƒa–Ÿœ/À”ä´9x60­qÛ7Õü®Á(–ÓR&c™"¬(üñ‡’Ö¬èÑf¾*˜Úf6kñæ7NùQæ¦òž.9šöòúñƒW|„ùžê¹öàœ6¿ý ¾’’’wïÞñx<PVVÎÎΆ f„M‰u8{÷î=pà’…MÎ%1Ò~q*¶DGG#}èéé©££ƒôáØ±cë}d¬ëÍ0ÔN#ןóbÞß8òæáÝŒ¤lj2Uäõºéô²3îcƦ}j'ò"<ž_:óáML ¥-#£×C·÷”öýÌÙ”œÔû£#ßä -{¯üG‘…Z‚@Ý„”ýþòÝБ–ÿÍÑkb±øÞ½{ÏŸ?g³ÙZZZHþUÌðñCêòö盦9~÷Ö!+,:ºzë«®Ù¥d¹lã4‰öav[¹«x×*·×Ù~ë¦ø}ÞÍè¼òТ–VX¡%Xâéå§¿K,×®OÊû(¡ðõÎéV^Ò¾ÒHLaa:1K SžIF¿¹nK÷ʆ„g eô:uH&1KžP^…™…"Œ$Ûí»Ëö­qy‘tvù¤³•j £ÅGVufKX±»tÏj×WyÿN ü¡-X5gþW¨âÂkÊ|Ç¢ÿsôNþ:)ã.Í~Ë”a|>?+++==½¬¬ŒðUXy&°ÌÌÌÐÐД” 333¨‹„MƒE8Çwrrº}ûö„Y_ ÌoÃ!V¹\îÁƒ‘DD-˜É8zzzUž¸iÓ¦µk×"…ÙºuëiӦ͘1CFFæûÊùXz¨/Åjiý4ë ‚î.°ÉÃWh:fÖKTð1+Â7<°›‘5î»äIÐLÛIS˜Æé6Ű¹‚¸81+ìN|ü´ÎHþñ3“}Çd`§­P e¹R³µíЈ€mÃ-:þN?œ¿ GIIiذa+V¬¨Nà¡é«W¯:tèеk׆É£Õ ï†þÉw™{È{n 3U«nU~ÆÙÅÑÍ»†7]™¬¨¨X!Q~üø±……¸3H7vëÖíåË—T*µsçÎ`!ü&Uì”åØ$²zýúõ!C†4‰h9¢¬¬lkkÛÌšÕˆò2Ý‘~ýú!íô{¹‹Ú(Ãp*¶DDD }xîܹÔÔÔ*O!$âž={nß¾}óæM´eáæCõ2(749“A#[i‘@å¯]9ø¸c(Úzõ·Ñ!Ë vÎäý©ù÷®‡Æ» Žw!Ž’Õãf3ÎìËiœé£Ž:©TŒ«t¬ÁCO¸4ûìêZ\èë7}f’#µÞi¿u¼4øúN™¾êÒ‘6vó»0ºž™uõKV‚g ç¦Fºžo¿kªle!JDË䯝tÛž®~F*¢œÓç/$Š"×G ·Ñ»_sZ4¬êLÖté.mtšz¡‰DH"z{{øð!111..®S§N222&&& ý¹,ÔÛ™¦ÓE€ €?ÉŠÕšJV7lØÚø£]±bEFFjý#UãU~¤Ï333ïܹ£¢¢‚Ä’¥¥åõJwã BèääDlÔÔÔDmî²²²gÏžµk×nôèÑ#GŽTSSC»BÞeÞò¹T?í$ùöª Xv.†e\‰KZ¤ØâS¯£¸8‰›%q¾MÍ/ÿ,dµ[L»ÖbÚ'™æ=úêŨÔó£OÆwXÓüs|µ0*â¾ÉÇgîǬ:3qç>&›NÃ(%E¢<¿[ÿÌxOtHÒd´²2b¦CaiÕ±“¤d´±ð ãá³aÐÙº–(q¯ÆÖœÖïßZ’ö.¥½žFS)¨é,K #%%eff†T_xx8ZÓ±cÇÊûùù½~ýºY³f¨d6VoàeίÝü²ËÚ ½cœx§ÜhÅ©_u ÊXÝ0174â‚Wº”Œß¶¤g=ΚœœŒž}<Éd²Ùl999‡ƒÂ¿ÿúÐó÷éÓ§¨þDu)~üXUUÕÍÍmðàÁÿá‹íß¿?„½{÷®,ÿ¾§kK•›eEúc°5Izsçùÿ,Kæ'½rlñJ¦} 33–àÃ×#2±¡ û¼tÑi×N^M*MJ½õ)e™ÖLU‹e”aÜAGÕMŒeËrŠ9VSµª™I‡¢åÐÅäH`’šå˜ú€¶æ¦2ŒòÒœÜ<ÃÎsúÂ&~ÄÕ T/gëá]¥)I‘›ì#~¶»æ´zþ UjIjQNRƒ>UŒººúäÉ“¿—ßS1jý¿{÷ŽËå²X,BÈËˣ𽘒7Gnë¸zï(ZNLC¥R—ƒÉ—–-¿ÄY||mg©?~³I²æ:qZ²¼¼œÍf;::îÙ³ÌòÓ˜™™¡–Ä«W¯PKbïÞ½cÆŒùO^f·n݈ΙÒÅzøó'Èݧüz¢d• C.LrŸÆ\=õ4øã­×¸4]Ñ@©µ¹¾‰*IâûŒ¡:`ªÎàԻÇÞ ð'ŸBkMkÛvc¦i«0Ð:«ßŒ°õ¾÷™Ô MwƒHtµFc5åªMRý¼·gö½yêÿêÔ5]©¥’¡Vi¹H¾³×”}CO{²Âç~'âÒ7=¼î…Ç'å•a½™ÍêM“Ô¢ªË‰0ïßé³þ!ñyègJ—S×1´ž>{€Ö§ÑüÌg—<Ά¥ñÈlíÎC§LØšMQ¶Z¾ tî^çËívŽÖj¸­ NÅ$ÒEEEñññèùئMTZê¡-N¥òx¼èèhTØ:vìhmmmccN!Œ+V$%%U¬ÆÆÆ¢ÕèÑ£aþë_¤C‡©©©è‘¯­­½~ýúiÓ¦ýµ¦ÐYÿ]ü»¼¸û$}‹z‰Âéf8ýU³›¡£5ø_ôWíù$–¼Å¿Ã,¾9@ÎpWŠauJM¾«Ño£9Uì¢ëŒ6[;ºÚ_ Yýëh™*3^Ω4›"ÃìÒœ«µMë»Øê åÔ‡B9yëΆC|üø1’‚íÛ·ÿ¦S5ÓåpþD¦ŠÞú…•«ï¥Y•#AÒÅå+. :9],ñþ'H÷wZ:ÕÓ|;٭r.­X~‰?`ƒóá…•šLßzdú§“/,]áø&Ï–ªÕJÙ²w'ÿ·å~‹åG½M*u“âïS°6•zQN«É‰!:±4áñƒ·˜ýÏJIU™Å´Ë+–_,±Z³óÐ?R(þ‡ÓŽ«oEäLá ñü²•WÅC69_Î@Ê1ÝwµãÌ»£·;Ðd hGs~p?i¸îïòƒD *-Ñ^\üäÉ}}}%¥Ú~MŒj.,,ÌÈÈHOOGQµlÙ<55µ¾}ûƒAÀŸ§b°he233‘ „£õ‚Abbâ‡Z´h±xñâùóçÿv˜?nÀáó·²#oC©hlÈÆß$‘EsÇùK®700ÐÜܼqM0#╈0¦,³ªJ—Ÿü0Ó›9¨å'ÉGU³×ùÂægñ¤#¤¡]ƒõ¿r/È~qÙã|PxRÒtIÓO)¯T„‚Õ¥Ïh9eçVÝ3޶çcl½>ãgLì­ÍªKN&U5â¶êœðSÝ—d{d)RU×û88ý¿¾fJ%·XhLÁ0M…)ÃÀJ KEà.IKKwïÞ=""B$©¨¨}È|WTT„„_ii)“É”‘‘Ahii!ù§  • ‚€Æ‹¥¥%‡Ã!ÆäææV<·¸\î¤I“<==ÁDõ‚®®îû÷ïSSSÛ´icgg·zõê¿ÐsÇØ\øêÕ7rWŒ “=6 äh¢¢‡îÍÛµjaú÷\u·nÝÂÂÂ:vìØˆÞy‘Ùº†JØÍ°”²~ÊRFÂÈ(kB¾KkÖ£‡ªÏÕ+~ï»MГHFaæƒóÏKÙ=ú¶Õ6åøø_õ‹ï:á³HÃJÂ÷/ÝÛcþS­$sç”Dì™»õç„¾Ž¹$)Ëië-§a˜8ÿ‘Óœ•«²¶í°•’C™)HËbR”ä¤Êï/«Ë M­£±œOßýäõ¾ë裩w‘\”œÝ.§êß´[…ܘø"’V; úºQdr~~þäÉ“«t» ø–'Ož4òFFF~iHH´À]ûÊ“ï_òyMê3cœA­# ¨¨¨¬¬¬:ØØØTø‚ÿ{Ò³Cß®F»j´'éÂd¶ SÎ|–ûbáì©RLÆ_uíýúõ³²²òññ‰ŽŽnÞ¼yƒNRkh:C§tÜqìRü{=:YÉbþŒØ½^K–d9m·oA׳çTŸ——=ÖÍ|•\,f¨ZŒÜz¢§–D’éÚ<Ùïù¯ ³Ã’ …UN×röÿæ9/<Âeî„´‰†aÊ)·0nÆÂ¿Aý6æOÉó}ö컞V,B-E9ŽcV/¢M%cV«7–=¼Æþl F‘Ó³š³brõ9©V»Î ÏV‘F«é‡]{ùŸ<ºÀîC¾ˆ"¯£ÇÊÇ(z"z û6ϯz®™ó:1_€âQjÕÓañ¤Ž²…Ï<.&«ÜÞUîiùàà`;;;PƒB~@xxø¤I“P;ÃÕÕõçæ< %¯ÇO=-úçúå©:?¬>‰^S&¬M±½8¹¦¹ •••_½z•ŸŸofffbbrèС¿jh.“N[µhvLBêù³»È}Åí ”ýfèL%/21Ŀ׻V»ü¥z˜B1bær¹?Ž/**RPPPRR’––f04íwf‰Äî°`ûô-Žk—Sÿuß’¡n9×Ùrn¥ö›RÇ1K;V9CMµó˜%¿ÞÕÕî]íª–žßÆŒoÔº|×Ð*r%¥7Øq××ã¼«Ï IÞb£÷× SU«Î‰ ó‘OH‘VOÛ¶ý°’Œ˜~Ä-íÇVÔ$ºJ—1KºTNE”÷d÷’IkwŽÔü}­YT?£ÒŠ–Ã‡·°°€:A@µ ö„½½=‹Årww ƒ4}D™¾ëö?Ïí{ÒV§ugÉëCûßb­Ö,ê,ýãƒåääP´¸¸ØÊÊJKKëĉ•ËìÖ:kW,É/,v9yœÏT #¾.”–2‚Ä'×IÅÙÃ'—ï³ BÀápªô“ŸŸŸ’’’““SXXˆ´"Ç#f …"‘ˆX $¨T*“ɤÓéHC¢eóæÍî-E©ÏÚ“½…ňjªJÏ ökVÆÌýÁ ™™Ù)ÌÞ®^K( ì—UÅåååéé鉉‰l6»cÇŽ]ºtéÑ£üR„T j1L™2¥  àäÉ“¨‰ùÓ”…oè1àÃ1èÞ’Ö’ü¥¯ztMU²óyêÜIòúY˜~~L—Å/Ì=ó®\ãµ`üJ¿ Œcdmeª#ó¾Fàõò¸¥ Vu|g(&Û³y„RzMÃÊnT[~ÂÅÃ^ÏÄT; êÓN•š—TÜÍivÁíg³0•}ë3äJßÝ>7Ad¼áѺÕ×»ÒÒÒAAA¨‰9|øpÔŽúøúóil¦QÿR–2âzDž)‹râŸÞ¤”æ·9œýjM§¡îå#=“ú*JZL¥¯VwwÃZ,[n.i ïtKÂL÷í_g«ò©=%Êñµï23HcQ@ü²¶_5øøqžÎjÓÏù8µÇw,·]`j{éÊ㬩º?ò Ì`0®_¿ŽZ'vvv—/_þò;mZ6o³PâfáedDhX7=‰¬¨+¥¢CcËS¥äH,y …»Æf.F– xùü¢<~q^aF¢ +^QMË´C»m[ÓM&‚‰”Ž8DX,ÇÅÅEFF&$$”––r8¤aÐïú7k3*•š––öáÃ%%%TÕ4!“"¥÷þýû¤¤$++«E‹Õp$ª7¢Cê® §GRa‹DH4b¸çI ÷!//ÿóZ@@€Ú뎎Ž÷îÝóðð¸pá¤ÖrœVÚ ,öC_ê3¾Ç²¼Åwîê¯é83õ}® säê^¶gd®0$ÅìZàž¡>Ão¡1QÖÍͧ2i–[ô%ް’.m (•µr€JE ®<öÄæ Rõç·ý¦IQü|ß‘÷X;ç™FŸv>?á_€™Lì£RÛ×ý¨õsîÜ9¡P8kÖ¬ØØX__ß¿ð-52BׯèÊñOÁÄÿTñpW0ÇŸç¤V8ßlG*%111%%%+++''=MÊËË‘HCB)$TPùGŽèÓ#Æ£Ú‚ÂHd¢@Eü„Ú!VÑ1¨Þ@  eIII~~>J£¦¦fnn®¯¯âGO®¨¨(T±¨««#iTac©»©T”s$³³³[´hÑ»wïZºÃ¥áÔ‹ËxA@µ §ÔªU«.]ºäêêzüøq0HcFª•Uk,à­ëTÓ-·‡Üž¨)|ÓQs½à4pÓÍ+—W®ƒ%*/éSÁ ’2JÄ“„‰ ÃNì|"bõŸa./im…ßxƒÕÖ­¥*U  ÚJöâñãäòö-‰¡"¡ˆLÁÒ¯o¹R(5pŰf„ü&_ÙæÇ“ö¿aêuþ…Z~¨€¡ÆßÂ… ?~|ûömðaÿ½ÎdV)ÃqˆpqqqDDÄÇSSSÑÕ3ŠŠŠH+"iÚÐÓ\!‹”*ÒÃ\.©V¤“UUU[¶lÙ¶m[$S¡œBà¿ByrЙ‹/H}ÿ™ÔVª)_ŸÏß´iû÷ï߃Óxò†ž¦²²²?ÎßE¥çHlC×1 j Þ•GÕ6kÃ8ÒÒõ•ÿ Ï]v´Ö‹‚Ô×,Zb¬É³´Í'8Ο7$È‹™'ÀØTLÞæ°ûŒÙ+çkÎÁè*ƃí¶×S{-®ã¥­«&mJ(ÆH²Í» ˜µeóXÁ±Ý¯0íŽfŸzôJ#Žî‰ÀZ,[Üõ'ßX£*r(°fÍšk×®¢Ô+ÔÒÒÒÝp¾Ù.ÒÓÓ333³³³‘`ËÏÏ/,,DŠ‘è¨¤ÑhD§% PpˆOøÐ³’8W„S^^NŒÞDa:Ž”0z² Í©¬¬Œª2uuu”:ÜAü·¦œn¿*aäÅ?VƒušÖÿwža»wïÞ±cÇ–-[œp›‰Åb±»»ûÊ•+;6tèÐFçªçtfÜI™Qy‹Lo÷÷)ßË.©ÖãwûßýeËè[ã¾Ä¢ÚoÕ~¾;GßvãÛ_o]û,em¥U¦É¦W)›êãR6á8;;?~<((HKK ê°ú¤R5qÀ‚~Òˆ#{"0ýU‹<û~ݦõÿ * i¡5kÖüïÿstt\ºtic6󜌌Œ^½zÉËË{yyýzç^CÄ ü:+pŽ9‚”a```Ë–-Á&€ êÂ3;÷Õßê3ÿäIœë;Ádfp×£¯Î Q$‰s®í8;XÆtØàŽ nØ‹OÒÉ6<¸J̧/.~ë6gÜú;9$µN­M[0c½Ü%3sL$ö–ÔyZaºï²¡3ϧP›÷¶íÛŠôöê™àLù·ï­4l° ùÏ;·`Á‚9sæ 58sæÌ&tëTUU–;°ßÜAøl…ù/í©ˆ$+Žd2Æòè–0õË-åIïÅóºMë=Ybf{–bw1z[wɇí%‘¬'6ûòq'õ-ýüüü3fÌ–-[ÆŽÛ¤ï¢Nff&R rrržžžµ÷ õ;ã~‘I8—/_¶¶¶¾qãF§NÀ&€ ~²·nûñæŽ`‡î=ük»ñŸv_h¶»$`&;æšOŸÿÑÿò{Œ=Ħ%©¿Å^™ê3üœ 5ˆa…Á;]>bvÍ6b %y¤®Óú âÝWžÍA/[/|‹LËÞcÞŽ¶5”©?ïM=BMê>}úìÞ½;99ù¿t3UTTîß¿'Ož\¾|¹‹‹KÅœr*Nà‰sóæMUUÕ‹/öìÙlBà§(ϊ˰V)\q‰yo=·ú`Æv6¨¸>zoºU";r5Þ{(È|´kŠÝþ0j¯÷ö’Áød :‰û.±PÔžIÆÄ%ÑžÎþ<¬Û5´T÷iý1º ’RÖG»Qþ¤ÅbqýL‚ýêÕ+{{{CCÃÇ¿ÿþ¿}W'ãdfföîÝ[FFÆËËë×;÷"NàWèß¿FFÆÃ‡ÕÔÔ\·±±›‚¨#2}ܺl\°Î\g€®ÑeÔ\Ç…SL·¿ÏËãcÍJŸïÞƒŽ¹<ºÕeŒÝ¬}œBL•>$Z«E·hmX¼ÎTk¶€®ÞÙvÁâ¹cÛ)ÎÎåc ´Ÿ™Ö_ËÁ?Úô̦uƒ[ÍN.Á0¦ŠAW›)ëÖM4úiId ‡Ã9~üxddä_uoUTTîÝ»‡§NZºt©««k½tÖ{œÀ¯Ð³gÏôôôÐÐP$ >ÈI$)©ßç3¸Bgòx¤¦¦ýl<œââb wþŽ´ÒZÄ’‰ƒÂh‰tu²8`F$؈À÷bµAï]zz:’â¥8(€n  eÅ}DÑÍ’Â!n"º§zzz¿3«‚jEnnî‚ ž?~òäÉk×®AjÆ'++ËÒÒ=áNŸ>ýë{ 'ðs´lÙ2!!!11Q__æÌ™Ë–-›ÀÏ~JÑÑÑè“——‡–4Ãá((( !'‡ƒ„––ª‰ÂÀ©ë÷ð)))EEEùŸ!ŠÒŠ(T6PÅkhhX?“Bø!ÅÅŨ±ëëëëîîîéé ©ÊÊÊwïÞÅ>wî=zô×??kˆ8ŸC[[;...##ÃÈÈhøðá›6m›À ~óæ jß7ÇQQQ100³•!“ÉDïnu£?|ø€ä"Ò‡C† !îB¨7ÊÊÊÖ¯_êÔ©Ã8`_¤¢s¯OŸ>L&ÓÛÛ»; ë1Nà'PUUŒŒär¹¦¦¦={öܽ{7¼·€ê8vìR€0a/ð‹HIIà-–-[¶Ìš5KMM ,€ €_E(nß¾}Ïž=;wî܆6©G”••ƒ‚‚PÀËËËÑÑñÈ‘#£Fj„q?‡Ã -,,´°°hÕª•‹‹ …B³À7¤¦¦êëëûúúëèè´hÑBII Þ¡u¥¨¨('77U¹®BøIÄb1ëׯ_·nÝÊ•+ÿ÷¿ÿM;œììl+++ƒqúôi…F'PWddd#//CÿB„Baaaa~~~AAZæá ɧ€ƒJˆ””” q|DD A?‰——×’%K.\¸|ùò¹sç‚A~'JJJÄ4­H¼9:::tÈÖÖ¶Æ ÔÔR¹yófyyùرcÑòâÅ‹pâõ7©EÏÞä'Dˆyy$•V$y Œ%‡±äIhIÉý~D9O\’¡¿Ò|qö{qn"]IÇØØØºSk&šul6Q©J8UîEb€˜¦²ä3<¯¨¨( 15å7SŒ³Œ¢%t<þqÄb1ŸÏ'nq+n% £%N'&­¸›Äí#“ÉÒ8êêê`I!Ô?>>>sæÌ±··wrrwjœ‰8ÙÙÙÖÖÖ4ÍÛÛû×;÷"N N VÎ¥K—„B¡ƒƒCZZúÑýNGdURÄ»¼â†‘ÔÚ[Ybͺ’›u…;õSwWŠD—Âä$íTRs‰ …†a¯fˆ£n’¥}ûÙt5lvªê:M¥H$"üR®ó*è>ôÐj9ÿk*;!¬pEH„Q Â!áxðˆ€rEl!~ï„ÂÆév 3ÂÓ ® ƒKÂ'!¡ß8„7Šá°:¬Âë ±$¼~cCÂ,h Ê3‚þ0AAAÓ¦M0`ÀŽ;RRRÀ %%¥ÀÀ@@âmñâÅ=zt#Œ¨¨)ééé‰ÚOÿüóOXX˜¿¿]§_¯Î=O޾sŽÜÂŒ¤ÛbÝîKA’U%uŒ·óËŽ]¢”åͰ§,/ –ù÷÷ÿ†›"T±DV0 úˆÂßk³ïEì÷«»®ï¢£ß´Œ¶pªûžI5ðõ ¿…SSÓýû÷øð ÒÈ™€“““Ctî>}šÃá4Â8:µM‰Ùz—/_~§ºrõ jˆî¿ñ"ïí}rg;ŠÕ¸¿±-@'™ŒaØÑû‘â¨[ClÇuhÙ ¬òWýä Ù_Bø“DFFNš4ISSóرcQQQ`¦…¢¢"ѹwæÌ™E‹8p`̘10N Nlß¾-7oÞìååuçÎ †KëêëÌ×þžäNã(}ÁòLh¡?ß—Oü®û,š3Mš 2  ‰wpp R©/_¾ƒ4uÆãäääôíÛ—L&#-÷ë{ 'P{VãìÝ»wÿþýAAAÍ›7¯ßø…bl›Ûe!Cz ºÝÅÍ»îvq36é4܆ì_#ʹµlÊ¡8 cõØè±¢=«Šn.rø]õüáìß^6åÊ>ÓlãÉ•íY¢‚×N[¯¾—î6óßešÁ±Bøm¤¦¦N›6-;;ÛÓÓóáÇ`ÿŠŠŠ·oßF³gÏ.X°àÀcÇŽm„qµgŽ›››““S@@áaù×)(í=|Œ¤×“¬nFnDÈ$³oß\ÏÊΞ1ªØã×(:0}Åí‚*ö¨õ_»}vgyrÓºž¯?3¬g„©—Ì>™„‡5Æ80Q§B£•F»Ì^îË•¥zmv_jüsZóë܋ТÞó0!/&6£¬3Ìõ € €†ËåΞ=ûÍ›7§Nò÷÷ƒü燓““Ó¯_?‰äíít]#Œ¨%Óp&·´´D?a“_j& ±½‡]IýHJ-À¶QÛ!ܸ@׋7gÚökÔÒ–[N,6’ȘÒX·–^ËJ¿¹q{ßáIº¸èDù{yú<ˆLãa4ÝŽ}ÇNÝ]ƒAÂÄ%ñ~'/GÅ'$gJæƒa©›ô=Ì€ûÀçö“¨ŒRŒÆÑ75cÆ öm),ˆ¾åuÚçQDj‘ˆ"£mÒ{Ø„ñVúèqÁc'‡m/HŽÙÈ®¹$m~ü‰Y‹¯fc˜¬õÖã Ú”½<µ÷䣘¤Ì"tÆPlij3aê(Så¯úÏJÂ/›œš˜ËǤ4Ú÷3ÝÞR‡UœªöÒ¾£,%<õs8õÜ¢uªÇ6[+‘%BñòòOjÁûÃ7£Ô*~QQ\ÀI÷Ë"ÓK0š’Žr~åôÈ :1ANi)…‚Žå׿Ú!Ô•ÂÂÂ%K–º»»Ÿ? ò·[@@†wî-\¸pß¾}HÑ5Â8:iò7nØØØ\½zµ{÷î?ÏÁ‹A˜’^“Pƒb^ÌûGÞ<¼›‘”Z‰$¦Š¼^7^vÆ}ÌØ’.^úÁ®WQcU¶Õ¦P+#éÏgE¿÷=úæÁÝô¤l!F¢qÚ¨™Ž41Y[]ª©tCðõ­3»G¼ïa ¢½žÊ’°¬ #î‰ïã,B(iÉ#Y#. Ý9ó߇ÅÆl3l¶]kjBàÉóç·…œ—2[w|EDz¸;~ߡõûO¬Uzñü³kûîaìöCÇÍâdÝ;ãs÷Øò»·&Ý7Jƒ‚‰‹BwÎÀcÃä; sè$›ñø’ÿõ/®Pè¿íè\Ãnófµšz(K¿èòhÐfK,ëÎa‰Ä]N5db$ª†Ž^éNƒ”äXâ¬WW<ƒ½ÿ ¾læä±²C%w4T¥ŽÃ¦¦_¸öúÆÞù7\{®w[ÚéÛijk¾4Sö׿~V\:®ÏTûW¿{&ìÍþ%.š.³5ßìvôøˆ®„ëŠW †eÆf–cÍX?Œ¿céíÿM?%’|!k>nR;Ùâø{j¸?déZ];€ €ÚQZZºfÍÔ^wqqquuƒüw$ŸŸ5dÙËnGo D¼d>rqÚzâfXºäeµÑ$× NÝeIU .—‹„„X,>sæL}uÖoœ@md´-ã±pÙ•t,ï‰ûqÃÎÛ†iP1ª|sT›¦#å¢3éàn[ío†'Šr¾^'‘)DÞÄ"Éq$*ýssÿ>ŽÊi¡NÁ2„XñÛÐÔr#=:&æÅ?ý ÑE[G[¢@1%ëÕ+žNÛZ²qV&©ÐßiQ'‰,~¶ÞnSú7Ÿrðâpmº0ùÂü¹žÕ;q_Æ·¹*“„[_-/-ýèÒ¾«8#»¿Cв4šÎ„íËã§m‘‡Ä¯úØíKºÈSÔd1 ý¸ró˜*åñ‹ó…*øÞ¢WâK Û²~pOëz킾®ÆE"Ô ßºuëÆ×®]»nÝ:°Iã@˜ýøØŽcþ!aQï2‹ÅKÛÜ~í®U5+›‚ì“Î;Ü®?M(SULl—îtšÐ†è»(zºÇEâ Ò}ž;ú';âBèA3ZôöÞVûø3ý™âw>óê’Í’·²n# Ü>ÇÙåHÄå¡<aêé¡—¿±pyqz°âWã±8\.·ÿþHEœ={ö×;÷"Nà‡tïÞ=##ãÕ«WòòòùùùÕ½*BúÝ///¡ÈÏ Æ^­å†&gâ!Õ‘­´¿¨Áò׋®|\Ž1m½úÛè¿=ëEJ6Rk óÕ¼$3#S™÷ 1,êc"¯­²L“¸¿±ïÕdþóÅX,#‰xóæÍììl:Œá=áõœ ‰ÕzÊÑ“-7ÍÚZë6{jò–£säzþëRæì¸?$áÔ¼Q§¾Liÿ?ÏÝë8T‘$k¶áÄʽ‹·Ý˸¼Øöò—*}×îüLj(’d9³ÿm9wÑåŒ":¥Õìí³ ˆ"ÎjÕ¿3+ìi öÑ}ž­{õÉ”¯;ô˪öð-έ]Ø¡Ÿ%.R(|±i¼m—õ§þuYPí¥}ó’AXFÔ,IwYÞ|«\ð« ¡l«®¦±'’R ò_^€-%Õl:¹ž\ʶ.Þÿ<×ÿcRc¶™ïå3ÿ«2¢ÐsÝùž_7ÈT­WŸ¶®&Åþ»}*MìCןéá3óË:Ûl‹Ï×…PÁÌÑÃç>=i-Žù8TQ€ÍVó©þ´o2ó=›-Wl*o©áÒ¾ÍÒ7Ÿ)_E¦hÜËÚ¸Ò…Yíô±Âjo:ÉÞµÞÕ§NþúrX5_;€ €¯¹pá‚ f̘±víÚiÓ¦A%¢Lÿmòè}öŒkþéç. 0LFC…IÂxO— q k¾ì^Ð"ýOý…Y5Ô†+$KFü”Fºìy5w\Ü­R³N”~Ýùz‘ÔÀåÃ4$òOÌ Üz"UføÞ¡êŸÆ¾1õûwa»_¿–»Ä@•ÿvçÈÅ!Ú‹î¸öWøáè«18\.wÀ€|>ÿܹs¿Þ¹×q5ƒÄ„¬,ÿ¾§ŸIó¨>¨½ƒ¹óŽü–%ó“^9¶x%Ó¾…™™‚Kð!¾Æ³ÚwÛéR4oV|qJؽ0z‹f]:ËR ¸73rˆ)ï›m¾`¨Ò4>HÒà'§³9µTƒ33³çÏŸ#ÅõðáÃúrORïXYY¹¹¹Mš4 •Ò¾}ûò!ÔŠ›7o"8jÔ¨­[·¢G)¤QSë¾ë™Xcæ2 9bCi„ë¾H¬ùâ:Iceá·‚ó0‡þnž žs7D‘:n[f*…=—Ž™ì²Ó¯ôMFY”ÛÞW˜Ö¼ÅÝññcå1'¶<è­XÔå‹f”î4m¨âuïS·“:Lﻟ?ýÆýeôZg™ÃáŸô\¸paÞ¼y{öì™0aÂ/š¡!âªcøðáâZø)“g³4Û[¤D\'·Ö˜/‡¬2aÈ… BîÓ˜«Ç¢ž¼õú½d3®h ÔÚ\ßDµÊ7d…ÁýN§ˆŠ"ã¯yóø~Ú£s)è‰+ߺYŸáF#§êj²›Ê,£d’8åñåQ£ÇÔé,55µ´´´:8999²^×qøµ€ ê@ppðäÉ“{öì¹oß¾¤¤$0H“ ðÉn·ÌdÇLbt'&λ»ãT¹ûa‡Ö…Öfâ#×Í{Œ›`¡VøÚ÷â“tšÉ’Q‹;H¦†á3dÑYaÿN›ó\K‰J1ØraƒþÓ=ÉXÇÝS ð m?ú‘jîbײòwü¬ y¯YafIë¶í‰¿½öÏU4£qrssX^^~öìY%%¥_4HCÄ ü4ÓuÛvÔ‹ŸNjÖ®‘g•Âéf8ýU·_Jm^Äœyß©)¶‘¾Ý!}»&|¤£¯«u2ÔQ«só‚Jˆˆ˜8qbXXØ¿ÿþ ¥½”½ó\²&ÔlëŽ ºôF“)Aêõµ‹=ùcwm©UÝ|0¢ÜÀe“÷—OsÝ;L­ÉÍÃ)ÊÙµ`‰ÃþÕ–Š0‰(‚ªäõë×vvv­Zµ:zôè»wïÀ M  רÊÓ£‘äœþøe­åÜ[)s+VÿÝ÷ÕÉ,Sç×)ÎßÄhy<®âüòè£kn•è,ØÐS¹»Cxèx$&7ðPБáj¿ZÉ(((øùù¡ÀÅ‹çλgÏÔÄl„q?ÇÊÙvÛ]½ËÊ‹IºÝÁ #½8­¡«7ºo·ŸŽäôéÓ;wîîÁ©'Ä­¸7¸_·x¸×9ð\¸PÜ ‚ìBw›¼OAMÓ§É“´ä׌§óµµ°/sõPË^Ák;¾˜ñ¯½š´ÈÛwÿçU††%²iÐç©y/£I:¿ÐoÕýª#VþÙÑJòù[¾8oüŠMëðá:¨G!*õü¬¡“úDŒØ¼¸£%Ëø×)~g}W¯¼î2§©!ð4 a‹÷$˜uYîíÈUEVCþs-°l7{êAAsöÕ_Ûßž«#€@~rqqqƒ–H$¡¡¡ÿüó4(iè¶àfü‚ÜǼ~=õûþï›»sïСC¿ÿþûòåË P ë_ep×Ö)éâõ›W¢Zí ËêÐ ?…xOOef§ü1q I–Ì^¡6mÚ\ºt©B… 7oÞtpp€Fþ<>„ôÝ»öîíÙÐ&o{Ùfôúا YÉ+p­÷§i)žnò›uÎñÏa ò\íV¥»wʳ‡JÿäÐ UìÞ±cöª»,^:-÷©ô§ÚÿEÌ1ÿ©[߸ø­:<Ó’ßb}>oñuiL›j·crxøßïÚö²c“Æ-§M½<8hŪœg51 &ñÄâX«Þ«½µç-¨ ­A“ï\kûƒ©×©>'àü¹˜^CªB"ÁÏéýû÷x;øåË—8ž={”~Ýu222:tè —Ëÿ÷¿ÿ™››—Â:A™Ìš:þÆÃç§ÿZBÖï…Lì¡Mþc"âÇF¤<½ÙÏÇÛÖܤd+¯X±âÛ·o׬YóÛo¿Ak‚RdÉ(Ä7äà¤öøLJ£É»«Ö¨)„X,]Ÿï¤^ž;uu£UI×7Îì=ÿ½ãˆMÌ>0í²lµpÛøÚvÇRó‡õóª:1dAK£ÜˆÏ«>býªª!GvÈ@¢jm}Göp6¥ª·/$ Çqݪ}ÞÃ{Ìö±ã5‚:C‚&¶²dü_w=%Z–“‰H[fÎC·)³Ý‡ÎZ²äê¶7é±›_9 ÙÔç »õ>©]à$‘>’‹å¸aÁÏ$33ÓÏÏ/""çÀC‡Aƒ€2ÇÈȈٹwøðá#F,_¾|àÀ¥°NPD;Oy›”º+˜´ª¥qlUª;¥(ðÆp5=Ùó¿²ølß~ÝyݾÓñx¼gÏžuíÚ522rúôéÐò‘UÍÑñ;qòŽ–zx‘ˆœ;ºp–„o:Ù.°‹‡ b'7…§²ê ªg =™N£¢Yœ ‰cÙ¸G'‡3[ÞF%*;ZæéÔñ) ÄÑÓcúY"…mFµ¥½ÜØ¥iÞ~~IÁkûé ñÆA|†éåw6Ç®icÓCaÿ;ö¼©Oõe;{ŸM+;PgŒï½Õ¢×²%ýD~o1 RQ-{ylþômQ"÷?g¸ä¾™°î˜À^Ñã–ø¯j$¹˜PêÖN^É&Ÿ ˜$MZô³,¾¾ô? ‚Ÿk#€ Μ9MÊ/ŒŒŒŽ;J¥Òýû÷çÞ÷¨…¥Ù“ÆáÂ{Ï;CUàÔj«àˆ eJ¹éKß¾º~RÈc7êÔ¾çïÿѯG3gN¯^½ð –BnÎrè1²ÉÉyk÷>ß8Ô‘‹H£æ\žŸÞ±ntŸ¨D©ö*¶Nͽ–ìYä(™ŽV&Ý8¼ïÄջѱ*üÇ\hY½q»€í]\LµÛt,=+;áíÜCF96Uë6ó Þ×¢’ð“<¨zshaСȄlJÛ!ŠC£óü»Wâ¨íü¥²U+&vÛ!C,#Çvf÷øtJ«Ý~Èóú¾-SÞ‰kðK«þæ÷gnT›vsþ×ìÞšQ“z_ö š¤pÅgoÇÒ$?|+í»qU7kVž€jfFJÿöïý·öרBµºMÚøl;â¨ÿï¼Ç©ÔoÑägC—Þ4ì¼Ì¿Ùǽž…×ÿ\kw¢nØkÓg›k#€@ʵ¾}ûîÛ·/ïæ¸êÕ«³Ù쨨(h"PÖ…‡‡ã‘#GFŒ±téRooïRX'(¢Æõjá.Èd²÷îÜ{ð@œ–±t˜Ú²õ Ù"c’oHr¸ÐP_@ˆTI5’ •$S-ÍÈLxE§¿µ¬X¥±‹³Sƒª¬FÃþûIÂÇÂÚµkß»wÃãót‹IÔpêêѳFNÃ^ìãÈ×öYâØ~T`ûQùŒËµvë3Á­Oþ5ñª]µoh2h¥î³7tÿ|8©WÍËý'}G. óøô¥VMÎlòé/yÆ!DõÆî«+çûšÄ+§cDîóÛZ’ôø5Fn<:²híE·˜{¨Å¿‡^C~sM¥_ ¹$Æ#p}ŸŠ°6„ ¼Û»wï¹sçRRRò¬Q£FBBô+Ê™n:žžžÙÙÙ(þνïQ'("@вI|ƒ¦ø&BÝÍFW.Ý{tíÚB[[Ûû÷ï[[[ÃÂXmµÑÐd¹ïø€eÛs]XÏR39¤qsÿ=MIœ ‚ŸÄÕ«WkÕªEQTîhPþ1½Ÿ=ztøðá%µÃ°Äëà'äèèøæÍü'içÎÍ›7‡Ñ"H²ø#2!¤AüDªW¯îëë»uëVæ¡———Z­† f€r¯«Nfff§N²²²öïßoaaQ ëà§" q&lÓ¦ þ7®N!ÇKNN†c@ ’Ëå\.úäÁ´eË–cÇŽ¥¤¤Ô¨Qãܹsp°(øy?~ðG`øðá‹/4hP)¬€ŸÊÙ³g'Ožìëë»}ûöÒ6mÓ§O‹ÅøoåÓ§O lmmMMMqŽ…¥Šˆ •J•ššŠ7·ÒÒÒZµjÕ´iShü`×®]«Y³¦¥¥%, ~N]t233;wîŒï8Pü{ߣN~K—.ݳg««ë7X¬Òuþ+éåõÉ5V222ž?ÿþý{¼‰ÏŒƒéë‡CpèçO$IF#“ɲ²²ð7¿X,Æ«bbbbnnnccS¹re¼¹  ‚â§_ƒ9tßóx<#Ùl6&ÿûPGÓ´ oZ©Õ ¹\ÎÜã¥ÆÜ#ÝÎx‘‰D"þñ:`¢caaWÊÁ—(²_'½¹”™‘•%—È(©‚”ÈH™IdD¶ŒÀK$ ñMÈGB¾ö^À£<Ägî¹´áÒ|ÒîÂÃß6†º[ Þ­%Bø¦,^5LúÄ_”91ôo”„ó'’)<§r|¯ÔÝ+tCäH"'¤ò-€³«ŸÖ !Ï&¥kBOOhlZÑ̦‘‘yUX…À£³ÞhÃ÷(þºß£NÊ7''§§OŸÖªUëØ±c®®®e:`˜ë|ÛËqüH$L†Ä&“0E™§¦ ÑhpþÄ™„¥ƒ³%¾ÇÁÁ“Á$Lf%Sfî™Ý°¸Ì„O<NDÌpZû[2ÂÕ2#àÌTááÌér_Ut8há—ÿkß/žB‡i1\9Ùá†áž;æ}ñ˜Úí ÝC\fÆÄ÷¸ÌŒŒŸÂCp‚_ÂÖÁó…„¹çå`¹žÎx"ÜÙ!(Z™üæø»¸G©Šô,RœM¦g8ä‰hcÚÄ@{o¬OŠh=mÌ«h‚IÞø¤ù‰ËÁ7ÜÚöúúW‹ŠBª(e ÊÌ&Ä·pºX{KÓØ,ÚH7/mˆo†Kk§ ÕZs¸z°n‚b200`vî?~|èСAAA¾¾¾¥°NÊ1CC÷oß6mÚtðàÁø#ós6‚P§l]ɧV&Iâ¸UÚú@ üj ñ“—Ž$%g§d°’ÒÈ,)aiBY™Ò–¦´¹maL™óPξ«Ÿ%àý¨TinLãÛÇÊFèuâ?‰™Äût29x—B$¤l¿–ÂùÜÄØ°RÖÖ\ =AÑuêÔ)))I,wéÒ%--íàÁƒÅß¹÷=ê \""""bÔ¨QcÆŒY³f 4HY ±Ð@ ,{(uê‹ÈM/c$±Iì41agAÙ[QöÖ´­UËáBjX?ÊÆJÌF8±[™æÎÓhú@ÒÃ1‰dÌ;2&‘`³­¹ÆÚÒÀɵ·‘iEh=PƒcÇŽ¡œ{ .”jxCÂR»ƒWÓ¨vÞĘ®ÈÚý„|ò†|Dz4¥ì¬ø®ÍÛ@‹Éݹ׵k×”””ƒZYY•Â:(g†Z·n];;»èèh¸ 6@ üòä“×#®ÜyÊ­lKÿâ¤v´£µñòZ<.ªé@á[ή`UVêÚó—Yw¢ÉŠVTz¿Ôª×Z äÂÛ£GE9;÷‡ R ë } Aa!ÎñOÉäôjC‘ä@ðíDBz@{íu®¯Ü½¶>øF>#Ì-àhRçç}ý#GŽàÂ_ý5dÈ ÿŠˆß£NÊ‹u÷î]Ÿû÷ï/\¸ èÍ9o9C»¨`¹‚’ÒÜ…V(•§Â6­>çééÉìÜóòòÂ…ƒçÞ÷¨€².$$$88¸mÛ¶§OŸ†Ö„ù«h––&6yŸFX˜Ð°hA‰PI¢^Uâ@ÿ,à‹ôõõ>Œ áááC† ™7oÞ°aÃJa”iãÆ«[·n¥J•?~ ý„éÔ>-- —Åb1Êé$P©TævòÎÜã‡jµö,zŠ¢˜žè™ä‘®ó¦6¦ç÷ÜÊ™áL¿ðÿz_<ÍÎç%—ËeêÌ[ ‡Ãa&àãöhN÷ÌS¸ÌÔÆb±Hü—™îæ1>ŸÏãñ˜žåMLL`¹ð’D29ý"eßk«^*+SX¾àÛIÅñ‡¯ZÈjl—Çów׆EѱcÇÄÄD¼5Ö½{w\8pà€M)¬€2ê×_½víš½½}DD„££c‹ÌÌÌwïÞá̆£š\.ÇQM¡P(?…‡àðÃËÁÉósÏpFb†ç˜XÅær›q¤ÌÒÁW|¯ÖQ哹g ÓÚø77?@OOÏÀÀÀÊÊÊÂÂ>waükQõ̯U‘Xn¶ï´{Rº ©³Â¥&É"aYƒ" ä/b²/?0W¨ÈŽõ“ürY÷KChðUD"Ñ¡C‡páĉõëן;wîðáÃKa”E¶¶¶ uêÔY²d‰§§gi˜$œî^¿~‘‘‘-Ƀ‡HGOg3&fà{>ŸïñŒÀÒ,)¹» K¶Z™LöêÕ+iü0wùâeSeîÂÅ ÚÈÈÈÞÞ¾R¥JÐs&€@XZðSú4<À”ã*Ý|UïIœ¡¹ª^uuu{‹‹hÑ´26>ûÞsÑ“XQ ™kå׎æ÷ª6‡†%£C‡Ìν=zàMÆÙ¹÷=ê ÌmýGGG÷êÕëîÝ»³fÍúoÞü¦©©©ÉÉÉjµÚÌÌÌTÇÄć[X4åÞdÔ×)âø/_¾LOOOKKKÕ‹ÅÌJR½zõ:uê@V+ý7ëâÛ‡‡’£çïk>{gûúž™‘ÆÑVVÉ–ek)±Üg?ur²øÍ;ÖËwú1‰|kS…£MRuËgöÂxûº¨K]h!ð½àŃâÂÉ“']\\‚‚‚|}}Ka”-û÷ï ìÖ­syÞï!>>Ä$ Þ”¯P¡ô…Šòk……ÎçOÝ»wïÉ“'±±±Íš5kÚ´)´€@ø#éq2êÙ^¯÷éyÊ4á›ô*1©¶1ÉÆïRyFúS¥µ™ÊÆŒ°2çÂ5EÊ J-ON—½K&Rù ©‚÷éc}µ½…ØÞì½ÉK nšEÔ¨´ø1Ú·oŸ””DD †·ïQ'e…¿¿xxxµjÕ>|ÈãñJ¼~\9ÒÇË\é€â011qss³²²:þ¼££#œ –:\–´ŠÙC|CÕ?Ëì$‰Íû,ë÷Y&)bÑû ¾XÊ2Ñט*Í UÆú´‰!alÈÕ׃þ+Z&“¥gªÓÄtz;-‹û>—’Éæqhs#¥…a¶™~†¥A’…(Î’%·´Bõ¬ Å üÃi­V­ZvvvwïÞÅ÷%[ù»wï<==âtôõõ­rä{EM>Ù€¤¨ôôô$ÄÄDCCà *XZZÒ4ý=~¿áwD’jKýX|ûÒH ¤’òÓ¤é2³ ©A†D$–áÜÈKØR9©'  E¡Z$Ðèñ)‘Òã#=! XzB›ýS«JSj…D¦‘Ê©,)’ÈÜ>YR–DÎÂm%–²Å—Cë 5†zJ¡ÜP 1f륚’±€‹lÌ2ƒµÀ8­áX¸uëVww÷¯_OO¯†N/‘H²²²ÄbqFFFff&Axs'Fý%~Pz¨Õêììì¬xMÀ÷x­ …F:x‰D\.—9‚ÔÙÙ @ ,ÿ8,yá¹ñß_'‰‘B-”¨ eJ=™J W d*¾LÉ“«¸2%G¦àÈUl¥Š”«H•šÀ7…’Ày‰ÃF\6ÍçR$Ió8ºña³i6ù¡Ìçj˜—Y$ÅaSºs23‹DºH£A*͇ °Ê•$Ó#†Âï…jË %IÑÚ×P4“Á$:¥š¤(íøÌ$É•žO …o\í½FÀU ¸*>W)à(\ßs¤ŽDÀ•9úé³¥ö?XqŽ^¯^½êرcddä¤I“¾÷Ûá¿°ÌD­­­ § …üS2¥RÉô'Á×É[À"· ‡ª~'&wÑ0]P0˜!L/&¼]Ä\˃¹Ï]Lx5`ž‚ƒ?BP\<¶ßZ€ò)!!aïÞ½y;w^¼x1Sððð¨_¿>4%"<<Üßß¿ÿþ»wï. ÓÃb±„:ßðZ&™0]Æ«säíjyˆÃ ÓwüçœX®_xœ[ò–™ÉtøŽtA!t˜qy˹øótÊôÿùÄãÉfº¼ÿWËû5È`ú¸Ç¹ ×ÏÌÊÓñ=žYüÈ´ÓLå¹e<©,¦Àô\Ïøan9wxî…™Žq¶‡€@à{ÑÓÓ›:u*³}Ø6m¾ïҥˉ'.]ºM@I {FØ|ζ)õ…Ŭ_õ:t”ß¡$\²jÚgD}3”•­ÿKE•ýJŠ4Ò§Ï’íl9¥§{dºà§äÑ«‡N;+ÎçÛëV÷´c×w@ €ÿsàè‹/‚ƒƒá`Qþ«V­ mҤɵkטëj~gª7»FÛŸ¨-Vè2gO}£œÞ†ÕY©J¡©. jÄONïÚvõaB6ÅÒ¯X¯e—~}[;Št“GË^„oÿßµè×± ï³Tx©gãìÞsðw¡nJüô~Seâµ}¯áÿõÜ«µoaÅã2—ñ”ËY,& Òª¤;Gö;ð¾ÔVù+üÝ©/Mþs=´Jòß{¶¸pïmnI®‘]5·¾†57gÁg @ €ÿ˜\žg¢giT\|Bvò[Jš”R¤ÈÆ÷´R¢-”b={öLOOOII (Áj§M›V²~\!âêÚ{!â‰X‘™•CŊΖ\X¯Áw4hРºuëÚÚÚFGG}×÷¢3®¯gÒ ¾ÇÂeCjçíšž­oŠ7»èìÛK‡Í½"ÁŒ\ºø40HŠ8tòøê;ÇW· Ú0ª–€–¾¸pêÆ ü|Ŷƒ=«ñWþwìþ_«üþÚ×sÕºyŽeÕè?¹ge.B×¼!é§A*ñ¯©#6=§ñxömOojO¾Ø»x×ãèÔX©swÏEèhʺpç)í”Ø´ع:_š’ÂjXY}gI_Ýôókv9 :;æ\èþýA7ö Ýfo™æ*"¨”33†®‰ÖölÓ¬wÉËKb }3ÉEÿÞ?>4í¼|ãÐ*ÊBÚ*Ÿi.ìÝé¬ÛK‡qúó›kó{³û=ÀµZµ7ñWK$Ž“h\ÓÒ €@ÿ…‡©èÒÓ”ôç·é„‡„ЙT"Lì “ŠHÏßÈ25/xëÁú;T[»ÐÂ[©Y=HÓ܇Òbh|K#Í«ZToÐ¥a%+!k>(AõêÕ‹ŠŠªY³æÉ“'qÙÞÞ>66éúu077ïÖ­Û”)S*W®\+¶æC34•滛£u GÏ=p#³G¬s{—YïSé§6]ì±¼ƒYΨ‚ ÍÚ¶«+@¿:¤ùμ"EI7¤ös°úB¸¦5ê7üxVܧo¨Š=}L›‘Ç‚%ckád*ã^à¡Çêo˜+CKÏÌ©bvŽÖM¿öhÏc¢?Ž$8ü@âêÆ‹;}P›Ç¨UàÊñÚDL¥roœ~ö¼÷ +µëßÚ6§ÿCBähÎVÅì/j[åÎuaﮊ=ºé‹Ó/Êo®qŒïÙÆôÁéT”x:8à4ǦAÇÞÞ- „ðýÜL¤ÏEÜQ=»Dè[–ÕÉZí¾r†dÆq¢J3f@2B›¯½¥cïÐIOxö.ÝÛ¶p4ãC;â355oܸñرcs¥(*))iƒÎ‡U2ODü–5Ú¨qÿz³/KPöÿ)Fsç ¬›{È(­ÊÊÔè™T¶f¡$ ’<¾ tªÂE´ôå?¯uaNd_Q?¿ß¸Hö7ü@±iìbtèTŸ[´Ìjh»*ìä›/%ŸT+0bÎfL• ¥¹EúymTÉŒD‰xzí½×,ïQñ³K×ÐF-t#dß½üRV«¶ ˆÓ.pháÙåÓCFi¢ð¶">æ)WPÌäòî…NþQÝÑÛÃFã5F–pûðš¥ÿ;ºâÎÑͺ£\áû @ €’BÓèÈ㬨ˆ3Ú=EÕ=ˆ ®¬ ®Ð,?!ÂÈßê¢FhßýD:*œ@tÖíÚÕµƒÆÅúý$oݺ… þùg¾#ØÛÛ»¸¸Èd²}ûöEGGs8OOϯ|a½É»¶4þcÉŤ˜Ã³¼úlÅAëWÍÙ6}å„ KI‡'ôÈó¬E›YKGkUBsË«1*ôíÏïØvðâ]Kn ¡UΓ¢X–m'û\òœŠZæÝuY?ž†ÍçnT,š|#fǘî;>>Áª;cç¼&"ís6*N¾•~rFï“Åú*0p+¤­‘ËoN¬çQÍù}{4úsÇÌ…¼{¡ÓŸ/MÂa¿‘!±ŸÔ«×´>U!”„$)Ú~]õê:éÒ“¬×äÙ"´"܆àÂí¬ì›«¶rD†#ûu5À/Prãß74hгgO//¯¼}Ù멹,‹æ67ŸPðnCÂ&[MÛ-Ë{8„ÀiÂÞ° _¡€á´äÁŽ·Q­>¿ê±•)Ï8§^©•«³×’SÁkÙQ/Tä)ù°íhéñÇn/m\ZzÌÚãQ´¶â×»+llÁ“`üŶÂMÝ6ðHÛ¯{÷B¦?¿¹fÙx­ ó‚ €@%ïišê{ö ®¬Ó•U© 4(ODº Ñ ´&,‚Ž¹Ñ­÷g{shðm"""<اOŸ¼ñ¯ÜQ§¾ÓÐIwÃ#OŠåþ™84èØ®wß¶5 HX?šD…Vî?¯I‹# D$œšŠŠ¨ì†oG¯^8~VðÕ¬­­ÇŽ[’5jäRŠ',Z×´üÍù#×õ:·¯©ÿ=£§b»QsJÓ™×%5ã´F¥&8œÒk)…TÃr g„à‡|¥Rú©ïi»ºeßIŠ(úÊͰC´k¯?Y…óC'(ØÅgi—m%"«µ†Öß «µ¢4ª%«7ÖkܼSSghðiŽNµMÝoͲ^E¹F‰$rÕ´àÇîK¼8O× Ÿq*ñÃ9ƒCÛêõ[xöìÚÄN{%uÜÁÉcw¨{¯èW© J)qÔ_{/ÈZ ïíX®l¢›ñG.îxw}Qc¶î&y”*f×è±ûÕ]WllŸø…y×u–˜ÖoÍš>KÉF,•uwÝÈ€[.³7NtÕ‡P ‚SÇk<k˜‹;ôã\Däž~,V7O1=Ëv]ÌY[÷[®±Nk/Ñ÷K =yDg!:ë 'ÿê@xk…ªë)m}Ý—p‚ëÅ pâQê­°m¬–~ˆ]þÎÇW¤îëql¤"÷²,}‘³UÝö5:ô±+¤/eúñQçþº-NKVªtH=¡Mm놽ëuëaa_ןaqˆæ£ÜÙ£T*º»Ãõ‡ÀA‹#æÝ–Õ#x]‘Ò ’Þ[·ä¯Ý"Ÿê<*2-•þ¸}Ò‡‹kªÞ™2ª÷²úÓ·ÿéfP¡ÇÊ£= I¢ÃÃÏ™ÖÒÛ±ô7TÎŒbñjðÊà×\šåä'õÛ£‹ö'švY6@»íR„y/EHýã7ü±`ðÜ ;V­÷qàÀ‡@ Ÿ½¥_ç”_ïQ÷±bhK°tA±sNÄ=A꺆&“Þ¢Þü7ýF‚¸&„{[Ö¬¾¤ïc™O¨À­š£èl„l*©Ÿ|)!FÊdˆ­;Š*õ¶ÆoCgê:²r$x³F7" ß §éaš•—éG±ô[1³iJ4õ`ÍDÖÒ+ÚÄ6;/«Ü7Ò„j âœèGàa¯«~ÝHSˆð˜ÊÞÚš€µ³\ºŸ}ëØ–û„ò˜µ«¼*å. ²LÛM«aC(Ó_¾¿uìEXÄ‹°YWq±ucó~BV+bn¤$¥!¤o×s¦£%©ÉŽM¼¼ùÙ‘›/LÒë|¢ßàºð¡È绪A¿§7CO í׆Öÿ9å‹ý›ïMæxU*RÞ_·è Û#з&ÿßýÈk7Ê ¬ÍxèUf†œF¢ŒsS+‡lZÙÅŠE‰ï¬õ 8›açæÙ²¦)‘‘$wêÝ¿óª´Ç×.«øÚ?Ó•ê;£ccÇì5±7°‰îoµ›qÒ¨ù´WÏ_¶âzíÙn†TbøÂ­oì®íÿùÞ5Mâ_sWÝ1é¿vMï Ì6kûêéý‚Ò¾<ñ$'fÏ»Àï³zˇëÚ»ß%ïå3BªlâÈ.ú¬}Åבc5q/^ÑÊX ä‰DI¯?DÙ¤ÛG£l½혠z`Bß“"Q3öáDàYMd&’%ÒéÄSϺôá•·«oå©'ü•ÙˆÆRkt̨ ûÌdRˆ¿ SÑéšûOš"ˆwšM'é¸,]0e¾˜cé Ò/â7 €èÔžÕLˆºU¦L£²}ö=¹2A61ʘBfÇ!nEÖ‰0rÍ8Õ»š¼ý û꟤ü´V®e¿&êtýéf›_³ZCó¨Óɽ‰—6´'%w¶ß(_œëügœ5š4·Ãð©‹žß0"ùÏ-15Çní^!¿R‚…«¡”úk&I š_fz‹÷›?¶ß-÷ÎÍ8‰7ÃOEÉêŽZã«]H_3kˆÊ¸:wÈâHNóyÛ¦8+ó/ã9£2¶íïQN!Á'Ôèu¦® D,m êÀ5½LÅiP½&duü•AÌ×c&JÓ ÂÜ»=b¬úô»§&Ï×!¹åìöúÚvog­>—FyuSþgªé!¤NIQôfU•Í%©CùðΚ-ÊÊkh_Ÿ~ýtÃì(-|]RMvå÷(&Aò#Ae8^üÂú~sÚœ6k¹Ë¦© ìàOþhKÐyµÛŸc\„y_:óPXAõ’ÆËÂrzOg™¶˜±§Åç#q¼7‡y:¬ÁÄÝy;rïÒgü‡RÞ ?þI6ýÕï¯ùm Ú´5&ç{»ÞtÅÓ×iHäÒȶ¨±'ßÏ£W„u.lÞICW¿0¿ÏvòÊã‚&^{Tj£ ¡arví3êg4j6çP³œ%V@Y“ptö’GUGnô²ƒ k|ŠKÌÚÉõÉ—rsÏóׂD½Vq{åýj·"·øÒÉEx„ÐC@Öp|ü[Cn=ògj5œÀ‰Ÿðñá'U!¤W‡}šõSØì°LÉУyž’ öpÀjSÞy6®þìùóì§Èê­Êãü±¸–Õ¬d*íô¢íÇÀÈÀ¾eõVjuô²2þò6›kë$â_Ζg½Ý7Q·/ŸàWµn1®f§áU™ë¬FÝÜzGDèÆ—·‚/kÅæò̪;4ªZA{•ñJ.‰~¹ïà=]Šä˜8;týK/O#^¹]£,åo’žÞš>~|¸ÀúE¢æï[ÖZý1;èˆe@wûü>åòè­‹Î*ÍS_¯lí‘È+w›ÒÍÓ©ˆ½\”¡ÿÚYûˆßÙ0ÿ¤ÃÌ¿FFp|€@E1q€çÊ]‰Ÿ] «•¿LÈ3éyȧ緽–kÒmïÀn…gN^u_÷@_÷üž#-º4Õ¥éO³.UR¿ysó/œÙ,Ø?§B·ÅÛ þðòkŽÞ6ºÌ'ˤn»n_1~šñ¯µ<_» F¯Û ” Ê¢~¹<ôœBYj¤Ï…e >?ÀóÔ­§7/®$› F|hðÕàEÜÙeb1sÒh aÙ¦Ž§: U«¼9á}Ñõ9ªþ÷Ƀ{ØMÊË ?éw4Ígj~Ç]ÝÁoø W»†ÕÛ6¬¾"ôˆD®$\z"ÖPTvÉÿ¼}9zÄc=´ø~\\\®^½êêêÊçó¡5@ñÑ4}ãÆjÕªÀ/¡!ÈûíIùŽTgôæüÝ—à"º L°Œ¾v¶¬C:êé)ㆬ+s膳T ×qþ¨ |D 4qP7¥†Z¿ÿLfÂK²A_¤gÍ "@Jòá™Lú[¿Þ6~ß[çÎÚKžÈd²ÈÈȨ¨¨äädKKKSSS===’„Ÿ±@X,^sð:óîÝ»””ccã5j4hÐÀÕÕ@ ÿö`¯úAîîIrL¥Š^6\µÜŒ½„d~Vúg‰²û òð.vc>J»¥n3“J©@nMZ!:NAŒ÷&©ëêÖs)UmÒ¯5‘}—Zy…nâÇÙ×`Sèã•ß’¾=I{ÿ€Úr®Ô—sÞ‡à!ôþZ¯Ê™u*õÇ\ªÃzrÚg° §Œ±§URÎYM []J€ã²H¿¾ípი«'·³lœé*-~ˆ6O¹“øào“:Íùö& hðßþ!4ÕÉ;¢¨ÄÄĸ¸8fs?33S*• …B}¡ŸÏçr¹l6lk•„®.…BÞT'++K,gggãÅmhhhbbbeee§‹@ ECÑQ²#«ãQu’¦ÏLŠßŸýÜ›ÌM•êDMû¹”^/öÙºmXæ*ÏÕê³Í9í™´fN ëEÚ³òbu]«ìx@s­'»E–¦¯jªn9‚ŠÉ©?x„2X÷Ý8jg «À§>ìd¡z5t…z© -°ÔAþZÕ±oUg.üýîÊÙ¸@×lGÙBËüœô4Yä³³âÄØ*=ºµu嶇ŸÕAiú‚$mt '‡´´4œ322prÀù‡hL”ìŸÂLD wT–†,ä; 8)”Bÿîèˆ"4ø›Žõéß š ðE=§?,æf죿Òùå1¾'HZE|éUÜJìˆÓÚª·aT‡õœi•>®H?õaj²Ri$"õY°ÈAá~­iýkÍ!¸"£Îߎ~þð6-N"íêÑ ¡1´O¹ý:TËõ“ïg¿¼­¦ «ê Ü׫ú‹4 (ëp¨°Õ)f=8:â)‘Hd:Lta0Ù†)à´ƒ 4MãœCQ~¨É²tpm8g2±–Ù‹…ï™#`sGÈ+ßø%ŸÌ‹IVÿ‚§ê ñôÓ:¸€t»añô3ÃqœÃïˆïñ”3÷x‚™ÀŒ‡ã{œ¥qI×ÌCA¡PˆïñX€@XJ¹u'ôþ v¿Dókä ’ÓGQ¹—R«¶dUÑo¹n¦ºérXKÂŒ¢žæ!û7%ìDfE»±\ÙÚË~¸BX¾ª¨ zýTÅ›¬×_ÃL@ön^á›Î»lêÖÓØ71o2céô·l=#¶iE%ß”æ WßmAˆHøá¡ô¡i¤’ÒJ)RJ …D@I8ÊLEê[YÚ;Bd.°°·«h_¯Z¥ê¿4&ˆÆÐZ|§/‘4ay£ïÊÚíIwõW·ØÎþÍh9‡_À˜F ÙQ§ó<öb­üP"¬ÜÙ?ï•ÌýÉ@2÷ÿÞÁÜÞyž¨1ˆ?èCUÖ½*ÇŽõ÷Éü7¸óJCo˜®þ§ûZ/‚˃µˆìÜ "·Ϩ($U£l’ªš*ÕsQË”xœZ’—’dg5´7¨îT÷Èß÷Jï¶,IØzzøÆA<ìBÑp,ç’µªÏ}zçÎgÏž-‘:ãâ↠"‹CBB®\¹ „PŠÐ4½zõê ,]ºtݺu%Rç»wcÇŽ3gÎ@#!”.§OŸ÷¦X¦g À¢%E¬²ð Šˆˆ0`@ûöí—-[æíí]ÌÚÔj5“k׮ŵé@ añvñõŒØrÒÓ§ÅI‘–.(.‚Œz„eûøƒÆøiÅÄÄà(BCC_½zUÌÚ4Í2œgë@ a‰±p^4±¶ôþ%åÖK‚–µî¹V~LÀB_y\lpí C»ÖS¦¹Cƒü„²³³ÇŽ{ýúõ={ö¿çwš¦×¬Y3wî\œ§L™2uêTha@ ü>HaÝVËë¶B”*%êÆ¦Ë·Ò<Ô¼Æýªæ/ ‚†EòG°)¡ûã8³«w$[Ø¢EË&í[¹u€ß~:E-Y²dùòåëÖ­Û¾}{ñ+ܺu«¿¿ÿäÉ“'Nœˆ&´0 þWÁcæÜÌß¹™î­~ÿ:ündÄפ©¾ªf…÷Õ,šèeÀðÓ¢y•ße;=e=zMª)^½ÚÖÎ º:µ°sjmó“:vìØˆ#pf›>}ú´iÓŠYÛ®]»&Mš4zôhœ‡ Í „?Á¶¨Ü¥-¾åæZñîå™gOn?“œ)´1•Ø[dU2µ3zÉfi`Í(G? ˆ$t­ØT‹˜DvL"+=[`gAUstp¬íaãXÉÆý Môs»ÿ~¿~ýêׯ¿víÚÄÄÄbÖvøðá1cÆx{{ 0šKkB$xÖU;áÛ¿ò€Jö>æÍµ„·ÏS’élÏÊXjm*·4ʲÐO4¾á²U°Þ”.,ãLUåä,Ó÷¤42!…•)á[+m,õ¬mìlíë›ÛÔ©I5¡¡@III>>>™™™»vízôèQ1k 1bDÏž= y°¬â,ìkv³/ =д:ý}TJbôûĸäÔŒÔtuz[©a™ˆäÆú*}µ±¾ÂX˜eÈO1äÇsH9´ç7vše‘­¶Î”gJiYìt1™žÅ©‚k(Tšf¦"33Ks«ª¶u ƆU…f…Q(Ó¦M;zôèŽ;Nž÷ÃÅWÙ,ÄÉY#x\’ µ×Ød‘—Ãbr¹’Eâ‡Ãå°Ù<ž€Çr¸|G qx¸¬Ïñ„æ|‚àÃJóçÏûúúöîÝ{Á‚#GŽ,NU·oß4hPíÚµ×­[÷ìÙ3h[@ ß—o‚o%[ç¾}ûG/ªZµª…m½>}ú@#ƒòçùóçýû÷·¶¶ÞºuklllqªŠŠŠòöö¶³³Û¸qcñÏ6€@~$Fƒ·nqáÅ‹Ç÷ôô‰DÐ, |ÈÈÈ9räãÇ÷îÝ{óæÍâTõäÉ)###¡m”Í›7W©>?jdd4hРC‡A³€2M­VÏŸ?Ó¦M›7oÞ·o_qªzýúµ¯¯¯F£ ù矠m”{÷î½~ýzîø¸8@€7 áÀQPFáµ×ÏÏoÚ´iþùçœ9s¾¹üY2dˆX,Æ9ðÒ¥Kа€@ÊF3hР |öì8 Êœ›7oöëׯuëÖ+V¬(ÎÏIIIxý‰‰ =sæ 4, ‚r«[·nl6ÛÙÙ™y˜žžnllŒ ‰oX߸qš”roß¾8p .ìܹóÅ‹ß\OJJÊØ±cûûû8p‡ÉuëÖA«@ ù³··?þ<Òuçíææ&¶mÛ†BË€ÿÒ£Gúöí[«V­ 6$$$|[%J¥2 ¯ÀÁÁÁ+t a ‚"qttŒˆˆÀ…¨¨¨úõëÛØØlÞ¼ÙÚÚZ|?)))C† Á pÏž=<ø¶JÔju``àºuë–.]º@!øFNNN‘‘‘¸pûöm:uê¬]»ÖÄÄZ”¥R9sæÌ½{÷çRŸE-_¾|ñâÅóçÏŸ5kÖìÙ³¡a ‚ãêêúèÑ#\¸zõªOË–-ñö·´ øf[·nõ÷÷X¬ó 5Ð4½~ýú9sæàH9qâÄÉ“'C«@ ßQ³f͘žßN:5lذnݺ …BhPD—/_öööîÚµë¢E‹† òÍarÆŒ“&MÂ!pÔ¨QЪÁª]»vqqq¸pøðá1cÆàMü€€-òõúõëþýû†„„¼yóæÛ*Ù·oŸŸŸßÈ‘#gΜùÍa@ %ÆKvíÚ5iҤѣGûûû³Ù°R-±X}ÊdC(ß^Ø{ðÑf´wmØŸ„  CÓôæÍ›gÍš5cÆŒqãÆ‘$ -óÒh4AAA«W¯Þ°aÃŽ;¾­’¼‡%sÿ¥¤=â÷ èãupXáiPvQßÁ»©ÑǶ‡ïf@ e AÃu(Š ^¸pá¼yóðf=ó38tèШQ£&Nœ8cÆŒ?þøãj¸pá^a<<<–.]Ê–\æÉ®_ñ9úOh¨WبÔûðÙÁ·ÒÛ„ö€4„  #Ir¼ŽZ­Æ™píڵ˖-0`´L¹Ù¯_¿&Mš¬^½:))éj¸~ýú Aƒ7nüòåË}ÇŽkÖ¬aN;eÝ»wïpŠ“J¥»wï~òäÉ7Ôp÷î]ooï5j¬]»öÙ³g¥c¶4ñG†KD¦wÑ¥A„T¯v-¾¬68ÝÔ@HùtÛÂkjËÁ»þ7¯_ûôÌÑ+Z¹/]·åáÀ.š›ÓšuÛ¥î±ãa\kíÓÊ'KÝW#ëaSÝ$½3³YçíJ¯qmLµÏÊïþÑd+ª,]³§x¼5øª4qBc3@r{庨ö¼ÑuÚÔvuñÆToÉ(g>ó´êÍÉï¨SÛª|œþ&ìzo=ìÄ"& "”uméÆ7ÈeÙH'N’ëÇmOÂÏ.aÒ ¢’O-Ø¡ÝyèãÈAHw(èŒÜ°ûôö¹i©_nŸ¾/võ¨±K7D¿jËÞ~gŸô¨¥çç!(‹„Ba°ŽX,:tè¥K—BBBš5k-SÊÑ4½víÚ¹sç.]ºtÍš5ßPÃóçÏ $¶mÛYJçS™ü<¡j&B]⢥w. #çmmðw¥úÍžù§e^èöªß_]æ; ø»ÅÒKkZè#ÉÂ/J{›EÕ哈–=Ù¹è¤ýò{[+<œ@ÚSiÅqI2ñ DeÝÛ¶ô:%h7¬™‘v×_öƒ¿!ÔØ£zÞ+ÏpõqìzlˆØÞÉœÌ]pN. ‚²ÏÀÀ`Ë–-¸––Ö·oß„††ºººBË”6gÏžõññÁY. `̘1_ûòׯ_ûúúj4œü¿¹ó‰ÿŽ~«­W6Î7»™ýL5צQ÷Qý|]¿ÊÈP![ù­å«žâq÷¬v‰lë6íÐ/àvŒ«Ù‡oQNµñg/W˜3a¶k…‘j®uÃã&Œê]w½$%]…Œ9œêÎ\°ž9~’³]-¨Ø¬ßıc:]ØÅOÉP#µ]·}ØÈécí~G\ çŽ×.X¥‚ÏÉ'®{çÏö¬6ò­ !¾EÆm}gÏî_C‘A9abb²wï^¤;'ÍÓÓ3!!aÇŽNNNÐ2?ÖÓ§OûõëW©R¥M›6ÅÇÇíËñK†š’’‚sþ¥K—ÊÐŒs+xÎ;â9/Ïæ­µ÷šw»ç ÚîŒÜÖÊ ÿ—ü*=‚Âz}âýkçÏ «÷]~²ïòÏö<ý±cE¶åosŽü6ç_5’úNýî¿ÖG@ åµµõ_ý… 111­[·–Éd8K8::BËü—ÒÒÒ†þêÕ«Ý»wß¹sçk_ž””ôûï¿ã—ãewòäÉòÔ2,ëþáñýa Bð3ø×õñ±Gw\Hv ¼qbëË×åGê/](_{Õ~—‘×L›õíêbFf&&§?ÀF©cCùýcØëàæNf$2stdÎ:‹ŠŠª_¿¾ÝæÍ›---a • ;wNž<ù?þ˜?þ‚ ¾êµb±xâĉ/^Ä1’9î@ eÛg×ÇGòk–ÜCU§OÖ^Šÿ‹×å/ìBù’[«6¼BÎ#s®ÚX-º×Ds®†=•ýRùúض³ž4[u}iÑ'“äääÄ\—òöíÛ­ZµªS§ÎÚµkMLL`a}³ëׯ0 mÛ¶Ë–-8pàW½V"‘L™2%<<|‹4&BP~I ”ñ&^¢»z>Ò$†ìxÏm4°2v]þ/^(ŸJ<x$KØ~š—ëã*XÑk\Óy¿ï›=ãéÓQM7ÜØÚÉ‚Uд¹ºº>zô._¾ìããƒÃáòåË `©Qll,Î</44ôåË—_õZ¹\>k֬ݻwoذaݺuИA9Ä©6þœöêùÔ¯0L-°vªÉyIÙšÞR×_Ú¯ËÏbéBùŠÇ›—ßEÇMjªÿI5õ˜Ú׼ˮãœEØm•lѢūW¯páÔ©SÆ ëÖ­[PPP(„Å—¯ììl??¿«W¯îÙ³gé¯z­R© ضm[ppðhO„ Ë{õ|ñ9—ADã©Ãkòrž.èºüŒ‚/”Ïsšu3~S–Ü ž¹+ÕˆŸñøüÑ«±<·™§žŽt}CïjíÚµ‹‹‹Ã…Ç3ÆÛÛ§K£(jÙ²e8­]»vëÖ­_õZµZ½xñâ•+Wâ—/Ðö€@~6!/ãK¾Z½zãVÔÓ•æ¯*™½tpa×®]“&M7nÜ´iÓØìŸt  1bĨQ£üýý§L™òUrÕªUAAAóæÍ›1c~9| Pf СizóæÍ³fÍ©‡C’$†yðàA¿~ýêÖ­»~ýúwïÞý…¸¹6lØ0{öì?þøÃÏÏo„ °"@  ¬"b¸EQ+V¬X´hÑüùó‡ öáú6åËû÷ï}}}SSS÷ìÙõU¯ :u*øþ÷߇5!åI’“tÔjõ¼yóÖ®]»lÙ²”ƒYS(3fÌ8tèÐŽ;ÂÃÿêµûöíÃ!päÈ‘3gÎ4h¬'(×ë:›=[ç¨éÓ§ãµfÍæ´Ã2gÓ¦M8È.×)ú ÃÂÂpìß¿?ÎÆ}úôµ!?¤#•JÇwüøñׯ_—•‰wppèÑ£Çüùó‡þµ/ìÔ©žë„„XBð³ …ÁÁÁ«W¯.Íyÿþýzõêå²TÇZ…B‘”ôÿöûÿøì°ÂPT–QAQ\¸Š{ jÝ[‹«¶õןmÕŽß¿µ¶Öº÷h­‚UkU'*´œˆ Te Y—äîIAQ±ò~=Ñ'ù^òÍ÷>w¹'oîr÷¨R¥J¥é'11‘ŸY,t@ (7|}}»víºwïÞ"íAAA¥Lƒ„åÒž={är¹Z­Îo NJJÚ¹s'Š„I’»wïîØ±#ÇqüC…BqáÂ…èèhT*¾öíÛwíÚ522’¿ßªU«€€€Úµk£,€@`vïÞ-—ËýüüîÝ»‡ƒE^›Á`ÈÊÊJOO’‘—ñ€Qf3¥–a´ü?žÑsüM­§–b9ÒBj"•JçÎ[.†:`À€ 6Lž<ù \Žf³Lˆ(VDó7B,¤ø›ÈH(–È%Öö"¹ƒÂQ¡P888XYYa³„PA°,ûðARâåãIwîeSbRë@g:P™‚,ÚxSÐ9Žëøâ+ùô'2Ý,ÏìùB‚ø©|ŒÕ‰X`C*øl¾ ‚P›nY…š5œ8àÈÐÛÿço¬Cš^a+aÝ«Uv¯ÛÜ£F-[[[lXÞ;ʼ'WoK¸qýA®ÈžÊt>¨.ºï&|èBj\"& à$¤¶š •¿C§Œ·tƒ}"ã’¤sMÔ»Ñ4]ÓÕÉ; ¨F­:E¡z€@ÿ–eïœÝ}ùÌá„l™«àa}Éu/Ñ­R Bð+‘NCÞ|L'ql'wV€Á˜pòAŸE4ð4TF¢)G:ÓQšÙXzééã'qpvf”Ýe­÷e7!4¨ëY¿Uo…µB(K© §OíûýfŽÜO|µ±4®]«"í÷ÓBHbX~4,ýPÝ«à/¹XrãFùgÉÆ{SOwâÞ {C®°×/â8‚y©/¸VZÁ Ÿ$ÇXc&š2V5³Òëõyv¼÷E’m¿!Ê_Üßz0eZÈ—­È\;›6Èg¦„H>ÅPYŒÙ²§³ÛÈNñ7・ù£¦z¬ºQ:W¹©Ÿ·‡b1Š „ðúr“/Ùº(!W(;ÛDz±ÀPQwªÓ×YÓ===i…Ä.LÓÆüš#wm•™Ò Š~DLs"Êàü6býïŸ*o¤’RGÖÝr¾«™k–ÄGì] ®¶è3>H$2W~¾[˜¤wkÛ¼¡_Ûþ8¸áÒ/ïþk÷^ŠczXè)ʲ„£@³Ó¨ ÓM¨4‚ÁëDÑcOš¸xP6áIÅ‘|`¼—Ljëq‚0ä >Z+ù#·P'RíÁ:Úx_•"ê¹Nt½¤=,Y`ç˜&PLäÜ6øƒÖz7b˜ò×WåO–üm½©«"áËCstÞ•*Å`^9;jùFÙ×ù¨aøz‚f%ø`¹8FOXÕÖí§w.}¾(2×ÂgíŒ t~ý”ÃÿÆi†+¸âf5RõuÕ—–«„WMÉ’6þÓø¯Žª¨¦¬€ ¯‘u:iÜ™9|„òÕ*ìºmOg÷³Þm¼OÄ]üù€²u#OÇ6!ÓhÛ[@ „‚ßšõêƒË>ŒÏ± µÙ5Ê6Í¢æ=9Å»ØY£µW~“nÎuÿ“ÝY[Øë ɧ ŸÆêö¯‘ìÓS)ÇŠ5ôÐE’£,AVÓž¦«BœŠþàéÑDq÷½ì™î©R0ÌÀœ´ÇÇèjPÆ£4{˜ŽÒ,‰muÒ¬gôÔ쥲չäú}Âñ£un¥L\ϲeÊEYã=”6QvMùg=ŽxÕ`8õ+fGF³F*;œ–¶?D±Tþ…éEÁ}Õ˽YºLªÿlä„–þh©4\EÿwÈk²¶)ýÂJS.¢„WUÑü|Q–HÆo­«ÀÑŸÓ áà§ù¼š¥¬ç¾’«üÈ$N-Œ:©òïß3ȽA;lúÐ⣠&óÏEÓÔzz íÎÎöË+ù(Ó)ÎÕÆ0d¤öúñÅxI‡xcò2TjGݶ"ˆl‚È£³„Uh:¾”KµùIL>[y­„DKê B§¢švÇY;±¥9W «¤',“îÑšМ­€È1ßçÈ7X6 Ö† žðƒUò/çØW F÷ªÙ1¥º·®íqñaÆôÈJ?ȽŒÒ`¡dÈØá*‚PSÉüIË´\$×»¿&z‰ô5ýñVQ†HxÑXÝÆ ƒÌòÖøæ’3üíaôövöíÓ¹y­¦½°B •õݶ3ûe”ÚBK` ïä™îHXkŠØé¶|@5Û$ä _ê[>‚pUÍ;šÔTºðrÐRCv‡4^0Q¡æmp’YydB;#XoÃ?ÍQÿi á‡wÈÜkRÿ'º°¬BM§”üþyEQ¦HS©žvqSƒ3E­¬Í)£õªÁˆ^9;yM$Íg´Î½USÓ$ÝN‡.tí«^QâNBòâEѼ»Ï m #}Ù⟫£ö]Þ'É£gE»2 .MµÁr¾ìe\.Rløq´&i©älŠèÿLÑrî0ÆÏ‚·:U>qXv!öÜò#1cg|E ¥Ø¡e¹ñQì­¼Â-º ,™dÎÂ"ÂÊ´ÍÚC{u–öù(ÂAdº£%ëø§±}){(é_‰~½A/=b\o­m õÜØLQ“OH$Û{²{½þ¸hS¼àÿ"{ÝÄRÎËÑЪ6÷âIdl<ÕWÇ ç쌸bj¢9—J¬_ƒíÛŸx啃¡^:;qj¯¬ÿãoí&ŽR~^…Ÿ¨ºà(m¶“Þ»]Ö<@Ý©ø=l·¯ˆ|ìÄô®_8á‘\=][’¾–A®ÛoˆÌšíÚ‚ù¸…Þë¥'ÚyE¹^zÆV­aa¶Å~1s U±Ã6€h(8ã"¿³ð{zêÌÙÈ„€@hYöÜMTì¶ô* ó§çÍÙ3¸ÃòzYY冑]Õ#»–¼6[ÆtU)ö 7dTÞ VNº9¡º9¥o“nÊänÏÇV¤+¹»æú¬×Ì«f§y°29¸PK¥zê;õJ3¶¢ªª_Sí¦¦%Ïç U*U¹Èâ^Å‘{~—KÌï™ýd¤jš36ÏÒ:ÞÄ)#z˼Cg£€@h) :5à -÷HQ°$×탼dÔ¡dVrɽi”x¦éü¨ØvV±þâó–Z òd¬@£`Bž¦ òÎmAœP{ò3c~3ä ûÿ"ù®‰*f*Ë}¦¨Ó2YJu8îÄø*ÁÓW,µj¬y<5©Wô0ÑÒöo_S³ÚÓ—Ä•vX&M›¤«(q6X¥`émRæ¥RläÈ<†0p„NKòÿkµDކ$HN"$ÄdÉ“ ìRkÚ–ñ¹*þáÕ¹+ÐR Nó£'æ]¢:ðéùgI—mõçUáIîYÌJœòö­E㈸£²ß‹ UÎw-y’›ñ(ÜïWÈ~á˜Èqy¿ Õ^±FVïsdS›¢®¾8¡aL nÈ%A|K¦oo4ö·§6Ô«ÙбŸŠm*a„@xôù8 N™õׯÿÝÍ$‚­£= –U–ÌÒ/./îNZjhaKüœB© c`XüeÄîtãn+áKú¤¸Vž,q‚N6õ^ú»Á’û''ˆfD?&u|»±î1ó² ,PB¶Š¸˜Må°«ŽäôÄÍT*%ôOHG¤>¦.éŒ× t«jp#Kœä^`‡šÀN?¿©¨ëqQt#­Ç9q¤ óWK:d¯è˜¯¶Ê)q¥ßÈŠ‰Ê$“K9#$áÛ\Û%Vºû<=ו+q’›A.ܑŷЂçÜá'8£bj[_¹Œ JÉYâg\§¨­j—Bv«SkðÀ1Øè!%”+zNü¿£Weݾ ö®¾ün+Q´œRUü™§¸îq–NÐé\‹»eŠœ1–¯¸ê]~†)íû׿òޏõVºïpe¢‹±1ï®ÄïwºÐK Æ|UˆØðcýéÍâöÄ®~ºÚ+Ëñq‘kèn0ƃ Êš"Ü]ÙVÏ }ɤB¡²A͈«Òÿ ª= G ×ùWÖvR6yi{—ìªm%.¦2y/‘•"“®Šê‰à¬üic±“ÂA×ÅZ¸ÊŠ96œ©Y¤³bÎRg’d%}-ËÙØP¢DE¯C÷:¡KÃ&šê„Í «†LÑfÈWmL÷sïÚ¿9.EXS–Ö\x¼ª ¥‚Î4çÓBÛõ¼äÓcÔ‰NÆÝtïy íá¾Âÿì’WË4†"ZĹT1xŠŸ&ØÐÞÚ³ÛE­–‘g| |€lkh®ü—¸q]<ÿ7ù‘‡$ŸæI1WßUÿAkfpμóNè ;ú™®ø¿ ”<éÙ3 s>`bVˆrš¨fVá³72„‰X"zä§þ¥Wl¶zÉŒ˜=¡g®—J¡ôWß[1]ÝTf<ÀµÄIÆÕ”39oä ñܵòæV¶† Í\O®ØÝ±iñâÒØéã NdEþðêÅN—ÅÝN'KH–iQßµ^§±£ÅÖØ¦!¼!k·FAc™3“^}ïìö+q箥‰ì„I‚à’ •[1æ””ê—Ó \&kO«¶gCGæ…æO£Ù“òfHDó¦çÍ{ú²‚Ï,ü*‚ój£JnSÜ$²´ý˽™ÍÞL±;èÕ-*~fˆ:uµ+ëjßQ­„ŽÌÑYÏG%°cxXtîL¥p/yFŒlôß(ð{¿RN" w/íZ¯fó…<¾&iµƒ ªüÈ©býƒ ïÉZ_Q׸ž!³(ëW·«Û¬[C÷€†Ør!”}dHÝšÀß‚Ÿµò%œÚzõzü­k‰Àà&Ér§n¹Rwè¬r¹Úêÿ˜™§Çêø®U/¦ÇRLzM•êj®z‚ò|Á ='x(öOâjÝSÊïçJléܺUE>þ­Ýëuu'énX‘þy´UåÚÂjùe’N•sûxRüß÷“&e ³YG‘Êž¿QYöÜC{2͞ζ¢”ïk>ÁºXA71ïw4t¶Á&“vÏ ]2 ŠL­$C#Òèɪâ,7'©{õZÕê¶v«T× ±(ÞoB™­WPþVÒÔiš´™2?x’óD¥QktœNÏ2zŽ1ZÍp´Ži81à Y߀‰«W¯úøø¼ÿãLJJŠ‹‹ëÙ³'ÙóO©QzÁHHf…'¦9±€ Bmk-³³w´w®nW­Žƒ¢–-ñDÉ*8©“ÄÍ©ª[`U”¢tH’ŒˆˆxÿÇ)—ËišV*•óæÍÃRBKÑ­[7•Êx…’M›6…„„4iÒ5B€ŠïÈ‘#‘‘‘æûýû÷OLL$Iî „]÷îÝóïkµZ™L6sæL8 „\pp°ù`Ñ|ñññOž<Á£€@P‘9rdïÞ½/¶ãÀQ@ ¨àÚµk—Ͳ¬ù¡½½}ff¦ù¾P(DB€ŠÌÖÖ¶àC…Bš! ! !! ! ! ! ”K+V¬˜0aBÁ’$ùÿíìì²³³ EQ¨ T@ãÇŸ?þ­[·Š´×«WoÚ´iHƒ€@P‘={ÖÁÁeÙü–~ýúñQÿÅB€ŠÌÎÎnΜ9_~ù¥ù¡¯¯ï¡C‡’““Q@ ¨ø¾øâ‹7šµ¶¶^½zµL&CYÀ"˜ æ£ B¨P”Jezzzfff^VªöÉ#2W«Õ0Œ–ѴƧ5ŒÒ±–{•öíÛïÞ½{Ö¬YsçÎ-./ãügP$'¢X‰Ñ„HH‰…B±X$–HE2[‘Ü^në¨P(øÌÏÿs!T4*•*)!.ñʉ»3³4ãHg8ÐYüÍ‘Îäÿ—S9A¸¿øJÚt“ „ч¿ÙÄ¢r1ØÙKø¡þ„…ö2ZÓ-§P› Ó ¶ézû ƒ‚¿¥³Ž{ޤÝ+YyÔmâQ«n¥J•Ì—@ „÷Qʽ›WŽo»õ 3G+t>ðÝçÿw<ö&8o~²•éÅ¡ ƒ#ÉߊNÐDœñ–ÇÊu®‰Œk¢ÞMÅÉ«;˽üÛy×÷°ñBøÇiÕyW­»tíVšVê#Nh ¾VM˜R… :Y„5ÊS¾åfQçS©Oˆ Æzwõx/XQÊzâxþöô1C§þOyBvNëuIãKØÕ¯U¥A›þ•« V€@e㸄˜Í'NžVêè¦Òó¾’ëHm#ì÷{Ÿ°ZjïYá†k‚si¤† H![« ÛÑG7²¡ZIŸ0­`ÐO’cÆ‹rSƪfVâ–ܹC63… Dúº~„ï59¥â?ŒüÍø ƒà¶ž¨s=£nxOïÚØÇ# è™LŽ*!¼¹Ô Fíß¯Ô :XÅÔÝ®ƒ½ïgTÑ3VJ#”¦4×ÌKçeMèÔÔ¥DÁª8Ã?C‰¯ëÿTy#•”:²îb²|# ÂCxŸ¿¤OÖ~õ—²E‚®fë&Þþ? i„{@ „Ò¹{há_§îñß,ƒ¬Ž·Ö  ï5=õ¿µæ4ÈM­üܹ¤ÔHþ¶^nÜõW iÔM¨§ûçh‹Ë„†<ÁGk%änµÓý>JÛFŠê¿¿l¨ÜžÖQÆ{·‰«×–ìËkÛ †}»ÓBŠ„P mVÒŽU_«õÔ›]SíU(HùȃJú˜ù”•vú~NÏÛsïŠ{íäœWKõÚ†ÜóÏXÁìǧėvΩè!‹LÇ”Ú颯kë NKÏX* Ï^BFLÑ´D¸(|ÄñüÈ!bEŨúuïäá×eBxŠI¹ðÛºU©­Ž åìÃ#g[Wó"[°#ñvæÌí¬–º‘C)V’Á½Yçº? œ¨ûÚ[0ì:ùäŠ4PÅŒ«ÉÝ¿!ÚpÏ8ÝÑOÛËÕ/—ª}â°ì\ì…•ÑÇFô -Ä?ÐRÅ,™™“7^±¥(ÇŸ[Ý–™:&?%Ü’@ï?/Š46s6Ö¬O5C¯ÜëœN„ ¤Ü+þæ 'Œk®ŸÒW™¤¤7D‹Ö\}s‡ $l`#ff[]#ü€°œóœ®&O\4ž2ókJ€]½€@hy4é·bWýãb”¢Y†vâo%L¦¸!£ò†”®±EsMdóŸO¹at7õèn¨qEãL¥6pÈ:¾õ‡6ƒf¡€@hqr³[QJÔÀbYË$ó°B‹ääÙB!þíª¶¶8Õ(âR´lÀMý¶‘ŒÏ;X=soI"¨qcÕÓíÉ5¿Ë¾Ó3‘ÃÏŠ}¡8–\¿V>‹Òü=B_…*£> Ô««Ç‘QJg0̲{ß ³L$Ž'îK‡î‡R¡…üÑÂ5ó?a8qCÉeT#?BÄì’…ÞÖGLz'iÐøE\O>1Ÿø“fGQ'8‰º¿¾ÒTä‚z©m”7^ÃÍÔ@&|FS­Ëò+}ƒÛ8T÷G5ÐB‘´pÌÌŸ/E.^x¾ÙÛp[ú j’s[<ü21a4ÓRl ‡g‹g_¦ã³I-AP2¶oK¦•J°6ŽŽË#I)Û¯­æÿ²²§„¼/š-<œAPR6¸…ö»ƒÂœ@8"þ’øóãÂS9|'†@GŠ0_ ‚£Â×Ë>ÒiOŽÑ¹›ž™y[2p}UiìÑÍ]73XÛÛ¾èÓîŠfF¥‘ITsÑ-¨õ7ž„|xKøåáþǤA̶öeæ´Ñ×w£ý¸Bö“æú½ù´ §ÿ’÷»¥ß>YÛT@\9+žG_xL© B⨋¥õ»ztÇØÕ¾TROq5j1kûê<é’ç®@–þ;Füåeúzɘ.‰ÑÑù2PïþâGü%£2€xœ(úêˆ`÷CJÇLÎú¸ëæõÐyé§`õHÓ’ºBßÈ"5ü$¾~Ì÷íôn´ñW‘CCÕQ ¥CŽë£Û°8} !Vì3ô˜ÀMþäKZ$G=ÐÒ5kLœÛå÷°Ÿu¤‡ðž%g䓱‚ q2ïÂ#ïÜÄ µ'?36Cž°ÿ/’b¦²‚ÐgŠ:-“©T‡Y¡ú~…ìމ—÷+iXjÅY½ Ì‘qLmŽš³T¶JĘW×´²gÇK}î¿W˾¦fÿ´§#‰?*í°Lš6I=Vñü êdqçß¾UÉ5¹‚ùjþJÙó×Xåjãu#È»g$­÷¢üÙå%3K]½*<É=»ü®~Z)ûÑ Û9V¹Bü¼ñû¥%Ì]¡c5ÉÛ·q‚grDÜQY‹ïц*ç»”z ¨«d󵺭T‹Mórù ¬ËßôÃ`]u-ùPKšg[*g«Êv¦%•ÿÖ,µj¬y<5©' H©aL nÈ%A|K¦oo´ ß?ÓšªT†Ác?Û:cÓ„ðœ©þ=ˆ'†nزڠ7ôE: Ò-® ,™¥%'/.¯ÑRC [âçJM¡ÀÚà/#v§÷†±éÂYüSDÁó ÆÁã®îË%üºëówp•|„#™œ šuTý˜Ôñ/6f-î1C®,/­¦=6†ý6JVm IH ´×~éË Ó…dߢáÓÇU÷c‚J#ÿ¦¿q1¿–+åÅéµéÂcWºFâB%Í]më—„k·¹¶K¬t÷yz® [Ü3Šÿ^áÆ4¤|B_Î$ÌUr᪼dgŵòd‰t²¨g*»\F ¥ä,ñ£m°©uÜÐùï{DP ×ÐA“°­B(‘K½áÿÌßÉ}8qÇöUw²Å­¬.ú‹ÎXÊeë)®;Gœ¥t:WqqÓÍaŽ{šê(âiX;èºX WY1dž35‹œàD¦ï«.ŒÅÔÖ´~éåûòîˆ[o¥ûW&šö§åÝ•øý^ÌÉR¬+뾦ûŽg¥ ÷ϲÎÙªèV¦·ˆÄy3 M‡&]íe¸>þ+šl`Ï÷…±jCçR\<ðéŒÄ.z1ù™ðesWl|v'éªè žnÀÊÍ¥3ƒ®)[s%Jl¯o-nŠ$ø0E޵¯¢ïY¥`œ$K5 cΧÎ>$ÉJúZ–³±!©‡Ý%;g«¹.u½Û¶Õ–ÀUJͺjÝ>a Ì÷ïŸÙzêdLR®ÌßúV€àD…¾XçÓBÛõ¼äÓcÔ‰NÆÝ€¥^‹Ù9“óFÞÏ]+?lÚ¿gek ÒÌõähÚðé$å𻂯¶ÈF<¤´üwu![ÛÃà#åŠì,´ª¡=ÜWøŸ]òj™ÆvZĹT1xÎ¥ÚDZÛDG2I– „r¶s[õêæ¬ˆ"ø·øà¦pÖzùÁ4’r-|™ýŸè¼ŒáëªÜQýÐ*OË6¯£hØuüP¹#6e€@oË5 „¿™ïë²ï]9±õÊÄ$¥MuiZ}Aœ§ð–€)Õ/§¸LÖžVlφŽÌ ÍŸF³3&åÍÈ(4Ì›ž7ïù+ w/íZ/m±9³ruݒ꺿ÃêŸàܽ™ÍÞÌK†'®¤Û8QWì[TódÖ{÷Z’ði¨ÝÝPûªw/0Œ:̺:E»*yî ³Ñ?¢ÀóGî [4V·èÕ£"“®w© !m #92ñ¾`å²Q'¦Å‹;lÉ‚ã/ZI¯6ªä6Æ{¯IZí B‡*?rªhÌGR¿«º—3¬œ¶^5q½€vu½:Ö%q*U@ „wFhçV¯ÛŒzÝž·(“bÏŸÚs-)ã¡Ö¾Š4×]üÈ‹w<“Ly]#mõÌÌÓcÕ|í0]RÂ|-œ{}fnþ£FÌÌ^oÕ]¥ºš«^„ <§$Ž  j'Ñõï©ì’žHhŽñrd||ý]÷¯,”µÇŠ„ð/’»7 ào…Ùì»®¹w'>éQî=µBL³ö"½ ×|lÏ%;ÐY *‡&ßãß%’X/+Ð&æ½Oƒ¹¬U&Q)SP#“pÊÐÊ3µÂ ÈIå® ÜÜ\ݼ›UöhV™`Y!” ”]u—–ühQìdƒ–ͺõðzfÊÝ̌ǹJ £Ó3:ƒVÏi ”Î@êXJÃIBİg¹kH|||rrr‡ÊÅh¯^½êããS¶}?~ÜÑÑÑÛÛ»<.>’àÄ”NL2"’’z ¥ M”DHJ$…ÂξR5ûj^ÖNu¬¥ŽîØp!XZL9ÖUð·DÍŠ2O$Ir\Y^Ö€a™LfccÓ·oßiÓ¦•‹ DDD”a‡kÖ¬9tè_ï¾ûÎÃßBKÑ´iSƒÁ••5gΜþýû»¸¸XÔìëtº &ÿz@Ó¡¡¡±±±X%,ªU«.^¼h¾OQÔ€NžeggÿüóÏåâÀÑ2±fÍšsçÎå?¼víÚ×_ݯ_?8 €@PÁ™-ØrýúuË9p4ÿ`Ñ‚(ŠÂ£„ÜÊ•+ó-’ˆ,äÀÑüƒE £„ï©”””²êjܸq!!!ùÝúøødffšŠÅâ÷¼ µk×~ËNN:Åg?ó}{{ûC‡ùúúòy˜hee…• à_¦Ñh6oÞ¼xñb>º„…… >¼L®9¡T*ÿýw¾[†aøn‡ V¶—²x§ø¡²,{ìØ±ðððmÛ¶¹ºº†††òáÖÝýõ.°'  E~Ÿüÿ‰‰‰ûöíÛ¿ttt­ZµºtéÒµk×€€š¦±* üŽ9ÂGµ£GòñoÒ¤I#MÞ²O>AEEE-Z´èܹs£Fš0aÂX“rZ"Š¢Z›,Y²ÄÜ¢×ëùŒˆˆØ¹s§··7û÷ïïììüZÝzxxL0)ؘššºÏäÀÕªUëb(‰°® ¼­Û·o/]ºtÆ ­Zµš2eJ;“·ïöÒ¥K|°üã?ºuëÖÕ¤Â~¼‚ “5kÖ˜[´Z-ÃÃÃ###øˆØ§O{{û×í™O•#L 6fffòùÐíììøÂò)‘¨‰ë3!ÀËÐ$¿%++kݺu|D<}ú4Þ еk×7ÞËgkkÛϤ`#¿h"##ù¥Ï§DFÓÙ¤S§No°¯¡Ü¸rå Õ¶nÝÌg¿n&oÙ'Çqû÷ïç»1bÄĉG› ÚoF¡Pù‰æ£GÖ®]Î/¾ž={†††vèÐA(¾ñ[Èd²`“‚|2ä—cTTÔRSSùˆÈQþ''',B(—ÒÓÓW¯^½lÙ2ww÷°°°>}ú,7yû`¹dÉ’ˆˆˆ   ¾[ó/åPíw¤råÊ“Lò[îß¿ar÷îÝþýû0 uëÖæ«S¼1‰DbÞUX°Q¯×;vÌ|Ä)ÿ^|5têââ‚å€@ïN·mÛ¶E‹=xðàÃ?=zôLÞ²ÛGñÁ’O’5jÔà»åƒå2ü_áêê:Ã$¿åæÍ›|> ÏÊÊ  Iòm·PùÔ©ÿûßÿòY–={ö,÷îÝ{íÚµ¶mÛšS¢‡‡ !üÓNŸ>Í'@þÛù€Šü&íÍh4š-[¶,Y²$;;{âĉ£Fše‚j¿Ÿ<==‹, Ë—/óùO‰ü}>"ò놯¯o™¼EQML¾øâ‹üFŽã.\¸`Þ—xþüù-Z˜/™èåå…¥€@e)99yùòå«W¯æ¿âOž<™ÿÚ½iÓ¦·ì“ÿBðàÁ¥K—ÆÄÄ 6lÒ¤I/^ØÊ‘ú&ß|óMþòݺu+íììBCCù”X»ví²z;’$š|öÙgÛãããÍg¯9}ú´¿¿§Nø èçç÷ö{/ÁR䟺S£Ñ„……ñík“·ìöÚµk|ܼysÇŽùn;™ ÚÀššÌŸ?ßܲltt4Ÿ·mÛæáá1ÀÄÕÕµlß×ËdúôéoݺÅGăò¨[·®ù’‰|\|ËŸA BÁqÜáÇù3bĈI“&•É©;ÓÒÒÖ¬Y³lÙ2—É“'÷ë×o± nøôÕÖdéÒ¥æNÇç´ððð¿þú«^½z¡¡¡üR¹rå2ëZµj…™l|ðàùˆÓC‡U¯^Ýü»Ä-ZØN!X€›7oò_Íýõ×víÚñß•;˜¼eŸZ­vëÖ­‹-⣠Ÿ*GU&ç˜ I(šcX~‹F£Ù±cGDDÄÞ½{›5k6`À€>}ú(Šwñî...cLŠü cÿþýü»óÿ;;;›‡(‰°¼Ê·œœœuëÖñ!ÐÚÚšO€ƒ Z`ò–Ý9rdñâÅÑÑÑÆ ›8qâTÞ€D"éc’ß’——·yóæððp~k×®{ôèaeeõŽàääôIÁÆÌÌ̘w'òom>{MÛ¶me2 Âû‹eÙÝ»wóiíÊ•+cÇŽ0aÂ4“·ì6!!ïó÷ßç¿ Oš4© ª ïÀ™Œgk×®ˆˆˆåƒù„Æ'Éw7{{ûP“‚|Rݹs§9%ò;vìȃÿßÖÖK á_séÒ%>­ýñÇ={ö ëiò–}fdd˜ èììÌ÷Ù¿ÿ_LPmøçññl”I~Kjj*¿ÂóñÚµk½zõâ#b‡ÞõOù¤Úˤ`£J¥ŠŒŒ4ïNÌÎÎ6qÚ©S'~ÌXp€@ïDZZÚªU«ø´V³fM>­õîÝ{¥ÉÛôÉ0 ÿ ›–)))&L=zô§&¨6¼‡Ì§(xΘ¤¤¤­[·†‡‡ß¿¿_¿~¡¡¡ÿÀ9Ee2Y°IÁ#±ùOÓáÇÍû“““ÍWÂàñÃÆ²Bxô¶hÑ¢G™Oú_“·ìöèÑ£K–,Ό4èÃ?l‚jCyäîîþ±I~KBBB¸É“'OÌW¼øg#‰Ú›Ì›7/¿Ñ`0œ:uÊ|ÉÄ[·nñSùˆTæ×á@ ¬Nž<É'À¨¨¨†……•IZ»yóæ²eË6nÜØºuk>ò_IÛ´iƒRC…T»ví/Lò[âââ"""øˆHQŸCCCëׯÿ‡¦éæ&_}õU~#˲gÏžå#"¯\¹ÒªU+ó%kÕª…%„–åÁƒK—.]³f¿¿ÿäÉ“ƒ‚‚Z´hñ–}fee­]»–ööö|ä³åO&¨6X _“o¿ýÖüã¸Ó§OóqëÖ­|D ñôôü'‡ÄGÓ&&ƒ+ïòåËæ#Nÿþû€Î;ó)ñŸŒ¯€@ïœJ¥Ú´iÓâÅ‹õz}XXØÐ¡Cÿgò6}êtºmÛ¶ñÁòîÝ»'N;vì T ’$›™äÿ‰Ä`09r$<<|ÇŽÕ«W å#¢‹‹Ë??¶ú&Ÿ|òIÁÆøøøýû÷GEEÅÄÄøùù™—ÈßágKËŽã<È'ÀS§N9ÒØxoÙ-ÿípÉ’%ù—4Aµ^MÓæKª,_¾ÜÜ¢Óé"##ùˆ¸k×.__ßôïßßÉÉé_ž—É”)S 6&&&š÷%òQ¶N:æK&ðó‚ „ÿ¾ü+øuèÐOkLÞ²Ï;wî,[¶lݺu-[¶äûìØ±c`` J Pæ„B¡ù$¢ù-jµzûöí|DŒŠŠjÞ¼yhhhïÞ½íììþ­zxxL0)ؘššjN‰tss3ïKlÖ¬™H$Â2@ „w+;;{íÚµK—.U(æýuo¿œœ>þñ}Z[[›ûüÁÕø‡I¥Ò¾&ù-¹¹¹›6mâ#â±cÇÚ·o?`À€=zÈåòqÎÎÎ#L 6¦¥¥íß¿ïÞ½üÿæ³×´nÝZ"‘`± ›»zõêØ±c9Ž3?<}út³fÍÌ•ñßÅbñÛ¿…ßÿ´iÓPm€÷µµµ··wjj*ÿJJÊÂ…  ÄoÌS‡ 6qâÄ÷aœüFé“‚$Iæo»Þ„——×¹sç†Éoá3!ÿß¾}8PðÚÙP!ùøø\¸pA¯×Ùôë×ïĉïI Bx'hšçã_Á?´ó)ñàÁƒ÷îÝC}*<±X¼lÙ²"'ˆò÷÷?|øpJJ ê„\ïÞ½Û´ißRµjÕÿþ÷¿¶¶¶(€%3f Ÿ ÏŸ?o~H’¤H$Z¿~}™4€@ø¾;xð \.×jµ„é`Q½^?tèP”Àrœt¤¤Fõ“;·ï_YLŽu«W}»Út¯^·i‘­^Öm]Û¯´®58i¶ù®îBä/ÝÏæ·?ÓkQˆW“Íñ*âÑœˆ¿ƒÆ¸™ÞžSߘ¶Ý˜ ‘÷Ê>µ‘¡B›šãš2®-:}gíŸËÖîõùmZ¿v’—¿ˆËM‰ûõ†q?dmÿΣÉ[W®ºsfúò3 ZM8Ú¾²èù3Å‚z³ç)iç,§Ê~ø¤]íÞ}öS©{ÇžõÜÓT!à”‘Û·2î6”…êÝA‚8€@hi8®ÀýüPT$‰Z´™hÜçÖ¨OÕu¾ÛïÚ«?ßêÚ¶ž¤tï!ªíÓhÿò•ý´ñ/-qïÆÝ”¶•ÝŸ'?Qu·ÚŸ2jxó)nÞcÄG÷—ý”I\Ù·eµçؾ¶NºeŒƒ5ÚÿÞC„* ™ý@ø0•piÌØÐå~nÔ)ÂÄÛ”4®Awµ£KËeW¹j%âác‚ˆ;u>Þ·­±š†Ûq1±¦ÉV•«9ûM’Í‹¾øÐüo;IVVæ(¥¼š¡a«Z½jç),”4I‘ȼ5÷Uú‹ôu»-ºÒô‘}bì8Á>þzÉÏóX%ÿ–Âê}¶µ®„¯™ð~áT‚‹Y?*¹sâWS±½®F€ºM?¥OÕg´a©›YŸº$|lÐ þ<¯jþ*ÌÒ1UÞr‹ ¤êÉÙ»Ÿ)òÇØÞ3nU˜ÁkÓ[8•iï,}î;'óìT 2B•dq¨}v8š\ðIÌÓ}>ûF\øðsCëyC|žÿBõŠv^R¹gå‡?çx[—8 ßì/¾V9ô¹o*­‹-ôG§ZŸ¤NmËâïP„…óJ–(!Ít¯ŠªyáãIkmëÆÜÅc|Ž\N"ŸBå§«sÜòË>»ó,ÒäìwœµPÄ^µÝxT=½=KäJ– ·¿n H¯ì¯PÙQ| ’,j=ÎvþbÝWÓ˜çgç-ŠŠC&­vØvÇ”]¿Êñ¬Æù¯ä‹¯%Ìu»…¿é>¦+´~Ö›.[2ÆîF¢Õ‚qäÇkr<„¥~B ¸<ÉŠaöWt¡PM]YxçdóŒ…{ò¢pÛ§#ô±e²ö •ŽÄ«ª—ß?_œáöñ|¬tÎûϲ'."‚S‰7q8*_2Šš²1«¶¸è,dG9}þ‹Pg·á˜fz€0ö¼)ú)Ô!ÿyÒÒÇ @@ |ï°*-'Óï×÷V—–“KÛØÛ¿«“b°j-+ (,xÐvŒ§#ñ Rd§“òúÔzž ¹\ñѳ橾¾[¡¬øò#F%U R‚P„&‡b –˦™öšqñ¶_‡Ú>ýØ‹8‰ˆª(¶ÔCÕœ·[ô—ùÓIžZåpIéá ®W̸(MóZÄë‘%L×/æ½W>¡h„N¦êL³é«v+¹,Éšñö•¦BV&$U*ó È"Ãzyõ ™¢tS¹¤ÞZ'Sn$eºúÄé8‚P ÒòˆÚâ¢/‘VÕK ¡±àÙ+×LÚ–zsÍž?d[gJ·šÃ~HúÇ#1VwÂ÷"we¬X³ä+C»ÃcZUMÜâ»)±eñk)ž c§,ØUsØùþÕå—~i­ïŽÜ ãþpÎ/¿þÊÅA'å?Í*zøíŠÕKÕ ~Ü»½”|ú56ioÛõgé¦#¹Y½:uy‰¿EÇl¼v7AÍ”´ºKÍ^MÛO©k›vnmÓHêû飇Øp7cV·?d˜:aüÇ•ß8²™Ig~ŒcBº´ö™ãà™‹6¬9øD_O\~Þ"]ŸŸ³²Ç+.(éCS«éë4cTÊ%ÉÝô§ ¥Í÷镌wªó±0•$2ä¿.1thÀjÒ)çÎyÞ¶¯Únº©º7²ZoÜmEUÔ­Z1öR™A§déƒÃ4ÒÒ “Ë•løFªá=¦)ŸŸ”E´û'«‚:9×®áú,ïüîÔ¢èyF5"-ÆúÀuS~këcS •ô’³2Ñ©–n]Éô¬òv,:;Á~V/’šh‡YUm[ë¬ *;…tp^2¥A›¶9#újmiÁ±9öGÓž½LXÚê ÜóBZÉ–§ÔGæ>ÉkïÏfœ´:~Å8ɺó“FNÅìT,„î[h¥©© œ¨éÌѱKmÏe)»¬dÖÁoÿuœ*róò¯Ô?lU‡&rMÚû~ñ8ê>±[à\—OTmVا-ö›õ±²}«Z¹0-[eÍ["Ù³N~î„í/ÑæÈÚ{èÝ›ëL«*uõHÖç?© 'ˆŽo°‰Ž±úóAZéëtÍí1\éaýêR²J¥àü&éAcz&Ä:ß¼žƒÔ•±É@ |hRŽq‹ìúAKïç;ÞäŒ {ÑöûïÛ÷[ácõÚ»äHi›^¯ÞjÕÿý*§r4®ßOýdˆÝ‹f21;7.ͬüÕ”‘£§RBŠ(|÷øâê†;õs¦Œ«  y×§,‹øÓà:¼µ¯§ööªc׺w?>Ìß•2?-£y@ËÎ ¡&'1<öF¢¢Õþ‰í}ž¯(Ob¯]ÑŠøAнkÕª#õœÛ¾R‹¨=5b…½„ð^ÅBk]ãaY‡½üÃÆUn—3µ]N‘æÀ… þE\?ë‡=YEžC+˜6amŠOŒEzx1#ùÎNY\ÂDÛŽé‹òOÚÄ>Û€Hµý~ÌzÙ>±—ÔquäŒu#¼%æo£AÍZŽ5&Ìfãêïm¾êäw‰-­•ÿÇx›¦uëu•<ÿFèT­š=qýX†.Ä ¦(û\ûªlùÊ' ¾³ïj2+‰€Èz¤-æ¤ Û†›?s_³n‰ß/ÎÕ™ç JjmkM¤Ägé8çg'Ëãt÷å¢jUž^ržM:»¡uäÃæ=§ðøkÉà3áËj…)o×Äþóú¼+ÙÍýíJ¿(Yã`™{))º§»ê.åcm_Lœ£)aP?;éõZÚ+–°zÖ&¯Þë6é]"æÕpª3ÈËÙVóhÿ…øxƒóŒ±½[÷Ã1磖ô8MŽýÉ\cHkÓ룓ÎkWÿÐwʆúÖ@¥*û³Þßç×…î»­šÔ«×¢’L¤Ë»}ÿö9y—}ýkÙÚ)dÄõ¥‡ÎTïÔÄóù‹#?è{zÙö‰[’{û{ùÙŠYMvçõ™ ýªsÒîu¼Ý¢c¦nÚ}»ž#—“)òí4±2søÄCÛNX•-¸Ö‚!×nœRcÊò:2ëZïÌ.üRܸËôäB×®Ök;áAÛçgº¼¨QÐôä B±Ò½é˜ûM‹}{RQ#(zvPñcóp30~c“ýž-t›úkfÖ/¶·JžfJ¥v'g·{>¯ÎNÍîPàùì¿NJªò¿É-kÑX-ܵ[ì$EȯËC>¬d9å2oFŽ Mathias Hasselmann2012-05-11T10:57:452012-10-11T10:58:23Murray CummingPT58M1S6LibreOffice/3.5$Linux_x86 LibreOffice_project/350m1$Build-2PKKGKA settings.xmlÝZÛrâ8}߯HQû°[[C;¦¦Ì-0\‚Áæâ7ÙRÀ‰,y$9óõ+È2܆1ñÖÔò…Ý:§»Õîn5Ü}~óðÕ+bÜ¥ä>“¿V2Wˆ8ºd~Ÿ1föSæså·;úôä:¨ ©xˆˆ,GBH~%—^^ݾÏŒ”)à./à!^N™úˆl–•·¥Ë1ÙêÊvÉË}f!„_ÎåÂ0¼o®)›çòªªæâ»QŸ!.¡€ˆ>p{Í6­CÉ“;?e%½½žRú®t´`eX¬xAQns«ïiî¹ø\®H6ëPÏ—:Ûø;_âzçÂD²{V¯´:­»º£;d <¹"¯ä"™ÌÕzs·Bª©lâg6•»µ3WYW /Š©«õåȦûŒ$,¿º(|¶Ì¡u߯»Cé3Ào1âÁòGÑ{Yì­:ó‘|Òp*ÛÓÀ®ç Ѐâeì·®¼ ´ˆïP^Ê•„9»Í÷¹Ò°h½!]°”…opTº­J%Ø2SÉýútŸL—L‘ü*Út©ó‚àUÉýªO5!ó¿Æöibº¯âåùn`ÄR,8Q²îÈ:¼[)”ÉG'ás3BX¦4#ðnó8z¦àŠ6¯Ó@:¹†]çÅ@o¢Ý£Uøš˜ ¶dކtu°H!&GØ…ˆÒðð&Ü&Ì¿Ò-ÑŒ„,*€í†NœsîïºOÅ^éøܨ Òà`÷š°üÿJ)n’wÊ5 GGµÏ+I ÿ¿àÇu¿ ½){×SŠ_†|RëäÐQÃ+=íb@P–"G7_GX>€cöØ1KÇŽ6Þˆ¦A¢~íè‰î’"ÑSè«-J½Gr ;8£¼¸¸=8v{=j8,À‘8¨´º°¸nÿÌtIó}¼49bu ÀÇ÷UWHC(Ìa÷Èäã÷?©ÀŸk‚_—ðkNíd¬²a@öz¬F½hRÍí Iù¡m_Ü\'¶ÌIFZ|‘˜&äDO ÀBÒŽÄán"_(&NJºò)IlÓ<o '¹#I³c1¢qA@¤Õe·y|`®Rú‚Q:3»m‚*p^Ò8’lsœœ>^L"“_:“§½éŠSeår‚– !ŠKW*Ã͘#Bï§5tŒ £týd¸©è|&N'Ii)€C %x™‚U=xÀIöæ 뺙ðŒRàÙˆ5)óÀÁAÂ!À@–ý9þbx^ZÉ5Ž$=Ø»n¿dÃ"Ö–-aô«§¬‰]°Ü l¢ Ó0+;ŠƒÈÌ‹(<Õ–Ö ûÿÎùŒD¶$[ëlãoE½Œn„D°ÛE|?Á¥a믜²Ð»¦êY~¶&>†5¥Ð7´°kš7½:´_è¥çǺùÍo›µüÀò,Vµ¦ºè?›a¯–oö¿iE{‹N4­§-õ÷¥ M[Œ¢O(ßFžêšÊl¤½ÕHu &EÅš¶ÕQSFÁòÀ¤7VºÆ,ß«kbh8áãä¶cß|ñ-);›}»p«šê¶¾,ÀTïÌ j&ão£Ñ±'Í¥UÀõÐü §}ÅžŒXWÂn]ã½ZÖ¼þ«-×Zã>ÌÕÞ³ÞqZ}nM-lK9©Ó‹å 1ŒöÄPÞ¶ä ‘nCë˜ÍjC/¨|߯¶èþ›Tó°Ùçö^Ò§cÅ« 8­JÜyÉðÔÀÒ«úl±YÀ%Ëð«ýÈ!ójÝPòº>> ›cÝhŒgS%?6ÌŽÖŽÍÆ[sÒT×YÂ)þ¼¢ñê7ýGRÔ§QFÀ(³v }zT Øô¡l‡dÃåÈóñ‰ÖèçÄñ6·÷¿‰Ü±âTþPK“¨ V0Ë#PKKGKA content.xmlí]Ýnã6¾ß§ÜØí¬l‘úš´)º(0Ó:3E±wŠL;êÈ’+ÉNÒ‹¾Â^îûí“ì9¤~(É´¥Ø™¤i:@,‘<üùÎÇs)JýêÕÍ*Ö¶,Ë£49Ÿ©1ÑX¦ó(YžO>¼ÿN÷&¯^þí«t±ˆBv6OÃÍŠ%…¦I¿H'ù™È=Ÿl²ä, ò(?K‚ËÏŠð,]³¤’:“KŸñ¶DJ^ÜÆƒÅyaYº`7ÅPa,Û’ .‡·Ì ËÒó,¸*ŒeTY|‘¾Éc}‘ê«uPD^ÜÄQòñ|rUë³ÙìúúzzmNÓl9#¾ïÏxnÝá°.·Þd1/5g,fØX>#S2«Ê®X í–•»”lV—, MP=­®3–C.sXE²L‹_Ûå`vm— ˜Ã« Ì3^¸Ms>œ*æ\–]Å•B¿Þì dò?o^7¼ÊVCÛ²-¨Â,Z¦(-˧iZwÄdçÝ¥†aÍĽTúzoñë,*X&÷ƒ8¬OW»@ƒrd%t¶EÊדÈt&²ëÂù\Yõ/o^¿ ¯Ø*h G‡ ëQ’AÒ “¯¢x° ¬‚´A ¦–íM É DÜželfE­ Åp'­Ð£«b«MæVE—Ù|¾³(tÇœ9c¢o#výÙ¤åöÓï“›úC"¼ì ö c†eêI±¸²uÑ2v³fY„h1’^_å@˜éúL’nû’lu3¬:$:_tkì‚0ÏÍbÎïšažŽ®œEÙ’BÐÉË*^ö!ŸÕ Á¦H‘d¡Î ~þò+aøù_M\c§Ï'ó5™” ‹~ IÂ…êë`É&3µè2ë‰.³`}…Uò:È0Šá7ºÂé7²ù¤ª·Ñ׀ˊˆåv yGóI“ó/NÁã}¶Xø¶ÁÊÄš ô«4‹~OQzGKÀé×M^D‹ÛnÁ-66ÅVнªÑÓ—Yz­_±hyêYqα˜I`ìC†>2 †ñh‘1™ÐÀŸm‘ž­¢¤N5ÂUvÍ1⨒®aTzº‘X’êx?Yë!gãܤ—söÃ!ã™a°X >Ÿüï¿ÿ©u!U"i„Ëàþk\BN½ã„«án÷xéw·+p›œA]ÉÛ¼d¹¾/²MO£–ýÇeßHGà@À!_!TÊiÆ*žBæÉ"Sú$²Nˆ÷$²O†ZO!çd™O÷tø–„?W,Ó¾^¯ã(ä‡jóg&îg¢9Œ‰v‹ˆöxR‰‡Ô™z',NÃcÔþ: ƒXè»Ún­ß¹ :yù÷˜·`fàÏþqjZ‰'> €U¦™¦I=.!ø×4ÛJÒØ­¢"Ú2ˆoš mh±áÚ²|¨×BVú®¡ÙÔ­®<¯lCs,¸¥u0ƒb7] 2ˆ ²kÜùÕ®ZP¯\쪉„ò^cCĆjhœPN“Øâ׳ø¯…MZØËÃa‡¡»¼¿på”s\QÜõ ÿõ|^=Öì#H›§…R¼èTdÙ^uéPZ_ºâGQ†‡P9¦šPg9•’|Ënær“L¨‹é–&–ãa8 ¶Aêk^9hÏåýE}Ú®t*¯-–Ç!´p Äã5›­FE(ª¯l÷½ì W'{)5\TWo¯ƒ)Õù‡U]Zž]]š6çEÔ8À®ƒM›âÒÀ²¨ ³º²3~ebqN“7m`E¥MÌÆj50›pµz6ö !Å%Æ‚UÓ: FzºW2–Ǧ¨á㘠¦ïxù†³¥b ûg"1]ÇʼnEªòQ¡ŠL^O`Š—NšR¦aéêkÏàPXBåpisl›v†â@°ŠZ áÒ¤ü’ üˆ•‘8y7øë—Ò=¡S¬ÖóýºÏq* ^ ,N'N-ʵ ip !±«15”ÃA Óãs›œÈޏv9JÎã æ$»Ð<ƒŠ8ûg6^rÓâ!6å¸Z]€VüŠX&șܸRÿ2]”h—Õpæq<9LC´ó)<¢ÙòˆæI="9Ú#¾IÁáhßEpäìÜá÷x,aų'|ö„ÏžðÙ>{Â§ç ­ažÐiyBç 't»žÐžÙNå}4ÿxWÈ—…ß'svs~ðõ&d {ñâH?èyê]ËÒ^-pÒNÜT–®½Ç yOƒ‹AW ³å4à ©¶hÓy´ˆX¥o†Êò¦qòñ¼ZPþ·'ðׄ,üµ4ZÙߺ{Ž×ôKˆÞ€ ¿à³ÅãBAC´#,žªIükcr þ8^3R»ªyW½ÕnûmÃw‡äýìE5z|%wç“Ï íK:³ê®R¤+…méÌÙ/GwÉé¯DÛ/gvå8¼:Jï´ú šÚ‹Ã Ú»::@ÎÙ 'Ý/åv¥¬ å<. •¸†)’GbïðÕÂÕ>¯TXæfA²dú­¾Š’hµYÁŒPä7"_̤Údöæí¶ÔVÚÒÖ£ «eK­ƒ¶ÔîÚR2uä_¿µã+ž‚gNÿõ®ó²z\{ü’Õãq­I0l1lîÌ-ªÚ…% p#(ÒLeùL¾ à þRw†'¢õ#á¡ü/rŸÊT‘rìöìÀ'`´Ez6ÖÔ?HÙ “S'‹âTo–QòÌSp'MBvÇQG±y/ _JÈK*@Å’ŒÁÓ&ÍÖíP…–ûe(ÞeùÁ¬HnTÆŸ4•ΛâTJ” Û¢)`ú q0Æ•mÐØ2|W'âŽìc«u€­æäeœ¦7ëŒVóÔ~]+¶Òâ­ºJ­*6 ÔZ¥­¶tÚÚÚ§SªÔ©»K§¦B§f£SÊuŠ«tÔ),r|W“Õ9»pêéà?ìÎt0eèìñÓa'tVövI{Óˆˆ}˜öÎaÚç°6Iúõ>^ÒÛûH¯Ôœ5Nsv—Üö'#·7šÜÆÔ4ˆ"»‰!<·‰± "g€­7 cº-’^·,¸¥@ýí‘ÆÞž¼Ü¬çA1v—üp½³ÓW£`Ç£ÈÖFNÅÞö:Š$´Ù¸ÙFZ­åq§ƒÏSSדŽØSƒÐ§NBdéǪŒ©ç¸cd›cUx$Ë·ÆÈRY–´ú|ð 1¥ñŽ•µ$¤m»}DgjΩ„ñ}7íut™Ùí=ì¿ñµ}k/^hø…¤5ËNþHjeYšIßਿæ$gW_Þhg>¯Lîqeâ—L­UiaTþ B7¢rY®ÚeÑ1.Ëm\–½Ͼ¶:8©X7A:…U ÞÓãV('q.ñý¶aª‰ù°±š×YŸÐæUkµÃlªŒÖ<µbÍ]ŠõŠõÅz|‰BÍ:áÏ‘œN,¢æN³éãµÜµ"Ôk¹ëCÎZv]<ðgmðz²Ÿ¶=s¤—oûjŸ8£üQuú°(U·;ÂUƒŸwÍÑ»k'ö¯úWîÅót“…L[Ç›¥%'w°ÏNòí¨ß]ѯYç¢%m õÔë&w®ì<…5­f1÷“¸²#ž'Ì©ædQ|B Ùs¼/Ú¹Ú‚Tá‹i;#û´¾hàs+ÒyçðK8½SíwˆÁ9;ìm /±Æ˜v_v ãD%oÔ•³pìÉžÆ+|«³yT¯@˜Ý•_›áTžÜÝRé{¹] %þ.UáŠ/Á(VœâbYöƒèÜå¶–õd7n)ã:‰“(«Ðpä:z%û±‹áÖPÝãÐÅÛ‡dºT©2”—ö€³ä=pÛ1lLJ¶À+°†m;žpÌÙ‰}O‘&è.*ªÅ„V®.$2µ©Bšuƒ:îRŸlQG¯;ƒ.%‘ˆD$Zr«Ãˆ^a†ò÷­Ž+O·ÕÑ>lIŸ¶<|HW^ú»ÇŸ´üþǧâÿjûªO|ìq½ãhíiÛ9éÞe7`÷¬•|fy"Í“bòEÿ¡iþa«záÕ(£z`šûûìª4ÍÛ/ø…ÑÝ·£I[Óœº­µ3µžè?±UºÅoÄŠo_ÜÃãŸï¾Ñr˜ÓójßþümþüšÓókNϯ9Irϯ9Ýɸ¶ßsªoOe\ýéñQÔEš,¢å&csí»4žƒõ¸ûúÇìmÐØ×?f?Gs–ÂÕt:}6´Ï†öÙÐþ… íݶÏÝÕË*Ÿú"õú Z>´ìêåÃÈwZ»JgÕº‚Zø׫ޗ¸ï£!÷ Q?ž¥B)æ(™Ý×àvA%ð­Í·a1‰õuÖú&l]þÅu¨¦€ß—ÿPK_ïQ“ Ì‚PKKGKA ôN4k$k$Thumbnails/thumbnail.png‰PNG  IHDRµ?`¶C$2IDATxœí |U¾ïÏ©ªÞ’tö¤³ïûB@Bd Ã* Ê*££>çzuÔî<½2ó>ÎÜ7ãöttÆeF•‹¢ÈÕÙ $!ûÒY:I§·Ú^uwÒét’Nw§º»’>_>t§N:Eÿg«ó?‡`Y ¾ áí Þ áÓ  |$„Oƒ€ði|…]»víܹÓåk-ÇÖ‰8.@ÜŽåY‡ ¸lOö/´oÊ¥¥¥ãÆt*\˜J@p;–Þl ;#žŸJjfsâŘLù![ï«NY¶By®aãoš,7tÖ´ÊÒ’ägÍ…§ž{úth ë}Ë£ÆÍjnœº./œØó ݰ*A4Êè­±„ S HÂXLÍô͘™º ¸ü˜LS}¶òhmãõ#¡Q]xïÓ½==É‹b•*¿¨«‡Ž÷ç+  N(ݰèä?NÿëÜ×ûûK~ýŸË¿÷ÉK͆ä’Å~íݯ6l¹CöÎÓïöÍ. méš}ç¼ú~Ú•ÏÛ³ åŽ6Jˆ <_ÖoKm—òq—33òF¯&2—-…xS%ɰ,ÃÀ𛲛N¨KK.ö@yRr„\Œ }läK®gdúµ¬$$jˆ”l©Ú€ãr :yF†~ˆ•·_lÒÈÍÂq3À)áðáÃSé“ð€'×Ή_Q‚¼lÃ} ,ÆÂŠJ¢¢-5ä ýðÀGÒýRp¶~ã^Ã,ËÃżˆ‡çQñØHZF ìr®µnÐs&h ÂÂJ~±Õ|v÷wö¿Ï6o0÷Äï‹ÌGå³–™îÿ¯·îúC–Ž\»¬tâlp·Ž„+€)v…û%¨æ¯>ì.¾³ÀßB·ÚÝR´m®Ü…Ô )üžy晡âŸGëÙ»WRš^싽âŠLCvVOÕ7Ò¿ë JJošO±4¤&`»÷‰+ƒ!1— =%Þ× ;)6直ŽeîY§/“¤Ìá`–¸˜ÖÍô‰šìÃPÊÊ35êØÂ…ñFÓÚB)âq›(\xCG—607'Ìö”]Ì·6kÀ™ëÜ‚p¦Ü_tSs‚é­©v¼}®WIÉ"}º¥ö~s£OUT®<`Ƀ[3%%bmô6S E5÷,3?{åX)§6àÌú4¶þ: Äa–Å1¦|>¹:|)¡ï.3èJ¾Ðàÿjc×bld#ë" KáLyhÕ`%jk&”3Ø¥‘ûÃÞŠÏÏõÆe·Ö]8ÚIæ•Ä6Wµê‰ôY‘µÇ[ÒF6·¿(mvXÀø¡zfÜ»p§¼^ü À³½Jœêë£E² µ†–êø¤`’î¬ý¾’İÈÄä°qþ»,vïȼڜºW‡)îïœu:›«7èM_ºeÃ!WqȈèÿؤ_8Zzœh]0 É þa¸f=M ˆ)‘4 E,#‹IŒ”bº„¸0†!õŒ_L¢"v4ö1>õƒÑÍ­QM/Ô ’Œ;È÷ ÕúõeñÖ¿á;…ý¸p?öP%à 1ìYÈäEÓ Þ¡SÁ¯•1ó8‘0ǘt6h`URf {0¦<û´™Ù}ÓÎæE³a"Øfz %²ºƒM ÀWÇ!|¦ƒ0öŽuóázîošn¹ñ:»^·Ñ³Hs‘å¤åh›Õ'GÆ:½Í¬kÖ„ùجd@wþëϯ|Û½hMqÐÓ•Õmby0 B—>´%]ìÀ"¼Æt€û±®8£·X¼ù`ò Õ©‰ –Ó7’W‡èkOQ, Ð&/œ£ê r²/âNÜ1ä‚<Âf œ•[kXÙý„J€þ)eÿë­2ÓqqÖ\äÓY\óv@‘GØÌd¬,áæ§: –¢—¯ù­|Y’#Î`‚·cc‘GØŒb" XΚQ‚e¾ŠgêGXIá©Wvûÿüþ\Ù¨S&§Çþs ½½\óæÎWƒÓgú«Åa’ëðäuo}è·nSQ×ÄcÕ?þéÙcç&Èã­ÊjÚÃ]µ6I4ΑGØ4þ,qÌ“v—ù²~KRôë“1ÑÀ…÷Þ=9 ÎÞ´9ø›.DÞSñ÷Oúf-Œ ZÀPZRÒ½ÿƒ#˜ºùå'/DλçßnÑ~òÎñ¶–&µö«šwNèJ~ýô*.G„D¤««í§24}mµo<ý×¾Ä,Ð8XôЃˢÇZ˜Y Bx ìãˆ,1Á÷K[Y¿¡÷«'Ï5††/ÜxìÁKã¢âÅ¥Î÷–¹4µ¡±a¬¶þЧÿÒK2 Y–$)†PddèµgÚ´´Ùaƒï|¿¯S~lÇéŠV,sےǶk^Zy\U–Z¯TéñÀœê탳£A#^þç/²vŠäÁ#ìæÏÞliû#¦üH40ä0ù“K†âîzd¶MÒÿnrŽ|h‹ñóþ–³Ë'ȉ¥pàÈÍØ) cC7߉pJ–K,Ç£šxTLzÛ¹üNzßM!É!âë&eH-ÊM+ŠT‡ÇKEº~ù¼h½RGd'¤¨@DoÍAáù1’ö ¤ÂQÖ¦à6A‡•R^8}J™7W1Ò†§UW.êÓf›B&òººër ÌíiÇ# Àú (\Ѐs'x¸$ Ûð—ò¡3%ñk‡#-)´Äýåß²¬./¸müd¹>€³dΉµ3î(Ó4ºAúKðžË‡.t$Eè{ügexãs|óÿÎïúqUô²¬ö ç¯\ $^ÞÛØ¥lÔ-X—Ùx¶^’¬è8Ó–µ2¿¿GËŽrDa3‡©h@ ¯4A‘ qœkÐSzŠeŒ ¤ÐI¨ …D©¥0EDPOÃ,b{H†+ãÅ!Ñ!@ÛM1,÷‡b_Lb„˜íÃÇuDaÓºëüYCnQŒˆî<¼§yî7ɧ¢®Àæk Èåñk—Hk†ô0®äê{ÌuSJé­)ÜWôŠü‘ëfÖÐIÉ;@ÌMù98ÝûÑñÚÛ@ªuÊB+ïÇ2]àð,Ç¡š¼S©n« Èbk:‰Ž `½ŸU®,–í¬©x?à[3$®iÀM/Â\I7¾ëaP™¿Äì¿øè…ˆñ‘Ñ+Öî-LK¸§)‚HÏt$^¡—Ç’7´¢h v±„<8¨¯.¹4·§3hèÿÊåz€wkðŠGظ.¿Ps©7íÎ[÷vÑX„´½Ë Á#©Š069:Ô}í0p­Â¶pªà7›¦uü±!¼à³˜IxXâô;O¦¥¦üVo·ZjÊ.™ebÂØ™ýHünØá B(ìÇÅK}ÓRS“Æšb±í™æÐ´Ó€Í`èöíÛ=pSaÎ…‚èO/V…40Î=côÖXî()@„(~ÛëžéØÑ€u“C >1o¿ý¶³¼Èc¢[¬Y³fÿþýSOŸ/'7Ø÷w &Ò€õ8:¿>1.Ë€³B;gí˃—[ Àcüx¤94Q‚£]xÀœãè'žÝõ½,<¼ìá{ †¦±Mh»lÿÙ÷¿ Ý\ªüàPø¶)CcuÚ‹/ìø¦xGá×/ÖmùãöÔ!¯±Îï^û¸¥`ÓO*´0À8EžiC{Qvö’Y´%à«5‰„R iéªÅ÷,“a“_<%XÀPd_‹*ÎOsö/;ßíHÉb›Ú5è'øIýrïX§Ýó-n«»qå<5·Ø¿µë…³ýªÄíTß™×÷®rοZ§!¥]{ß>ˆ©ÿôËsQ x|µf÷ëGZš4â+¯Õ-ÿí¶÷^Gx_î.øé®ógúä2Mýé pñú¥èØh`òâÄK†ÒÑ­-—gß´ãÞ—ÿÜ£_+³w Nac²”ò"êL£: ¥¤F¶1Qj9ðý92^Ä0Xôüü¨^¼$ê•ýP$Vµ2þÆøŠ@ÜÐ.ÊY¹4”{ä×w«!f¯1L$‚zƒ<;ôiè©dнxYn(øÙ¾¯½‘ ]úÀråGû«{»z©n­"+T±Ž9òÞQ98ÐLþ$–pk—À9QY¼dÂîËÆcbs»/½ýG:bõâ wcÁY¸1 ºÈþúQî{)÷wÙo­½ÃnÝ<|µó9£sײõ6iýâ·óLß ýÌô½¾xx[›„ŸïæÚ0cñšÜVðko\‹^˜ÕÛÄvŸRÑ@D² Ú¤ uLÙæìÓŸxzÁ¿-&€û›C–tF;ÄŒË(/ÇoAØeÛ©ežê8{¢J—J\:×&K^4‹:~¢5°à–åIf°¹‰ŠN¨:épCU…6³pxÂÓ{öÓ£ÊДù /V€¶êRƒW¤Ì/Í f”•W`n~¸°»^€;[ü~wo:ÜúÈÐÎ>dˉ³x´¶æXƒbáÆ…–ßÄc‡¦ŒZ©‘Ëqã¤Ø0¿þî*¡t=MwÿèÐ 6~iD¶²¶íÆ…ëëuýQÅ쉫±kÊ’$PS07àj'›5p²^•œ ýþÓ÷;Ên[(W5hÙð‰öÏž€»[üã#Š]0ŸûŠ*Ûb» ˜gÞð¾4Ù'Ʀõ?%õB‚™úb™¢oQi&%HøG†ôŸj")sª D¸F+N é®WÑI VßZq:(uA®TÝzfʯž:-OËhªWQñ,† Ûú'àÓwϼ%ŽOÌø@¿Ø ·.×T=ÎáþùvÉW<ø˜é aayCûôÛÓ©ŽŠJXpÛ­C6”¿õ§ÜgÞ­¦,tÏ52vÒýó¼Žç`)Y…‰gšCîö‰™Bž1ybŠsWŠ9¶ ¤X‡¦'Œ,¨×ÀÀë£@‚ÂGº6“á<óÊÖb÷™dÆ9دp'­ßŸyæ™Iãx÷?Èc¾îÃÑzw:´Á,8'û gï¼ßvã ðúÒòó%pNU\žÿOŽõ—›@C“±ø°x¬ÓôâÔYi!œ€ûL,æ»xKÓZÓ(«ÞÅù>€GLß/Ê`t Æ2iœ—^ Ú9vp¾xÃ8CÅ“2ºã¦·w}²Ýc7µ0}»vüì›8/1ÝBhÂpZœ!£Qnî—ÅôÝzG˜¦Í¡q3é—ȉn1C\"­e0ÈË(Ð#s …`ú3äiaJ/Âl ÔF¼¤)„üÛ>ãVÈ%ÒŸo‚…i»¼ ðyü2]l—ÐTˆ™ÏÄݲéÀk7g”ݶ*ÝoXßšKo¾t2õ¶ùlODÉÍxôÒ‡?¨Ì¼sy”æøïž>ž¸tÅú5AêSï ß¶>à¸éT´À†ðE¸¶4Ú¸ó81N·oºë|¥|ÕcwgŠúϾõÜñ>mo‹ ”ªÕ!™tû™“ç/ß®l零KÒZ~¼JÅ­½;§ò¯»;R ‚ú{“RLŠ‚¸H ê>xáˆ^£ìg¼öçöuR²êÌ_ž}·#5¶Ë·¥|óLþ/YiwÝc/á‹°™™ìòýß;s¡w—F³n¸Pâ¡9)ŸüýìâåQ€¡iÚ J*Nj=r5m}¡¢õŒ88ZÞ«TÊ% §®ëiqvqadßÅ ”ÔÈÐAuZ$6ñP–\vk)µ»æ†÷#z:5þŠÄ`ÃáþíþE…ц¶ Öʃ·Ä»ÛÇßy|QÀ ë”LŠåvÞš fí¤oú¦:.\ Þô;pÀ T}y&)5?{óœä…ÿJ“dñc÷6µ§N’¹ö±{ó-í£a?`f°­ÝÐrô$LX”?ÐCäÿì~ÿ Mê\«2~8¦‘ÛŸzŽRV^î’ ÏC’G°?ü‰Ú]B,;@_ÞF<•f iþ–9–‰eV±áeX¼qWqðåé GñÅ~Ãg³°-1ï\öÖ=»­., O|´deÈÿº·N)¿åù›³ü½±©Á¨•ƒòt£=2ƒ ×´¸$"‚設︤’ÏIí:óㅺİ› óëj÷¼Y]Pª¨mŒ_S–$3ÿXÌ`ãu]R¡É 2ȯ±þÜ¥ºß³û@kvyZû¥vÿ8I{WòH|ÎÐärÕ zHò(8`mÓ{0¶úYò;VÄv €ì ®`ö}CíÓâïþ™+mØ/ž%ß0&ôÏÂC/QhåQà:‰ýê,Óý È8Wü³€•ädß¾áúkŸU jÚZõ ¾[­ºQýâ'TPuÃÕ>qÙÿ[º"Åñ‚—µLxmШš[t2R˜¢˜=;àêmM’@ì'÷v]k‰ÈI OQ±8&<°§Âè)…Ú¾¶6ã%Џ,)0ºA¦çô«H†ýã’RØÆ0˜_”ÙgRbho`¸Ûa€¤‡$`X•Û¶ @ <ýO—jСf%\³Ú@Œ«Œëð¤+e54že!›—†èX‰žÕ p ¾êòn­hî9+‚ý®¹á“„LV}©V/K 멼b(Oqû V¼côÄ)y\ФöHDVq^ ÷F§G«WY9{å?¢L>`þì¸mí‘Шt1³$ˆãâ˜öÒ+0~D¯5~QºK=TtdT¼i |ºç-DIþ€Á;v •ƒëËGŸú ±Ñrü”hµé{›%äÑD[Š» Ö)±&$åÉ/-΂)¿ùlè¸øýñ<þ&Ç…ý}Ýð¤,µ$ϱ+,>Ž\bã09‡¤×á¿Üt†93V.Åì.j6 Å¿ n.€>Ú÷=5ð/†m„A höȬ–€R54ö~ÉTå`iµLsL¨fkò±íal{$ÛÉvµCõ,ìÙÖ!ïë”8¸k™xžIgƒòò&x"C÷úãÛÀ· $f°•=Ö„ùu1µÁ0‡b ,þ07 àOš;V€†J¦RuuìÉ&PêYîXµ|"bú¼ë ˆ1€axHM† K°H®;„/2™:M&keèÑD.÷™‚oirž3áŽ/×ãì*œcïbAP0wçMPÏÎÛ;$ə≼gÁiùI8ë·‰æ²?€ ,ÀÇAO„Oƒ€ði&g¬üt÷‹GX@˜„± }á»#gÚÀ+KBØØ:*øgN/h0ô¤÷£Ùô=€Wª›=ºê½ßJ׬JĺΟ5ä q…?„‹x¹ìɪÀ႟éüö­K¡U§ëE#eÁru]HP½ÒØDaŠ\?EõÁ}Udôʇ6§º #„›ñþ(gªëô'“Õ×>Ps©İH…´½Ë Á#©ŠEcÒ˜Í×jÛg-U z`&à}˜qߎCc›=“ÝEœ~çãéTë×{”Å[ üÆžÏ}á%Þs‰ðÝó㇟Ÿ«é-z|kß_ ­ø¾N„‹dA†æÓªýß¾## Žæ³9äLÁ?"fÅÖ¾²,Ùq¥]73ãE}Õ\3£]¢éº6˜1&nÜXóÙ…¤&=ŽóÓr¾àGø"€4{ûƒú7¿–¦qÍŒáPf æð—} 7.4/ž=õž±ë?ÂÇð|ÀoöÏï° Âäe[2¬C¦Ò3¶±~dú;¥<.ÎVÖ‘QÁpA 8\ ?Â5„.ñ©mL<2C€@¸Ï˜Èkqê Oš‚Í6¦S¹#Z»ÓwàYí¿»3âyûþð¹lwŽ~Y…ôr±ö©¨¡æ{óEÉËÝôÃK¨ ì _~&Z¥_,º¤ù’äX¬aKÈp[Ÿ…¦ÿj"icø¼çs\}8[g¤‡™‹[š@fÓŸÔè­€h;)ރ꽲߯'ÕG%{»±«ZøV³xŸ~÷.gâ_약™{ÖhÓÙÖ³²7Ety¼NÑ¿ZI™“ª¿U÷cÇ?—”.%ƒ¾“þ猌&wßehüÜïkÔ3 ©T_nwû›œ­ƒd0á_ÆV¦o‚*‚Þ–ÏudÁÓßüh6L´˜q·1Z3Sˆq«A@Ly8h5ÀãYV„ƒ¼(&€Ä%,Ð1€Õãïeejdš¨&20·d1½Zîr¨‘/.Â_ï!ÿÛɽÚÌÃÉi`æá†>€³Öϱw¬3˜ŽÈõ³†Â2Êue6Ñ6êV[ŽG%Í_[4¶›û¬×ìÏ·šÜ[YH…¹4\Ä=ÒÀÌC8£@°©%ÒQ˜qkÞö>Ä*¹Ï`vd ^;Yæ¤1uEUÔê$Vê”)ãìš}H3¤+œÇFxþ;ÁÆbÒ…J€-ZÐyB\I±±)Lý!B±˜Ì¡à™Ëøy.$N¬_H3ÒаYD¯If™nüz Û·Òi;P…_5€Žöç%tÓ1És*fS6­èÇ;‚èh%QGftcɳ©àA\ÍÒ.ìÉ·Kù8*þgîés¶â¬ ( aòq``wOAÎÚ¥æèýh)0m ¾¨Éx‚ÑuâLJ«%8 IbèÔú³x—qs2Š6~B)“ p’Ijà.Y¿Ó× ¦ü ÀfÙPG•ÙT“ÆDš·\O³g|"c9Ì›Ešöi6Ìå>¢ æ=› n6 GXYDú‡€¢tpêµ2“‘Sj¡ÆFÉø;ö ÖFÊþ™Š»Ã‹±S·/†ÆÎYçÀ˜âLã·ùÓ:¼ ÂÞuÈè} ·÷G¿›RRSÙ¨ÔA&Ý@©b†áÑá)ZÏØJÇâÖy Èúgh<áÓ  |$„Oƒ€ði> §™6@.¿w0m€ÜÞî`Úpîñ³»×¯Ë)Û¸½»ãè]¯¯á®Ù Ö8å?ðÚ9ñ+J—m¸/\¨$*:aÑRCÞðsÝçë·5u‰ÁpcÈb÷S6z+x´ž½{%¥éžØ+yº†ýI:3‡ß”|¡aç/1Ü<¥‰@ÖYEbð²*„Ã`Ìüì•Cb¥œÚ@ÐëÓa êu€3{š4¯³àÆŠÉ`æ!ŒU!œ!gîÕ¡Cj$t¬=¯ …!f*²rŠ·ÀÀ3õ0"Œ³vŸ”aßy‰%Äìe/'›Á͉È#1‚[:Á®øÅ[;ŧӉ×EUñT~/~ Ð$¬:/ºš¡{, î?A´@&g 1À¨°.ŒÍÏ¢{ω« %ÀïNt"G®â Äj¼ZDÏ ÀTᇺ™Ì*Ü¥× È)~Fâ®ÒÐi¿xk§x£3<“Ì`=8ÀdÁäÏʼn÷Ãv³s¨«ñc²™84®(dŒÏr•I}Þ! «å"p}–ͧj >„Žé€¬«ÖïÊeÁã¸âoãoòžk(:mö‘g æè_)µ¾¼È´6©²Lsã‡.gAã ¬cгŒM£ˆp&Ìá§@þÁ¾€ÛÃÎúÅóà?ì ? Éä]Vgí{Ädô¾‡':„<úÅOÊç‘S¼¯áéÇ h¬{»#ïcçyt“GÖ?ó@C‚ŸF ;óÙñ¹Ð®UßDp»½©SžZï‘jþ§MˆãI!|¡ÀÚd' qû‰ 1 ,x_S,òí3®ÆR‚oâ5ð[äOÊDwî¦ „‰àÖ"R&U@bð%<'ù“b'H ¾ƒ'àÝ"Rì uf6n€ÐŠüI™4Ÿ¨Ã0óp‹^äOŠãJH Óž0ÝMß%Œû3æa}>0ÑÏï;–¯›vð)€üóÏàGóq&€kë«»xÛT–j³— É<>飹Ö^Á^ ÍÃOò·¿ñö»¶µñ&¤YëŸá+)>]Ï}8xå>§/Að‚÷ç!^ áÓ  |$„O3e°•Ÿ¾¸$ûlEdQºä…u’ì@û•³~Y¥æÕ~è¦+‡s°FÍ27ülåùCƒKo]€FÞƒ—Àü¢‹Ô´ÿÏ¥Oª³òãúðyOŠ>\©÷ǵƒ!@Vý%Ð=_P¸ºŠIY$ë̹Ϙ̧ý‘õ#¼Ê”#Â;Žë”e=*ê#X,®®i Ì… ç¤G5Ô¶IF"—TŸnõË…Æuj‡d-éz÷Äâ\[¥ÓÀ¶/‚¾i´T??ƒ‰ZH¶}àôSu¬hT$ºÓÿå'Å+^êÍöb°‹ÿð ¹KCËKuaæ%|YØ|LÚ¥aã—‡ „ÊÔkitÉ·™³?še}fƒqؾÐ|œ÷~úPè›Ê¹Ï›¶?r4^s‘YòØ`çóÁ§®@|@Q'=üDa¢VC¦A¿Euk1Óu\*Ë'΃ǂ.@ÖЉŒNE­¦¡ò¼ôJµL“¨Uœ :ЫŸí'>^pMÖ§gÓ¶ˆß >¥aåF.Vmû …:^—߂T]ÿ²¦a0&Yݨ^œ&m"R .|×#ÊÏß[¹·C–éüÃÂã‚BL}¦¿«‹ Ó54ÁĤlÒä=Ngäcßü]N)4yrY09YF\¬câÓhY“_DãQù”¥2ÍßJ$@«b®TÓ  €1`ƒrCªJBYÖ`¬í0,  *?¿ÖÇ(/Ú”\ÏŒ0‡˜¾>}xp`oc[/›B`hxÀÅZ¿Ubº»X“êßX]«lëk¯øÇ‰Ö˜œ¢Õ=0d©L}EÏÆÈÑ b£×ªî:Öq%Æ5§ŠC•ÿ©ËøÍà§_À—ýbPùªÄÃýIæmžP GÓ˜¿Öï°ºô‰¾1©!à A†ÆüçÄ1_wh~¨#óge›¾$FW£‹K4|RU›%5 Ò"9¦Å%ϦY gi–aŽ Âú£ç=ÑÏ}'™>Ó^ W„ÑáyØì„á°ÔY óAÔì{¢F¢.ȶ2­ŠT1·G \‡—>.‹Ìsú"L3Ùbå„»AŸ áÓ  |$„OcOÍÿÁ€ºª{sνIEND®B`‚PKKGKAConfigurations2/images/Bitmaps/PKKGKAConfigurations2/popupmenu/PKKGKAConfigurations2/toolpanel/PKKGKAConfigurations2/statusbar/PKKGKAConfigurations2/progressbar/PKKGKAConfigurations2/toolbar/PKKGKAConfigurations2/menubar/PKKGKA'Configurations2/accelerator/current.xmlPKPKKGKAConfigurations2/floater/PKKGKA styles.xmlÝ[ÛrÛ6}ïWh˜Ißh’’œXªåLz›t&δqÚN!‘€-Ë?ÑÇþ_¿¤ € @‰”©X¾TÍL'Âì’9s“ÒÁ5æaÙÌ N|g€³E$[Ìœß?ýìž9o.¾9gqLBzRæÊ£ȯ:¬qè\˜ãOŸzçòÁ°-ùÌêïú™ó–s‡L%"ß³›™ãüÁÐŒ|=$0ær,ú·Žwq®Ù/Â1*iu¶ôXŒ ÙV3gÁQžÐ1ºÕo7ç8ÎbùøBpöCrRôûâÌ—ícL(­%aŒ_ǧΠfÓ%<Êe¹&ÚŒ¹ò÷Ú©q¤&kL¥D1•‚9’Ç?‰0Óªˆæ r*µ¼ÌBQªLWÏž9Is¹’Zvç#8ÄÀ #‘»ÎL7e<žrWÌ(fp !Y„eªË ‰zŠtD]\bD \ƒ‰ ²¼€¨¼î°ju×V´e]8Á#¶tÕä’‚—XÁ¨Ír}‹rV|÷‰À†|ÀËÁG–¢L6"ÐúîgŸ9\ê54r"B`ÿkĉN3UAn”á8jŒ¢lQ¢ EZ)de&8øòãOmSc Ì8ú#þŒþ(W(+îtÒX«BàtÛW#_{liH§‚v½ÊÊ{#»MŒ¤ Ã~øÐæš$cŠo¾*œÚ¶3 Z£;¤Z¥5¨ZšÍ°jÑ/œuv6˜À¤¥M šo ²ñÈÙŸ,#h¶€ç0J"Ç&%‰äÃSçN^Ñ<¾Â•§6<JÓmy{UÞ õm(ÀFÞa.¥mÆ’ÕêºyNIäÆF@4nÂ8¹…ÅC¨Š, ¯>—… ñJíœE²üq1¤+ÁðT:c æLy»h“Q À¦€“EbIô$(R7 ‰5]8r ,Ü›&MáªU¸±Jçªê¡¤h-9d«Q¸Q­ìÎKJ±h¡‡3ÑÑ?µÈ•—Ý™óï?× g=ÄÊ9e“’Ì¥h’z­_A^ùn2ë¬àÕ*3êìCÌš-ǧ/Õ.Ûé8 ïƒ:3Ý9† 86 B£ƒ!èѪ˜öÀÍ2d¥Öuš%RŽˆ„³r‘¸ÕkŸŒe-”·PjÓÃöJ‚³ªW¢ÇÌì<• ÅÜÊÀ9 ¿,8+!ôZÍä±xg%èu®W%HQ!TN®ÖBPrHvž×¿íïuÔr¶vùÚß>:ºì/ºUCHRÎèŠ$³*-o3 J¬Ä¸±JWß9ÙºÎ_Ãk Q«ë­QÞÓkÿG ÿPKcu|1PKKGKAMETA-INF/manifest.xml­SÁnà ½÷+"î­§ %íaÒ¾ ûFœ S5?©m¦©S£õfcû½g=ÓìÏÎV'ˆÉxlÙ+a öÁ¡eŸ‡úíw›Æ)4=$’— *s˜®iËrDéU2I¢r$ié`çuv€$öË™éš-lÙnSÝøzc¡.óq¼u÷ÙÚ:(:¶Lܹ=;茪i Ð2‚5ZQi'ìø,˜/uò!ªp4:1±F‡R¼¬x‡—àLb*¯M@T|HOÖiÚõÙ¸‡cv_¨ŒM‚.!8Ü!1N ¦ú*–w½rœmL[¡´ %õQèãß›ýëÁ»J' <®—+ý§Ñ£î7â×Ý}PKªåÇÞPKKGKAŸ.Ä++mimetypePKKGKAÝõI]¦¦Qmeta.xmlPKKGKA“¨ V0Ë# settings.xmlPKKGKA_ïQ“ Ì‚ ‡ content.xmlPKKGKA ôN4k$k$ÉThumbnails/thumbnail.pngPKKGKAjAConfigurations2/images/Bitmaps/PKKGKA§AConfigurations2/popupmenu/PKKGKAßAConfigurations2/toolpanel/PKKGKABConfigurations2/statusbar/PKKGKAOBConfigurations2/progressbar/PKKGKA‰BConfigurations2/toolbar/PKKGKA¿BConfigurations2/menubar/PKKGKA'õBConfigurations2/accelerator/current.xmlPKKGKALCConfigurations2/floater/PKKGKAcu|1 ‚Cstyles.xmlPKKGKAªåÇÞºKMETA-INF/manifest.xmlPK6Mmediascanner-0.3.93+14.04.20131024.1/docs/generate-properties-schema-dox.cpp0000644000015700001700000001511412232220161026365 0ustar pbuserpbgroup00000000000000/* Ubuntu TV Media Scanner * Copyright (C) 2012 Canonical Ltd. - All Rights Reserved * Contact: Jim Hodapp */ // GLib related libraries #include // Boost C++ #include #include #include // C++ Standard Library #include #include #include #include // Media Scanner Library #include "mediascanner/locale.h" #include "mediascanner/mediaindex.h" #include "mediascanner/propertyschema.h" #include "mediascanner/settings.h" namespace mediascanner { namespace internal { class GenerateSchemaDox { public: GenerateSchemaDox(); int Run(); struct less_by_name { bool operator()(const Property &a, const Property &b) const { return a.metadata_key().name() < b.metadata_key().name(); } }; private: static std::string gtype_name(GType gtype); static std::wstring category_name(Property::Category category); static std::wstring category_title(Property::Category category); bool collect_property(const Property &prop); void print_dox(const mediascanner::Property &prop); std::wostream& out_; typedef std::set PropertySet; typedef std::map CategoryMap; CategoryMap categories_; }; GenerateSchemaDox::GenerateSchemaDox() : out_(std::wcout) { } std::string GenerateSchemaDox::gtype_name(GType type) { const char* name = g_type_name(type); if (!name) return "Unknown type"; if (type == G_TYPE_STRING) return "String (" + std::string(name) + ")"; return name; } std::wstring GenerateSchemaDox::category_name(Property::Category category) { std::wstring name = category_title(category); boost::algorithm::to_lower(name); return name; } std::wstring GenerateSchemaDox::category_title(Property::Category category) { if (category == Property::Category::Generic) return L"Generic"; if (category == Property::Category::File) return L"File"; if (category == Property::Category::Media) return L"Media"; if (category == Property::Category::Music) return L"Music"; if (category == Property::Category::Image) return L"Image"; if (category == Property::Category::Photo) return L"Photo"; if (category == Property::Category::Movie) return L"Movie"; return L"Other"; } bool GenerateSchemaDox::collect_property(const Property &prop) { categories_[prop.category()].insert(prop); return false; } static std::wstring to_string(Property::MergeStrategy strategy) { switch (strategy) { case Property::MergeAppend: return L"appending the new value"; case Property::MergeReplace: return L"replacing the old value with the new value"; case Property::MergePreserve: return L"ignoring the new value and preserving the old one"; } return L"unknown strategy"; } void GenerateSchemaDox::print_dox(const Property &prop) { const Property::MetadataKey& key = prop.metadata_key(); const std::string name = key.name(); out_ << "- @anchor " << name.c_str() << " Key: @c " << name.c_str() << "" << std::endl // (The convention is for GObject property descriptions to // have no . at the end, so we add one. << " - Description: " << key.description().c_str() << "." << std::endl << " - Type: @c " << gtype_name(key.gtype()).c_str() << std::endl << " - Stored in Lucene as @c \"" << prop.field_name() << "\"." << std::endl << " - Merge conflicts are resolved by " << to_string(prop.merge_strategy()) << "." << std::endl; out_ << " - Supports " << "@ref grl-mediascanner-range-filters \"range filters\"." << std::endl; if (prop.supports_full_text_search()) { out_ << " - Covered by " << "@ref grl-mediascanner-full-text-search \"full text search\"." << std::endl; } const std::set origins = prop.origins(); if (not origins.empty()) { std::set::const_iterator it = origins.begin(); out_ << " - Retrieved from " << it->c_str(); while (++it != origins.end()) out_ << ", " << it->c_str(); out_ << "." << std::endl; } else { std::wcerr << "Warning: No origin for " << prop.field_name() << std::endl; } const GList *const relation = prop.metadata_key().relation(); if (relation && relation->next) { bool separator_needed = false; out_ << " - Related to "; for (const GList *l = relation; l; l = l->next) { const GrlKeyID related_key = GRLPOINTER_TO_KEYID(l->data); if (related_key == prop.metadata_key().id()) continue; if (separator_needed) { out_ << ", "; } else { separator_needed = true; } out_ << "@ref " << grl_metadata_key_get_name(related_key); } out_ << "." << std::endl; } } int GenerateSchemaDox::Run() { ::setenv("XDG_DATA_DIRS", USER_DATA_DIR ":/usr/share", true); Settings settings; settings.LoadMetadataSources(); Property::VisitAll(boost::bind(&GenerateSchemaDox::collect_property, this, _1)); out_ << "/**" << std::endl << "\n" << std::endl << "@page properties-schema Properties Schema" << std::endl << std::endl << "These are the fields that may be used with the @ref grilo-plugin, " << "for instance with " << "" << "grl_source_query()." << std::endl << "@tableofcontents" << std::endl << std::endl; for (const auto &p: categories_) { const Property::Category category = p.first; const PropertySet &properties = p.second; out_ << "@section properties-" << category_name(category) << " " << category_title(category) << " Properties" << std::endl << std::endl; std::for_each(properties.begin(), properties.end(), boost::bind(&GenerateSchemaDox::print_dox, this, _1)); out_ << std::endl; } out_ << "*/" << std::endl; return EXIT_SUCCESS; } } // namespace internal } // namespace mediascanner int main(int argc, char *argv[]) { mediascanner::SetupLocale(); grl_init(&argc, &argv); return mediascanner::internal::GenerateSchemaDox().Run(); } mediascanner-0.3.93+14.04.20131024.1/docs/CMakeLists.txt0000644000015700001700000000616512232220161022415 0ustar pbuserpbgroup00000000000000# Build the helper executable: include_directories(${DEPS_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/src) set(user_data_dir ${CMAKE_BINARY_DIR}/tests/data) add_definitions(-DUSER_DATA_DIR="${user_data_dir}") add_executable(generate-properties-schema-dox generate-properties-schema-dox.cpp) target_link_libraries(generate-properties-schema-dox mediascanner) # ============================================================================= # Check for Doxygen # ============================================================================= find_package(Doxygen 1.8) find_program(XSLTPROC xsltproc) if(DOXYGEN_FOUND) # Generate the properties-schema.dox file, # used by doxygen. add_custom_target(run-generate-properties-schema-dox COMMAND generate-properties-schema-dox > properties-schema.dox) add_dependencies(run-generate-properties-schema-dox generate-properties-schema-dox settingsdir) if(XSLTPROC) # Generate the gschema.dox file, # used by doxygen. add_custom_target(run-generate-gschema-dox COMMAND ${XSLTPROC} ${CMAKE_CURRENT_SOURCE_DIR}/gschema_xml_to_dox.xsl ${MEDIASCANNER_SETTINGS_FILE} > gschema.dox) add_dependencies(run-generate-gschema-dox ${MEDIASCANNER_SETTINGS_FILE}) else(XSLTPROC) message("xsltproc not found. Documentation will not be built completely") endif(XSLTPROC) # Developer docs (for maintainers of the code): set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/developer/Doxyfile) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/developer/Doxyfile.in ${DOXYFILE}) add_custom_target(docs-developer COMMAND rm -rf developer/html COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} COMMAND cp -f ${CMAKE_CURRENT_SOURCE_DIR}/images/*.png developer/html) add_dependencies(docs-developer run-generate-properties-schema-dox run-generate-gschema-dox images/*.png) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES developer/html) # API Reference docs (for users of the API): set(DOXYFILE ${CMAKE_CURRENT_BINARY_DIR}/reference/Doxyfile) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/reference/Doxyfile.in ${DOXYFILE}) add_custom_target(docs-reference COMMAND rm -rf reference/html COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE} COMMAND cp -f ${CMAKE_CURRENT_SOURCE_DIR}/images/*.png reference/html) add_dependencies(docs-reference run-generate-properties-schema-dox run-generate-gschema-dox images/*.png) set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES reference/html) install(CODE "execute_process(COMMAND ${CMAKE_BUILD_TOOL} docs-reference)") install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/reference/html/ DESTINATION ${CMAKE_INSTALL_DOCDIR}/reference) add_custom_target(docs DEPENDS docs-developer docs-reference) else(DOXYGEN_FOUND) message("Doxygen not found. Documentation will not be built") endif(DOXYGEN_FOUND) mediascanner-0.3.93+14.04.20131024.1/docs/grilo-plugin.dox0000644000015700001700000000337012232220161022774 0ustar pbuserpbgroup00000000000000/** @page grilo-plugin Grilo MediaScanner plugin The grl-mediascanner Grilo plugin provides access to the mediascanner index via the Grilo API. Application developers should generally use the Grilo API instead of using the mediascanner API directly. @tableofcontents @section grl-mediascanner-full-text-search Full Text Search This Grilo plugin supports grl_source_search(), performing a case-insensitive search for substrings in all text fields that have enabled full text search (see the @ref properties-schema). It also searches for substrings of similar meaning using Lucene's stemming alogrithms. This example-grilo-search.c example shows how to do perform a full text search with Grilo: @include example-grilo-search.c @section grl-mediascanner-query-syntax Query Syntax When using this plugin with grl_source_query(), you may use the lucene query syntax, using the mediascanner @ref properties-schema. This example-grilo-query.c example shows how to perform a query with Grilo: @include example-grilo-query.c @section grl-mediascanner-range-filters Range Filters This Grilo plugin supports range filters with grl_operation_options_set_key_range_filter_value() on fields that have enabled range filters (see the @ref properties-schema). This example-grilo-search-range-filter.c example shows how to use a range filter with Grilo: @include example-grilo-search-range-filter.c */ mediascanner-0.3.93+14.04.20131024.1/docs/overview.dox0000644000015700001700000000332712232220161022234 0ustar pbuserpbgroup00000000000000/** @mainpage mediascanner Reference Manual @section description Description The Ubuntu TV media scanner maintains a list of media files available to the user along with meta-data for those files. These may be accessed from a local file system which is physically attached to the device or via network mounts or sharing protocols. This mediascanner API provides access to this information and can be used to schedule scans and to control the scanning process. Most applications will access the mediascanner via the Grilo API, via the @ref grilo-plugin "grl-mediascanner Grilo plugin", which uses this mediascanner API. See mediascanner::MediaIndex and mediascanner::WritableMediaIndex. See the @ref architecture "Architecture" page for more details. @section control Control Most users will configure the mediascanner via the control panel, which uses the mediascanner's @ref gsettings-schema "DConf settings". @section basics Basic Usage Include, for instance, the mediaindex header: @code #include @endcode (You may include other headers, such as @c mediascanner/writeablemediaindex.h instead.) If your source file is @c program.cc, you can compile it with: @code g++ program.cc -o program `pkg-config --cflags --libs mediascanner-1.0` @endcode Alternatively, if using autoconf, use the following in @c configure.ac: @code PKG_CHECK_MODULES([MEDIASCANNER], [mediascanner-1.0]) @endcode Then use the generated @c MEDIASCANNER_CFLAGS and @c MEDIASCANNER_LIBS variables in the project @c Makefile.am files. For example: @code program_CPPFLAGS = $(MEDIASCANNER_CFLAGS) program_LDADD = $(MEDIASCANNER_LIBS) @endcode */ mediascanner-0.3.93+14.04.20131024.1/docs/architecture.dox0000644000015700001700000001457712232220161023061 0ustar pbuserpbgroup00000000000000/** @page architecture Architecture @section programming-language Programming Language The media scanner is be implemented in C++, using Boost C++ and glib where useful. This is intended to match the typical APIs used in Ubuntu TV. @section monitoring Monitoring We monitor the standard XDG_VIDEOS_DIR directory, its sub folders, and any plugged-in removable media. The exact set of folders to be monitored are configurable via dconf. The media scanner schedules a quick re-scan upon start up and when detecting the mounting of removable media because it cannot predict changes that happened while not monitoring those folders. We usually fully trust this combination of fast re-scanning and monitoring to reliably find any media files. However, we support additionally scheduled scans, with the timing being configurable. These scheduled scans are disabled by default, and by default we prevent scheduled scans when the system is running on low battery. Battery status is monitored via libupower-glib. @subsection monitoring-implementation Monitoring Implementation We use GFileMonitor to watch directory changes. This is the standard API for watching files in GLib. Its Linux backend uses inotify. @note Because the number of watch handles is limited for inotify, we could hit resource and performance limits. Hitting the inotify watch handle limit would cause us to miss file additions, removals and changes. However this is unlikely if we (and other applications) only watch the standard directories. Sadly fanotify cannot yet be used for file system monitoring since it lacks far too many features, such as notifications on file renaming or removal. Despite its limitations inotify is therefore the best choice for file system monitoring on Linux today. The typical user should have far less than 65,536 movie folders, which is the configured limit for user watches on Ubuntu 12.04. @note To reduce the impact of inotify's resource limits the media scanner checks the kernel settings regarding the maximum number of inotify watches per user. If too many watches must be created then only the most recently changed directories are watched, and periodic scanning of folders is enabled. The exact threshold for entering this fallback mode is configurable. @subsection removable-media-detection Removable Media Detection We monitor the presence of removable media with GVolumeMonitor. @note We also considered using Grilo's optical-media plugin for volume monitoring, but it doesn't currently expose sufficient information to serve as the basis for the media scanner. For instance, it doesn't clearly distinguish between optical drives and USB sticks. Also it doesn't expose UUIDs or similar for reliable media identification. When indexing a media file we also store the removable medium's UUID. Matches for detached removable media are automatically dropped from search results. If a medium is detached we just keep the entry in the database, because there is no reliable way to always mark the database entries as being offline files. For instance, sudden power loss would not give us the chance to make any database changes. Once a medium is inserted again, we first revalidate our existing records by checking for file existence and comparing basic file system meta data. If validation succeeds, the file will be included again in future search results. To distinguish verified files from other files on the removable medium we store a "last-verified" timestamp. As soon all such files have been validated, we then start scanning the media for new files. @section image-files Image Files Currently GstDiscoverer doesn't emit EXIF or similar picture tags. Therefore, as of this writing, we depend on a patch (see https://bugzilla.gnome.org/show_bug.cgi?id=676542#c5 ) to GStreamer. @section cover-art-and-synopsis Cover Art and Synopsis We have created a Grilo TMDb plugin, which is now part of the regular Grilo code base, which we use to discover further details about the media files. However, other Grilo plugins may be provided. The media scanner will query them in order of their priorities. @section data-store-api Data Store API The media catalog is stored in a Lucene++ index. It is searchable, it is quick, and it uses disk space efficiently. We don't require applications, or Grilo, to use Lucene++ directly. Instead we provide a minimal C++ library to: - Locate media files matching specified criteria. - Explicitly update selected media file properties in the media index. - Explicitly request re-scanning of all, or just a subset, of the files. - Receive notifications about additions, changes and removal of media files matching specified criterion. This library provides the foundation for our Grilo combined meta-data and media plug-in, allowing 3rd-party applications to conveniently access the media catalog via Grilo's API. Note that updates (writes) change the local index, but do not attempt to change the original files or change other original sources of metadata. To reliably support concurrent access without implementing complex locking schemes, we apply the "single writer, multiple readers" pattern: For reading data, the backend directly accesses the Lucene++ index, but all writes are executed by the media scanner. The media scanner provides a D-Bus interface for changing document properties and watching gallery changes. This D-Bus interface is be intended only for use by the access library. It should not be used by applications. @section Grilo Grilo allows applications to iterate over sets of media files using text matching accompanied by a set of filtering options to further narrow down the result. For more complex searches we define and implement a query language, to be used by the client. Grilo supports a basic set of properties for each file type by default, but our plug-in extends the the list of supported properties. Grilo allows property values to be written to the index. We enable writing for properties such as comments, rating, lastPlayed, playCount. Enabling writing for a given property is just a matter of toggling a flag in the schema definition. Note that updates (writes) will change the local index, but will not attempt to change the original files or change other original sources of metadata. @section Architecture Diagram This diagram shows the various parts of the mediascanner and how applications may use it via Grilo. @image html media-scanner.png "The Architecture of the Ubuntu TV Media Scanner" width=10cm */ mediascanner-0.3.93+14.04.20131024.1/docs/reference/0000755000015700001700000000000012232220310021577 5ustar pbuserpbgroup00000000000000mediascanner-0.3.93+14.04.20131024.1/docs/reference/Doxyfile.in0000644000015700001700000023020312232220161023716 0ustar pbuserpbgroup00000000000000# Doxyfile 1.8.1.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = "Ubuntu TV Media Scanner" # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer # a quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "A centralized index for removable media content." # With the PROJECT_LOGO tag one can specify an logo or icon that is # included in the documentation. The maximum height of the logo should not # exceed 55 pixels and the maximum width should not exceed 200 pixels. # Doxygen will copy the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = @CMAKE_BINARY_DIR@/docs/reference # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = NO # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@/src # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = YES # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you # can mix doxygen, HTML, and XML commands with Markdown formatting. # Disable only in case of backward compatibilities issues. MARKDOWN_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and # unions are shown inside the group in which they are included (e.g. using # @ingroup) instead of on a separate page (for HTML and Man pages) or # section (for LaTeX and RTF). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data fields will be shown inline in the documentation # of the scope in which they are defined (i.e. file, namespace, or group # documentation), provided this scope is documented. If set to NO (the default), # structs, classes, and unions are shown on a separate page (for HTML and Man # pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = YES # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 # Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be # set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given # their name and scope. Since this can be an expensive process and often the # same symbol appear multiple times in the code, doxygen keeps a cache of # pre-resolved symbols. If the cache is too small doxygen will become slower. # If the cache is too large, memory is wasted. The cache size is given by this # formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespaces are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = YES # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to # do proper type resolution of all parameters of a function it will reject a # match between the prototype and the implementation of a member function even # if there is only one candidate or it is obvious which candidate to choose # by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen # will still accept a match between prototype and implementation in such cases. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = NO # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = NO # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style # of the bibliography can be controlled using LATEX_BIB_STYLE. To use this # feature you need bibtex and perl available in the search path. CITE_BIB_FILES = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The WARN_NO_PARAMDOC option can be enabled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = doxygen-reference.log #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = @CMAKE_SOURCE_DIR@/src/mediascanner \ @CMAKE_SOURCE_DIR@/docs/ \ @CMAKE_BINARY_DIR@/docs/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = *.h \ *.dox # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. EXCLUDE_SYMLINKS = YES # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = Lucene* mediascanner::internal* mediascanner::logging* # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/examples # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) # and it is also possible to disable source filtering for a specific pattern # using *.ext= (so without naming a filter). This option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # style sheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that # the files will be copied as-is; there are no commands or markers available. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the style sheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 25 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 220 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. HTML_DYNAMIC_SECTIONS = YES # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of # entries shown in the various tree structured indices initially; the user # can expand and collapse entries dynamically later on. Doxygen will expand # the tree to such a level that at most the specified number of entries are # visible (unless a fully collapsed tree already exceeds this amount). # So setting the number of entries 1 will produce a full collapsed tree by # default. 0 is a special value representing an infinite number of entries # and will result in a full expanded tree by default. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = YES # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = net.launchpad.MediaScanner # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = @QHELPGENERATOR_PATH@ # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) that doxygen will group on one line in the generated HTML # documentation. Note that a value of 0 will completely suppress the enum # values from appearing in the overview section. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered bitmaps. Use this if you do not # have LaTeX installed or if you want to formulas look prettier in the HTML # output. When enabled you may also need to install MathJax separately and # configure the path to it using the MATHJAX_RELPATH option. USE_MATHJAX = NO # When MathJax is enabled you need to specify the location relative to the # HTML output directory using the MATHJAX_RELPATH option. The destination # directory should contain the MathJax.js script. For instance, if the mathjax # directory is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to # the MathJax Content Delivery Network so you can quickly see the result without # installing MathJax. However, it is strongly recommended to install a local # copy of MathJax from http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4 # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for # the generated latex document. The footer should contain everything after # the last chapter. If it is left blank doxygen will generate a # standard footer. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition that # overrules the definition found in the source code. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all references to function-like macros # that are alone on a line, have an all uppercase name, and do not end with a # semicolon, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. For each # tag file the location of the external documentation should be added. The # format of a tag file without this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths # or URLs. Note that each tag file must have a unique name (where the name does # NOT include the path). If a tag file is not located in the directory in which # doxygen is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = NO # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. You need to make sure dot is able to find # the font, which can be done by putting it in a standard location or by setting # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the # directory containing the font. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = NO # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = NO # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If the UML_LOOK tag is enabled, the fields and methods are shown inside # the class node. If there are many fields or methods and many nodes the # graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS # threshold limits the number of items for each type to make the size more # managable. Set this to 0 for no limit. Note that the threshold may be # exceeded by 50% before the limit is enforced. UML_LIMIT_NUM_FIELDS = 10 # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = NO # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = NO # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. If you choose svg you need to set # HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible in IE 9+ (other browsers do not have this requirement). DOT_IMAGE_FORMAT = png # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to # enable generation of interactive SVG images that allow zooming and panning. # Note that this requires a modern browser other than Internet Explorer. # Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you # need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files # visible. Older versions of IE do not have SVG support. INTERACTIVE_SVG = NO # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = @DOXYGEN_DOT_PATH@ # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the # \mscfile command). MSCFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES