diff options
Diffstat (limited to 'upnpcomponents')
-rw-r--r-- | upnpcomponents/connectionmanager.cpp | 393 | ||||
-rw-r--r-- | upnpcomponents/connectionmanager.h | 67 | ||||
-rw-r--r-- | upnpcomponents/contentdirectory.cpp | 306 | ||||
-rw-r--r-- | upnpcomponents/contentdirectory.h | 38 | ||||
-rw-r--r-- | upnpcomponents/dlna.cpp | 235 | ||||
-rw-r--r-- | upnpcomponents/dlna.h | 64 | ||||
-rw-r--r-- | upnpcomponents/upnpservice.cpp | 118 | ||||
-rw-r--r-- | upnpcomponents/upnpservice.h | 27 | ||||
-rw-r--r-- | upnpcomponents/upnpwebserver.cpp | 335 | ||||
-rw-r--r-- | upnpcomponents/upnpwebserver.h | 123 |
10 files changed, 1706 insertions, 0 deletions
diff --git a/upnpcomponents/connectionmanager.cpp b/upnpcomponents/connectionmanager.cpp new file mode 100644 index 0000000..b2c7149 --- /dev/null +++ b/upnpcomponents/connectionmanager.cpp @@ -0,0 +1,393 @@ +/* + * File: connectionmanager.cpp + * Author: savop + * + * Created on 21. August 2009, 18:35 + */ + +#include "upnpservice.h" +#include <string.h> +#include <upnp/ixml.h> +#include <upnp/upnptools.h> +#include <vdr/tools.h> +#include "connectionmanager.h" +#include "dlna.h" + +cVirtualConnection::cVirtualConnection() : mRcsID(-1) {} + +cConnectionManager::cConnectionManager(UpnpDevice_Handle DeviceHandle) : cUpnpService(DeviceHandle) { + this->mVirtualConnections = new cList<cVirtualConnection>; + this->mDefaultConnection = this->createVirtualConnection(); + this->mSupportedProtocols = cDlna::getInstance()->getSupportedProtocols(); +} + +cConnectionManager::~cConnectionManager() { + delete this->mDefaultConnection; + delete this->mVirtualConnections; +} + +int cConnectionManager::subscribe(Upnp_Subscription_Request* Request){ + IXML_Document* PropertySet = NULL; + /* The protocol infos which this server supports */ + UpnpAddToPropertySet(&PropertySet, "SourceProtocolInfo", this->mSupportedProtocols); + /* Not set, this field is only used by Media Renderers */ + UpnpAddToPropertySet(&PropertySet, "SinkProtocolInfo", ""); + /* The current connection IDs of all virtual connections */ + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return UPNP_E_INTERNAL_ERROR; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + // Accept subscription + int ret = UpnpAcceptSubscriptionExt(this->mDeviceHandle, Request->UDN, Request->ServiceId, PropertySet, Request->Sid); + + if(ret != UPNP_E_SUCCESS){ + ERROR("Subscription failed (Error code: %d)", ret); + } + + ixmlDocument_free(PropertySet); + return ret; +} + +int cConnectionManager::execute(Upnp_Action_Request* Request){ + if (Request == NULL) { + ERROR("CMS Action Handler - request is null"); + return UPNP_E_BAD_REQUEST; + } + + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETPROTOCOLINFO)) + return this->getProtocolInfo(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETCURRENTCONNECTIONIDS)) + return this->getCurrentConnectionIDs(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_GETCURRENTCONNECTIONINFO)) + return this->getCurrentConnectionInfo(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_PREPAREFORCONNECTION)) + return this->prepareForConnection(Request); + if(!strcmp(Request->ActionName, UPNP_CMS_ACTION_CONNECTIONCOMPLETE)) + return this->connectionComplete(Request); + + return UPNP_E_BAD_REQUEST; +} + +int cConnectionManager::getProtocolInfo(Upnp_Action_Request* Request){ + MESSAGE("Protocol info requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Source>%s</Source> \ + <Sink></Sink> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + *this->mSupportedProtocols, + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + return Request->ErrCode; +} + +int cConnectionManager::getCurrentConnectionIDs(Upnp_Action_Request* Request){ + MESSAGE("Current connection IDs requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + cString Result; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + Request->ErrCode = UPNP_E_INTERNAL_ERROR; + return Request->ErrCode; + } + Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <ConnectionIDs>%s</ConnectionIDs> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + IDs, + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + return Request->ErrCode; +} + +int cConnectionManager::getCurrentConnectionInfo(Upnp_Action_Request* Request){ + MESSAGE("Current connection info requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + int ConnectionID; + + if(this->parseIntegerValue(Request->ActionRequest, "ConnectionID", &ConnectionID) != 0){ + ERROR("Invalid arguments. ConnectionID missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + cVirtualConnection* Connection; + for(Connection = this->mVirtualConnections->First(); Connection && Connection->mConnectionID != ConnectionID; Connection = this->mVirtualConnections->Next(Connection)){} + + if(Connection){ + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\">\ + <ProtocolInfo>%s</ProtocolInfo>\ + <PeerConnectionManager>%s</PeerConnectionManager>\ + <PeerConnectionID>%d</PeerConnectionID>\ + <Direction>%s</Direction>\ + <RcsID>%d</RcsID>\ + <AVTransportID>%d</AVTransportID>\ + <Status>%s</Status>\ + </u:%sResponse>", + Request->ActionName, + UPNP_CMS_SERVICE_TYPE, + *Connection->mRemoteProtocolInfo, + *Connection->mRemoteConnectionManager, + -1, + cVirtualConnection::getDirectionString(OUTPUT), + Connection->mRcsID, + Connection->mAVTransportID, + cVirtualConnection::getStatusString(Connection->mStatus), + Request->ActionName + ); + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + } + else { + ERROR("No valid connection found with given ID=%d!", ConnectionID); + this->setError(Request, 706); + } + + return Request->ErrCode; + +} + +int cConnectionManager::prepareForConnection(Upnp_Action_Request* Request){ + MESSAGE("Request for a new connection by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + //char* Result = NULL; + char* RemoteProtocolInfo = NULL; + char* PeerConnectionManager = NULL; + int PeerConnectionID = 0; + char* DirectionStr = NULL; + int Direction; + + if(this->parseStringValue(Request->ActionRequest, "RemoteProtocolInfo", &RemoteProtocolInfo) != 0){ + ERROR("Invalid argument RemoteProtocolInfo: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseStringValue(Request->ActionRequest, "PeerConnectionManager", &PeerConnectionManager) != 0){ + ERROR("Invalid argument PeerConnectionManager: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseStringValue(Request->ActionRequest, "Direction", &DirectionStr) != 0 && (Direction = cVirtualConnection::getDirection(DirectionStr)) == -1){ + ERROR("Invalid argument Direction: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + if(this->parseIntegerValue(Request->ActionRequest, "PeerConnectionID", &PeerConnectionID) != 0){ + ERROR("Invalid argument PeerConnectionID: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + + /* TODO: + Create Connection + Notify AVTransport that a new connection was established + Send back the response */ + this->setError(Request, UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED); + return Request->ErrCode; +} + +int cConnectionManager::connectionComplete(Upnp_Action_Request* Request){ + MESSAGE("Request for closing an open connection by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + //char* Result = NULL; + int ConnectionID; + + if(this->parseIntegerValue(Request->ActionRequest, "ConnectionID", &ConnectionID) != 0){ + ERROR("Invalid argument ConnectionID: Missing or wrong"); + this->setError(Request, 402); + return Request->ErrCode; + } + + // TODO: + // Close and clear any open resources + // Close and delete the connection + // Free other resources left + this->setError(Request, UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED); + return Request->ErrCode; +} + +bool cConnectionManager::setProtocolInfo(const char* ProtocolInfo){ + if(strcmp(this->mSupportedProtocols, ProtocolInfo)){ + // ProtocolInfo changed, save and invoke a event notification + this->mSupportedProtocols = ProtocolInfo; + + IXML_Document* PropertySet = NULL; + UpnpAddToPropertySet(&PropertySet, "SourceProtocolInfo", this->mSupportedProtocols); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return false; + } + } + return true; +} + +cVirtualConnection* cConnectionManager::createVirtualConnection(const char* RemoteProtocolInfo, const char* RemoteConnectionManager, int RemoteConnectionID, eDirection Direction){ + static int lastConnectionID = 0; + MESSAGE("Create virtual connection"); + if(lastConnectionID == 2147483647) lastConnectionID = 1; + cVirtualConnection* Connection = new cVirtualConnection; + // AVT is available + Connection->mAVTransportID = 0; + // The ProtocolInfo of the remote device (i.e. Media Renderer) + Connection->mRemoteProtocolInfo = RemoteProtocolInfo; + // The responsible connection manager + Connection->mRemoteConnectionManager = RemoteConnectionManager; + // The virtual connection direction is output + Connection->mDirection = Direction; + // The remote connection ID, -1 says ID is unknown + Connection->mRemoteConnectionID = RemoteConnectionID; + // Connection status, assume that its ok. + Connection->mStatus = OK; + // new assigned ConnectionID + Connection->mConnectionID = lastConnectionID++; + + // Notify the subscribers + IXML_Document* PropertySet = NULL; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return NULL; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return NULL; + } + MESSAGE("Notification of connection creation sent"); + this->mVirtualConnections->Add(Connection); + return Connection; +} + +bool cConnectionManager::destroyVirtualConnection(int ConnectionID){ + if(ConnectionID == 0){ + ERROR("Cannot delete default connection with ID 0!"); + return false; + } + + cVirtualConnection* Connection; + for(Connection = this->mVirtualConnections->First(); Connection && Connection->mConnectionID != ConnectionID; Connection = this->mVirtualConnections->Next(Connection)){} + + if(Connection){ + this->mVirtualConnections->Del(Connection); + // Notify the subscribers + IXML_Document* PropertySet = NULL; + const char* IDs = this->getConnectionIDsCVS(); + if(!IDs){ + return false; + } + UpnpAddToPropertySet(&PropertySet, "CurrentConnectionIDs", IDs); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + ERROR("State change notification failed (Error code: %d)",ret); + return false; + } + return true; + } + ERROR("No connection with ID=%d found!", ConnectionID); + return false; +} + +const char* cConnectionManager::getConnectionIDsCVS(){ + cString IDs; + for(cVirtualConnection* Connection = this->mVirtualConnections->First(); Connection; Connection = this->mVirtualConnections->Next(Connection)){ + IDs = cString::sprintf("%s,%d", (*IDs)?*IDs:"", Connection->mConnectionID); + } + return IDs; +} + +void cConnectionManager::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case 701: + strn0cpy(Request->ErrStr,_("Incompatible protocol info"),LINE_SIZE); + break; + case 702: + strn0cpy(Request->ErrStr,_("Incompatible directions"),LINE_SIZE); + break; + case 703: + strn0cpy(Request->ErrStr,_("Insufficient network resources"),LINE_SIZE); + break; + case 704: + strn0cpy(Request->ErrStr,_("Local restrictions"),LINE_SIZE); + break; + case 705: + strn0cpy(Request->ErrStr,_("Access denied"),LINE_SIZE); + break; + case 706: + strn0cpy(Request->ErrStr,_("Invalid connection reference"),LINE_SIZE); + break; + case 707: + strn0cpy(Request->ErrStr,_("Not in network"),LINE_SIZE); + break; + default: + cUpnpService::setError(Request, Error); + break; + } +} + +const char* cVirtualConnection::getDirectionString(eDirection Direction){ + switch(Direction){ + case INPUT: + return "Input"; + case OUTPUT: + return "Output"; + default: + return NULL; + } +} + +const char* cVirtualConnection::getStatusString(eConnectionStatus Status){ + switch(Status){ + case OK: + return "OK"; + case CONTENT_FORMAT_MISMATCH: + return "ContentFormatMismatch"; + case INSUFFICIENT_BANDWIDTH: + return "InsufficientBandwidth"; + case UNRELIABLE_CHANNEL: + return "UnreliableChannel"; + case UNKNOWN: + return "Unknown"; + default: + return NULL; + } +} + +int cVirtualConnection::getConnectionStatus(const char* eConnectionStatus){ + if(!strcasecmp(eConnectionStatus,"OK")) + return OK; + if(!strcasecmp(eConnectionStatus,"ContentFormatMismatch")) + return CONTENT_FORMAT_MISMATCH; + if(!strcasecmp(eConnectionStatus,"InsufficientBandwidth")) + return INSUFFICIENT_BANDWIDTH; + if(!strcasecmp(eConnectionStatus,"UnreliableChannel")) + return UNRELIABLE_CHANNEL; + if(!strcasecmp(eConnectionStatus,"Unknown")) + return UNKNOWN; + return -1; +} + +int cVirtualConnection::getDirection(const char* Direction){ + if(!strcasecmp(Direction, "Output")) + return OUTPUT; + if(!strcasecmp(Direction, "Input")) + return INPUT; + return -1; +}
\ No newline at end of file diff --git a/upnpcomponents/connectionmanager.h b/upnpcomponents/connectionmanager.h new file mode 100644 index 0000000..202df59 --- /dev/null +++ b/upnpcomponents/connectionmanager.h @@ -0,0 +1,67 @@ +/* + * File: connectionmanager.h + * Author: savop + * + * Created on 21. August 2009, 18:35 + */ + +#ifndef _CONNECTIONMANAGER_H +#define _CONNECTIONMANAGER_H + +#include "upnpservice.h" + +enum eConnectionStatus { + OK, + CONTENT_FORMAT_MISMATCH, + INSUFFICIENT_BANDWIDTH, + UNRELIABLE_CHANNEL, + UNKNOWN +}; + +enum eDirection { + OUTPUT, + INPUT +}; + +class cVirtualConnection : public cListObject { + friend class cConnectionManager; +private: + cString mRemoteProtocolInfo; + cString mRemoteConnectionManager; + eDirection mDirection; + int mRemoteConnectionID; + int mConnectionID; + int mAVTransportID; + const int mRcsID; + eConnectionStatus mStatus; + cVirtualConnection(); + static const char* getStatusString(eConnectionStatus Status); + static const char* getDirectionString(eDirection Direction); + static int getDirection(const char* Direction); + static int getConnectionStatus(const char* ConnectionStatus); +}; + +class cConnectionManager : public cUpnpService { +public: + cConnectionManager(UpnpDevice_Handle DeviceHandle); + virtual ~cConnectionManager(); + virtual int execute(Upnp_Action_Request* Request); + virtual int subscribe(Upnp_Subscription_Request* Request); + bool setProtocolInfo(const char* ProtocolInfo); +private: + virtual void setError(Upnp_Action_Request* Request, int Error); + int getProtocolInfo(Upnp_Action_Request* Request); + int getCurrentConnectionIDs(Upnp_Action_Request* Request); + int getCurrentConnectionInfo(Upnp_Action_Request* Request); + int prepareForConnection(Upnp_Action_Request* Request); + int connectionComplete(Upnp_Action_Request* Request); + cVirtualConnection* createVirtualConnection(const char* RemoteProtocolInfo = NULL, const char* RemoteConnectionManager = NULL, int RemoteConnectionID = -1, eDirection Direction = OUTPUT); + bool destroyVirtualConnection(int ConnectionID); + const char* getConnectionIDsCVS(); + cVirtualConnection* mDefaultConnection; + cList<cVirtualConnection>* mVirtualConnections; + cString mSupportedProtocols; +}; + +#endif /* _CONNECTIONMANAGER_H */ + diff --git a/upnpcomponents/contentdirectory.cpp b/upnpcomponents/contentdirectory.cpp new file mode 100644 index 0000000..a9afdfa --- /dev/null +++ b/upnpcomponents/contentdirectory.cpp @@ -0,0 +1,306 @@ +/* + * File: contentdirectory.cpp + * Author: savop + * + * Created on 21. August 2009, 16:12 + */ + +#include <upnp/ixml.h> +#include <upnp/upnptools.h> +#include "contentdirectory.h" +#include "../common.h" +#include "../misc/util.h" + +cContentDirectory::cContentDirectory(UpnpDevice_Handle DeviceHandle, cMediaDatabase* MediaDatabase) +: cUpnpService(DeviceHandle) { + this->mMediaDatabase = MediaDatabase; +} + +cContentDirectory::~cContentDirectory() {} + +int cContentDirectory::subscribe(Upnp_Subscription_Request* Request){ + IXML_Document* PropertySet = NULL; + + /* The system update ID */ + UpnpAddToPropertySet(&PropertySet, "SystemUpdateID", itoa(this->mMediaDatabase->getSystemUpdateID())); + /* The container update IDs as CSV list */ + UpnpAddToPropertySet(&PropertySet, "ContainerUpdateIDs", this->mMediaDatabase->getContainerUpdateIDs()); + /* The transfer IDs, which are not supported, i.e. empty */ + UpnpAddToPropertySet(&PropertySet, "TransferIDs", ""); + // Accept subscription + int ret = UpnpAcceptSubscriptionExt(this->mDeviceHandle, Request->UDN, Request->ServiceId, PropertySet, Request->Sid); + + if(ret != UPNP_E_SUCCESS){ + ERROR("Subscription failed (Error code: %d)", ret); + } + + ixmlDocument_free(PropertySet); + return ret; +} + +void cContentDirectory::Action(){ + static int Retry = 5; + MESSAGE("Start Content directory thread"); + while(this->Running()){ + IXML_Document* PropertySet = NULL; + UpnpAddToPropertySet(&PropertySet, "SystemUpdateID", itoa(this->mMediaDatabase->getSystemUpdateID())); + int ret = UpnpNotifyExt(this->mDeviceHandle, UPNP_DEVICE_UDN, UPNP_CMS_SERVICE_ID, PropertySet); + ixmlDocument_free(PropertySet); + + if(ret != UPNP_E_SUCCESS){ + Retry--; + ERROR("State change notification failed (Error code: %d)",ret); + ERROR("%d of %d notifications failed", (5-Retry), 5); + } + else { + Retry = 5; + } + if (!Retry){ + ERROR("Maximum retries of notifications reached. Stopping..."); + this->Cancel(); + } + // Sleep 2 seconds + cCondWait::SleepMs(2000); + } +} + +int cContentDirectory::execute(Upnp_Action_Request* Request){ + if (Request == NULL) { + ERROR("CMS Action Handler - request is null"); + return UPNP_E_BAD_REQUEST; + } + + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_BROWSE)) + return this->browse(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SEARCHCAPABILITIES)) + return this->getSearchCapabilities(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SORTCAPABILITIES)) + return this->getSortCapabilities(Request); + if(!strcmp(Request->ActionName, UPNP_CDS_ACTION_SYSTEMUPDATEID)) + return this->getSystemUpdateID(Request); + + return UPNP_E_BAD_REQUEST; +} + + +int cContentDirectory::browse(Upnp_Action_Request* Request){ + MESSAGE("Browse requested by %s.", inet_ntoa(Request->CtrlPtIPAddr)); + + char* ObjectID = NULL; + if(this->parseStringValue(Request->ActionRequest, "ObjectID", &ObjectID)){ + ERROR("Invalid arguments. ObjectID missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* BrowseFlag = NULL; + bool BrowseMetadata = false; + if(this->parseStringValue(Request->ActionRequest, "BrowseFlag", &BrowseFlag)){ + ERROR("Invalid arguments. Browse flag missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + if(!strcasecmp(BrowseFlag, "BrowseMetadata")){ + BrowseMetadata = true; + } + else if(!strcasecmp(BrowseFlag, "BrowseDirectChildren")){ + BrowseMetadata = false; + } + else { + ERROR("Invalid argument. Browse flag invalid"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* Filter = NULL; + if(this->parseStringValue(Request->ActionRequest, "Filter", &Filter)){ + ERROR("Invalid arguments. Filter missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + int StartingIndex = 0; + if(this->parseIntegerValue(Request->ActionRequest, "StartingIndex", &StartingIndex)){ + ERROR("Invalid arguments. Starting index missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + int RequestedCount = 0; + if(this->parseIntegerValue(Request->ActionRequest, "RequestedCount", &RequestedCount)){ + ERROR("Invalid arguments. Requested count missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + char* SortCriteria = NULL; + if(this->parseStringValue(Request->ActionRequest, "SortCriteria", &SortCriteria)){ + ERROR("Invalid arguments. Sort criteria missing or wrong"); + this->setError(Request, UPNP_SOAP_E_INVALID_ARGS); + return Request->ErrCode; + } + + cUPnPResultSet* ResultSet; + + int ret = this->mMediaDatabase->browse(&ResultSet, ObjectID, BrowseMetadata, Filter, StartingIndex, RequestedCount, SortCriteria); + if(ret!=UPNP_E_SUCCESS){ + ERROR("Error while browsing. Code: %d", ret); + this->setError(Request, ret); + return Request->ErrCode; + } + + char* escapedResult = NULL; + escapeXMLCharacters(ResultSet->mResult, &escapedResult); + + if(!escapedResult){ + ERROR("Escaping XML data failed"); + this->setError(Request, UPNP_SOAP_E_ACTION_FAILED); + return Request->ErrCode; + } + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Result>%s</Result> \ + <NumberReturned>%d</NumberReturned> \ + <TotalMatches>%d</TotalMatches> \ + <UpdateID>%d</UpdateID> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + escapedResult, + ResultSet->mNumberReturned, + ResultSet->mTotalMatches, + this->mMediaDatabase->getSystemUpdateID(), + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + free(escapedResult); + + return Request->ErrCode; + +} + +int cContentDirectory::getSystemUpdateID(Upnp_Action_Request* Request){ + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <Id>%d</Id> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + this->mMediaDatabase->getSystemUpdateID(), + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +int cContentDirectory::getSearchCapabilities(Upnp_Action_Request* Request){ + MESSAGE("Sorry, no search capabilities yet"); + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <SearchCaps>%s</SearchCaps> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + UPNP_CDS_SEARCH_CAPABILITIES, + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +int cContentDirectory::getSortCapabilities(Upnp_Action_Request* Request){ + MESSAGE("Sorry, no sort capabilities yet"); + + cString Result = cString::sprintf( + "<u:%sResponse xmlns:u=\"%s\"> \ + <SortCaps>%s</SortCaps> \ + </u:%sResponse>", + Request->ActionName, + UPNP_CDS_SERVICE_TYPE, + UPNP_CDS_SORT_CAPABILITIES, + Request->ActionName + ); + + Request->ActionResult = ixmlParseBuffer(Result); + Request->ErrCode = UPNP_E_SUCCESS; + + return Request->ErrCode; +} + +void cContentDirectory::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case UPNP_CDS_E_BAD_METADATA: + strn0cpy(Request->ErrStr,_("Bad metadata"),LINE_SIZE); + break; + case UPNP_CDS_E_CANT_PROCESS_REQUEST: + strn0cpy(Request->ErrStr,_("Cannot process the request"),LINE_SIZE); + break; + case UPNP_CDS_E_DEST_RESOURCE_ACCESS_DENIED: + strn0cpy(Request->ErrStr,_("Destination resource access denied"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_CURRENT_TAG: + strn0cpy(Request->ErrStr,_("Invalid current tag"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_NEW_TAG: + strn0cpy(Request->ErrStr,_("Invalid new tag"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_SEARCH_CRITERIA: + strn0cpy(Request->ErrStr,_("Invalid or unsupported search criteria"),LINE_SIZE); + break; + case UPNP_CDS_E_INVALID_SORT_CRITERIA: + strn0cpy(Request->ErrStr,_("Invalid or unsupported sort criteria"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_CONTAINER: + strn0cpy(Request->ErrStr,_("No such container"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_DESTINATION_RESOURCE: + strn0cpy(Request->ErrStr,_("No such destination resource"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_FILE_TRANSFER: + strn0cpy(Request->ErrStr,_("No such file transfer"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_OBJECT: + strn0cpy(Request->ErrStr,_("No such objectID"),LINE_SIZE); + break; + case UPNP_CDS_E_NO_SUCH_SOURCE_RESOURCE: + strn0cpy(Request->ErrStr,_("No such source resource"),LINE_SIZE); + break; + case UPNP_CDS_E_PARAMETER_MISMATCH: + strn0cpy(Request->ErrStr,_("Parameter mismatch"),LINE_SIZE); + break; + case UPNP_CDS_E_READ_ONLY_TAG: + strn0cpy(Request->ErrStr,_("Read only tag"),LINE_SIZE); + break; + case UPNP_CDS_E_REQUIRED_TAG: + strn0cpy(Request->ErrStr,_("Required tag"),LINE_SIZE); + break; + case UPNP_CDS_E_RESOURCE_ACCESS_DENIED: + strn0cpy(Request->ErrStr,_("Resource access denied"),LINE_SIZE); + break; + case UPNP_CDS_E_RESTRICTED_OBJECT: + strn0cpy(Request->ErrStr,_("Restricted object"),LINE_SIZE); + break; + case UPNP_CDS_E_RESTRICTED_PARENT: + strn0cpy(Request->ErrStr,_("Restricted parent"),LINE_SIZE); + break; + case UPNP_CDS_E_TRANSFER_BUSY: + strn0cpy(Request->ErrStr,_("Transfer busy"),LINE_SIZE); + break; + default: + cUpnpService::setError(Request, Error); + break; + } + return; +}
\ No newline at end of file diff --git a/upnpcomponents/contentdirectory.h b/upnpcomponents/contentdirectory.h new file mode 100644 index 0000000..a504fdc --- /dev/null +++ b/upnpcomponents/contentdirectory.h @@ -0,0 +1,38 @@ +/* + * File: contentdirectory.h + * Author: savop + * + * Created on 21. August 2009, 16:12 + */ + +#ifndef _CONTENTDIRECTORY_H +#define _CONTENTDIRECTORY_H + +#include <upnp/upnp.h> +#include "upnpservice.h" +#include "../database/metadata.h" + +class cContentDirectory : public cUpnpService, public cThread { +public: + cContentDirectory(UpnpDevice_Handle DeviceHandle, cMediaDatabase* MediaDatabase); + virtual ~cContentDirectory(); + virtual int subscribe(Upnp_Subscription_Request* Request); + virtual int execute(Upnp_Action_Request* Request); + virtual void setError(Upnp_Action_Request* Request, int Error); +private: + cMediaDatabase* mMediaDatabase; + void Action(); + int getSearchCapabilities(Upnp_Action_Request* Request); + int getSortCapabilities(Upnp_Action_Request* Request); + int getSystemUpdateID(Upnp_Action_Request* Request); + int browse(Upnp_Action_Request* Request); +// int search(Upnp_Action_Request* Request); +// int createObject(Upnp_Action_Request* Request); +// int destroyObject(Upnp_Action_Request* Request); +// int updateObject(Upnp_Action_Request* Request); +// int deleteResource(Upnp_Action_Request* Request); +// int createReference(Upnp_Action_Request* Request); +}; + +#endif /* _CONTENTDIRECTORY_H */ + diff --git a/upnpcomponents/dlna.cpp b/upnpcomponents/dlna.cpp new file mode 100644 index 0000000..a68c6db --- /dev/null +++ b/upnpcomponents/dlna.cpp @@ -0,0 +1,235 @@ +/* + * File: dlna.cpp + * Author: savop + * + * Created on 18. April 2009, 23:27 + */ + +#include <stdio.h> +#include <vdr/tools.h> +#include "dlna.h" + +cDlna* cDlna::mInstance = NULL; + +cDlna* cDlna::getInstance(void){ + if(cDlna::mInstance == NULL) + cDlna::mInstance = new cDlna; + + if(cDlna::mInstance != NULL) + return cDlna::mInstance; + else return NULL; +} + +cDlna::cDlna() { + this->mRegisteredProfiles = new cRegisteredProfiles; + this->init(); +} + +cDlna::~cDlna() { + delete this->mRegisteredProfiles; +} + + +void cDlna::init(void){ + this->registerMainProfiles(); +} + +void cDlna::registerProfile(DLNAProfile* Profile, int Op, const char* Ps, int Ci, unsigned int Flags){ + cRegisteredProfile *RegisteredProfile = new cRegisteredProfile(); + RegisteredProfile->Profile = Profile; + RegisteredProfile->Operation = Op; + RegisteredProfile->PlaySpeeds = Ps; + RegisteredProfile->Conversion = Ci; + RegisteredProfile->PrimaryFlags = Flags; + this->mRegisteredProfiles->Add(RegisteredProfile); +} + +void cDlna::registerMainProfiles(){ + this->registerProfile(&DLNA_PROFILE_MPEG2_TS_SD_EU, -1, NULL, -1, DLNA_FLAGS_PLUGIN_SUPPORT); + this->registerProfile(&DLNA_PROFILE_AVC_TS_HD_EU, -1, NULL, -1, DLNA_FLAGS_PLUGIN_SUPPORT); +} + +const char* cDlna::getSupportedProtocols(){ + cString Protocols; + cRegisteredProfile* Profile; + for(Profile = this->mRegisteredProfiles->First(); Profile; Profile = this->mRegisteredProfiles->Next(Profile)){ + Protocols = cString::sprintf("%s%s%s",(*Protocols)?*Protocols:"",(*Protocols)?",":"",this->getRegisteredProtocolInfoString(Profile)); + } + return Protocols; +} + +const char* cDlna::getProtocolInfo(DLNAProfile *Prof){ + cRegisteredProfile* Profile; + for(Profile = this->mRegisteredProfiles->First(); Profile && Profile->Profile != Prof; Profile = this->mRegisteredProfiles->Next(Profile)){} + if(Profile){ + return this->getRegisteredProtocolInfoString(Profile); + } + return NULL; +} + +DLNAProfile* cDlna::getProfileOfChannel(cChannel* Channel){ + if(Channel == NULL) return NULL; + // Switching the video types of the DVB-Stream + switch(Channel->Vtype()){ + case 0x02: + return &DLNA_PROFILE_MPEG2_TS_SD_EU; + case 0x1B: + return &DLNA_PROFILE_AVC_TS_HD_EU; + default: + ERROR("Unknown video type %d for channel %s!", Channel->Vtype(), Channel->Name()); + return NULL; + } +} + +DLNAProfile* cDlna::getProfileOfRecording(cRecording* Recording){ + // Get the data of the first file of the recording + cString File = cString::sprintf(VDR_RECORDFILE_PATTERN_TS, Recording->FileName(), 1); + return this->getProfileOfFile(File); +} + +DLNAProfile* cDlna::getProfileOfFile(cString){ + WARNING("Not yet supported"); + return NULL; +} + +const char* cDlna::getRegisteredProtocolInfoString(cRegisteredProfile *Profile){ + cString DLNA4thField = NULL; + DLNA4thField = cString::sprintf("DLNA.ORG_PN=%s", Profile->Profile->ID); + if(Profile->Operation != -1) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_OP=%d",*DLNA4thField,Profile->Operation); + if(Profile->PlaySpeeds != NULL) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_PS=%s",*DLNA4thField,Profile->PlaySpeeds); + if(Profile->Conversion != -1) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_CI=%d",*DLNA4thField,Profile->Conversion); + if(Profile->PrimaryFlags != 0) + DLNA4thField = cString::sprintf("%s;DLNA.ORG_FLAGS=%.8x%.24x",*DLNA4thField,Profile->PrimaryFlags,0); + + char* Protocol = strdup(cString::sprintf("http-get:*:%s:%s", Profile->Profile->mime, *DLNA4thField)); + return Protocol; +} + +const char* cDlna::getDeviceDescription(const char* URLBase){ + cString description = cString::sprintf( + "<?xml version = \"1.0\" encoding = \"utf-8\"?> \ + <root xmlns=\"%s\" xmlns:%s=\"%s\"> \ + <specVersion> \ + <major>1</major> \ + <minor>0</minor> \ + </specVersion> \ + <URLBase>%s</URLBase> \ + <device> \ + <deviceType>%s</deviceType> \ + <friendlyName>%s</friendlyName> \ + <manufacturer>%s</manufacturer> \ + <manufacturerURL>%s</manufacturerURL> \ + <modelDescription>%s</modelDescription> \ + <modelName>%s</modelName> \ + <modelNumber>%s</modelNumber> \ + <modelURL>%s</modelURL> \ + <serialNumber>%s</serialNumber> \ + <UDN>%s</UDN> \ + <iconList> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + <icon> \ + <mimetype>%s</mimetype> \ + <width>%d</width> \ + <height>%d</height> \ + <depth>%d</depth> \ + <url>%s</url> \ + </icon> \ + </iconList> \ + <presentationURL>%s</presentationURL> \ + <%s:X_DLNADOC>%s</dlna:X_DLNADOC> \ + <serviceList> \ + <service> \ + <serviceType>%s</serviceType> \ + <serviceId>%s</serviceId> \ + <SCPDURL>%s</SCPDURL> \ + <controlURL>%s</controlURL> \ + <eventSubURL>%s</eventSubURL> \ + </service> \ + <service> \ + <serviceType>%s</serviceType> \ + <serviceId>%s</serviceId> \ + <SCPDURL>%s</SCPDURL> \ + <controlURL>%s</controlURL> \ + <eventSubURL>%s</eventSubURL> \ + </service> \ + </serviceList> \ + </device> \ + </root>", + UPNP_XMLNS_UPNP_DEV, // UPnP Device Namespace (2) + UPNP_XMLNS_PREFIX_DLNA, // DLNA Namespace prefix (2) + UPNP_XMLNS_DLNA_DEV, // DLNA Device Namespace (2) + URLBase, // URLBase (IP:PORT) (7) + UPNP_DEVICE_TYPE, // UPnP Device Type (MediaServer:1) (9) + UPNP_DEVICE_FRIENDLY_NAME, // UPnP Device Friendly Name (10) + UPNP_DEVICE_MANUFACTURER, // UPnP Device Manufacturer (11) + UPNP_DEVICE_MANUFACTURER_URL, // UPnP Device Manufacturer URL (12) + UPNP_DEVICE_MODEL_DESCRIPTION, // UPnP Device Model Description (13) + UPNP_DEVICE_MODEL_NAME, // UPnP Device Model Name (14) + UPNP_DEVICE_MODEL_NUMBER, // UPnP Device Model Number (15) + UPNP_DEVICE_MODEL_URL, // UPnP Device Model URL (16) + UPNP_DEVICE_SERIAL_NUMBER, // UPnP Device Serialnumber (17) + UPNP_DEVICE_UDN, // UPnP Device UDN (18) + DLNA_ICON_JPEG_LRG_24.mime, // UPnP Device Large Icon JPEG Mimetype (21) + DLNA_ICON_JPEG_LRG_24.width, // UPnP Device Large Icon Width (22) + DLNA_ICON_JPEG_LRG_24.height, // UPnP Device Large Icon Height (23) + DLNA_ICON_JPEG_LRG_24.bitDepth, // UPnP Device Large Icon Bit Depth (24) + UPNP_DEVICE_ICON_JPEG_LRG, // UPnP Device Large Icon Path (25) + DLNA_ICON_JPEG_SM_24.mime, // UPnP Device Small Icon JPEG Mimetype (28) + DLNA_ICON_JPEG_SM_24.width, // UPnP Device Small Icon Width (29) + DLNA_ICON_JPEG_SM_24.height, // UPnP Device Small Icon Height (30) + DLNA_ICON_JPEG_SM_24.bitDepth, // UPnP Device Small Icon Bit Depth (31) + UPNP_DEVICE_ICON_JPEG_SM, // UPnP Device Small Icon Path (32) + DLNA_ICON_PNG_SM_24A.mime, // UPnP Device Small Icon PNG Mimetype (35) + DLNA_ICON_PNG_SM_24A.width, // UPnP Device Small Icon Width (36) + DLNA_ICON_PNG_SM_24A.height, // UPnP Device Small Icon Height (37) + DLNA_ICON_PNG_SM_24A.bitDepth, // UPnP Device Small Icon Bit Depth (38) + UPNP_DEVICE_ICON_PNG_SM, // UPnP Device Small Icon Path (39) + DLNA_ICON_PNG_LRG_24A.mime, // UPnP Device Large Icon PNG Mimetype (42) + DLNA_ICON_PNG_LRG_24A.width, // UPnP Device Large Icon Width (43) + DLNA_ICON_PNG_LRG_24A.height, // UPnP Device Large Icon Height (44) + DLNA_ICON_PNG_LRG_24A.bitDepth, // UPnP Device Large Icon Bit Depth (45) + UPNP_DEVICE_ICON_PNG_LRG, // UPnP Device Large Icon Path (46) + UPNP_WEB_PRESENTATION_URL, // UPnP Presentation URL (49) + UPNP_XMLNS_PREFIX_DLNA, // DLNA Namespace prefix (50) + DLNA_DEVICE_DMS_1_5, // DLNA Device Type/Version (50) + UPNP_CMS_SERVICE_TYPE, // UPnP CMS Service Type + UPNP_CMS_SERVICE_ID, // UPnP CMS Service ID + UPNP_CMS_SCPD_URL, // UPnP CMS Service Description + UPNP_CMS_CONTROL_URL, // UPnP CMS Control URL + UPNP_CMS_EVENT_URL, // UPnP CMS Event URL + UPNP_CDS_SERVICE_TYPE, // UPnP CDS Service Type + UPNP_CDS_SERVICE_ID, // UPnP CDS Service ID + UPNP_CDS_SCPD_URL, // UPnP CDS Service Description + UPNP_CDS_CONTROL_URL, // UPnP CDS Control URL + UPNP_CDS_EVENT_URL // UPnP CDS Event URL +// UPNP_AVT_SERVICE_TYPE, // UPnP AVT Service Type +// UPNP_AVT_SERVICE_ID, // UPnP AVT Service ID +// UPNP_AVT_SCPD_URL, // UPnP AVT Service Description +// UPNP_AVT_CONTROL_URL, // UPnP AVT Control URL +// UPNP_AVT_EVENT_URL // UPnP AVT Event URL + ); + return description; +}
\ No newline at end of file diff --git a/upnpcomponents/dlna.h b/upnpcomponents/dlna.h new file mode 100644 index 0000000..c05d69a --- /dev/null +++ b/upnpcomponents/dlna.h @@ -0,0 +1,64 @@ +/* + * File: dlna.h + * Author: savop + * + * Created on 18. April 2009, 23:27 + */ + +#ifndef _DLNA_H +#define _DLNA_H + +#include "../common.h" +#include <vdr/channels.h> +#include <vdr/recording.h> + +class cDlna; + +class cRegisteredProfile : public cListObject { + friend class cDlna; +private: + DLNAProfile* Profile; + int Operation; + const char* PlaySpeeds; + int Conversion; + int PrimaryFlags; +public: + cRegisteredProfile(){}; + virtual ~cRegisteredProfile(){}; +}; + +class cRegisteredProfiles : public cList<cRegisteredProfile> { + friend class cDlna; +}; + +/** + * Enable DLNA compliant media transfer + * + * This class enables media transmission with DLNA conformity. Its compliant with + * version 1.5 of the DLNA guidelines. + * + */ +class cDlna { + friend class cUPnPServer; +public: + static cDlna* getInstance(void); + virtual ~cDlna(); + //const char* getProtocolInfo(UPnPObjectID OID); + const char* getDeviceDescription(const char* URLBase); + void registerProfile(DLNAProfile* Profile, int Op = -1, const char* Ps = NULL, int Ci = -1, unsigned int Flags = 0); + void registerMainProfiles(); + const char* getSupportedProtocols(); + const char* getProtocolInfo(DLNAProfile *Prof); + DLNAProfile* getProfileOfChannel(cChannel* Channel); + DLNAProfile* getProfileOfRecording(cRecording* Recording); + DLNAProfile* getProfileOfFile(cString File); +private: + const char* getRegisteredProtocolInfoString(cRegisteredProfile *Profile); + cDlna(); + void init(void); + static cDlna* mInstance; + cRegisteredProfiles* mRegisteredProfiles; +}; + +#endif /* _DLNA_H */ + diff --git a/upnpcomponents/upnpservice.cpp b/upnpcomponents/upnpservice.cpp new file mode 100644 index 0000000..a1d6a47 --- /dev/null +++ b/upnpcomponents/upnpservice.cpp @@ -0,0 +1,118 @@ +/* + * File: upnpservice.cpp + * Author: savop + * + * Created on 21. August 2009, 18:38 + */ + +#include "upnpservice.h" +#include "../common.h" +#include "../misc/util.h" + +cUpnpService::cUpnpService(UpnpDevice_Handle DeviceHandle) { + this->mDeviceHandle = DeviceHandle; +} + +int cUpnpService::parseIntegerValue(IXML_Document* Document, const char* Item, int* Value){ + char* Val = NULL; + int Error = 0; + + Val = ixmlGetFirstDocumentItem(Document, Item, &Error); + + if(Error != 0){ + ERROR("Error while parsing integer value for item=%s", Item); + Error = -1; + } + else if(!Value){ + WARNING("Value %s empty!", Item); + *Value = 0; + } + else { + *Value = atoi(Val); + free(Val); + } + return Error; +} + +int cUpnpService::parseStringValue(IXML_Document* Document, const char* Item, char** Value){ + char* Val = NULL; + int Error = 0; + + Val = ixmlGetFirstDocumentItem(Document, Item, &Error); + + if(Error != 0){ + ERROR("Error while parsing string value for item=%s", Item); + Error = -1; + } + else if(!Val){ + WARNING("Value %s empty!", Item); + *Value = NULL; + } + else { + *Value = strdup(Val); + free(Val); + } + + return Error; +} + +void cUpnpService::setError(Upnp_Action_Request* Request, int Error){ + Request->ErrCode = Error; + switch(Error){ + case UPNP_SOAP_E_INVALID_ACTION: + strn0cpy(Request->ErrStr,_("Invalid action"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_ARGS: + strn0cpy(Request->ErrStr,_("Invalid args"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_VAR: + strn0cpy(Request->ErrStr,_("Invalid var"),LINE_SIZE); + break; + case UPNP_SOAP_E_ACTION_FAILED: + strn0cpy(Request->ErrStr,_("Action failed"),LINE_SIZE); + break; + case UPNP_SOAP_E_ARGUMENT_INVALID: + strn0cpy(Request->ErrStr,_("Argument value invalid"),LINE_SIZE); + break; + case UPNP_SOAP_E_ARGUMENT_OUT_OF_RANGE: + strn0cpy(Request->ErrStr,_("Argument value out of range"),LINE_SIZE); + break; + case UPNP_SOAP_E_ACTION_NOT_IMPLEMENTED: + strn0cpy(Request->ErrStr,_("Optional action not implemented"),LINE_SIZE); + break; + case UPNP_SOAP_E_OUT_OF_MEMORY: + strn0cpy(Request->ErrStr,_("Out of memory"),LINE_SIZE); + break; + case UPNP_SOAP_E_HUMAN_INTERVENTION: + strn0cpy(Request->ErrStr,_("Human intervention required"),LINE_SIZE); + break; + case UPNP_SOAP_E_STRING_TO_LONG: + strn0cpy(Request->ErrStr,_("String argument to long"),LINE_SIZE); + break; + case UPNP_SOAP_E_NOT_AUTHORIZED: + strn0cpy(Request->ErrStr,_("Action not authorized"),LINE_SIZE); + break; + case UPNP_SOAP_E_SIGNATURE_FAILURE: + strn0cpy(Request->ErrStr,_("Signature failure"),LINE_SIZE); + break; + case UPNP_SOAP_E_SIGNATURE_MISSING: + strn0cpy(Request->ErrStr,_("Signature missing"),LINE_SIZE); + break; + case UPNP_SOAP_E_NOT_ENCRYPTED: + strn0cpy(Request->ErrStr,_("Not encrypted"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_SEQUENCE: + strn0cpy(Request->ErrStr,_("Invalid sequence"),LINE_SIZE); + break; + case UPNP_SOAP_E_INVALID_CONTROL_URL: + strn0cpy(Request->ErrStr,_("Invalid control URL"),LINE_SIZE); + break; + case UPNP_SOAP_E_NO_SUCH_SESSION: + strn0cpy(Request->ErrStr,_("No such session"),LINE_SIZE); + break; + case UPNP_SOAP_E_OUT_OF_SYNC: + default: + strn0cpy(Request->ErrStr,_("Unknown error code. Contact the device manufacturer"),LINE_SIZE); + break; + } +}
\ No newline at end of file diff --git a/upnpcomponents/upnpservice.h b/upnpcomponents/upnpservice.h new file mode 100644 index 0000000..c8630b5 --- /dev/null +++ b/upnpcomponents/upnpservice.h @@ -0,0 +1,27 @@ +/* + * File: upnpservice.h + * Author: savop + * + * Created on 21. August 2009, 18:38 + */ + +#ifndef _UPNPSERVICE_H +#define _UPNPSERVICE_H + +#include <upnp/upnp.h> + +class cUpnpService { +public: + cUpnpService(UpnpDevice_Handle DeviceHandle); + virtual ~cUpnpService(){}; + virtual int subscribe(Upnp_Subscription_Request* Request) = 0; + virtual int execute(Upnp_Action_Request* Request) = 0; +protected: + virtual void setError(Upnp_Action_Request* Request, int Error); + int parseIntegerValue(IN IXML_Document* Document, IN const char* Item, OUT int* Value); + int parseStringValue(IN IXML_Document* Document, IN const char* Item, OUT char** Value); + UpnpDevice_Handle mDeviceHandle; +}; + +#endif /* _UPNPSERVICE_H */ + diff --git a/upnpcomponents/upnpwebserver.cpp b/upnpcomponents/upnpwebserver.cpp new file mode 100644 index 0000000..9be3d6a --- /dev/null +++ b/upnpcomponents/upnpwebserver.cpp @@ -0,0 +1,335 @@ +/* + * File: upnpwebserver.cpp + * Author: savop + * + * Created on 30. Mai 2009, 18:13 + */ + +#include <time.h> +#include <vdr/channels.h> +#include <map> +#include <upnp/upnp.h> +#include "upnpwebserver.h" +#include "../server/server.h" +#include "../receiver/livereceiver.h" +#include "../receiver/recplayer.h" +#include "../misc/search.h" + +/* COPIED FROM INTEL UPNP TOOLS */ +/******************************************************************************* + * + * Copyright (c) 2000-2003 Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ +struct File_Info_ +{ + /** The length of the file. A length less than 0 indicates the size + * is unknown, and data will be sent until 0 bytes are returned from + * a read call. */ + off64_t file_length; + + /** The time at which the contents of the file was modified; + * The time system is always local (not GMT). */ + time_t last_modified; + + /** If the file is a directory, {\bf is_directory} contains + * a non-zero value. For a regular file, it should be 0. */ + int is_directory; + + /** If the file or directory is readable, this contains + * a non-zero value. If unreadable, it should be set to 0. */ + int is_readable; + + /** The content type of the file. This string needs to be allocated + * by the caller using {\bf ixmlCloneDOMString}. When finished + * with it, the SDK frees the {\bf DOMString}. */ + + DOMString content_type; + +}; + +struct cWebFileHandle { + cString Filename; + off64_t Size; + cFileHandle* FileHandle; +}; + +/**************************************************** + * + * The web server + * + * Handles the virtual directories and the + * provision of data + * + * Interface between the channels/recordings of the + * VDR and the outer world + * + ****************************************************/ + +cUPnPWebServer::cUPnPWebServer(const char* root) : mRootdir(root) { +} + +cUPnPWebServer::~cUPnPWebServer(){ + delete this->mRootdir; +} + +cUPnPWebServer* cUPnPWebServer::mInstance = NULL; + +UpnpVirtualDirCallbacks cUPnPWebServer::mVirtualDirCallbacks = { + cUPnPWebServer::getInfo, + cUPnPWebServer::open, + cUPnPWebServer::read, + cUPnPWebServer::write, + cUPnPWebServer::seek, + cUPnPWebServer::close +}; + +bool cUPnPWebServer::init(){ + MESSAGE("Initialize callbacks for virtual directories."); + + if(UpnpSetWebServerRootDir(this->mRootdir) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The root directory of the webserver is invalid."); + return false; + } + MESSAGE("Setting up callbacks"); + + if(UpnpSetVirtualDirCallbacks(&cUPnPWebServer::mVirtualDirCallbacks) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The virtual directory callbacks are invalid."); + return false; + } + + if(UpnpIsWebserverEnabled() == FALSE){ + WARNING("The webserver has not been started. For whatever reason..."); + return false; + } + + MESSAGE("Add virtual directories."); + if(UpnpAddVirtualDir(UPNP_DIR_SHARES) == UPNP_E_INVALID_ARGUMENT){ + ERROR("The virtual directory %s is invalid.",UPNP_DIR_SHARES); + return false; + } + return true; +} + +cUPnPWebServer* cUPnPWebServer::getInstance(const char* rootdir){ + if(cUPnPWebServer::mInstance == NULL) + cUPnPWebServer::mInstance = new cUPnPWebServer(rootdir); + + if(cUPnPWebServer::mInstance){ + return cUPnPWebServer::mInstance; + } + else return NULL; +} + +void cUPnPWebServer::free(){ + delete cUPnPWebServer::mInstance; +} + +int cUPnPWebServer::getInfo(const char* filename, File_Info* info){ + MESSAGE("Getting information of file '%s'", filename); + + propertyMap Properties; + int Method; + int Section; + + if(cPathParser::parse(filename, &Section, &Method, &Properties)){ + switch(Section){ + case 0: + switch(Method){ + case UPNP_WEB_METHOD_STREAM: + { + MESSAGE("Stream request"); + propertyMap::iterator It = Properties.find("resId"); + unsigned int ResourceID = 0; + if(It == Properties.end()){ + ERROR("No resourceID for stream request"); + return -1; + } + else { + ResourceID = (unsigned)atoi(It->second); + cUPnPResource* Resource = cUPnPResources::getInstance()->getResource(ResourceID); + if(!Resource){ + ERROR("No such resource with ID (%d)", ResourceID); + return -1; + } + else { + File_Info_ finfo; + + finfo.content_type = ixmlCloneDOMString(Resource->getContentType()); + finfo.file_length = Resource->getFileSize(); + finfo.is_directory = 0; + finfo.is_readable = 1; + finfo.last_modified = Resource->getLastModification(); + memcpy(info, &finfo, sizeof(File_Info_)); + + MESSAGE("==== File info of Resource #%d ====", Resource->getID()); + MESSAGE("Size: %lld", finfo.file_length); + MESSAGE("Dir: %s", finfo.is_directory?"yes":"no"); + MESSAGE("Read: %s", finfo.is_readable?"allowed":"not allowed"); + MESSAGE("Last modified: %s", ctime(&(finfo.last_modified))); + MESSAGE("Content-type: %s", finfo.content_type); + } + } + } + break; + case UPNP_WEB_METHOD_BROWSE: + // break; + case UPNP_WEB_METHOD_SHOW: + // break; + case UPNP_WEB_METHOD_SEARCH: + case UPNP_WEB_METHOD_DOWNLOAD: + default: + ERROR("Unknown or unsupported method ID (%d)", Method); + return -1; + } + break; + default: + ERROR("Unknown or unsupported section ID (%d).", Section); + return -1; + } + } + else { + return -1; + } + + return 0; +} + +UpnpWebFileHandle cUPnPWebServer::open(const char* filename, UpnpOpenFileMode mode){ + MESSAGE("File %s was opened for %s.",filename,mode==UPNP_READ ? "reading" : "writing"); + + propertyMap Properties; + int Method; + int Section; + cWebFileHandle* WebFileHandle = NULL; + + if(cPathParser::parse(filename, &Section, &Method, &Properties)){ + switch(Section){ + case 0: + switch(Method){ + case UPNP_WEB_METHOD_STREAM: + { + MESSAGE("Stream request"); + propertyMap::iterator It = Properties.find("resId"); + unsigned int ResourceID = 0; + if(It == Properties.end()){ + ERROR("No resourceID for stream request"); + return NULL; + } + else { + ResourceID = (unsigned)atoi(It->second); + cUPnPResource* Resource = cUPnPResources::getInstance()->getResource(ResourceID); + if(!Resource){ + ERROR("No such resource with ID (%d)", ResourceID); + return NULL; + } + else { + WebFileHandle = new cWebFileHandle; + WebFileHandle->Filename = Resource->getResource(); + WebFileHandle->Size = Resource->getFileSize(); + switch(Resource->getResourceType()){ + case UPNP_RESOURCE_CHANNEL: + { + char* ChannelID = strtok(strdup(Resource->getResource()),":"); + int AudioID = atoi(strtok(NULL,":")); + MESSAGE("Try to create Receiver for Channel %s with Audio ID %d", ChannelID, AudioID); + cChannel* Channel = Channels.GetByChannelID(tChannelID::FromString(ChannelID)); + if(!Channel){ + ERROR("No such channel with ID %s", ChannelID); + return NULL; + } + cLiveReceiver* Receiver = cLiveReceiver::newInstance(Channel,0); + if(!Receiver){ + ERROR("Unable to tune channel. No available tuners?"); + return NULL; + } + WebFileHandle->FileHandle = Receiver; + } + break; + case UPNP_RESOURCE_RECORDING: + // break; + case UPNP_RESOURCE_FILE: + // break; + case UPNP_RESOURCE_URL: + default: + return NULL; + } + } + } + } + break; + case UPNP_WEB_METHOD_BROWSE: + // break; + case UPNP_WEB_METHOD_SHOW: + // break; + case UPNP_WEB_METHOD_SEARCH: + case UPNP_WEB_METHOD_DOWNLOAD: + default: + ERROR("Unknown or unsupported method ID (%d)", Method); + return NULL; + } + break; + default: + ERROR("Unknown or unsupported section ID (%d).", Section); + return NULL; + } + } + else { + return NULL; + } + MESSAGE("Open the file handle"); + WebFileHandle->FileHandle->open(mode); + return (UpnpWebFileHandle)WebFileHandle; +} + +int cUPnPWebServer::write(UpnpWebFileHandle fh, char* buf, size_t buflen){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Writing to %s", *FileHandle->Filename); + return FileHandle->FileHandle->write(buf, buflen); +} + +int cUPnPWebServer::read(UpnpWebFileHandle fh, char* buf, size_t buflen){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Reading from %s", *FileHandle->Filename); + return FileHandle->FileHandle->read(buf, buflen); +} + +int cUPnPWebServer::seek(UpnpWebFileHandle fh, off_t offset, int origin){ + cWebFileHandle* FileHandle = (cWebFileHandle*)fh; + MESSAGE("Seeking on %s", *FileHandle->Filename); + return FileHandle->FileHandle->seek(offset, origin); +} + +int cUPnPWebServer::close(UpnpWebFileHandle fh){ + cWebFileHandle *FileHandle = (cWebFileHandle *)fh; + MESSAGE("Closing file %s", *FileHandle->Filename); + FileHandle->FileHandle->close(); + delete FileHandle->FileHandle; + delete FileHandle; + return 0; +} diff --git a/upnpcomponents/upnpwebserver.h b/upnpcomponents/upnpwebserver.h new file mode 100644 index 0000000..55ef260 --- /dev/null +++ b/upnpcomponents/upnpwebserver.h @@ -0,0 +1,123 @@ +/* + * File: upnpwebserver.h + * Author: savop + * + * Created on 30. Mai 2009, 18:13 + */ + +#ifndef _UPNPWEBSERVER_H +#define _UPNPWEBSERVER_H + +#include "../common.h" +#include <upnp/upnp.h> + +class cUPnPWebServer { + friend class cUPnPServer; +private: + static cUPnPWebServer *mInstance; + static UpnpVirtualDirCallbacks mVirtualDirCallbacks; + const char* mRootdir; + cUPnPWebServer(const char* root = "/"); +protected: + bool enable(bool enable); + static void free(); +public: + bool init(); + static cUPnPWebServer* getInstance(const char* rootdir = "/"); + virtual ~cUPnPWebServer(); +//}; + + /**************************************************** + * + * The callback functions for the webserver + * + ****************************************************/ + /** + * Retrieve file information + * + * Returns file related information for an virtual directory file + * + * @return 0 on success, -1 otherwise + * @param filename The filename of which the information is gathered + * @param info The File_Info structure with the data + */ + static int getInfo(const char* filename, struct File_Info* info); + /** + * Opens a virtual directory file + * + * Opens a file in a virtual directory with the specified mode. + * + * Possible modes are: + * - UPNP_READ : Opens the file for reading + * - UPNP_WRITE: Opens the file for writing + * + * It returns a file handle to the opened file, NULL otherwise + * + * @return FileHandle to the opened file, NULL otherwise + * @param filename The file to open + * @param mode UPNP_WRITE for writing, UPNP_READ for reading. + */ + static UpnpWebFileHandle open(const char* filename, UpnpOpenFileMode mode); + /** + * Reads from the opened file + * + * Reads <code>buflen</code> bytes from the file and stores the content + * to the buffer + * + * Returns 0 no more bytes read (EOF) + * >0 bytes read from file + * + * @return number of bytes read, 0 on EOF + * @param fh the file handle of the opened file + * @param buf the buffer to write the bytes to + * @param buflen the maximum count of bytes to read + * + */ + static int read(UpnpWebFileHandle fh, char* buf, size_t buflen); + /** + * Writes to the opened file + * + * Writes <code>buflen</code> bytes from the buffer and stores the content + * in the file + * + * Returns >0 bytes wrote to file, maybe less the buflen in case of write + * errors + * + * @return number of bytes read, 0 on EOF + * @param fh the file handle of the opened file + * @param buf the buffer to read the bytes from + * @param buflen the maximum count of bytes to write + * + */ + static int write(UpnpWebFileHandle fh, char* buf, size_t buflen); + /** + * Seek in the file + * + * Seeks in the opened file and sets the file pointer to the specified offset + * + * Returns 0 on success, non-zero value otherwise + * + * @return 0 on success, non-zero value otherwise + * @param fh the file handle of the opened file + * @param offset a negative oder positive value which moves the pointer + * forward or backward + * @param origin SEEK_CUR, SEEK_END or SEEK_SET + * + */ + static int seek(UpnpWebFileHandle fh, off_t offset, int origin); + /** + * Closes the file + * + * closes the opened file + * + * Returns 0 on success, non-zero value otherwise + * + * @return 0 on success, non-zero value otherwise + * @param fh the file handle of the opened file + * + */ + static int close(UpnpWebFileHandle fh); +}; + +#endif /* _UPNPWEBSERVER_H */ + |