VirtualBox

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

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

DnD: Rollback handling, bugfixes, cleanup.

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

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