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;
+}