Mercurial > projects > hoofbaby
view 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 source
/***************************************************************** | | 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; }