diff options
-rw-r--r-- | include/media/mediaManager.h | 12 | ||||
-rw-r--r-- | include/plugin.h | 32 | ||||
-rw-r--r-- | include/pluginManager.h | 5 | ||||
-rw-r--r-- | include/server.h | 2 | ||||
-rw-r--r-- | media/mediaManager.cpp | 337 | ||||
-rw-r--r-- | media/pluginManager.cpp | 39 | ||||
-rw-r--r-- | plugins/provider/vdrProvider/vdrProvider.cpp | 101 |
7 files changed, 380 insertions, 148 deletions
diff --git a/include/media/mediaManager.h b/include/media/mediaManager.h index c9e042d..9c5b9a1 100644 --- a/include/media/mediaManager.h +++ b/include/media/mediaManager.h @@ -15,14 +15,15 @@ #include <list> #include <string> #include <stdint.h> -#include <tntdb/connection.h> #include <tntdb/connect.h> +#include <tntdb/connection.h> namespace upnp { class cResourceStreamer; class cMediaManager : public cThread { + friend void cUPnPResourceProvider::OnContainerUpdate(const string& uri, long updateID, const string& target = string()); private: struct MediaRequest { @@ -81,13 +82,18 @@ private: int CreateResponse(MediaRequest&, const string&, const string&); - void OnContainerUpdate(string uri, long updateID); + void OnContainerUpdate(const string& uri, long updateID, const string& target); + bool UpdateContainerUpdateId(const string& objectID, long updateID); + + bool ScanURI(const string& uri, cUPnPResourceProvider* provider); + + bool RefreshObject(const cMetadata& metadata); cUPnPResourceProvider* CreateResourceProvider(const std::string& uri); uint32_t systemUpdateID; IdList eventedContainerUpdateIDs; - StringList scanDirectories; + StringList scanTargets; string databaseFile; string pluginDirectory; tntdb::Connection connection; diff --git a/include/plugin.h b/include/plugin.h index cd24e91..c8abd57 100644 --- a/include/plugin.h +++ b/include/plugin.h @@ -12,6 +12,8 @@ #include <map> #include <list> #include <stdint.h> +#include <vdr/thread.h> +#include "../include/tools.h" using namespace std; @@ -116,6 +118,10 @@ public: long GetInteger() const; bool GetBoolean() const; + bool IsEmpty() { return key.empty() && value.empty(); } + + static Property Empty; + private: string key; string value; @@ -186,6 +192,8 @@ public: bool SetObjectIDByUri(const string& uri); bool SetParentIDByUri(const string& uri); + string ToString(); + private: PropertyMap properties; @@ -201,11 +209,10 @@ public: #define UPNP_REGISTER_RESOURCE_PROVIDER(cls) extern "C" void *UPnPCreateResourceProvider(void) { return new cls; } -class cUPnPResourceProvider { +class cUPnPResourceProvider : public cThread { + friend class cMediaManager; public: - typedef list<string> EntryList; - virtual ~cUPnPResourceProvider(){}; /** @@ -257,7 +264,7 @@ public: * * The given URI is an absolute URI. */ - virtual EntryList GetContainerEntries(const string& uri) = 0; + virtual StringList GetContainerEntries(const string& uri) = 0; /** * Checks if the given URI is a container. @@ -445,8 +452,23 @@ protected: * was moved from * - change of the parent container, where the file or container * was moved to + * + * The optional target must be an element inside the container + * specified in the URI. If the target is not empty, only this + * element will be checked for changes. Otherwise the full container + * is scanned. + */ + void OnContainerUpdate(const string& uri, long containerUpdateId, const string& target = string()); + + /** + * Thread action to check for updates + * + * This should be used to determine changes on the containers. It should + * be overridden by the implementor. However, it is not required and + * therefore empty by default. + * */ - void OnContainerUpdate(const string& uri, long containerUpdateId, const string& changesUri = string()); + virtual void Action(); }; diff --git a/include/pluginManager.h b/include/pluginManager.h index 8520b01..563d4fb 100644 --- a/include/pluginManager.h +++ b/include/pluginManager.h @@ -20,6 +20,7 @@ class cPluginManager { public: typedef std::list< boost::shared_ptr<cMediaProfiler> > ProfilerList; + typedef std::list< boost::shared_ptr<cUPnPResourceProvider> > ProviderList; cPluginManager(); virtual ~cPluginManager(); @@ -29,6 +30,7 @@ public: int Count() const; const ProfilerList& GetProfilers() const; + const ProviderList& GetProviders() const; cUPnPResourceProvider* CreateProvider(const string& schema); private: @@ -59,8 +61,9 @@ private: typedef std::map<string, ResourceProviderFuncPtr > ProviderMap; DLLList dlls; - ProviderMap providers; + ProviderMap providerFactory; ProfilerList profilers; + ProviderList providers; }; diff --git a/include/server.h b/include/server.h index 3f24725..48e85a9 100644 --- a/include/server.h +++ b/include/server.h @@ -10,9 +10,9 @@ #include <string> #include <upnp/upnp.h> -#include "../include/webserver.h" #include "../include/config.h" #include "../include/service.h" +#include "../include/webserver.h" using namespace std; diff --git a/media/mediaManager.cpp b/media/mediaManager.cpp index 409b49d..55c8164 100644 --- a/media/mediaManager.cpp +++ b/media/mediaManager.cpp @@ -124,17 +124,63 @@ IdList cMediaManager::GetContainerUpdateIDs(bool unevented){ return list; } -void cMediaManager::OnContainerUpdate(string uri, long updateID){ - ++systemUpdateID; +void cMediaManager::OnContainerUpdate(const string& uri, long updateID, const string& target){ + systemUpdateID = time(NULL); - eventedContainerUpdateIDs[tools::GenerateUUIDFromURL(uri)] = updateID; + string objectID = tools::GenerateUUIDFromURL(uri); - scanDirectories.push_back(uri); + eventedContainerUpdateIDs[objectID] = updateID; + + // If we cannot update the containerUpdateID, we do not know, it is very likely, that this container + // does not exist. Therefore, we cannot scan this directory successfully. + if(!UpdateContainerUpdateId(objectID, updateID)) return; + + stringstream ss; + + ss << uri; + + if(!target.empty()){ + ss << target; + } + + scanTargets.push_back(ss.str()); // Start scanning for changed files. Start(); } +bool cMediaManager::UpdateContainerUpdateId(const string& objectID, long int updateID){ + stringstream update; + + update << "UPDATE " << db::Metadata << " SET " + << " `" << property::object::KEY_OBJECT_UPDATE_ID << "`=" << updateID + << " WHERE `" << property::object::KEY_OBJECTID << "`" + << " = :objectID"; + + try { + tntdb::Statement stmt = connection.prepare(update.str()); + + stmt.setString("objectID", objectID); + + if(stmt.execute() == 0){ + isyslog("UPnP\tContainer with ID '%s' not found. Cannot update it.", objectID.c_str()); + return false; + } + + dsyslog("UPnP\tUpdated container with ID '%s': %ld", objectID.c_str(), updateID); + + return true; + + } catch (const std::exception& e) { + esyslog("UPnP\tException occurred while updating container with ID '%s': %s", objectID.c_str(), e.what()); + + return false; + } + + return false; + +} + StringList cMediaManager::GetSearchCapabilities() const { StringList list; @@ -391,15 +437,6 @@ cMediaManager::BrowseFlag cMediaManager::ToBrowseFlag(const std::string& browseF bool cMediaManager::Initialise(){ - pluginManager = new upnp::cPluginManager(); - - if(!pluginManager->LoadPlugins(pluginDirectory)){ - esyslog("UPnP\tError while loading upnp plugin directory '%s'", pluginDirectory.c_str()); - return false; - } else { - dsyslog("UPnP\tFound %d plugins", pluginManager->Count()); - } - try { stringstream ss; ss << "sqlite:" << databaseFile; @@ -408,103 +445,101 @@ bool cMediaManager::Initialise(){ dsyslog("UPNP\tPreparing database structure..."); - if(CheckIntegrity()) return true; - - connection.beginTransaction(); - - ss.str(string()); - - ss << "CREATE TABLE " << db::Metadata - << "(" - << "`" << property::object::KEY_OBJECTID << "` TEXT PRIMARY KEY," - << "`" << property::object::KEY_PARENTID << "` TEXT NOT NULL," - << "`" << property::object::KEY_TITLE << "` TEXT NOT NULL," - << "`" << property::object::KEY_CLASS << "` TEXT NOT NULL," - << "`" << property::object::KEY_RESTRICTED << "` INTEGER NOT NULL," - << "`" << property::object::KEY_CREATOR << "` TEXT," - << "`" << property::object::KEY_DESCRIPTION << "` TEXT," - << "`" << property::object::KEY_LONG_DESCRIPTION << "` TEXT," - << "`" << property::object::KEY_DATE << "` TEXT," - << "`" << property::object::KEY_LANGUAGE << "` TEXT," - << "`" << property::object::KEY_CHANNEL_NR << "` INTEGER," - << "`" << property::object::KEY_CHANNEL_NAME << "` TEXT," - << "`" << property::object::KEY_SCHEDULED_START << "` TEXT," - << "`" << property::object::KEY_SCHEDULED_END << "` TEXT" - << "`" << property::object::KEY_OBJECT_UPDATE_ID << "` INTEGER" - << ")"; - - tntdb::Statement objectTable = connection.prepare(ss.str()); - - objectTable.execute(); - - ss.str(string()); - - ss << "CREATE TABLE " << db::Details - << "(" - << " `propertyID` INTEGER PRIMARY KEY," - << " `" << property::object::KEY_OBJECTID << "` TEXT " - << " REFERENCES metadata (`"<< property::object::KEY_OBJECTID <<"`) ON DELETE CASCADE ON UPDATE CASCADE," - << " `property` TEXT," - << " `value` TEXT" - << ")"; - - tntdb::Statement detailsTable = connection.prepare(ss.str()); - - detailsTable.execute(); - - ss.str(string()); - - ss << "CREATE TABLE " << db::Resources - << "(" - << " resourceID INTEGER PRIMARY KEY," - << " `" << property::object::KEY_OBJECTID << "` TEXT " - << " REFERENCES metadata (`"<< property::object::KEY_OBJECTID <<"`) ON DELETE CASCADE ON UPDATE CASCADE," - << "`" << property::resource::KEY_RESOURCE << "` TEXT NOT NULL," - << "`" << property::resource::KEY_PROTOCOL_INFO << "` TEXT NOT NULL," - << "`" << property::resource::KEY_SIZE << "` INTEGER," - << "`" << property::resource::KEY_DURATION << "` TEXT," - << "`" << property::resource::KEY_RESOLUTION << "` TEXT," - << "`" << property::resource::KEY_BITRATE << "` INTEGER," - << "`" << property::resource::KEY_SAMPLE_FREQUENCY << "` INTEGER," - << "`" << property::resource::KEY_BITS_PER_SAMPLE << "` INTEGER," - << "`" << property::resource::KEY_NR_AUDIO_CHANNELS << "` INTEGER," - << "`" << property::resource::KEY_COLOR_DEPTH << "` INTEGER" - << ")"; - - tntdb::Statement resourcesTable = connection.prepare(ss.str()); - - resourcesTable.execute(); - - ss.str(string()); - - ss << "INSERT INTO " << db::Metadata << " (" - << "`" << property::object::KEY_OBJECTID << "`, " - << "`" << property::object::KEY_PARENTID << "`, " - << "`" << property::object::KEY_TITLE << "`, " - << "`" << property::object::KEY_CLASS << "`, " - << "`" << property::object::KEY_RESTRICTED << "`, " - << "`" << property::object::KEY_CREATOR << "`, " - << "`" << property::object::KEY_DESCRIPTION << "`, " - << "`" << property::object::KEY_LONG_DESCRIPTION << "`) " - << " VALUES (:objectID, :parentID, :title, :class, :restricted, :creator, :description, :longDescription)"; - - tntdb::Statement rootContainer = connection.prepare(ss.str()); - - const cMediaServer::Description desc = cMediaServer::GetInstance()->GetServerDescription(); - - rootContainer.setString("objectID", "0") - .setString("parentID", "-1") - .setString("title", desc.friendlyName) - .setString("creator", desc.manufacturer) - .setString("class", "object.container") - .setBool("restricted", true) - .setString("description", desc.modelName) - .setString("longDescription", desc.modelDescription) - .execute(); - - connection.commitTransaction(); - - return true; + if(!CheckIntegrity()){ + connection.beginTransaction(); + + ss.str(string()); + + ss << "CREATE TABLE " << db::Metadata + << "(" + << "`" << property::object::KEY_OBJECTID << "` TEXT PRIMARY KEY," + << "`" << property::object::KEY_PARENTID << "` TEXT NOT NULL," + << "`" << property::object::KEY_TITLE << "` TEXT NOT NULL," + << "`" << property::object::KEY_CLASS << "` TEXT NOT NULL," + << "`" << property::object::KEY_RESTRICTED << "` INTEGER NOT NULL," + << "`" << property::object::KEY_CREATOR << "` TEXT," + << "`" << property::object::KEY_DESCRIPTION << "` TEXT," + << "`" << property::object::KEY_LONG_DESCRIPTION << "` TEXT," + << "`" << property::object::KEY_DATE << "` TEXT," + << "`" << property::object::KEY_LANGUAGE << "` TEXT," + << "`" << property::object::KEY_CHANNEL_NR << "` INTEGER," + << "`" << property::object::KEY_CHANNEL_NAME << "` TEXT," + << "`" << property::object::KEY_SCHEDULED_START << "` TEXT," + << "`" << property::object::KEY_SCHEDULED_END << "` TEXT" + << "`" << property::object::KEY_OBJECT_UPDATE_ID << "` INTEGER" + << ")"; + + tntdb::Statement objectTable = connection.prepare(ss.str()); + + objectTable.execute(); + + ss.str(string()); + + ss << "CREATE TABLE " << db::Details + << "(" + << " `propertyID` INTEGER PRIMARY KEY," + << " `" << property::object::KEY_OBJECTID << "` TEXT " + << " REFERENCES metadata (`"<< property::object::KEY_OBJECTID <<"`) ON DELETE CASCADE ON UPDATE CASCADE," + << " `property` TEXT," + << " `value` TEXT" + << ")"; + + tntdb::Statement detailsTable = connection.prepare(ss.str()); + + detailsTable.execute(); + + ss.str(string()); + + ss << "CREATE TABLE " << db::Resources + << "(" + << " resourceID INTEGER PRIMARY KEY," + << " `" << property::object::KEY_OBJECTID << "` TEXT " + << " REFERENCES metadata (`"<< property::object::KEY_OBJECTID <<"`) ON DELETE CASCADE ON UPDATE CASCADE," + << "`" << property::resource::KEY_RESOURCE << "` TEXT NOT NULL," + << "`" << property::resource::KEY_PROTOCOL_INFO << "` TEXT NOT NULL," + << "`" << property::resource::KEY_SIZE << "` INTEGER," + << "`" << property::resource::KEY_DURATION << "` TEXT," + << "`" << property::resource::KEY_RESOLUTION << "` TEXT," + << "`" << property::resource::KEY_BITRATE << "` INTEGER," + << "`" << property::resource::KEY_SAMPLE_FREQUENCY << "` INTEGER," + << "`" << property::resource::KEY_BITS_PER_SAMPLE << "` INTEGER," + << "`" << property::resource::KEY_NR_AUDIO_CHANNELS << "` INTEGER," + << "`" << property::resource::KEY_COLOR_DEPTH << "` INTEGER" + << ")"; + + tntdb::Statement resourcesTable = connection.prepare(ss.str()); + + resourcesTable.execute(); + + ss.str(string()); + + ss << "INSERT INTO " << db::Metadata << " (" + << "`" << property::object::KEY_OBJECTID << "`, " + << "`" << property::object::KEY_PARENTID << "`, " + << "`" << property::object::KEY_TITLE << "`, " + << "`" << property::object::KEY_CLASS << "`, " + << "`" << property::object::KEY_RESTRICTED << "`, " + << "`" << property::object::KEY_CREATOR << "`, " + << "`" << property::object::KEY_DESCRIPTION << "`, " + << "`" << property::object::KEY_LONG_DESCRIPTION << "`) " + << " VALUES (:objectID, :parentID, :title, :class, :restricted, :creator, :description, :longDescription)"; + + tntdb::Statement rootContainer = connection.prepare(ss.str()); + + const cMediaServer::Description desc = cMediaServer::GetInstance()->GetServerDescription(); + + rootContainer.setString("objectID", "0") + .setString("parentID", "-1") + .setString("title", desc.friendlyName) + .setString("creator", desc.manufacturer) + .setString("class", "object.container") + .setBool("restricted", true) + .setString("description", desc.modelName) + .setString("longDescription", desc.modelDescription) + .execute(); + + connection.commitTransaction(); + } } catch (const std::exception& e) { esyslog("UPnP\tException occurred while initializing database '%s': %s", databaseFile.c_str(), e.what()); @@ -514,7 +549,25 @@ bool cMediaManager::Initialise(){ return false; } - return false; + dsyslog("UPNP\tLoading Plugins..."); + pluginManager = new upnp::cPluginManager(); + + if(!pluginManager->LoadPlugins(pluginDirectory)){ + esyslog("UPnP\tError while loading upnp plugin directory '%s'", pluginDirectory.c_str()); + return false; + } else { + dsyslog("UPnP\tFound %d plugins", pluginManager->Count()); + } + + dsyslog("UPNP\tScanning directories..."); + // Do an full initial scan on startup. + upnp::cPluginManager::ProviderList providers = pluginManager->GetProviders(); + for(upnp::cPluginManager::ProviderList::iterator it = providers.begin(); it != providers.end(); ++it){ + scanTargets.push_back((*it)->GetRootContainer()); + } + Start(); + + return true; } bool cMediaManager::CheckIntegrity(){ @@ -622,7 +675,69 @@ void cMediaManager::SetPluginDirectory(const string& directory){ } void cMediaManager::Action(){ + string uri; + while(!scanTargets.empty()){ + uri = scanTargets.front(); + boost::shared_ptr<cUPnPResourceProvider> provider(CreateResourceProvider(uri)); + if(!ScanURI(uri, provider.get())){ + isyslog("UPnP\tAn error occured while scanning '%s'!", uri.c_str()); + } + scanTargets.pop_front(); + } +} + +bool cMediaManager::ScanURI(const string& uri, cUPnPResourceProvider* provider){ + if (provider == NULL) return false; + + cMetadata metadata; + + if(!provider->IsContainer(uri)){ + + if(!RefreshObject(metadata)){ + isyslog("UPnP\tUnable to save the metadata of '%s'", uri.c_str()); + return false; + } + } else { + if(!provider->GetMetadata(uri, metadata)){ + isyslog("UPnP\tUnable to get the metadata of '%s'", uri.c_str()); + return false; + } + + if(!RefreshObject(metadata)){ + isyslog("UPnP\tUnable to save the metadata of '%s'", uri.c_str()); + return false; + } + + StringList entries = provider->GetContainerEntries(uri); + stringstream ss, uristrm; + + ss << "DELETE FROM " << db::Metadata << " WHERE " + << " `" << property::object::KEY_PARENTID << "`" + << " = '" << tools::GenerateUUIDFromURL(uri) << "'"; + + for(StringList::iterator it = entries.begin(); it != entries.end(); ++it){ + uristrm.str(string()); + uristrm << uri << *it; + ss << " AND" + << " `" << property::object::KEY_OBJECTID << "`" + << " != '" << tools::GenerateUUIDFromURL(uristrm.str()) << "'"; + } + + cout << ss.str() << endl; + + for(StringList::iterator it = entries.begin(); it != entries.end(); ++it){ + uristrm.str(string()); + uristrm << uri << *it; + ScanURI(uristrm.str(), provider); + } + } + + return true; +} + +bool cMediaManager::RefreshObject(const cMetadata& metadata){ + return true; } } // namespace upnp diff --git a/media/pluginManager.cpp b/media/pluginManager.cpp index a76af8f..59dccfa 100644 --- a/media/pluginManager.cpp +++ b/media/pluginManager.cpp @@ -5,10 +5,13 @@ * Author: savop */ +#include "../include/server.h" +#include "../include/media/mediaManager.h" #include "../include/pluginManager.h" #include "../include/tools/string.h" #include "../include/tools/uuid.h" #include <string> +#include <sstream> #include <dlfcn.h> #include <dirent.h> @@ -112,9 +115,25 @@ cMetadata::PropertyRange cMetadata::GetAllProperties() { } cMetadata::Property& cMetadata::GetPropertyByKey(const string& property) { - return (*properties.find(property)).second; + PropertyMap::iterator it = properties.find(property); + + return (it != properties.end()) ? (*it).second : cMetadata::Property::Empty; } +string cMetadata::ToString() { + stringstream ss; + + cMetadata::PropertyRange properties = GetAllProperties(); + + for(PropertyMap::iterator it = properties.first; it != properties.second; ++it){ + ss << "'" << (*it).first << "' ('" << (*it).second.GetKey() << "') = '" << (*it).second.GetString() << "'" << endl; + } + + return ss.str(); +} + +cMetadata::Property cMetadata::Property::Empty; + cMetadata::Property::Property(string key, bool val) : key(key) { @@ -222,7 +241,7 @@ bool cUPnPResourceProvider::GetMetadata(const string& uri, cMetadata& metadata){ metadata.SetObjectIDByUri(uri); metadata.SetParentIDByUri(uri.substr(0,uri.find_last_of("/"))); metadata.SetProperty(cMetadata::Property(property::object::KEY_TITLE, uri.substr(uri.find_last_of("/")+1))); - metadata.SetProperty(cMetadata::Property(property::object::KEY_CLASS, "object.container")); + metadata.SetProperty(cMetadata::Property(property::object::KEY_CLASS, string("object.container"))); metadata.SetProperty(cMetadata::Property(property::object::KEY_RESTRICTED, true)); return true; @@ -252,7 +271,11 @@ bool cUPnPResourceProvider::Seek(size_t offset, int origin){ void cUPnPResourceProvider::Close(){ } +void cUPnPResourceProvider::OnContainerUpdate(const string& uri, long int cUID, const string& target){ + cMediaServer::GetInstance()->GetManager().OnContainerUpdate(uri, cUID, target); +} +void cUPnPResourceProvider::Action(){} upnp::cPluginManager::cPluginManager() {} @@ -263,13 +286,17 @@ const cPluginManager::ProfilerList& upnp::cPluginManager::GetProfilers() const { return profilers; } +const cPluginManager::ProviderList& upnp::cPluginManager::GetProviders() const { + return providers; +} + int upnp::cPluginManager::Count() const { return dlls.size(); } cUPnPResourceProvider* upnp::cPluginManager::CreateProvider(const string& schema) { - if(providers[schema]) - return providers[schema](); + if(providerFactory[schema]) + return providerFactory[schema](); else return NULL; } @@ -300,7 +327,9 @@ bool upnp::cPluginManager::LoadPlugins(const string& directory){ if(dll->IsProvider()){ boost::shared_ptr<cUPnPResourceProvider> provider((cUPnPResourceProvider*)(dll->GetFunc()())); - providers[provider->ProvidesSchema()] = (ResourceProviderFuncPtr)dll->GetFunc(); + providerFactory[provider->ProvidesSchema()] = (ResourceProviderFuncPtr)dll->GetFunc(); + providers.push_back( provider ); + provider->Start(); } else { boost::shared_ptr<cMediaProfiler> profiler((cMediaProfiler*)(dll->GetFunc()())); profilers.push_back( profiler ); diff --git a/plugins/provider/vdrProvider/vdrProvider.cpp b/plugins/provider/vdrProvider/vdrProvider.cpp index bcf06a4..fa7a32b 100644 --- a/plugins/provider/vdrProvider/vdrProvider.cpp +++ b/plugins/provider/vdrProvider/vdrProvider.cpp @@ -6,6 +6,7 @@ */ #include <plugin.h> +#include <vdr/epg.h> #include <vdr/channels.h> #include <vdr/tools.h> #include <vdr/config.h> @@ -13,34 +14,45 @@ #include <sstream> #include <tools.h> #include <vdr/thread.h> +#include <iostream> using namespace std; namespace upnp { -class VdrProvider : public cUPnPResourceProvider, cThread { +class VdrProvider : public cUPnPResourceProvider { private: - int lastUpdateID; + time_t lastModified; + + bool IsRootContainer(const string& uri){ + if(uri.find(GetRootContainer(), 0) != 0){ + isyslog("VdrProvider\tUri does not contain the root."); + return false; + } else { + return true; + } + } public: VdrProvider() - : lastUpdateID(0) + : lastModified(0) {} + virtual ~VdrProvider(){ + Cancel(2); + } + virtual string ProvidesSchema(){ return "vdr"; } virtual string GetRootContainer(){ return ProvidesSchema() + "://"; } - virtual cUPnPResourceProvider::EntryList GetContainerEntries(const string& uri){ - if(uri.find(GetRootContainer(), 0) != 0){ - isyslog("VdrProvider\tUri does not contain the root."); - return cUPnPResourceProvider::EntryList(); - } + virtual StringList GetContainerEntries(const string& uri){ + if(!IsRootContainer(uri)) return StringList(); - EntryList list; + StringList list; // Check if this is the root: if(uri.compare(GetRootContainer()) == 0){ @@ -64,37 +76,82 @@ public: return false; } - virtual long GetContainerUpdateId(const string&){ - // TODO: provide a container update id - return lastUpdateID; + virtual long GetContainerUpdateId(const string& uri){ + if(IsRootContainer(uri)) return 0; + + // We have no containers. So just return the last modification date. + // Containers like groups are about to come soon. + return (long)lastModified; } virtual bool GetMetadata(const string& uri, cMetadata& metadata){ - if(uri.find(GetRootContainer(), 0) != 0){ - isyslog("VdrProvider\tUri does not contain the root."); - return false; - } + if(!IsRootContainer(uri)) return false; - return false; + if(!cUPnPResourceProvider::GetMetadata(uri, metadata)) return false; + + metadata.SetProperty(cMetadata::Property(property::object::KEY_PARENTID, string("0"))); + metadata.SetProperty(cMetadata::Property(property::object::KEY_TITLE, string("VDR Live-TV"))); + metadata.SetProperty(cMetadata::Property("dlna:containerType", string("Tuner_1_0"))); + + return true; } virtual string GetHTTPUri(const string& uri, const string& currentIP){ - if(uri.find(GetRootContainer(), 0) != 0){ - isyslog("VdrProvider\tUri does not contain the root."); - return string(); - } + if(!IsRootContainer(uri)) return string(); int port = 3000; stringstream ss; - ss << "http://" << currentIP << ":" << port << "/TS/" << uri.substr(6); + ss << "http://" << currentIP << ":" << port + << "/" + << "EXT;" + << "PROG=cat;" + << "DLNA_contentFeatures=DLNA.ORG_PN=MPEG_TS_SD_EU_ISO+DLNA.ORG_OP=00+DLNA.ORG_CI=0+DLNA.ORG_FLAGS=ED100000000000000000000000000000" + << "/" + << uri.substr(6); return ss.str(); } virtual void Action(){ + dsyslog("VdrProvider\tStarting vdrProvider thread"); + + const cSchedules* Schedules; + long now = 0; + bool modified = false; + while(Running()){ + now = time(NULL); + + if(!Channels.BeingEdited() && Channels.Modified() > 0){ + OnContainerUpdate(GetRootContainer(), now); + modified = true; + } + + { // Reduce Scope of Schedules lock. + cSchedulesLock lock; + Schedules = cSchedules::Schedules(lock); + // iterate over all the schedules, find those, which were modified and tell + // it to the media manager + for(cSchedule* Schedule = Schedules->First(); Schedule; Schedule = Schedules->Next(Schedule)) + { + if(Schedule->Modified() > lastModified && Schedule->PresentSeenWithin(30)){ + OnContainerUpdate(GetRootContainer(), now, *Schedule->ChannelID().ToString()); + modified = true; + } + } + } + + if(modified){ + modified = false; + lastModified = now; + } + + sleep(2); + } + + dsyslog("VdrProvider\tStopping vdrProvider thread"); } }; |