1 /*****************************************************************
3 | Platinum - AV Media Server Device
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
7 | http://www.plutinosoft.com
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 | GNU General Public License for more details.
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
33 ****************************************************************/
35 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
39 #include "PltMediaServer.h"
40 #include "PltMediaItem.h"
41 #include "PltService.h"
42 #include "PltTaskManager.h"
43 #include "PltHttpServer.h"
46 NPT_SET_LOCAL_LOGGER("platinum.media.server")
48 /*----------------------------------------------------------------------
50 +---------------------------------------------------------------------*/
51 extern NPT_UInt8 MS_ConnectionManagerSCPD
[];
52 extern NPT_UInt8 MS_ContentDirectorywSearchSCPD
[];
54 const char* BrowseFlagsStr
[] = {
56 "BrowseDirectChildren"
59 /*----------------------------------------------------------------------
60 | PLT_MediaServer::PLT_MediaServer
61 +---------------------------------------------------------------------*/
62 PLT_MediaServer::PLT_MediaServer(const char* friendly_name
,
63 bool show_ip
/* = false */,
64 const char* uuid
/* = NULL */,
65 NPT_UInt16 port
/* = 0 */,
66 bool port_rebind
/* = false */) :
67 PLT_DeviceHost("/DeviceDescription.xml",
69 "urn:schemas-upnp-org:device:MediaServer:1",
76 m_ModelDescription
= "Plutinosoft AV Media Server Device";
77 m_ModelName
= "AV Media Server Device";
78 m_ModelURL
= "http://www.plutinosoft.com/platinum";
79 m_DlnaDoc
= "DMS-1.50";
82 /*----------------------------------------------------------------------
83 | PLT_MediaServer::~PLT_MediaServer
84 +---------------------------------------------------------------------*/
85 PLT_MediaServer::~PLT_MediaServer()
89 /*----------------------------------------------------------------------
90 | PLT_MediaServer::SetupServices
91 +---------------------------------------------------------------------*/
93 PLT_MediaServer::SetupServices()
95 NPT_Reference
<PLT_Service
> service
;
98 service
= new PLT_Service(
100 "urn:schemas-upnp-org:service:ContentDirectory:1",
101 "urn:upnp-org:serviceId:ContentDirectory",
103 NPT_CHECK_FATAL(service
->SetSCPDXML((const char*) MS_ContentDirectorywSearchSCPD
));
104 NPT_CHECK_FATAL(AddService(service
.AsPointer()));
106 service
->SetStateVariable("ContainerUpdateIDs", "");
107 service
->SetStateVariableRate("ContainerUpdateIDs", NPT_TimeInterval(2.));
108 service
->SetStateVariable("SystemUpdateID", "0");
109 service
->SetStateVariableRate("SystemUpdateID", NPT_TimeInterval(2.));
110 service
->SetStateVariable("SearchCapability", "@id,@refID,dc:title,upnp:class,upnp:genre,upnp:artist,upnp:author,upnp:author@role,upnp:album,dc:creator,res@size,res@duration,res@protocolInfo,res@protection,dc:publisher,dc:language,upnp:originalTrackNumber,dc:date,upnp:producer,upnp:rating,upnp:actor,upnp:director,upnp:toc,dc:description,microsoft:userRatingInStars,microsoft:userEffectiveRatingInStars,microsoft:userRating,microsoft:userEffectiveRating,microsoft:serviceProvider,microsoft:artistAlbumArtist,microsoft:artistPerformer,microsoft:artistConductor,microsoft:authorComposer,microsoft:authorOriginalLyricist,microsoft:authorWriter,upnp:userAnnotation,upnp:channelName,upnp:longDescription,upnp:programTitle");
111 service
->SetStateVariable("SortCapability", "dc:title,upnp:genre,upnp:album,dc:creator,res@size,res@duration,res@bitrate,dc:publisher,dc:language,upnp:originalTrackNumber,dc:date,upnp:producer,upnp:rating,upnp:actor,upnp:director,upnp:toc,dc:description,microsoft:year,microsoft:userRatingInStars,microsoft:userEffectiveRatingInStars,microsoft:userRating,microsoft:userEffectiveRating,microsoft:serviceProvider,microsoft:artistAlbumArtist,microsoft:artistPerformer,microsoft:artistConductor,microsoft:authorComposer,microsoft:authorOriginalLyricist,microsoft:authorWriter,microsoft:sourceUrl,upnp:userAnnotation,upnp:channelName,upnp:longDescription,upnp:programTitle");
118 service
= new PLT_Service(
120 "urn:schemas-upnp-org:service:ConnectionManager:1",
121 "urn:upnp-org:serviceId:ConnectionManager",
122 "ConnectionManager");
123 NPT_CHECK_FATAL(service
->SetSCPDXML((const char*) MS_ConnectionManagerSCPD
));
124 NPT_CHECK_FATAL(AddService(service
.AsPointer()));
126 service
->SetStateVariable("CurrentConnectionIDs", "0");
127 service
->SetStateVariable("SinkProtocolInfo", "");
128 service
->SetStateVariable("SourceProtocolInfo", "http-get:*:*:*");
137 /*----------------------------------------------------------------------
138 | PLT_MediaServer::UpdateSystemUpdateID
139 +---------------------------------------------------------------------*/
141 PLT_MediaServer::UpdateSystemUpdateID(NPT_UInt32 update
)
143 NPT_COMPILER_UNUSED(update
);
146 /*----------------------------------------------------------------------
147 | PLT_MediaServer::UpdateContainerUpdateID
148 +---------------------------------------------------------------------*/
149 void PLT_MediaServer::UpdateContainerUpdateID(const char* id
, NPT_UInt32 update
)
151 NPT_COMPILER_UNUSED(id
);
152 NPT_COMPILER_UNUSED(update
);
155 /*----------------------------------------------------------------------
156 | PLT_MediaServer::OnAction
157 +---------------------------------------------------------------------*/
159 PLT_MediaServer::OnAction(PLT_ActionReference
& action
,
160 const PLT_HttpRequestContext
& context
)
162 /* parse the action name */
163 NPT_String name
= action
->GetActionDesc().GetName();
166 if (name
.Compare("Browse", true) == 0) {
167 return OnBrowse(action
, context
);
169 if (name
.Compare("Search", true) == 0) {
170 return OnSearch(action
, context
);
172 if (name
.Compare("UpdateObject", true) == 0) {
173 return OnUpdate(action
, context
);
175 if (name
.Compare("GetSystemUpdateID", true) == 0) {
176 return OnGetSystemUpdateID(action
, context
);
178 if (name
.Compare("GetSortCapabilities", true) == 0) {
179 return OnGetSortCapabilities(action
, context
);
181 if (name
.Compare("GetSearchCapabilities", true) == 0) {
182 return OnGetSearchCapabilities(action
, context
);
185 // ConnectionMananger
186 if (name
.Compare("GetCurrentConnectionIDs", true) == 0) {
187 return OnGetCurrentConnectionIDs(action
, context
);
189 if (name
.Compare("GetProtocolInfo", true) == 0) {
190 return OnGetProtocolInfo(action
, context
);
192 if (name
.Compare("GetCurrentConnectionInfo", true) == 0) {
193 return OnGetCurrentConnectionInfo(action
, context
);
196 action
->SetError(401,"No Such Action.");
200 /*----------------------------------------------------------------------
201 | PLT_FileMediaServer::ProcessHttpGetRequest
202 +---------------------------------------------------------------------*/
204 PLT_MediaServer::ProcessHttpGetRequest(NPT_HttpRequest
& request
,
205 const NPT_HttpRequestContext
& context
,
206 NPT_HttpResponse
& response
)
208 /* Try to handle file request */
209 if (m_Delegate
) return m_Delegate
->ProcessFileRequest(request
, context
, response
);
211 return NPT_ERROR_NO_SUCH_ITEM
;
214 /*----------------------------------------------------------------------
215 | PLT_MediaServer::OnGetCurrentConnectionIDs
216 +---------------------------------------------------------------------*/
218 PLT_MediaServer::OnGetCurrentConnectionIDs(PLT_ActionReference
& action
,
219 const PLT_HttpRequestContext
& context
)
221 NPT_COMPILER_UNUSED(context
);
223 return action
->SetArgumentsOutFromStateVariable();
226 /*----------------------------------------------------------------------
227 | PLT_MediaServer::OnGetProtocolInfo
228 +---------------------------------------------------------------------*/
230 PLT_MediaServer::OnGetProtocolInfo(PLT_ActionReference
& action
,
231 const PLT_HttpRequestContext
& context
)
233 NPT_COMPILER_UNUSED(context
);
235 return action
->SetArgumentsOutFromStateVariable();
238 /*----------------------------------------------------------------------
239 | PLT_MediaServer::OnGetCurrentConnectionInfo
240 +---------------------------------------------------------------------*/
242 PLT_MediaServer::OnGetCurrentConnectionInfo(PLT_ActionReference
& action
,
243 const PLT_HttpRequestContext
& context
)
245 NPT_COMPILER_UNUSED(context
);
247 if (NPT_FAILED(action
->VerifyArgumentValue("ConnectionID", "0"))) {
248 action
->SetError(706,"No Such Connection.");
252 if (NPT_FAILED(action
->SetArgumentValue("RcsID", "-1"))){
255 if (NPT_FAILED(action
->SetArgumentValue("AVTransportID", "-1"))) {
258 if (NPT_FAILED(action
->SetArgumentValue("ProtocolInfo", "http-get:*:*:*"))) {
261 if (NPT_FAILED(action
->SetArgumentValue("PeerConnectionManager", "/"))) {
264 if (NPT_FAILED(action
->SetArgumentValue("PeerConnectionID", "-1"))) {
267 if (NPT_FAILED(action
->SetArgumentValue("Direction", "Output"))) {
270 if (NPT_FAILED(action
->SetArgumentValue("Status", "Unknown"))) {
277 /*----------------------------------------------------------------------
278 | PLT_MediaServer::OnGetSortCapabilities
279 +---------------------------------------------------------------------*/
281 PLT_MediaServer::OnGetSortCapabilities(PLT_ActionReference
& action
,
282 const PLT_HttpRequestContext
& context
)
284 NPT_COMPILER_UNUSED(context
);
286 return action
->SetArgumentsOutFromStateVariable();
289 /*----------------------------------------------------------------------
290 | PLT_MediaServer::OnGetSearchCapabilities
291 +---------------------------------------------------------------------*/
293 PLT_MediaServer::OnGetSearchCapabilities(PLT_ActionReference
& action
,
294 const PLT_HttpRequestContext
& context
)
296 NPT_COMPILER_UNUSED(context
);
298 return action
->SetArgumentsOutFromStateVariable();
301 /*----------------------------------------------------------------------
302 | PLT_MediaServer::OnGetSystemUpdateID
303 +---------------------------------------------------------------------*/
305 PLT_MediaServer::OnGetSystemUpdateID(PLT_ActionReference
& action
,
306 const PLT_HttpRequestContext
& context
)
308 NPT_COMPILER_UNUSED(context
);
310 return action
->SetArgumentsOutFromStateVariable();
313 /*----------------------------------------------------------------------
314 | PLT_MediaServer::ParseBrowseFlag
315 +---------------------------------------------------------------------*/
317 PLT_MediaServer::ParseBrowseFlag(const char* str
, BrowseFlags
& flag
)
319 if (NPT_String::Compare(str
, BrowseFlagsStr
[0], true) == 0) {
320 flag
= BROWSEMETADATA
;
323 if (NPT_String::Compare(str
, BrowseFlagsStr
[1], true) == 0) {
324 flag
= BROWSEDIRECTCHILDREN
;
330 /*----------------------------------------------------------------------
331 | PLT_MediaServer::ParseSort
332 +---------------------------------------------------------------------*/
334 PLT_MediaServer::ParseSort(const NPT_String
& sort
, NPT_List
<NPT_String
>& list
)
336 // reset output params first
340 if (sort
.GetLength() == 0 || sort
== "*") return NPT_SUCCESS
;
342 list
= sort
.Split(",");
344 // verify each property has a namespace
345 NPT_List
<NPT_String
>::Iterator property
= list
.GetFirstItem();
347 NPT_List
<NPT_String
> parsed_property
= (*property
).Split(":");
348 if (parsed_property
.GetItemCount() != 2)
349 parsed_property
= (*property
).Split("@");
350 if (parsed_property
.GetItemCount() != 2 ||
351 (!(*property
).StartsWith("-") && !(*property
).StartsWith("+"))) {
352 NPT_LOG_WARNING_1("Invalid SortCriteria property %s", (*property
).GetChars());
361 /*----------------------------------------------------------------------
362 | PLT_MediaServer::ParseTagList
363 +---------------------------------------------------------------------*/
365 PLT_MediaServer::ParseTagList(const NPT_String
& updates
, NPT_Map
<NPT_String
,NPT_String
>& tags
)
367 // reset output params first
370 NPT_List
<NPT_String
> split
= updates
.Split(",");
371 NPT_XmlNode
* node
= NULL
;
372 NPT_XmlElementNode
* didl_partial
= NULL
;
373 NPT_XmlParser parser
;
375 // as these are single name value pairs, separated by commas we wrap in a tag
376 // to create a valid tree
377 NPT_String
xml("<TagValueList>");
378 for (NPT_List
<NPT_String
>::Iterator entry
= split
.GetFirstItem(); entry
; entry
++) {
379 NPT_String
& element
= (*entry
);
380 if (element
.IsEmpty())
381 xml
.Append("<empty>empty</empty>");
385 xml
.Append("</TagValueList>");
387 NPT_LOG_FINE("Parsing TagList...");
388 NPT_CHECK_LABEL_SEVERE(parser
.Parse(xml
, node
), cleanup
);
389 if (!node
|| !node
->AsElementNode()) {
390 NPT_LOG_SEVERE("Invalid node type");
394 didl_partial
= node
->AsElementNode();
395 if (didl_partial
->GetTag().Compare("TagValueList", true)) {
396 NPT_LOG_SEVERE("Invalid node tag");
400 for (NPT_List
<NPT_XmlNode
*>::Iterator children
= didl_partial
->GetChildren().GetFirstItem(); children
; children
++) {
401 NPT_XmlElementNode
* child
= (*children
)->AsElementNode();
402 if (!child
) continue;
403 const NPT_String
*txt
= child
->GetText();
404 tags
[child
->GetTag()] = txt
? txt
->GetChars() : "";
410 if (node
) delete node
;
415 /*----------------------------------------------------------------------
416 | PLT_MediaServer::OnBrowse
417 +---------------------------------------------------------------------*/
419 PLT_MediaServer::OnBrowse(PLT_ActionReference
& action
,
420 const PLT_HttpRequestContext
& context
)
423 NPT_String object_id
;
424 NPT_String browse_flag_val
;
429 NPT_List
<NPT_String
> sort_list
;
431 if (NPT_FAILED(action
->GetArgumentValue("ObjectId", object_id
)) ||
432 NPT_FAILED(action
->GetArgumentValue("BrowseFlag", browse_flag_val
)) ||
433 NPT_FAILED(action
->GetArgumentValue("Filter", filter
)) ||
434 NPT_FAILED(action
->GetArgumentValue("StartingIndex", start
)) ||
435 NPT_FAILED(action
->GetArgumentValue("RequestedCount", count
)) ||
436 NPT_FAILED(action
->GetArgumentValue("SortCriteria", sort
))) {
437 NPT_LOG_WARNING("Missing arguments");
438 action
->SetError(402, "Invalid args");
444 if (NPT_FAILED(ParseBrowseFlag(browse_flag_val
, flag
))) {
446 NPT_LOG_WARNING_1("BrowseFlag value not allowed (%s)", (const char*)browse_flag_val
);
447 action
->SetError(402, "Invalid args");
451 /* convert index and counts to int */
452 NPT_UInt32 starting_index
, requested_count
;
453 if (NPT_FAILED(start
.ToInteger(starting_index
)) ||
454 NPT_FAILED(count
.ToInteger(requested_count
)) ||
455 PLT_Didl::ConvertFilterToMask(filter
) == 0) {
456 NPT_LOG_WARNING_3("Invalid arguments (%s, %s, %s)",
457 start
.GetChars(), count
.GetChars(), filter
.GetChars());
458 action
->SetError(402, "Invalid args");
462 /* parse sort criteria for validation */
463 if (NPT_FAILED(ParseSort(sort
, sort_list
))) {
464 NPT_LOG_WARNING_1("Unsupported or invalid sort criteria error (%s)",
466 action
->SetError(709, "Unsupported or invalid sort criteria error");
470 NPT_LOG_FINE_6("Processing %s from %s with id=\"%s\", filter=\"%s\", start=%d, count=%d",
471 (const char*)browse_flag_val
,
472 (const char*)context
.GetRemoteAddress().GetIpAddress().ToString(),
473 (const char*)object_id
,
478 /* Invoke the browse function */
479 if (flag
== BROWSEMETADATA
) {
480 res
= OnBrowseMetadata(
489 res
= OnBrowseDirectChildren(
499 if (NPT_FAILED(res
) && (action
->GetErrorCode() == 0)) {
500 action
->SetError(800, "Internal error");
506 /*----------------------------------------------------------------------
507 | PLT_MediaServer::OnSearch
508 +---------------------------------------------------------------------*/
510 PLT_MediaServer::OnSearch(PLT_ActionReference
& action
,
511 const PLT_HttpRequestContext
& context
)
513 NPT_COMPILER_UNUSED(context
);
516 NPT_String container_id
;
522 NPT_List
<NPT_String
> sort_list
;
524 if (NPT_FAILED(action
->GetArgumentValue("ContainerId", container_id
)) ||
525 NPT_FAILED(action
->GetArgumentValue("SearchCriteria", search
)) ||
526 NPT_FAILED(action
->GetArgumentValue("Filter", filter
)) ||
527 NPT_FAILED(action
->GetArgumentValue("StartingIndex", start
)) ||
528 NPT_FAILED(action
->GetArgumentValue("RequestedCount", count
)) ||
529 NPT_FAILED(action
->GetArgumentValue("SortCriteria", sort
))) {
530 NPT_LOG_WARNING("Missing arguments");
531 action
->SetError(402, "Invalid args");
535 /* convert index and counts to int */
536 NPT_UInt32 starting_index
, requested_count
;
537 if (NPT_FAILED(start
.ToInteger(starting_index
)) ||
538 NPT_FAILED(count
.ToInteger(requested_count
))) {
539 NPT_LOG_WARNING_2("Invalid arguments (%s, %s)",
540 start
.GetChars(), count
.GetChars());
541 action
->SetError(402, "Invalid args");
545 /* parse sort criteria */
546 if (NPT_FAILED(ParseSort(sort
, sort_list
))) {
547 NPT_LOG_WARNING_1("Unsupported or invalid sort criteria error (%s)",
549 action
->SetError(709, "Unsupported or invalid sort criteria error");
553 NPT_LOG_INFO_5("Processing Search from %s with id=\"%s\", search=\"%s\", start=%d, count=%d",
554 (const char*)context
.GetRemoteAddress().GetIpAddress().ToString(),
555 (const char*)container_id
,
560 if (search
.IsEmpty() || search
== "*") {
561 res
= OnBrowseDirectChildren(
570 res
= OnSearchContainer(
581 if (NPT_FAILED(res
) && (action
->GetErrorCode() == 0)) {
582 action
->SetError(800, "Internal error");
588 /*----------------------------------------------------------------------
589 | PLT_MediaServer::OnUpdate
590 +---------------------------------------------------------------------*/
592 PLT_MediaServer::OnUpdate(PLT_ActionReference
& action
,
593 const PLT_HttpRequestContext
& context
)
596 return NPT_ERROR_NOT_IMPLEMENTED
;
599 const char* msg
= NULL
;
601 NPT_String object_id
, current_xml
, new_xml
;
602 NPT_Map
<NPT_String
,NPT_String
> curr_values
;
603 NPT_Map
<NPT_String
,NPT_String
> new_values
;
605 NPT_CHECK_LABEL(action
->GetArgumentValue("ObjectID", object_id
), args
);
606 NPT_CHECK_LABEL(object_id
.IsEmpty(),args
);
607 NPT_CHECK_LABEL(action
->GetArgumentValue("CurrentTagValue", current_xml
), args
);
608 NPT_CHECK_LABEL(action
->GetArgumentValue("NewTagValue", new_xml
), args
);
610 if (NPT_FAILED(ParseTagList(current_xml
, curr_values
))) {
612 msg
= "Invalid currentTagvalue";
615 if (NPT_FAILED(ParseTagList(new_xml
, new_values
))) {
617 msg
= "Invalid newTagValue";
621 if (curr_values
.GetEntryCount() != new_values
.GetEntryCount()) {
623 msg
= "Paramater mismatch";
627 return m_Delegate
->OnUpdateObject(action
, object_id
, curr_values
, new_values
, context
);
631 msg
= "Invalid args";
634 NPT_LOG_WARNING_1("%s", msg
);
635 action
->SetError(err
, msg
);
639 /*----------------------------------------------------------------------
640 | PLT_MediaServer::OnBrowseMetadata
641 +---------------------------------------------------------------------*/
643 PLT_MediaServer::OnBrowseMetadata(PLT_ActionReference
& action
,
644 const char* object_id
,
646 NPT_UInt32 starting_index
,
647 NPT_UInt32 requested_count
,
648 const char* sort_criteria
,
649 const PLT_HttpRequestContext
& context
)
652 return m_Delegate
->OnBrowseMetadata(action
,
660 return NPT_ERROR_NOT_IMPLEMENTED
;
663 /*----------------------------------------------------------------------
664 | PLT_MediaServer::OnBrowseDirectChildren
665 +---------------------------------------------------------------------*/
667 PLT_MediaServer::OnBrowseDirectChildren(PLT_ActionReference
& action
,
668 const char* object_id
,
670 NPT_UInt32 starting_index
,
671 NPT_UInt32 requested_count
,
672 const char* sort_criteria
,
673 const PLT_HttpRequestContext
& context
)
676 return m_Delegate
->OnBrowseDirectChildren(action
,
684 return NPT_ERROR_NOT_IMPLEMENTED
;
687 /*----------------------------------------------------------------------
688 | PLT_MediaServer::OnSearchContainer
689 +---------------------------------------------------------------------*/
691 PLT_MediaServer::OnSearchContainer(PLT_ActionReference
& action
,
692 const char* object_id
,
693 const char* search_criteria
,
695 NPT_UInt32 starting_index
,
696 NPT_UInt32 requested_count
,
697 const char* sort_criteria
,
698 const PLT_HttpRequestContext
& context
)
701 return m_Delegate
->OnSearchContainer(action
,
710 return NPT_ERROR_NOT_IMPLEMENTED
;