Mercurial > projects > hoofbaby
diff deps/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp @ 0:3425707ddbf6
Initial import (hopefully this mercurial stuff works...)
author | fraserofthenight |
---|---|
date | Mon, 06 Jul 2009 08:06:28 -0700 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/deps/Platinum/Source/Devices/MediaServer/PltFileMediaServer.cpp Mon Jul 06 08:06:28 2009 -0700 @@ -0,0 +1,641 @@ +/***************************************************************** +| +| Platinum - File Media Server +| +| Copyright (c) 2004-2008, Plutinosoft, LLC. +| All rights reserved. +| http://www.plutinosoft.com +| +| This program is free software; you can redistribute it and/or +| modify it under the terms of the GNU General Public License +| as published by the Free Software Foundation; either version 2 +| of the License, or (at your option) any later version. +| +| OEMs, ISVs, VARs and other distributors that combine and +| distribute commercially licensed software with Platinum software +| and do not wish to distribute the source code for the commercially +| licensed software under version 2, or (at your option) any later +| version, of the GNU General Public License (the "GPL") must enter +| into a commercial license agreement with Plutinosoft, LLC. +| +| This program is distributed in the hope that it will be useful, +| but WITHOUT ANY WARRANTY; without even the implied warranty of +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +| GNU General Public License for more details. +| +| You should have received a copy of the GNU General Public License +| along with this program; see the file LICENSE.txt. If not, write to +| the Free Software Foundation, Inc., +| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +| http://www.gnu.org/licenses/gpl-2.0.html +| +****************************************************************/ + +/*---------------------------------------------------------------------- +| includes ++---------------------------------------------------------------------*/ +#include "PltUPnP.h" +#include "PltFileMediaServer.h" +#include "PltMediaItem.h" +#include "PltService.h" +#include "PltTaskManager.h" +#include "PltHttpServer.h" +#include "PltDidl.h" +#include "PltMetadataHandler.h" +#include "PltVersion.h" + +NPT_SET_LOCAL_LOGGER("platinum.media.server.file") + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::PLT_FileMediaServer ++---------------------------------------------------------------------*/ +PLT_FileMediaServer::PLT_FileMediaServer(const char* path, + const char* friendly_name, + bool show_ip /* = false */, + const char* uuid /* = NULL */, + NPT_UInt16 port /* = 0 */, + bool port_rebind /* = false */) : + PLT_MediaServer(friendly_name, + show_ip, + uuid, + port, + port_rebind) +{ + /* set up the server root path */ + m_Path = path; + m_Path.TrimRight("/\\"); +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::~PLT_FileMediaServer ++---------------------------------------------------------------------*/ +PLT_FileMediaServer::~PLT_FileMediaServer() +{ +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::AddMetadataHandler ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::AddMetadataHandler(PLT_MetadataHandler* handler) +{ + // make sure we don't have a metadatahandler registered for the same extension +/* PLT_MetadataHandler* prev_handler; + NPT_Result ret = NPT_ContainerFind(m_MetadataHandlers, PLT_MetadataHandlerFinder(handler->GetExtension()), prev_handler); + if (NPT_SUCCEEDED(ret)) { + return NPT_ERROR_INVALID_PARAMETERS; + } */ + + m_MetadataHandlers.Add(handler); + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::SetupDevice ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::SetupDevice() +{ + // FIXME: hack for now: find the first valid non local ip address + // to use in item resources. TODO: we should advertise all ips as + // multiple resources instead. + NPT_List<NPT_String> ips; + PLT_UPnPMessageHelper::GetIPAddresses(ips); + if (ips.GetItemCount() == 0) return NPT_ERROR_INTERNAL; + + // set the base paths for content and album arts + m_FileBaseUri = NPT_HttpUrl(*ips.GetFirstItem(), GetPort(), "/content"); + m_AlbumArtBaseUri = NPT_HttpUrl(*ips.GetFirstItem(), GetPort(), "/albumart"); + + return PLT_MediaServer::SetupDevice(); +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::ProcessHttpRequest ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::ProcessHttpRequest(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse& response) +{ + if (request.GetUrl().GetPath().StartsWith(m_FileBaseUri.GetPath()) || + request.GetUrl().GetPath().StartsWith(m_AlbumArtBaseUri.GetPath())) { + return ProcessFileRequest(request, context, response); + } + + return PLT_MediaServer::ProcessHttpRequest(request, context, response); +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::ProcessGetDescription ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::ProcessGetDescription(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse& response) +{ + NPT_String m_OldModelName = m_ModelName; + NPT_String m_OldModelNumber = m_ModelNumber; + + // change some things based on User-Agent header + NPT_HttpHeader* user_agent = request.GetHeaders().GetHeader(NPT_HTTP_HEADER_USER_AGENT); + if (user_agent && user_agent->GetValue().Find("Sonos", 0, true)>=0) { + // Force "Rhapsody" so that Sonos is happy to find us + m_ModelName = "Rhapsody"; + m_ModelNumber = "3.0"; + + // return modified description + NPT_String doc; + NPT_Result res = GetDescription(doc); + + // reset to old values now + m_ModelName = m_OldModelName; + m_ModelNumber = m_OldModelNumber; + + NPT_CHECK_FATAL(res); + + PLT_HttpHelper::SetBody(response, doc); + PLT_HttpHelper::SetContentType(response, "text/xml"); + + return NPT_SUCCESS; + } + + return PLT_MediaServer::ProcessGetDescription(request, context, response); +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::ExtractResourcePath ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::ExtractResourcePath(const NPT_HttpUrl& url, NPT_String& file_path) +{ + // Extract uri path from url + NPT_String uri_path = NPT_Uri::PercentDecode(url.GetPath()); + + // extract file path from query + NPT_HttpUrlQuery query(url.GetQuery()); + file_path = query.GetField("path"); + if (file_path.GetLength()) return NPT_SUCCESS; + + // hack for XBMC support for 360, we urlencoded the ? to that the 360 doesn't strip out the query + // but then the query ends being parsed as part of the path + int index = uri_path.Find("path="); + if (index>0) { + file_path = uri_path.Right(uri_path.GetLength()-index-5); + return NPT_SUCCESS; + } + + uri_path = url.GetPath(); + if (uri_path.StartsWith(m_FileBaseUri.GetPath(), true)) { + file_path = NPT_Uri::PercentDecode(uri_path.SubString(m_FileBaseUri.GetPath().GetLength())); + return NPT_SUCCESS; + } else if (uri_path.StartsWith(m_AlbumArtBaseUri.GetPath(), true)) { + file_path = NPT_Uri::PercentDecode(uri_path.SubString(m_AlbumArtBaseUri.GetPath().GetLength())); + return NPT_SUCCESS; + } + + return NPT_FAILURE; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::ProcessFileRequest ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::ProcessFileRequest(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse& response) +{ + NPT_LOG_FINE("PLT_FileMediaServer::ProcessFileRequest Received Request:"); + PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request); + + response.GetHeaders().SetHeader("Accept-Ranges", "bytes"); + + if (request.GetMethod().Compare("GET") && request.GetMethod().Compare("HEAD")) { + response.SetStatus(500, "Internal Server Error"); + return NPT_SUCCESS; + } + + // Extract file path from url + NPT_String file_path; + NPT_CHECK_LABEL_WARNING(ExtractResourcePath(request.GetUrl(), file_path), + failure); + + // Serve file now + if (request.GetUrl().GetPath().StartsWith(m_FileBaseUri.GetPath(), true)) { + NPT_CHECK_WARNING(ServeFile(request, context, response, NPT_FilePath::Create(m_Path, file_path))); + return NPT_SUCCESS; + } else if (request.GetUrl().GetPath().StartsWith(m_AlbumArtBaseUri.GetPath(), true)) { + NPT_CHECK_WARNING(OnAlbumArtRequest(response, NPT_FilePath::Create(m_Path, file_path))); + return NPT_SUCCESS; + } + + // fallthrough + +failure: + response.SetStatus(404, "File Not Found"); + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::ServeFile ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::ServeFile(NPT_HttpRequest& request, + const NPT_HttpRequestContext& context, + NPT_HttpResponse& response, + const NPT_String& file_path) +{ + NPT_COMPILER_UNUSED(context); + + NPT_Position start, end; + PLT_HttpHelper::GetRange(request, start, end); + + return PLT_FileServer::ServeFile(response, + file_path, + start, + end, + !request.GetMethod().Compare("HEAD")); +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::OnAlbumArtRequest ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::OnAlbumArtRequest(NPT_HttpResponse& response, + NPT_String file_path) +{ + NPT_LargeSize total_len; + NPT_File file(file_path); + NPT_InputStreamReference stream; + + // prevent hackers from accessing files outside of our root + if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0)) { + goto filenotfound; + } + + if (NPT_FAILED(file.Open(NPT_FILE_OPEN_MODE_READ)) || + NPT_FAILED(file.GetInputStream(stream)) || + NPT_FAILED(stream->GetSize(total_len)) || (total_len == 0)) { + goto filenotfound; + } else { + NPT_String extension = NPT_FilePath::FileExtension(file_path); + if (extension.GetLength() == 0) { + goto filenotfound; + } + + PLT_MetadataHandler* metadataHandler = NULL; + char* caData; + int caDataLen; + NPT_Result ret = NPT_ContainerFind(m_MetadataHandlers, + PLT_MetadataHandlerFinder(extension), + metadataHandler); + if (NPT_FAILED(ret) || metadataHandler == NULL) { + goto filenotfound; + } + // load the metadatahandler and read the cover art + if (NPT_FAILED(metadataHandler->Load(*stream)) || + NPT_FAILED(metadataHandler->GetCoverArtData(caData, caDataLen))) { + goto filenotfound; + } + + PLT_HttpHelper::SetContentType(response, "application/octet-stream"); + PLT_HttpHelper::SetBody(response, caData, caDataLen); + delete caData; + return NPT_SUCCESS; + } + +filenotfound: + response.SetStatus(404, "File Not Found"); + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::OnBrowseMetadata ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::OnBrowseMetadata(PLT_ActionReference& action, + const char* object_id, + const PLT_HttpRequestContext& context) +{ + NPT_String didl; + PLT_MediaObjectReference item; + + /* locate the file from the object ID */ + NPT_String filepath; + if (NPT_FAILED(GetFilePath(object_id, filepath))) { + /* error */ + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowse - ObjectID not found."); + action->SetError(701, "No Such Object."); + return NPT_FAILURE; + } + + item = BuildFromFilePath(filepath, context, true); + + if (item.IsNull()) return NPT_FAILURE; + + NPT_String filter; + NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter)); + + NPT_String tmp; + NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp)); + + /* add didl header and footer */ + didl = didl_header + tmp + didl_footer; + + NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl)); + NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", "1")); + NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", "1")); + + // update ID may be wrong here, it should be the one of the container? + // TODO: We need to keep track of the overall updateID of the CDS + NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1")); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::OnBrowseDirectChildren ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::OnBrowseDirectChildren(PLT_ActionReference& action, + const char* object_id, + const PLT_HttpRequestContext& context) +{ + /* locate the file from the object ID */ + NPT_String dir; + if (NPT_FAILED(GetFilePath(object_id, dir))) { + /* error */ + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowse - ObjectID not found."); + action->SetError(701, "No Such Object."); + return NPT_FAILURE; + } + + /* retrieve the item type */ + NPT_FileInfo info; + NPT_Result res = NPT_File::GetInfo(dir, &info); + if (NPT_FAILED(res)) { + /* Object does not exist */ + NPT_LOG_WARNING_1("PLT_FileMediaServer::OnBrowse - BROWSEDIRECTCHILDREN failed for item %s", dir.GetChars()); + action->SetError(800, "Can't retrieve info " + dir); + return NPT_FAILURE; + } + + if (info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY) { + /* error */ + NPT_LOG_WARNING("PLT_FileMediaServer::OnBrowse - BROWSEDIRECTCHILDREN not allowed on an item."); + action->SetError(710, "item is not a container."); + return NPT_FAILURE; + } + + NPT_String filter; + NPT_String startingInd; + NPT_String reqCount; + + NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter)); + NPT_CHECK_SEVERE(action->GetArgumentValue("StartingIndex", startingInd)); + NPT_CHECK_SEVERE(action->GetArgumentValue("RequestedCount", reqCount)); + + // in case someone forgot to pass a filter + if (filter.GetLength() == 0) filter = "*"; + + NPT_UInt32 start_index, req_count; + if (NPT_FAILED(startingInd.ToInteger(start_index)) || + NPT_FAILED(reqCount.ToInteger(req_count))) { + action->SetError(412, "Precondition failed"); + return NPT_FAILURE; + } + + NPT_List<NPT_String> entries; + res = NPT_File::ListDirectory(dir, entries, 0, 0); + if (NPT_FAILED(res)) { + NPT_LOG_WARNING_1("PLT_FileMediaServer::OnBrowseDirectChildren - failed to open dir %s", (const char*) dir); + return res; + } + + unsigned long cur_index = 0; + unsigned long num_returned = 0; + unsigned long total_matches = 0; + NPT_String didl = didl_header; + + PLT_MediaObjectReference item; + for (NPT_List<NPT_String>::Iterator it = entries.GetFirstItem(); + it; + ++it) { + NPT_String filepath = NPT_FilePath::Create(dir, *it); + + // verify we want to process this file first + if (!ProcessFile(filepath)) continue; + + item = BuildFromFilePath( + filepath, + context, + true); + + if (!item.IsNull()) { + if ((cur_index >= start_index) && ((num_returned < req_count) || (req_count == 0))) { + NPT_String tmp; + NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp)); + + didl += tmp; + num_returned++; + } + cur_index++; + total_matches++; + } + }; + + didl += didl_footer; + + NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl)); + NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned))); + NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total_matches))); // 0 means we don't know how many we have but most browsers don't like that!! + NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1")); + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::GetFilePath ++---------------------------------------------------------------------*/ +NPT_Result +PLT_FileMediaServer::GetFilePath(const char* object_id, + NPT_String& filepath) +{ + if (!object_id) return NPT_ERROR_INVALID_PARAMETERS; + + filepath = m_Path; + + if (NPT_StringLength(object_id) > 2 || object_id[0]!='0') { + filepath += (const char*)object_id + (object_id[0]=='0'?1:0); + } + + return NPT_SUCCESS; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::BuildResourceUri ++---------------------------------------------------------------------*/ +NPT_String +PLT_FileMediaServer::BuildResourceUri(const NPT_HttpUrl& base_uri, + const char* host, + const char* file_path) +{ + NPT_String result; + NPT_HttpUrl uri = base_uri; + + if (host) uri.SetHost(host); + + if (file_path) { + NPT_HttpUrlQuery query(uri.GetQuery()); + query.AddField("path", file_path); + uri.SetQuery(query.ToString()); + } + + // 360 hack: force inclusion of port + result = uri.ToStringWithDefaultPort(0); + + // 360 hack: it removes the query, so we make it look like a path + // and we replace + with urlencoded value of space + result.Replace('?', "%3F"); + result.Replace('+', "%20"); + return result; +} + +/*---------------------------------------------------------------------- +| PLT_FileMediaServer::BuildFromFilePath ++---------------------------------------------------------------------*/ +PLT_MediaObject* +PLT_FileMediaServer::BuildFromFilePath(const NPT_String& filepath, + const PLT_HttpRequestContext& context, + bool with_count /* = true */, + bool keep_extension_in_title /* = false */) +{ + NPT_String root = m_Path; + PLT_MediaItemResource resource; + PLT_MediaObject* object = NULL; + + NPT_LOG_INFO_1("Building didl for file '%s'", (const char*)filepath); + + /* retrieve the entry type (directory or file) */ + NPT_FileInfo info; + NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(filepath, &info), failure); + + if (info.m_Type == NPT_FileInfo::FILE_TYPE_REGULAR) { + object = new PLT_MediaItem(); + + /* Set the title using the filename for now */ + object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title); + if (object->m_Title.GetLength() == 0) goto failure; + + /* make sure we return something with a valid mimetype */ + if (NPT_StringsEqual(PLT_MediaItem::GetMimeType(filepath), "application/unknown")) + goto failure; + + /* Set the protocol Info from the extension */ + resource.m_ProtocolInfo = PLT_MediaItem::GetProtInfo(filepath, context); + if (resource.m_ProtocolInfo.GetLength() == 0) goto failure; + + /* Set the resource file size */ + resource.m_Size = info.m_Size; + + /* format the resource URI */ + NPT_String url = filepath.SubString(root.GetLength()+1); + + // get list of ip addresses + NPT_List<NPT_String> ips; + NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure); + + // if we're passed an interface where we received the request from + // move the ip to the top + if (context.GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") { + ips.Remove(context.GetLocalAddress().GetIpAddress().ToString()); + ips.Insert(ips.GetFirstItem(), context.GetLocalAddress().GetIpAddress().ToString()); + } + + // iterate through list and build list of resources + NPT_List<NPT_String>::Iterator ip = ips.GetFirstItem(); + + /* Look to see if a metadatahandler exists for this extension */ + PLT_MetadataHandler* handler = NULL; + NPT_Result res = NPT_ContainerFind( + m_MetadataHandlers, + PLT_MetadataHandlerFinder(NPT_FilePath::FileExtension(filepath)), + handler); + if (NPT_SUCCEEDED(res) && handler) { + /* if it failed loading data, reset the metadatahandler so we don't use it */ + if (NPT_SUCCEEDED(handler->LoadFile(filepath))) { + /* replace the title with the one from the Metadata */ + NPT_String newTitle; + if (handler->GetTitle(newTitle) != NULL) { + object->m_Title = newTitle; + } + + /* assign description */ + handler->GetDescription(object->m_Description.long_description); + + /* assign album art uri if we haven't yet */ + /* prepend the album art base URI and url encode it */ + if (object->m_ExtraInfo.album_art_uri.GetLength() == 0) { + object->m_ExtraInfo.album_art_uri = + NPT_Uri::PercentEncode(BuildResourceUri(m_AlbumArtBaseUri, *ip, url), + NPT_Uri::UnsafeCharsToEncode); + } + + /* duration */ + handler->GetDuration(resource.m_Duration); + + /* protection */ + handler->GetProtection(resource.m_Protection); + } + } + + object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, context); + + while (ip) { + /* prepend the base URI and url encode it */ + //resource.m_Uri = NPT_Uri::Encode(uri.ToString(), NPT_Uri::UnsafeCharsToEncode); + resource.m_Uri = BuildResourceUri(m_FileBaseUri, *ip, url); + object->m_Resources.Add(resource); + ++ip; + } + } else { + object = new PLT_MediaContainer; + + /* Assign a title for this container */ + if (filepath.Compare(root, true) == 0) { + object->m_Title = "Root"; + } else { + object->m_Title = NPT_FilePath::BaseName(filepath, keep_extension_in_title); + if (object->m_Title.GetLength() == 0) goto failure; + } + + /* Get the number of children for this container */ + NPT_Cardinal count = 0; + if (with_count && NPT_SUCCEEDED(NPT_File::GetCount(filepath, count))) { + ((PLT_MediaContainer*)object)->m_ChildrenCount = count; + } + + object->m_ObjectClass.type = "object.container.storageFolder"; + } + + /* is it the root? */ + if (filepath.Compare(root, true) == 0) { + object->m_ParentID = "-1"; + object->m_ObjectID = "0"; + } else { + NPT_String directory = NPT_FilePath::DirectoryName(filepath); + /* is the parent path the root? */ + if (directory.GetLength() == root.GetLength()) { + object->m_ParentID = "0"; + } else { + object->m_ParentID = "0" + filepath.SubString(root.GetLength(), directory.GetLength() - root.GetLength()); + } + object->m_ObjectID = "0" + filepath.SubString(root.GetLength()); + } + + return object; + +failure: + delete object; + return NULL; +}