dndmanager.cpp revision 6aa04d8e8d3a39ca628c07d7f800cd681cf2c40f
/* $Id$ */
/** @file
* Drag and Drop manager.
*/
/*
* Copyright (C) 2011-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/******************************************************************************
* Header Files *
******************************************************************************/
#ifdef LOG_GROUP
#endif
#define LOG_GROUP LOG_GROUP_GUEST_DND
#include "dndmanager.h"
/******************************************************************************
* Private declarations *
******************************************************************************/
typedef FNDNDPRIVATEPROGRESS *PFNDNDPRIVATEPROGRESS;
/**
* Internal DnD message class for informing the guest about a new directory.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendDirPrivate: public DnDMessage
{
public:
DnDHGSendDirPrivate(const RTCString &strPath, uint32_t fMode, uint64_t cbSize, PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser)
{
}
{
/* Advance progress info */
if ( RT_SUCCESS(rc)
return rc;
}
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/**
* Internal DnD message class for informing the guest about a new file.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendFilePrivate: public DnDMessage
{
public:
DnDHGSendFilePrivate(const RTCString &strHostPath, const RTCString &strGuestPath, uint32_t fMode, uint64_t cbSize, PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser);
virtual ~DnDHGSendFilePrivate();
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/**
* Internal DnD message class for informing the guest about new drag & drop
* data.
*
* @see DnDHGSendDataMessage
*/
class DnDHGSendDataMessagePrivate: public DnDMessage
{
public:
DnDHGSendDataMessagePrivate(uint32_t uMsg, uint32_t cParms, VBOXHGCMSVCPARM paParms[], PFNDNDPRIVATEPROGRESS pfnProgressCallback, void *pvProgressUser);
protected:
/* Progress stuff */
void *m_pvProgressUser;
};
/******************************************************************************
* Implementation *
******************************************************************************/
/******************************************************************************
* DnDHGSendFilePrivate *
******************************************************************************/
DnDHGSendFilePrivate::DnDHGSendFilePrivate(const RTCString &strHostPath, const RTCString &strGuestPath,
, m_cbFileProcessed(0)
, m_hCurFile(0)
{
}
{
if (m_hCurFile)
}
{
if (!m_pNextMsg)
return VERR_NO_DATA;
clearNextMsg();
if (RT_FAILURE(rc))
return rc;
if (!m_hCurFile)
{
/* Open files on the host with RTFILE_O_DENY_WRITE to prevent races where the host
* writes to the file while the guest transfers it over. */
}
if (RT_SUCCESS(rc))
{
/* Get buffer size + pointer to buffer from guest side. */
{
/* Advance. */
/* Tell the guest the actual size. */
}
}
if (RT_SUCCESS(rc))
{
/* Check if we are done. */
if (!fDone)
{
try
{
/* More data! Prepare the next message. */
}
{
rc = VERR_NO_MEMORY;
}
}
/* Advance progress info. */
if ( RT_SUCCESS(rc)
{
}
if ( fDone
|| RT_FAILURE(rc))
{
m_hCurFile = 0;
}
}
return rc;
}
/******************************************************************************
* DnDHGSendDataMessagePrivate *
******************************************************************************/
void *pvProgressUser)
, m_cbDone(0)
{
/* Create the initial data message. This might throw
* a bad_alloc exception. */
}
{
/** @todo Don't copy the data parts ... just move the data pointer in
* the original data ptr. */
if (!m_pNextMsg)
return VERR_NO_DATA;
int rc = VINF_SUCCESS;
m_pNextMsg = 0;
/* Depending on the current message, the data pointer is on a
* different position (HOST_DND_HG_SND_DATA=3;
* HOST_DND_HG_SND_MORE_DATA=0). */
/* Info + data send already? */
if (rc == VERR_BUFFER_OVERFLOW)
{
void *pvOldData;
paTmpParms[0].setPointer(static_cast<uint8_t*>(pvOldData) + paParms[iPos].u.pointer.size, cOldData - paParms[iPos].u.pointer.size);
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
if (pCurMsg)
delete pCurMsg;
/* Advance progress info. */
if ( RT_SUCCESS(rc)
{
}
return rc;
}
/******************************************************************************
* DnDHGSendDataMessage *
******************************************************************************/
/*
* This class is a meta message class. It doesn't consist of any own message
* data, but handle the meta info, the data itself as well as any files or
* directories which have to be transfered to the guest.
*/
void *pvProgressUser)
: m_cbAll(0)
, m_cbTransfered(0)
{
/* Check the format for any uri type. */
{
/* The list is separated by newline (even if only one file is listed). */
{
{
/* Query the path component of a file URI. If this hasn't a
* file scheme NULL is returned. */
char *pszFilePath;
{
/* Add the path to our internal file list (recursive in
* the case of a directory). */
char *pszFilename;
{
if (pszNewURI)
{
}
}
}
else /* Just append the raw data. */
}
/* We have to change the actual DnD data. Remove any host paths and
* just decode the filename into the new data. The guest tools will
* add the correct path again, before sending the DnD drop event to
* some window. */
/* Note: We don't delete the old pointer here, cause this is done
* by the caller. We just use the RTString data, which has the
* scope of this ctor. This is enough cause the data is copied in
* the DnDHGSendDataMessagePrivate anyway. */
}
}
/* Add the size of the data to the todo list. */
/* The first message is the meta info for the data and the data itself. */
&DnDHGSendDataMessage::progressCallback, this);
#ifdef DEBUG
LogFlowFunc(("file: %s : %s - %o - %ld\n",
#endif
}
{
if (m_pNextPathMsg)
delete m_pNextPathMsg;
}
{
if (!m_pNextPathMsg)
return NULL;
return m_pNextPathMsg->nextHGCMMessage();
}
{
if (!m_pNextPathMsg)
return VERR_NO_DATA;
}
{
if (!m_pNextPathMsg)
return VERR_NO_DATA;
/* Fill the data out of our current queued message. */
/* Has this message more data to deliver? */
if (!m_pNextPathMsg->isMessageWaiting())
{
delete m_pNextPathMsg;
}
/* File data to send? */
if (!m_pNextPathMsg)
{
return rc;
/* Create new messages based on our internal path list. Currently
* this could be directories or regular files. */
try
{
&DnDHGSendDataMessage::progressCallback, this);
&DnDHGSendDataMessage::progressCallback, this);
else
AssertMsgFailedReturn(("type '%d' is not supported for path '%s'",
}
{
rc = VERR_NO_MEMORY;
}
}
return rc;
}
{
}
{
if (RT_FAILURE(rc))
return rc;
/*
* These are the types we currently support. Symlinks are not directly
* supported. First the guest could be an OS which doesn't support it and
* second the symlink could point to a file which is out of the base tree.
* Both things are hard to support. For now we just copy the target file in
* this case.
*/
return VINF_SUCCESS;
if (rc == VERR_IS_A_DIRECTORY)
rc = VINF_SUCCESS;
if (RT_FAILURE(rc))
return rc;
/* We have to try to open even symlinks, cause they could be symlinks
* to directories. */
/* The following error happens when this was a symlink to an file or a
* regular file. */
if (rc == VERR_PATH_NOT_FOUND)
return VINF_SUCCESS;
if (RT_FAILURE(rc))
return rc;
while (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
{
if (rc == VERR_NO_MORE_FILES)
rc = VINF_SUCCESS;
break;
}
{
case RTDIRENTRYTYPE_DIRECTORY:
{
/* Skip "." and ".." entries. */
break;
{
}
else
rc = VERR_NO_MEMORY;
break;
}
case RTDIRENTRYTYPE_SYMLINK:
case RTDIRENTRYTYPE_FILE:
{
char *pszNewFile;
{
/* We need the size and the mode of the file. */
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
break;
}
else
rc = VERR_NO_MEMORY;
break;
}
default:
break;
}
}
return rc;
}
{
/* How many bytes are transfered already. */
/* Advance progress info */
int rc = VINF_SUCCESS;
if ( pSelf->m_pfnProgressCallback
{
}
return rc;
}
/******************************************************************************
* DnDManager *
******************************************************************************/
{
int rc = VINF_SUCCESS;
try
{
switch (uMsg)
{
{
clear();
LogFlowFunc(("HOST_DND_HG_EVT_ENTER\n"));
/* Verify parameter count and types. */
if ( cParms != 7
else
{
m_fOpInProcess = true;
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_MOVE\n"));
/* Verify parameter count and types. */
if ( cParms != 7
{
}
else
{
m_fOpInProcess = true;
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_LEAVE\n"));
/* Verify parameter count and types. */
if (cParms != 0)
else
{
}
m_fOpInProcess = false;
break;
}
{
LogFlowFunc(("HOST_DND_HG_EVT_DROPPED\n"));
/* Verify parameter count and types. */
if ( cParms != 7
{
}
else
{
}
break;
}
{
LogFlowFunc(("HOST_DND_HG_SND_DATA\n"));
/* Verify parameter count and types. */
if ( cParms != 5
{
}
else
{
}
break;
}
#ifdef VBOX_WITH_DRAG_AND_DROP_GH
{
LogFlowFunc(("HOST_DND_GH_REQ_PENDING\n"));
/* Verify parameter count and types. */
if ( cParms != 1
{
}
else
{
}
break;
}
{
LogFlowFunc(("HOST_DND_GH_EVT_DROPPED\n"));
/* Verify parameter count and types. */
if ( cParms != 3
{
}
else
{
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
break;
}
#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
default:
break;
}
}
{
rc = VERR_NO_MEMORY;
}
return rc;
}
{
if (m_pCurMsg)
return m_pCurMsg->nextHGCMMessage();
if (m_dndMessageQueue.isEmpty())
return NULL;
}
{
int rc = VINF_SUCCESS;
if (m_pCurMsg)
else
{
if (m_dndMessageQueue.isEmpty())
{
rc = VERR_NO_DATA;
// if (m_pfnProgressCallback)
// m_pfnProgressCallback(100.0, DragAndDropSvc::DND_OP_CANCELLED, m_pvProgressUser);
}
else
}
return rc;
}
{
if (!m_pCurMsg)
{
/* Check for pending messages in our queue. */
if (m_dndMessageQueue.isEmpty())
{
LogFlowFunc(("Message queue is empty, returning\n"));
return VERR_NO_DATA;
}
}
/* Fetch the current message info */
/* If this message doesn't provide any additional sub messages, clear it. */
if (!m_pCurMsg->isMessageWaiting())
{
delete m_pCurMsg;
}
/*
* If there was an error handling the current message or the user has canceled
* the operation, we need to cleanup all pending events and inform the progress
* callback about our exit.
*/
if ( RT_FAILURE(rc)
{
/* Clear any pending messages. */
clear();
/* Create a new cancel message to inform the guest + call
* the host whether the current transfer was canceled or aborted
* due to an error. */
try
{
m_pCurMsg = new DnDHGCancelMessage();
rc == VERR_CANCELLED
}
{
rc = VERR_NO_MEMORY;
}
}
return rc;
}
void DnDManager::clear(void)
{
if (m_pCurMsg)
{
delete m_pCurMsg;
m_pCurMsg = 0;
}
while (!m_dndMessageQueue.isEmpty())
{
delete m_dndMessageQueue.last();
}
}