view deps/Platinum/Source/Devices/FrameStreamer/PltFrameServer.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 - Frame 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 "PltMediaItem.h"
#include "PltService.h"
#include "PltDidl.h"
#include "PltFrameStream.h"
#include "PltFrameServer.h"

NPT_SET_LOCAL_LOGGER("platinum.media.server.frame")

/*----------------------------------------------------------------------
|   constants
+---------------------------------------------------------------------*/
#define BOUNDARY "BOUNDARYGOAWAY"

/*----------------------------------------------------------------------
|   PLT_SocketPolicyServer
+---------------------------------------------------------------------*/
class PLT_SocketPolicyServer : public NPT_Thread
{
public:
    PLT_SocketPolicyServer(const char* policy, NPT_UInt32 port = 0) :
        m_Policy(policy),
        m_Port(port),
        m_Aborted(false) {}
        
    ~PLT_SocketPolicyServer() {
        Stop();
    }
        
    NPT_Result Start() {
        NPT_Result result = NPT_FAILURE;
        
        // bind
        // randomly try a port for our http server
        int retries = 100;
        do {    
            int random = NPT_System::GetRandomInteger();
            int port = (unsigned short)(50000 + (random % 15000));
                        
            result = m_Socket.Bind(
                NPT_SocketAddress(NPT_IpAddress::Any, m_Port?m_Port:port), 
                true);
                
            if (NPT_SUCCEEDED(result) || m_Port)
                break;
        } while (--retries > 0);

        if (NPT_FAILED(result) || retries == 0) return NPT_FAILURE;

        // remember that we're bound
        NPT_SocketInfo info;
        m_Socket.GetInfo(info);
        m_Port = info.local_address.GetPort();
        
        return NPT_Thread::Start();
    }
    
    NPT_Result Stop() {
        m_Aborted = true;
        m_Socket.Disconnect();
        
        return Wait();
    }
    
    void Run() {
        do {
            // wait for a connection
            NPT_Socket* client = NULL;
            NPT_LOG_FINE_1("waiting for connection on port %d...", m_Port);
            NPT_Result result = m_Socket.WaitForNewClient(client, NPT_TIMEOUT_INFINITE);
            if (NPT_FAILED(result) || client == NULL) return;
                    
            NPT_SocketInfo client_info;
            client->GetInfo(client_info);
            NPT_LOG_FINE_2("client connected (%s)",
                       client_info.local_address.ToString().GetChars(),
                       client_info.remote_address.ToString().GetChars());

            // get the output stream
            NPT_OutputStreamReference output;
            client->GetOutputStream(output);
            
            NPT_MemoryStream* policy = new NPT_MemoryStream();
            policy->Write(m_Policy.GetChars(), m_Policy.GetLength());
            NPT_InputStreamReference input(policy);
            
            NPT_StreamToStreamCopy(*input, *output);
            
            
            delete client;
        } while (!m_Aborted);
    }
    
    NPT_TcpServerSocket m_Socket;
    NPT_String          m_Policy;
    NPT_UInt32          m_Port;
    bool                m_Aborted;
};

/*----------------------------------------------------------------------
|   PLT_FrameServer::PLT_FrameServer
+---------------------------------------------------------------------*/
PLT_FrameServer::PLT_FrameServer(PLT_FrameBuffer& frame_buffer, 
                                 const char*      www_root,
                                 const char*      friendly_name, 
                                 bool             show_ip, 
                                 const char*      uuid, 
                                 NPT_UInt16       port) :	
    PLT_FileMediaServer(www_root,
                        friendly_name, 
                        show_ip,
                        uuid, 
                        port),
    m_FrameBuffer(frame_buffer),
    m_PolicyServer(NULL)
{
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::~PLT_FrameServer
+---------------------------------------------------------------------*/
PLT_FrameServer::~PLT_FrameServer()
{
    delete m_PolicyServer;
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::SetupDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_FrameServer::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
    m_StreamBaseUri = NPT_HttpUrl(*ips.GetFirstItem(), GetPort(), "/screensplitr");
    
    // start the xml socket policy server for flash
    m_PolicyServer = new PLT_SocketPolicyServer(
        "<cross-domain-policy><allow-access-from domain=\"192.168.1.101\" to-ports=\"5900\" /></cross-domain-policy>", 
        8989);
    NPT_CHECK_SEVERE(m_PolicyServer->Start());

    return PLT_FileMediaServer::SetupDevice();
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::ProcessHttpRequest
+---------------------------------------------------------------------*/
NPT_Result 
PLT_FrameServer::ProcessHttpRequest(NPT_HttpRequest&              request, 
                                    const NPT_HttpRequestContext& context,
                                    NPT_HttpResponse&             response)
{
    if (request.GetUrl().GetPath().StartsWith(m_StreamBaseUri.GetPath())) {
        return ProcessStreamRequest(request, context, response);
    }

    return PLT_FileMediaServer::ProcessHttpRequest(request, context, response);
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::ProcessStreamRequest
+---------------------------------------------------------------------*/
NPT_Result 
PLT_FrameServer::ProcessStreamRequest(NPT_HttpRequest&              request, 
                                      const NPT_HttpRequestContext& context,
                                      NPT_HttpResponse&             response)
{
    NPT_COMPILER_UNUSED(context);
    
    NPT_LOG_FINE("Received Request:");
    PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request);

    if (request.GetMethod().Compare("GET")) {
        response.SetStatus(500, "Internal Server Error");
        return NPT_SUCCESS;
    }

    response.SetProtocol(NPT_HTTP_PROTOCOL_1_0);
    response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONNECTION, "close");
    response.GetHeaders().SetHeader("Cache-Control", "no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0");
    response.GetHeaders().SetHeader("Pragma", "no-cache");
    response.GetHeaders().SetHeader("Expires", "Tue, 4 Jan 2000 02:43:05 GMT");

    NPT_HttpEntity* entity = new NPT_HttpEntity();
    entity->SetContentType("multipart/x-mixed-replace;boundary=" BOUNDARY);

    NPT_InputStreamReference body(new PLT_InputFrameStream(m_FrameBuffer, BOUNDARY));
    entity->SetInputStream(body, false);

    return response.SetEntity(entity);
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::OnBrowseMetadata
+---------------------------------------------------------------------*/
NPT_Result
PLT_FrameServer::OnBrowseMetadata(PLT_ActionReference&          action, 
                                  const char*                   object_id, 
                                  const PLT_HttpRequestContext& context)
{
    NPT_String didl;
    PLT_MediaObjectReference item;

    item = BuildFromID(object_id, &context.GetLocalAddress());
    if (item.IsNull())  {
        /* error */
        NPT_LOG_WARNING("PLT_FrameServer::OnBrowse - ObjectID not found.");
        action->SetError(701, "No Such Object.");
        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_FrameServer::OnBrowseDirectChildren
+---------------------------------------------------------------------*/
NPT_Result
PLT_FrameServer::OnBrowseDirectChildren(PLT_ActionReference&          action, 
                                        const char*                   object_id, 
                                        const PLT_HttpRequestContext& context)
{
    /* Only root allowed */
    if (!NPT_StringsEqual(object_id, "0")) {
        /* error */
        NPT_LOG_WARNING("PLT_FrameServer::OnBrowse - ObjectID not found.");
        action->SetError(701, "No Such Object.");
        return NPT_FAILURE;
    }

    NPT_String startingInd;
    NPT_CHECK_SEVERE(action->GetArgumentValue("StartingIndex", startingInd));

    NPT_UInt32 start_index;
    if (NPT_FAILED(startingInd.ToInteger(start_index))) {        
        action->SetError(412, "Precondition failed");
        return NPT_FAILURE;
    }

    NPT_String filter;
    NPT_CHECK_SEVERE(action->GetArgumentValue("Filter", filter));

    NPT_String didl = didl_header;
    PLT_MediaObjectReference item;

    unsigned long num_returned = 0;
    unsigned long total_matches = 0;
    if (start_index == 0) {
        item = BuildFromID("1", &context.GetLocalAddress());
        if (!item.IsNull()) {
            NPT_String tmp;
            NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp));
            didl += tmp;

            num_returned++;
            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)));
    NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1"));

    return NPT_SUCCESS;
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::BuildResourceUri
+---------------------------------------------------------------------*/
NPT_String
PLT_FrameServer::BuildResourceUri(const NPT_HttpUrl& base_uri, 
                                  const char*        host, 
                                  const char*        file_path)
{
    NPT_HttpUrl uri = base_uri;

    if (host) uri.SetHost(host);

    if (file_path) {
        uri.SetPath(uri.GetPath() + file_path);
    }
    
    return uri.ToStringWithDefaultPort(0);
}

/*----------------------------------------------------------------------
|   PLT_FrameServer::BuildFromID
+---------------------------------------------------------------------*/
PLT_MediaObject*
PLT_FrameServer::BuildFromID(const char* id,
                             const NPT_SocketAddress* req_local_address /* = NULL */)
{
    PLT_MediaItemResource resource;
    PLT_MediaObject*      object = NULL;

    if (NPT_StringsEqual(id, "1")) {
        object = new PLT_MediaItem();
        object->m_ObjectID = "1";
        object->m_ParentID = "0";

        /* Set the title using the filename for now */
        object->m_Title = "ScreenSplitr";

        /* Set the protocol Info from the extension */
        resource.m_ProtocolInfo = "http-get:*:video/x-motion-jpg:DLNA.ORG_OP=01";

        /* Set the resource file size */
        //resource.m_Size = info.m_Size;

        // 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 (req_local_address && req_local_address->GetIpAddress().ToString() != "0.0.0.0") {
            ips.Remove(req_local_address->GetIpAddress().ToString());
            ips.Insert(ips.GetFirstItem(), req_local_address->GetIpAddress().ToString());
        }

        // iterate through list and build list of resources
        NPT_List<NPT_String>::Iterator ip = ips.GetFirstItem();
        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_StreamBaseUri, *ip, NULL);

            object->m_ObjectClass.type = "object.item.videoItem.movie";
            object->m_Resources.Add(resource);

            ++ip;
        }
    } else if (NPT_StringsEqual(id, "0")) {
        object = new PLT_MediaContainer;

        object->m_ObjectID = "0";
        object->m_ParentID = "-1";
        object->m_Title    = "Root";
        ((PLT_MediaContainer*)object)->m_ChildrenCount = 1;

        object->m_ObjectClass.type = "object.container";
    }

    return object;

failure:
    delete object;
    return NULL;
}