VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/GuestDnDSourceImpl.cpp@ 55644

Last change on this file since 55644 was 55644, checked in by vboxsync, 10 years ago

Main: Made it compile without VBOX_WITH_GUEST_CONTROL.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.9 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 55644 2015-05-04 13:27:35Z vboxsync $ */
2/** @file
3 * VBox Console COM Class implementation - Guest drag and drop source.
4 */
5
6/*
7 * Copyright (C) 2014-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "GuestImpl.h"
23#include "GuestDnDSourceImpl.h"
24#include "GuestDnDPrivate.h"
25#include "ConsoleImpl.h"
26
27#include "Global.h"
28#include "AutoCaller.h"
29
30#include <iprt/asm.h>
31#include <iprt/dir.h>
32#include <iprt/file.h>
33#include <iprt/path.h>
34#include <iprt/uri.h>
35
36#include <iprt/cpp/utils.h> /* For unconst(). */
37
38#include <VBox/com/array.h>
39#include <VBox/GuestHost/DragAndDrop.h>
40#include <VBox/HostServices/DragAndDropSvc.h>
41
42#ifdef LOG_GROUP
43 #undef LOG_GROUP
44#endif
45#define LOG_GROUP LOG_GROUP_GUEST_DND
46#include <VBox/log.h>
47
48/**
49 * Base class for a source task.
50 */
51class GuestDnDSourceTask
52{
53public:
54
55 GuestDnDSourceTask(GuestDnDSource *pSource)
56 : mSource(pSource),
57 mRC(VINF_SUCCESS) { }
58
59 virtual ~GuestDnDSourceTask(void) { }
60
61 int getRC(void) const { return mRC; }
62 bool isOk(void) const { return RT_SUCCESS(mRC); }
63 const ComObjPtr<GuestDnDSource> &getSource(void) const { return mSource; }
64
65protected:
66
67 const ComObjPtr<GuestDnDSource> mSource;
68 int mRC;
69};
70
71/**
72 * Task structure for receiving data from a source using
73 * a worker thread.
74 */
75class RecvDataTask : public GuestDnDSourceTask
76{
77public:
78
79 RecvDataTask(GuestDnDSource *pSource, PRECVDATACTX pCtx)
80 : GuestDnDSourceTask(pSource)
81 , mpCtx(pCtx) { }
82
83 virtual ~RecvDataTask(void) { }
84
85 PRECVDATACTX getCtx(void) { return mpCtx; }
86
87protected:
88
89 /** Pointer to receive data context. */
90 PRECVDATACTX mpCtx;
91};
92
93// constructor / destructor
94/////////////////////////////////////////////////////////////////////////////
95
96DEFINE_EMPTY_CTOR_DTOR(GuestDnDSource)
97
98HRESULT GuestDnDSource::FinalConstruct(void)
99{
100 /* Set the maximum block size this source can handle to 64K. This always has
101 * been hardcoded until now. */
102 /* Note: Never ever rely on information from the guest; the host dictates what and
103 * how to do something, so try to negogiate a sensible value here later. */
104 mData.mcbBlockSize = _64K; /** @todo Make this configurable. */
105
106 LogFlowThisFunc(("\n"));
107 return BaseFinalConstruct();
108}
109
110void GuestDnDSource::FinalRelease(void)
111{
112 LogFlowThisFuncEnter();
113 uninit();
114 BaseFinalRelease();
115 LogFlowThisFuncLeave();
116}
117
118// public initializer/uninitializer for internal purposes only
119/////////////////////////////////////////////////////////////////////////////
120
121int GuestDnDSource::init(const ComObjPtr<Guest>& pGuest)
122{
123 LogFlowThisFuncEnter();
124
125 /* Enclose the state transition NotReady->InInit->Ready. */
126 AutoInitSpan autoInitSpan(this);
127 AssertReturn(autoInitSpan.isOk(), E_FAIL);
128
129 unconst(m_pGuest) = pGuest;
130
131 /* Confirm a successful initialization when it's the case. */
132 autoInitSpan.setSucceeded();
133
134 return VINF_SUCCESS;
135}
136
137/**
138 * Uninitializes the instance.
139 * Called from FinalRelease().
140 */
141void GuestDnDSource::uninit(void)
142{
143 LogFlowThisFunc(("\n"));
144
145 /* Enclose the state transition Ready->InUninit->NotReady. */
146 AutoUninitSpan autoUninitSpan(this);
147 if (autoUninitSpan.uninitDone())
148 return;
149}
150
151// implementation of wrapped IDnDBase methods.
152/////////////////////////////////////////////////////////////////////////////
153
154HRESULT GuestDnDSource::isFormatSupported(const com::Utf8Str &aFormat, BOOL *aSupported)
155{
156#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
157 ReturnComNotImplemented();
158#else /* VBOX_WITH_DRAG_AND_DROP */
159
160 AutoCaller autoCaller(this);
161 if (FAILED(autoCaller.rc())) return autoCaller.rc();
162
163 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
164
165 return GuestDnDBase::i_isFormatSupported(aFormat, aSupported);
166#endif /* VBOX_WITH_DRAG_AND_DROP */
167}
168
169HRESULT GuestDnDSource::getFormats(std::vector<com::Utf8Str> &aFormats)
170{
171#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
172 ReturnComNotImplemented();
173#else /* VBOX_WITH_DRAG_AND_DROP */
174
175 AutoCaller autoCaller(this);
176 if (FAILED(autoCaller.rc())) return autoCaller.rc();
177
178 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
179
180 return GuestDnDBase::i_getFormats(aFormats);
181#endif /* VBOX_WITH_DRAG_AND_DROP */
182}
183
184HRESULT GuestDnDSource::addFormats(const std::vector<com::Utf8Str> &aFormats)
185{
186#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
187 ReturnComNotImplemented();
188#else /* VBOX_WITH_DRAG_AND_DROP */
189
190 AutoCaller autoCaller(this);
191 if (FAILED(autoCaller.rc())) return autoCaller.rc();
192
193 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
194
195 return GuestDnDBase::i_addFormats(aFormats);
196#endif /* VBOX_WITH_DRAG_AND_DROP */
197}
198
199HRESULT GuestDnDSource::removeFormats(const std::vector<com::Utf8Str> &aFormats)
200{
201#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
202 ReturnComNotImplemented();
203#else /* VBOX_WITH_DRAG_AND_DROP */
204
205 AutoCaller autoCaller(this);
206 if (FAILED(autoCaller.rc())) return autoCaller.rc();
207
208 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
209
210 return GuestDnDBase::i_removeFormats(aFormats);
211#endif /* VBOX_WITH_DRAG_AND_DROP */
212}
213
214HRESULT GuestDnDSource::getProtocolVersion(ULONG *aProtocolVersion)
215{
216#if !defined(VBOX_WITH_DRAG_AND_DROP)
217 ReturnComNotImplemented();
218#else /* VBOX_WITH_DRAG_AND_DROP */
219
220 AutoCaller autoCaller(this);
221 if (FAILED(autoCaller.rc())) return autoCaller.rc();
222
223 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
224
225 return GuestDnDBase::i_getProtocolVersion(aProtocolVersion);
226#endif /* VBOX_WITH_DRAG_AND_DROP */
227}
228
229// implementation of wrapped IDnDSource methods.
230/////////////////////////////////////////////////////////////////////////////
231
232HRESULT GuestDnDSource::dragIsPending(ULONG uScreenId, std::vector<com::Utf8Str> &aFormats,
233 std::vector<DnDAction_T> &aAllowedActions, DnDAction_T *aDefaultAction)
234{
235#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
236 ReturnComNotImplemented();
237#else /* VBOX_WITH_DRAG_AND_DROP */
238
239 AutoCaller autoCaller(this);
240 if (FAILED(autoCaller.rc())) return autoCaller.rc();
241
242 /* Determine guest DnD protocol to use. */
243 GuestDnDBase::getProtocolVersion(&mDataBase.mProtocolVersion);
244
245 /* Default is ignoring the action. */
246 DnDAction_T defaultAction = DnDAction_Ignore;
247
248 HRESULT hr = S_OK;
249
250 GuestDnDMsg Msg;
251 Msg.setType(DragAndDropSvc::HOST_DND_GH_REQ_PENDING);
252 Msg.setNextUInt32(uScreenId);
253
254 int rc = GuestDnDInst()->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
255 if (RT_SUCCESS(rc))
256 {
257 bool fFetchResult = true;
258 GuestDnDResponse *pResp = GuestDnDInst()->response();
259 if (pResp)
260 {
261 if (pResp->waitForGuestResponse() == VERR_TIMEOUT)
262 fFetchResult = false;
263
264 if (isDnDIgnoreAction(pResp->defAction()))
265 fFetchResult = false;
266
267 /* Fetch the default action to use. */
268 if (fFetchResult)
269 {
270 defaultAction = GuestDnD::toMainAction(pResp->defAction());
271
272 GuestDnD::toFormatVector(m_strFormats, pResp->format(), aFormats);
273 GuestDnD::toMainActions(pResp->allActions(), aAllowedActions);
274 }
275 }
276
277 if (aDefaultAction)
278 *aDefaultAction = defaultAction;
279 }
280
281 LogFlowFunc(("hr=%Rhrc, defaultAction=0x%x\n", hr, defaultAction));
282 return hr;
283#endif /* VBOX_WITH_DRAG_AND_DROP */
284}
285
286HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
287{
288#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
289 ReturnComNotImplemented();
290#else /* VBOX_WITH_DRAG_AND_DROP */
291
292 AutoCaller autoCaller(this);
293 if (FAILED(autoCaller.rc())) return autoCaller.rc();
294
295 /* Input validation. */
296 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
297 return setError(E_INVALIDARG, tr("No drop format specified"));
298
299 uint32_t uAction = GuestDnD::toHGCMAction(aAction);
300 if (isDnDIgnoreAction(uAction)) /* If there is no usable action, ignore this request. */
301 return S_OK;
302
303 /* Note: At the moment we only support one transfer at a time. */
304 if (ASMAtomicReadBool(&mDataBase.mfTransferIsPending))
305 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
306
307 ASMAtomicWriteBool(&mDataBase.mfTransferIsPending, true);
308
309 /* Dito. */
310 GuestDnDResponse *pResp = GuestDnDInst()->response();
311 AssertPtr(pResp);
312
313 HRESULT hr = pResp->resetProgress(m_pGuest);
314 if (FAILED(hr))
315 return hr;
316
317 try
318 {
319 mData.mRecvCtx.mIsActive = false;
320 mData.mRecvCtx.mpSource = this;
321 mData.mRecvCtx.mpResp = pResp;
322 mData.mRecvCtx.mFormat = aFormat;
323
324 RecvDataTask *pTask = new RecvDataTask(this, &mData.mRecvCtx);
325 AssertReturn(pTask->isOk(), pTask->getRC());
326
327 int rc = RTThreadCreate(NULL, GuestDnDSource::i_receiveDataThread,
328 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndSrcRcvData");
329 if (RT_SUCCESS(rc))
330 {
331 hr = pResp->queryProgressTo(aProgress.asOutParam());
332 ComAssertComRC(hr);
333
334 /* Note: pTask is now owned by the worker thread. */
335 }
336 else
337 hr = setError(VBOX_E_IPRT_ERROR, tr("Starting thread failed (%Rrc)"), rc);
338 }
339 catch(std::bad_alloc &)
340 {
341 hr = setError(E_OUTOFMEMORY);
342 }
343
344 /* Note: mDataBase.mfTransferIsPending will be set to false again by i_receiveDataThread. */
345
346 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
347 return hr;
348#endif /* VBOX_WITH_DRAG_AND_DROP */
349}
350
351HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
352{
353#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
354 ReturnComNotImplemented();
355#else /* VBOX_WITH_DRAG_AND_DROP */
356
357 /* Input validation. */
358
359 AutoCaller autoCaller(this);
360 if (FAILED(autoCaller.rc())) return autoCaller.rc();
361
362 /* Don't allow receiving the actual data until our transfer
363 * actually is complete. */
364 if (ASMAtomicReadBool(&mDataBase.mfTransferIsPending))
365 return setError(E_INVALIDARG, tr("Current drop operation still in progress"));
366
367 PRECVDATACTX pCtx = &mData.mRecvCtx;
368
369 if (pCtx->mData.vecData.empty())
370 {
371 aData.resize(0);
372 return S_OK;
373 }
374
375 HRESULT hr = S_OK;
376 size_t cbData;
377
378 try
379 {
380 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
381 if (fHasURIList)
382 {
383 Utf8Str strURIs = pCtx->mURI.lstURI.RootToString(RTCString(DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir)));
384 cbData = strURIs.length();
385
386 LogFlowFunc(("Found %zu root URIs (%zu bytes)\n", pCtx->mURI.lstURI.RootCount(), cbData));
387
388 aData.resize(cbData + 1 /* Include termination */);
389 memcpy(&aData.front(), strURIs.c_str(), cbData);
390 }
391 else
392 {
393 cbData = pCtx->mData.vecData.size();
394
395 /* Copy the data into a safe array of bytes. */
396 aData.resize(cbData);
397 memcpy(&aData.front(), &pCtx->mData.vecData[0], cbData);
398 }
399 }
400 catch (std::bad_alloc &)
401 {
402 hr = E_OUTOFMEMORY;
403 }
404
405 LogFlowFunc(("Returning cbData=%zu, hr=%Rhrc\n", cbData, hr));
406 return hr;
407#endif /* VBOX_WITH_DRAG_AND_DROP */
408}
409
410// implementation of internal methods.
411/////////////////////////////////////////////////////////////////////////////
412
413#ifdef VBOX_WITH_DRAG_AND_DROP_GH
414int GuestDnDSource::i_onReceiveData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData, uint64_t cbTotalSize)
415{
416 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
417 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
418 AssertReturn(cbData, VERR_INVALID_PARAMETER);
419 AssertReturn(cbTotalSize, VERR_INVALID_PARAMETER);
420
421 LogFlowFunc(("cbData=%RU32, cbTotalSize=%RU64\n", cbData, cbTotalSize));
422
423 int rc = VINF_SUCCESS;
424
425 try
426 {
427 if ( cbData > cbTotalSize
428 || cbData > mData.mcbBlockSize)
429 {
430 LogFlowFunc(("Data sizes invalid: cbData=%RU32, cbTotalSize=%RU64\n", cbData, cbTotalSize));
431 rc = VERR_INVALID_PARAMETER;
432 }
433 else if (cbData < pCtx->mData.vecData.size())
434 {
435 AssertMsgFailed(("New size (%RU64) is smaller than current size (%zu)\n", cbTotalSize, pCtx->mData.vecData.size()));
436 rc = VERR_INVALID_PARAMETER;
437 }
438
439 if (RT_SUCCESS(rc))
440 {
441 pCtx->mData.vecData.insert(pCtx->mData.vecData.begin(), (BYTE *)pvData, (BYTE *)pvData + cbData);
442
443 LogFlowFunc(("vecDataSize=%zu, cbData=%RU32, cbTotalSize=%RU64\n", pCtx->mData.vecData.size(), cbData, cbTotalSize));
444
445 /* Data transfer complete? */
446 Assert(cbData <= pCtx->mData.vecData.size());
447 if (cbData == pCtx->mData.vecData.size())
448 {
449 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
450 LogFlowFunc(("fHasURIList=%RTbool, cbTotalSize=%RU32\n", fHasURIList, cbTotalSize));
451 if (fHasURIList)
452 {
453 /* Try parsing the data as URI list. */
454 rc = pCtx->mURI.lstURI.RootFromURIData(&pCtx->mData.vecData[0], pCtx->mData.vecData.size(), 0 /* uFlags */);
455 if (RT_SUCCESS(rc))
456 {
457 pCtx->mData.cbProcessed = 0;
458
459 /*
460 * Assign new total size which also includes all file data to receive
461 * from the guest.
462 */
463 pCtx->mData.cbToProcess = cbTotalSize;
464
465 LogFlowFunc(("URI data => cbToProcess=%RU64\n", pCtx->mData.cbToProcess));
466 }
467 }
468 }
469 }
470 }
471 catch (std::bad_alloc &)
472 {
473 rc = VERR_NO_MEMORY;
474 }
475
476 LogFlowFuncLeaveRC(rc);
477 return rc;
478}
479
480int GuestDnDSource::i_onReceiveDir(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
481{
482 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
483 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
484 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
485
486 LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
487
488 int rc;
489 char *pszDir = RTPathJoinA(DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir), pszPath);
490 if (pszDir)
491 {
492 rc = RTDirCreateFullPath(pszDir, fMode);
493 if (RT_FAILURE(rc))
494 LogRel2(("DnD: Error creating guest directory \"%s\" on the host, rc=%Rrc\n", pszDir, rc));
495
496 RTStrFree(pszDir);
497 }
498 else
499 rc = VERR_NO_MEMORY;
500
501 if (RT_SUCCESS(rc))
502 {
503 if (mDataBase.mProtocolVersion <= 2)
504 {
505 /*
506 * Protocols v1/v2 do *not* send root element names (files/directories)
507 * in URI format. The initial GUEST_DND_GH_SND_DATA message(s) however
508 * did take those element names into account, but *with* URI decoration
509 * when it comes to communicating the total bytes being sent.
510 *
511 * So translate the path into a valid URI path and add the resulting
512 * length (+ "\r\n" and termination) to the total bytes received
513 * to keep the accounting right.
514 */
515 char *pszPathURI = RTUriCreate("file" /* pszScheme */, "/" /* pszAuthority */,
516 pszPath /* pszPath */,
517 NULL /* pszQuery */, NULL /* pszFragment */);
518 if (pszPathURI)
519 {
520 bool fHasPath = RTPathHasPath(pszPath); /* Use original data received. */
521 if (!fHasPath) /* Root path? */
522 {
523 cbPath = strlen(pszPathURI);
524 cbPath += 3; /* Include "\r" + "\n" + termination -- see above. */
525
526 rc = i_updateProcess(pCtx, cbPath);
527 }
528
529 LogFlowFunc(("URI pszPathURI=%s, fHasPath=%RTbool, cbPath=%RU32\n", pszPathURI, fHasPath, cbPath));
530 RTStrFree(pszPathURI);
531 }
532 else
533 rc = VERR_NO_MEMORY;
534 }
535 }
536
537 LogFlowFuncLeaveRC(rc);
538 return rc;
539}
540
541int GuestDnDSource::i_onReceiveFileHdr(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath,
542 uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
543{
544 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
545 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
546 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
547 AssertReturn(fMode, VERR_INVALID_PARAMETER);
548 /* fFlags are optional. */
549
550 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
551
552 int rc = VINF_SUCCESS;
553
554 do
555 {
556 if (!pCtx->mURI.objURI.IsComplete())
557 {
558 LogFlowFunc(("Warning: Object \"%s\" not complete yet\n", pCtx->mURI.objURI.GetDestPath().c_str()));
559 rc = VERR_INVALID_PARAMETER;
560 break;
561 }
562
563 if (pCtx->mURI.objURI.IsOpen()) /* File already opened? */
564 {
565 LogFlowFunc(("Warning: Current opened object is \"%s\"\n", pCtx->mURI.objURI.GetDestPath().c_str()));
566 rc = VERR_WRONG_ORDER;
567 break;
568 }
569
570 char pszPathAbs[RTPATH_MAX];
571 rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir), pszPath);
572 if (RT_FAILURE(rc))
573 {
574 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
575 break;
576 }
577
578 rc = DnDPathSanitize(pszPathAbs, sizeof(pszPathAbs));
579 if (RT_FAILURE(rc))
580 {
581 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
582 break;
583 }
584
585 LogFunc(("Rebased to: %s\n", pszPathAbs));
586
587 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
588 rc = pCtx->mURI.objURI.OpenEx(pszPathAbs, DnDURIObject::File, DnDURIObject::Target,
589 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
590 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
591 if (RT_SUCCESS(rc))
592 {
593 /* Note: Protocol v1 does not send any file sizes, so always 0. */
594 if (mDataBase.mProtocolVersion >= 2)
595 rc = pCtx->mURI.objURI.SetSize(cbSize);
596
597 /** @todo Unescpae path before printing. */
598 LogRel2(("DnD: Transferring guest file to host: %s (%RU64 bytes, mode 0x%x)\n",
599 pCtx->mURI.objURI.GetDestPath().c_str(), pCtx->mURI.objURI.GetSize(), pCtx->mURI.objURI.GetMode()));
600
601 /** @todo Set progress object title to current file being transferred? */
602
603 if (!cbSize) /* 0-byte file? Close again. */
604 pCtx->mURI.objURI.Close();
605 }
606 else
607 {
608 LogRel2(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n",
609 pCtx->mURI.objURI.GetDestPath().c_str(), rc));
610 break;
611 }
612
613 if (mDataBase.mProtocolVersion <= 2)
614 {
615 /*
616 * Protocols v1/v2 do *not* send root element names (files/directories)
617 * in URI format. The initial GUEST_DND_GH_SND_DATA message(s) however
618 * did take those element names into account, but *with* URI decoration
619 * when it comes to communicating the total bytes being sent.
620 *
621 * So translate the path into a valid URI path and add the resulting
622 * length (+ "\r\n" and termination) to the total bytes received
623 * to keep the accounting right.
624 */
625 char *pszPathURI = RTUriCreate("file" /* pszScheme */, "/" /* pszAuthority */,
626 pszPath /* pszPath */,
627 NULL /* pszQuery */, NULL /* pszFragment */);
628 if (pszPathURI)
629 {
630 bool fHasPath = RTPathHasPath(pszPath); /* Use original data received. */
631 if (!fHasPath) /* Root path? */
632 {
633 cbPath = strlen(pszPathURI);
634 cbPath += 3; /* Include "\r" + "\n" + termination -- see above. */
635
636 rc = i_updateProcess(pCtx, cbPath);
637 }
638
639 LogFlowFunc(("URI pszPathURI=%s, fHasPath=%RTbool, cbPath=%RU32\n", pszPathURI, fHasPath, cbPath));
640 RTStrFree(pszPathURI);
641 }
642 else
643 {
644 rc = VERR_NO_MEMORY;
645 break;
646 }
647 }
648
649 } while (0);
650
651 LogFlowFuncLeaveRC(rc);
652 return rc;
653}
654
655int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
656{
657 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
658 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
659 AssertReturn(cbData, VERR_INVALID_PARAMETER);
660
661 int rc = VINF_SUCCESS;
662
663 do
664 {
665 if (pCtx->mURI.objURI.IsComplete())
666 {
667 LogFlowFunc(("Warning: Object \"%s\" already completed\n", pCtx->mURI.objURI.GetDestPath().c_str()));
668 rc = VERR_WRONG_ORDER;
669 break;
670 }
671
672 if (!pCtx->mURI.objURI.IsOpen()) /* File opened on host? */
673 {
674 LogFlowFunc(("Warning: Object \"%s\" not opened\n", pCtx->mURI.objURI.GetDestPath().c_str()));
675 rc = VERR_WRONG_ORDER;
676 break;
677 }
678
679 uint32_t cbWritten;
680 rc = pCtx->mURI.objURI.Write(pvData, cbData, &cbWritten);
681 if (RT_SUCCESS(rc))
682 {
683 Assert(cbWritten <= cbData);
684 if (cbWritten < cbData)
685 {
686 /** @todo What to do when the host's disk is full? */
687 rc = VERR_DISK_FULL;
688 }
689
690 if (RT_SUCCESS(rc))
691 rc = i_updateProcess(pCtx, cbWritten);
692 }
693
694 if (RT_SUCCESS(rc))
695 {
696 if (pCtx->mURI.objURI.IsComplete())
697 {
698 /** @todo Sanitize path. */
699 LogRel2(("DnD: File transfer to host complete: %s\n", pCtx->mURI.objURI.GetDestPath().c_str()));
700 rc = VINF_EOF;
701
702 /* Prepare URI object for next use. */
703 pCtx->mURI.objURI.Reset();
704 }
705 }
706 else
707 LogRel(("DnD: Error writing guest file to host to \"%s\": %Rrc\n", pCtx->mURI.objURI.GetDestPath().c_str(), rc));
708
709 } while (0);
710
711 LogFlowFuncLeaveRC(rc);
712 return rc;
713}
714#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
715
716int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
717{
718 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
719
720 GuestDnD *pInst = GuestDnDInst();
721 if (!pInst)
722 return VERR_INVALID_POINTER;
723
724 GuestDnDResponse *pResp = pCtx->mpResp;
725 AssertPtr(pCtx->mpResp);
726
727 /* Is this context already in receiving state? */
728 if (ASMAtomicReadBool(&pCtx->mIsActive))
729 return VERR_WRONG_ORDER;
730
731 ASMAtomicWriteBool(&pCtx->mIsActive, true);
732
733 int rc = pCtx->mCallback.Reset();
734 if (RT_FAILURE(rc))
735 return rc;
736
737 /*
738 * Reset any old data.
739 */
740 pCtx->mData.Reset();
741 pCtx->mURI.Reset();
742
743 /* Set the format we are going to retrieve to have it around
744 * when retrieving the data later. */
745 pResp->reset();
746 pResp->setFormat(pCtx->mFormat);
747
748 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
749 LogFlowFunc(("strFormat=%s, uAction=0x%x, fHasURIList=%RTbool\n", pCtx->mFormat.c_str(), pCtx->mAction, fHasURIList));
750 if (fHasURIList)
751 {
752 rc = i_receiveURIData(pCtx, msTimeout);
753 }
754 else
755 {
756 rc = i_receiveRawData(pCtx, msTimeout);
757 }
758
759 ASMAtomicWriteBool(&pCtx->mIsActive, false);
760
761 LogFlowFuncLeaveRC(rc);
762 return rc;
763}
764
765/* static */
766DECLCALLBACK(int) GuestDnDSource::i_receiveDataThread(RTTHREAD Thread, void *pvUser)
767{
768 LogFlowFunc(("pvUser=%p\n", pvUser));
769
770 RecvDataTask *pTask = (RecvDataTask *)pvUser;
771 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
772
773 const ComObjPtr<GuestDnDSource> pSource(pTask->getSource());
774 Assert(!pSource.isNull());
775
776 int rc;
777
778 AutoCaller autoCaller(pSource);
779 if (SUCCEEDED(autoCaller.rc()))
780 {
781 rc = pSource->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
782 }
783 else
784 rc = VERR_COM_INVALID_OBJECT_STATE;
785
786 ASMAtomicWriteBool(&pSource->mDataBase.mfTransferIsPending, false);
787
788 LogFlowFunc(("pSource=%p returning rc=%Rrc\n", (GuestDnDSource *)pSource, rc));
789
790 if (pTask)
791 delete pTask;
792 return rc;
793}
794
795int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
796{
797 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
798
799 int rc;
800
801 GuestDnDResponse *pResp = pCtx->mpResp;
802 AssertPtr(pCtx->mpResp);
803
804 GuestDnD *pInst = GuestDnDInst();
805 if (!pInst)
806 return VERR_INVALID_POINTER;
807
808#define REGISTER_CALLBACK(x) \
809 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
810 if (RT_FAILURE(rc)) \
811 return rc;
812
813#define UNREGISTER_CALLBACK(x) \
814 rc = pCtx->mpResp->setCallback(x, NULL); \
815 AssertRC(rc);
816
817 /*
818 * Register callbacks.
819 */
820 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
821 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
822
823 do
824 {
825 /*
826 * Receive the raw data.
827 */
828 GuestDnDMsg Msg;
829 Msg.setType(DragAndDropSvc::HOST_DND_GH_EVT_DROPPED);
830 Msg.setNextPointer((void*)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
831 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
832 Msg.setNextUInt32(pCtx->mAction);
833
834 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
835 * the host and therefore now waiting for the actual raw data. */
836 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
837 if (RT_SUCCESS(rc))
838 rc = waitForEvent(msTimeout, pCtx->mCallback, pCtx->mpResp);
839
840 } while (0);
841
842 /*
843 * Unregister callbacks.
844 */
845 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
846 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
847
848#undef REGISTER_CALLBACK
849#undef UNREGISTER_CALLBACK
850
851 if (rc == VERR_CANCELLED)
852 {
853 int rc2 = sendCancel();
854 AssertRC(rc2);
855 }
856
857 LogFlowFuncLeaveRC(rc);
858 return rc;
859}
860
861int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
862{
863 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
864
865 int rc;
866
867 GuestDnDResponse *pResp = pCtx->mpResp;
868 AssertPtr(pCtx->mpResp);
869
870 GuestDnD *pInst = GuestDnDInst();
871 if (!pInst)
872 return VERR_INVALID_POINTER;
873
874#define REGISTER_CALLBACK(x) \
875 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
876 if (RT_FAILURE(rc)) \
877 return rc;
878
879#define UNREGISTER_CALLBACK(x) \
880 { \
881 int rc2 = pResp->setCallback(x, NULL); \
882 AssertRC(rc2); \
883 }
884
885 /*
886 * Register callbacks.
887 */
888 /* Guest callbacks. */
889 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
890 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
891 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DIR);
892 if (mDataBase.mProtocolVersion >= 2)
893 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR);
894 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA);
895
896 do
897 {
898 rc = DnDDirDroppedFilesCreateAndOpenTemp(&pCtx->mURI.mDropDir);
899 if (RT_FAILURE(rc))
900 break;
901 LogFlowFunc(("strDropDir=%s\n", rc, DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir)));
902 if (RT_FAILURE(rc))
903 break;
904
905 /*
906 * Receive the URI list.
907 */
908 GuestDnDMsg Msg;
909 Msg.setType(DragAndDropSvc::HOST_DND_GH_EVT_DROPPED);
910 Msg.setNextPointer((void*)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
911 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
912 Msg.setNextUInt32(pCtx->mAction);
913
914 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
915 * the host and therefore now waiting for the actual URI data. */
916 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
917 if (RT_SUCCESS(rc))
918 rc = waitForEvent(msTimeout, pCtx->mCallback, pCtx->mpResp);
919
920 } while (0);
921
922 /*
923 * Unregister callbacks.
924 */
925 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
926 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
927 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DIR);
928 if (mDataBase.mProtocolVersion >= 2)
929 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR);
930 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA);
931
932#undef REGISTER_CALLBACK
933#undef UNREGISTER_CALLBACK
934
935 int rc2;
936
937 if (rc == VERR_CANCELLED)
938 {
939 rc2 = sendCancel();
940 AssertRC(rc2);
941 }
942
943 if (RT_FAILURE(rc))
944 {
945 rc2 = DnDDirDroppedFilesRollback(&pCtx->mURI.mDropDir); /** @todo Inform user on rollback failure? */
946 LogFlowFunc(("Rolling back ended with rc=%Rrc\n", rc2));
947 }
948
949 rc2 = DnDDirDroppedFilesClose(&pCtx->mURI.mDropDir, RT_FAILURE(rc) ? true : false /* fRemove */);
950 if (RT_SUCCESS(rc))
951 rc = rc2;
952
953 LogFlowFuncLeaveRC(rc);
954 return rc;
955}
956
957/* static */
958DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
959{
960 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
961 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
962
963 GuestDnDSource *pThis = pCtx->mpSource;
964 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
965
966 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
967
968 int rc = VINF_SUCCESS;
969
970 switch (uMsg)
971 {
972#ifdef VBOX_WITH_DRAG_AND_DROP_GH
973 case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
974 {
975 DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
976 AssertPtr(pCBData);
977 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
978 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
979
980 rc = pThis->i_onReceiveData(pCtx, pCBData->pvData, pCBData->cbData, pCBData->cbTotalSize);
981 break;
982 }
983 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
984 {
985 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
986 AssertPtr(pCBData);
987 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
988 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
989
990 pCtx->mpResp->reset();
991 rc = pCtx->mpResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
992 if (RT_SUCCESS(rc))
993 rc = pCBData->rc;
994 break;
995 }
996#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
997 default:
998 rc = VERR_NOT_SUPPORTED;
999 break;
1000 }
1001
1002 if (RT_FAILURE(rc))
1003 {
1004 int rc2 = pCtx->mCallback.Notify(rc);
1005 AssertRC(rc2);
1006 }
1007
1008 LogFlowFuncLeaveRC(rc);
1009 return rc; /* Tell the guest. */
1010}
1011
1012/* static */
1013DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1014{
1015 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1016 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1017
1018 GuestDnDSource *pThis = pCtx->mpSource;
1019 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1020
1021 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1022
1023 int rc = VINF_SUCCESS;
1024 bool fNotify = false;
1025
1026 switch (uMsg)
1027 {
1028#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1029 case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
1030 {
1031 DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
1032 AssertPtr(pCBData);
1033 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1034 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1035
1036 rc = pThis->i_onReceiveData(pCtx, pCBData->pvData, pCBData->cbData, pCBData->cbTotalSize);
1037 break;
1038 }
1039 case DragAndDropSvc::GUEST_DND_GH_SND_DIR:
1040 {
1041 DragAndDropSvc::PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDIRDATA>(pvParms);
1042 AssertPtr(pCBData);
1043 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1044 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1045
1046 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1047 break;
1048 }
1049 case DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR:
1050 {
1051 DragAndDropSvc::PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1052 AssertPtr(pCBData);
1053 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1054 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1055
1056 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1057 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1058 break;
1059 }
1060 case DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA:
1061 {
1062 DragAndDropSvc::PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1063 AssertPtr(pCBData);
1064 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1065 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1066
1067 if (pThis->mDataBase.mProtocolVersion <= 1)
1068 {
1069 /**
1070 * Notes for protocol v1 (< VBox 5.0):
1071 * - Every time this command is being sent it includes the file header,
1072 * so just process both calls here.
1073 * - There was no information whatsoever about the total file size; the old code only
1074 * appended data to the desired file. So just pass 0 as cbSize.
1075 */
1076 rc = pThis->i_onReceiveFileHdr(pCtx,
1077 pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1078 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1079 if (RT_SUCCESS(rc))
1080 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1081 }
1082 else /* Protocol v2 and up. */
1083 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1084 break;
1085 }
1086 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
1087 {
1088 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
1089 AssertPtr(pCBData);
1090 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1091 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1092
1093 pCtx->mpResp->reset();
1094 rc = pCtx->mpResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
1095 if (RT_SUCCESS(rc))
1096 rc = pCBData->rc;
1097 break;
1098 }
1099#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1100 default:
1101 rc = VERR_NOT_SUPPORTED;
1102 break;
1103 }
1104
1105 if (RT_FAILURE(rc))
1106 fNotify = true;
1107
1108 /* All URI data processed? */
1109 if (pCtx->mData.cbProcessed >= pCtx->mData.cbToProcess)
1110 {
1111 Assert(pCtx->mData.cbProcessed == pCtx->mData.cbToProcess);
1112 fNotify = true;
1113 }
1114
1115 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rc=%Rrc\n",
1116 pCtx->mData.cbProcessed, pCtx->mData.cbToProcess, fNotify, rc));
1117
1118 if (fNotify)
1119 {
1120 int rc2 = pCtx->mCallback.Notify(rc);
1121 AssertRC(rc2);
1122 }
1123
1124 LogFlowFuncLeaveRC(rc);
1125 return rc; /* Tell the guest. */
1126}
1127
1128int GuestDnDSource::i_updateProcess(PRECVDATACTX pCtx, uint32_t cbDataAdd)
1129{
1130 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1131
1132 pCtx->mData.cbProcessed += cbDataAdd;
1133 Assert(pCtx->mData.cbProcessed <= pCtx->mData.cbToProcess);
1134
1135 int64_t cbTotal = pCtx->mData.cbToProcess;
1136 uint8_t uPercent = pCtx->mData.cbProcessed * 100 / (cbTotal ? cbTotal : 1);
1137
1138 int rc = pCtx->mpResp->setProgress(uPercent,
1139 uPercent >= 100
1140 ? DragAndDropSvc::DND_PROGRESS_COMPLETE
1141 : DragAndDropSvc::DND_PROGRESS_RUNNING);
1142 return rc;
1143}
1144
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette