VirtualBox

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

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

DnD:

  • Overhauled "dropped files" directory + general file handling: Keep the directories/files open when doing the actual transfers (only protocol >= 2)
  • Unified "dropped files" directory creation/rollback handling for guest/host parts.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 KB
Line 
1/* $Id: GuestDnDSourceImpl.cpp 55640 2015-05-04 12:38:57Z 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
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 IDnDSource 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 LogFlowFunc(("hr=%Rhrc, defaultAction=0x%x\n", hr, defaultAction));
280 return hr;
281#endif /* VBOX_WITH_DRAG_AND_DROP */
282}
283
284HRESULT GuestDnDSource::drop(const com::Utf8Str &aFormat, DnDAction_T aAction, ComPtr<IProgress> &aProgress)
285{
286#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
287 ReturnComNotImplemented();
288#else /* VBOX_WITH_DRAG_AND_DROP */
289
290 AutoCaller autoCaller(this);
291 if (FAILED(autoCaller.rc())) return autoCaller.rc();
292
293 /* Input validation. */
294 if (RT_UNLIKELY((aFormat.c_str()) == NULL || *(aFormat.c_str()) == '\0'))
295 return setError(E_INVALIDARG, tr("No drop format specified"));
296
297 uint32_t uAction = GuestDnD::toHGCMAction(aAction);
298 if (isDnDIgnoreAction(uAction)) /* If there is no usable action, ignore this request. */
299 return S_OK;
300
301 /* Note: At the moment we only support one transfer at a time. */
302 if (ASMAtomicReadBool(&mDataBase.mfTransferIsPending))
303 return setError(E_INVALIDARG, tr("Another drop operation already is in progress"));
304
305 ASMAtomicWriteBool(&mDataBase.mfTransferIsPending, true);
306
307 /* Dito. */
308 GuestDnDResponse *pResp = GuestDnDInst()->response();
309 AssertPtr(pResp);
310
311 HRESULT hr = pResp->resetProgress(m_pGuest);
312 if (FAILED(hr))
313 return hr;
314
315 try
316 {
317 mData.mRecvCtx.mIsActive = false;
318 mData.mRecvCtx.mpSource = this;
319 mData.mRecvCtx.mpResp = pResp;
320 mData.mRecvCtx.mFormat = aFormat;
321
322 RecvDataTask *pTask = new RecvDataTask(this, &mData.mRecvCtx);
323 AssertReturn(pTask->isOk(), pTask->getRC());
324
325 int rc = RTThreadCreate(NULL, GuestDnDSource::i_receiveDataThread,
326 (void *)pTask, 0, RTTHREADTYPE_MAIN_WORKER, 0, "dndSrcRcvData");
327 if (RT_SUCCESS(rc))
328 {
329 hr = pResp->queryProgressTo(aProgress.asOutParam());
330 ComAssertComRC(hr);
331
332 /* Note: pTask is now owned by the worker thread. */
333 }
334 else
335 hr = setError(VBOX_E_IPRT_ERROR, tr("Starting thread failed (%Rrc)"), rc);
336 }
337 catch(std::bad_alloc &)
338 {
339 hr = setError(E_OUTOFMEMORY);
340 }
341
342 /* Note: mDataBase.mfTransferIsPending will be set to false again by i_receiveDataThread. */
343
344 LogFlowFunc(("Returning hr=%Rhrc\n", hr));
345 return hr;
346#endif /* VBOX_WITH_DRAG_AND_DROP */
347}
348
349HRESULT GuestDnDSource::receiveData(std::vector<BYTE> &aData)
350{
351#if !defined(VBOX_WITH_DRAG_AND_DROP) || !defined(VBOX_WITH_DRAG_AND_DROP_GH)
352 ReturnComNotImplemented();
353#else /* VBOX_WITH_DRAG_AND_DROP */
354
355 /* Input validation. */
356
357 AutoCaller autoCaller(this);
358 if (FAILED(autoCaller.rc())) return autoCaller.rc();
359
360 /* Don't allow receiving the actual data until our transfer
361 * actually is complete. */
362 if (ASMAtomicReadBool(&mDataBase.mfTransferIsPending))
363 return setError(E_INVALIDARG, tr("Current drop operation still in progress"));
364
365 PRECVDATACTX pCtx = &mData.mRecvCtx;
366
367 if (pCtx->mData.vecData.empty())
368 {
369 aData.resize(0);
370 return S_OK;
371 }
372
373 HRESULT hr = S_OK;
374 size_t cbData;
375
376 try
377 {
378 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
379 if (fHasURIList)
380 {
381 Utf8Str strURIs = pCtx->mURI.lstURI.RootToString(RTCString(DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir)));
382 cbData = strURIs.length();
383
384 LogFlowFunc(("Found %zu root URIs (%zu bytes)\n", pCtx->mURI.lstURI.RootCount(), cbData));
385
386 aData.resize(cbData + 1 /* Include termination */);
387 memcpy(&aData.front(), strURIs.c_str(), cbData);
388 }
389 else
390 {
391 cbData = pCtx->mData.vecData.size();
392
393 /* Copy the data into a safe array of bytes. */
394 aData.resize(cbData);
395 memcpy(&aData.front(), &pCtx->mData.vecData[0], cbData);
396 }
397 }
398 catch (std::bad_alloc &)
399 {
400 hr = E_OUTOFMEMORY;
401 }
402
403 LogFlowFunc(("Returning cbData=%zu, hr=%Rhrc\n", cbData, hr));
404 return hr;
405#endif /* VBOX_WITH_DRAG_AND_DROP */
406}
407
408// implementation of internal methods.
409/////////////////////////////////////////////////////////////////////////////
410
411#ifdef VBOX_WITH_DRAG_AND_DROP_GH
412int GuestDnDSource::i_onReceiveData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData, uint64_t cbTotalSize)
413{
414 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
415 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
416 AssertReturn(cbData, VERR_INVALID_PARAMETER);
417 AssertReturn(cbTotalSize, VERR_INVALID_PARAMETER);
418
419 LogFlowFunc(("cbData=%RU32, cbTotalSize=%RU64\n", cbData, cbTotalSize));
420
421 int rc = VINF_SUCCESS;
422
423 try
424 {
425 if ( cbData > cbTotalSize
426 || cbData > mData.mcbBlockSize)
427 {
428 LogFlowFunc(("Data sizes invalid: cbData=%RU32, cbTotalSize=%RU64\n", cbData, cbTotalSize));
429 rc = VERR_INVALID_PARAMETER;
430 }
431 else if (cbData < pCtx->mData.vecData.size())
432 {
433 AssertMsgFailed(("New size (%RU64) is smaller than current size (%zu)\n", cbTotalSize, pCtx->mData.vecData.size()));
434 rc = VERR_INVALID_PARAMETER;
435 }
436
437 if (RT_SUCCESS(rc))
438 {
439 pCtx->mData.vecData.insert(pCtx->mData.vecData.begin(), (BYTE *)pvData, (BYTE *)pvData + cbData);
440
441 LogFlowFunc(("vecDataSize=%zu, cbData=%RU32, cbTotalSize=%RU64\n", pCtx->mData.vecData.size(), cbData, cbTotalSize));
442
443 /* Data transfer complete? */
444 Assert(cbData <= pCtx->mData.vecData.size());
445 if (cbData == pCtx->mData.vecData.size())
446 {
447 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
448 LogFlowFunc(("fHasURIList=%RTbool, cbTotalSize=%RU32\n", fHasURIList, cbTotalSize));
449 if (fHasURIList)
450 {
451 /* Try parsing the data as URI list. */
452 rc = pCtx->mURI.lstURI.RootFromURIData(&pCtx->mData.vecData[0], pCtx->mData.vecData.size(), 0 /* uFlags */);
453 if (RT_SUCCESS(rc))
454 {
455 pCtx->mData.cbProcessed = 0;
456
457 /*
458 * Assign new total size which also includes all file data to receive
459 * from the guest.
460 */
461 pCtx->mData.cbToProcess = cbTotalSize;
462
463 LogFlowFunc(("URI data => cbToProcess=%RU64\n", pCtx->mData.cbToProcess));
464 }
465 }
466 }
467 }
468 }
469 catch (std::bad_alloc &)
470 {
471 rc = VERR_NO_MEMORY;
472 }
473
474 LogFlowFuncLeaveRC(rc);
475 return rc;
476}
477
478int GuestDnDSource::i_onReceiveDir(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath, uint32_t fMode)
479{
480 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
481 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
482 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
483
484 LogFlowFunc(("pszPath=%s, cbPath=%RU32, fMode=0x%x\n", pszPath, cbPath, fMode));
485
486 int rc;
487 char *pszDir = RTPathJoinA(DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir), pszPath);
488 if (pszDir)
489 {
490 rc = RTDirCreateFullPath(pszDir, fMode);
491 if (RT_FAILURE(rc))
492 LogRel2(("DnD: Error creating guest directory \"%s\" on the host, rc=%Rrc\n", pszDir, rc));
493
494 RTStrFree(pszDir);
495 }
496 else
497 rc = VERR_NO_MEMORY;
498
499 if (RT_SUCCESS(rc))
500 {
501 if (mDataBase.mProtocolVersion <= 2)
502 {
503 /*
504 * Protocols v1/v2 do *not* send root element names (files/directories)
505 * in URI format. The initial GUEST_DND_GH_SND_DATA message(s) however
506 * did take those element names into account, but *with* URI decoration
507 * when it comes to communicating the total bytes being sent.
508 *
509 * So translate the path into a valid URI path and add the resulting
510 * length (+ "\r\n" and termination) to the total bytes received
511 * to keep the accounting right.
512 */
513 char *pszPathURI = RTUriCreate("file" /* pszScheme */, "/" /* pszAuthority */,
514 pszPath /* pszPath */,
515 NULL /* pszQuery */, NULL /* pszFragment */);
516 if (pszPathURI)
517 {
518 bool fHasPath = RTPathHasPath(pszPath); /* Use original data received. */
519 if (!fHasPath) /* Root path? */
520 {
521 cbPath = strlen(pszPathURI);
522 cbPath += 3; /* Include "\r" + "\n" + termination -- see above. */
523
524 rc = i_updateProcess(pCtx, cbPath);
525 }
526
527 LogFlowFunc(("URI pszPathURI=%s, fHasPath=%RTbool, cbPath=%RU32\n", pszPathURI, fHasPath, cbPath));
528 RTStrFree(pszPathURI);
529 }
530 else
531 rc = VERR_NO_MEMORY;
532 }
533 }
534
535 LogFlowFuncLeaveRC(rc);
536 return rc;
537}
538
539int GuestDnDSource::i_onReceiveFileHdr(PRECVDATACTX pCtx, const char *pszPath, uint32_t cbPath,
540 uint64_t cbSize, uint32_t fMode, uint32_t fFlags)
541{
542 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
543 AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
544 AssertReturn(cbPath, VERR_INVALID_PARAMETER);
545 AssertReturn(fMode, VERR_INVALID_PARAMETER);
546 /* fFlags are optional. */
547
548 LogFlowFunc(("pszPath=%s, cbPath=%RU32, cbSize=%RU64, fMode=0x%x, fFlags=0x%x\n", pszPath, cbPath, cbSize, fMode, fFlags));
549
550 int rc = VINF_SUCCESS;
551
552 do
553 {
554 if (!pCtx->mURI.objURI.IsComplete())
555 {
556 LogFlowFunc(("Warning: Object \"%s\" not complete yet\n", pCtx->mURI.objURI.GetDestPath().c_str()));
557 rc = VERR_INVALID_PARAMETER;
558 break;
559 }
560
561 if (pCtx->mURI.objURI.IsOpen()) /* File already opened? */
562 {
563 LogFlowFunc(("Warning: Current opened object is \"%s\"\n", pCtx->mURI.objURI.GetDestPath().c_str()));
564 rc = VERR_WRONG_ORDER;
565 break;
566 }
567
568 char pszPathAbs[RTPATH_MAX];
569 rc = RTPathJoin(pszPathAbs, sizeof(pszPathAbs), DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir), pszPath);
570 if (RT_FAILURE(rc))
571 {
572 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
573 break;
574 }
575
576 rc = DnDPathSanitize(pszPathAbs, sizeof(pszPathAbs));
577 if (RT_FAILURE(rc))
578 {
579 LogFlowFunc(("Warning: Rebasing current file failed with rc=%Rrc\n", rc));
580 break;
581 }
582
583 LogFunc(("Rebased to: %s\n", pszPathAbs));
584
585 /** @todo Add sparse file support based on fFlags? (Use Open(..., fFlags | SPARSE). */
586 rc = pCtx->mURI.objURI.OpenEx(pszPathAbs, DnDURIObject::File, DnDURIObject::Target,
587 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE,
588 (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR);
589 if (RT_SUCCESS(rc))
590 {
591 /* Note: Protocol v1 does not send any file sizes, so always 0. */
592 if (mDataBase.mProtocolVersion >= 2)
593 rc = pCtx->mURI.objURI.SetSize(cbSize);
594
595 /** @todo Unescpae path before printing. */
596 LogRel2(("DnD: Transferring guest file to host: %s (%RU64 bytes, mode 0x%x)\n",
597 pCtx->mURI.objURI.GetDestPath().c_str(), pCtx->mURI.objURI.GetSize(), pCtx->mURI.objURI.GetMode()));
598
599 /** @todo Set progress object title to current file being transferred? */
600
601 if (!cbSize) /* 0-byte file? Close again. */
602 pCtx->mURI.objURI.Close();
603 }
604 else
605 {
606 LogRel2(("DnD: Error opening/creating guest file '%s' on host, rc=%Rrc\n",
607 pCtx->mURI.objURI.GetDestPath().c_str(), rc));
608 break;
609 }
610
611 if (mDataBase.mProtocolVersion <= 2)
612 {
613 /*
614 * Protocols v1/v2 do *not* send root element names (files/directories)
615 * in URI format. The initial GUEST_DND_GH_SND_DATA message(s) however
616 * did take those element names into account, but *with* URI decoration
617 * when it comes to communicating the total bytes being sent.
618 *
619 * So translate the path into a valid URI path and add the resulting
620 * length (+ "\r\n" and termination) to the total bytes received
621 * to keep the accounting right.
622 */
623 char *pszPathURI = RTUriCreate("file" /* pszScheme */, "/" /* pszAuthority */,
624 pszPath /* pszPath */,
625 NULL /* pszQuery */, NULL /* pszFragment */);
626 if (pszPathURI)
627 {
628 bool fHasPath = RTPathHasPath(pszPath); /* Use original data received. */
629 if (!fHasPath) /* Root path? */
630 {
631 cbPath = strlen(pszPathURI);
632 cbPath += 3; /* Include "\r" + "\n" + termination -- see above. */
633
634 rc = i_updateProcess(pCtx, cbPath);
635 }
636
637 LogFlowFunc(("URI pszPathURI=%s, fHasPath=%RTbool, cbPath=%RU32\n", pszPathURI, fHasPath, cbPath));
638 RTStrFree(pszPathURI);
639 }
640 else
641 {
642 rc = VERR_NO_MEMORY;
643 break;
644 }
645 }
646
647 } while (0);
648
649 LogFlowFuncLeaveRC(rc);
650 return rc;
651}
652
653int GuestDnDSource::i_onReceiveFileData(PRECVDATACTX pCtx, const void *pvData, uint32_t cbData)
654{
655 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
656 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
657 AssertReturn(cbData, VERR_INVALID_PARAMETER);
658
659 int rc = VINF_SUCCESS;
660
661 do
662 {
663 if (pCtx->mURI.objURI.IsComplete())
664 {
665 LogFlowFunc(("Warning: Object \"%s\" already completed\n", pCtx->mURI.objURI.GetDestPath().c_str()));
666 rc = VERR_WRONG_ORDER;
667 break;
668 }
669
670 if (!pCtx->mURI.objURI.IsOpen()) /* File opened on host? */
671 {
672 LogFlowFunc(("Warning: Object \"%s\" not opened\n", pCtx->mURI.objURI.GetDestPath().c_str()));
673 rc = VERR_WRONG_ORDER;
674 break;
675 }
676
677 uint32_t cbWritten;
678 rc = pCtx->mURI.objURI.Write(pvData, cbData, &cbWritten);
679 if (RT_SUCCESS(rc))
680 {
681 Assert(cbWritten <= cbData);
682 if (cbWritten < cbData)
683 {
684 /** @todo What to do when the host's disk is full? */
685 rc = VERR_DISK_FULL;
686 }
687
688 if (RT_SUCCESS(rc))
689 rc = i_updateProcess(pCtx, cbWritten);
690 }
691
692 if (RT_SUCCESS(rc))
693 {
694 if (pCtx->mURI.objURI.IsComplete())
695 {
696 /** @todo Sanitize path. */
697 LogRel2(("DnD: File transfer to host complete: %s\n", pCtx->mURI.objURI.GetDestPath().c_str()));
698 rc = VINF_EOF;
699
700 /* Prepare URI object for next use. */
701 pCtx->mURI.objURI.Reset();
702 }
703 }
704 else
705 LogRel(("DnD: Error writing guest file to host to \"%s\": %Rrc\n", pCtx->mURI.objURI.GetDestPath().c_str(), rc));
706
707 } while (0);
708
709 LogFlowFuncLeaveRC(rc);
710 return rc;
711}
712#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
713
714int GuestDnDSource::i_receiveData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
715{
716 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
717
718 GuestDnD *pInst = GuestDnDInst();
719 if (!pInst)
720 return VERR_INVALID_POINTER;
721
722 GuestDnDResponse *pResp = pCtx->mpResp;
723 AssertPtr(pCtx->mpResp);
724
725 /* Is this context already in receiving state? */
726 if (ASMAtomicReadBool(&pCtx->mIsActive))
727 return VERR_WRONG_ORDER;
728
729 ASMAtomicWriteBool(&pCtx->mIsActive, true);
730
731 int rc = pCtx->mCallback.Reset();
732 if (RT_FAILURE(rc))
733 return rc;
734
735 /*
736 * Reset any old data.
737 */
738 pCtx->mData.Reset();
739 pCtx->mURI.Reset();
740
741 /* Set the format we are going to retrieve to have it around
742 * when retrieving the data later. */
743 pResp->reset();
744 pResp->setFormat(pCtx->mFormat);
745
746 bool fHasURIList = DnDMIMENeedsDropDir(pCtx->mFormat.c_str(), pCtx->mFormat.length());
747 LogFlowFunc(("strFormat=%s, uAction=0x%x, fHasURIList=%RTbool\n", pCtx->mFormat.c_str(), pCtx->mAction, fHasURIList));
748 if (fHasURIList)
749 {
750 rc = i_receiveURIData(pCtx, msTimeout);
751 }
752 else
753 {
754 rc = i_receiveRawData(pCtx, msTimeout);
755 }
756
757 ASMAtomicWriteBool(&pCtx->mIsActive, false);
758
759 LogFlowFuncLeaveRC(rc);
760 return rc;
761}
762
763/* static */
764DECLCALLBACK(int) GuestDnDSource::i_receiveDataThread(RTTHREAD Thread, void *pvUser)
765{
766 LogFlowFunc(("pvUser=%p\n", pvUser));
767
768 RecvDataTask *pTask = (RecvDataTask *)pvUser;
769 AssertPtrReturn(pTask, VERR_INVALID_POINTER);
770
771 const ComObjPtr<GuestDnDSource> pSource(pTask->getSource());
772 Assert(!pSource.isNull());
773
774 int rc;
775
776 AutoCaller autoCaller(pSource);
777 if (SUCCEEDED(autoCaller.rc()))
778 {
779 rc = pSource->i_receiveData(pTask->getCtx(), RT_INDEFINITE_WAIT /* msTimeout */);
780 }
781 else
782 rc = VERR_COM_INVALID_OBJECT_STATE;
783
784 ASMAtomicWriteBool(&pSource->mDataBase.mfTransferIsPending, false);
785
786 LogFlowFunc(("pSource=%p returning rc=%Rrc\n", (GuestDnDSource *)pSource, rc));
787
788 if (pTask)
789 delete pTask;
790 return rc;
791}
792
793int GuestDnDSource::i_receiveRawData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
794{
795 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
796
797 int rc;
798
799 GuestDnDResponse *pResp = pCtx->mpResp;
800 AssertPtr(pCtx->mpResp);
801
802 GuestDnD *pInst = GuestDnDInst();
803 if (!pInst)
804 return VERR_INVALID_POINTER;
805
806#define REGISTER_CALLBACK(x) \
807 rc = pResp->setCallback(x, i_receiveRawDataCallback, pCtx); \
808 if (RT_FAILURE(rc)) \
809 return rc;
810
811#define UNREGISTER_CALLBACK(x) \
812 rc = pCtx->mpResp->setCallback(x, NULL); \
813 AssertRC(rc);
814
815 /*
816 * Register callbacks.
817 */
818 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
819 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
820
821 do
822 {
823 /*
824 * Receive the raw data.
825 */
826 GuestDnDMsg Msg;
827 Msg.setType(DragAndDropSvc::HOST_DND_GH_EVT_DROPPED);
828 Msg.setNextPointer((void*)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
829 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
830 Msg.setNextUInt32(pCtx->mAction);
831
832 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
833 * the host and therefore now waiting for the actual raw data. */
834 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
835 if (RT_SUCCESS(rc))
836 rc = waitForEvent(msTimeout, pCtx->mCallback, pCtx->mpResp);
837
838 } while (0);
839
840 /*
841 * Unregister callbacks.
842 */
843 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
844 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
845
846#undef REGISTER_CALLBACK
847#undef UNREGISTER_CALLBACK
848
849 if (rc == VERR_CANCELLED)
850 {
851 int rc2 = sendCancel();
852 AssertRC(rc2);
853 }
854
855 LogFlowFuncLeaveRC(rc);
856 return rc;
857}
858
859int GuestDnDSource::i_receiveURIData(PRECVDATACTX pCtx, RTMSINTERVAL msTimeout)
860{
861 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
862
863 int rc;
864
865 GuestDnDResponse *pResp = pCtx->mpResp;
866 AssertPtr(pCtx->mpResp);
867
868 GuestDnD *pInst = GuestDnDInst();
869 if (!pInst)
870 return VERR_INVALID_POINTER;
871
872#define REGISTER_CALLBACK(x) \
873 rc = pResp->setCallback(x, i_receiveURIDataCallback, pCtx); \
874 if (RT_FAILURE(rc)) \
875 return rc;
876
877#define UNREGISTER_CALLBACK(x) \
878 { \
879 int rc2 = pResp->setCallback(x, NULL); \
880 AssertRC(rc2); \
881 }
882
883 /*
884 * Register callbacks.
885 */
886 /* Guest callbacks. */
887 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
888 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
889 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DIR);
890 if (mDataBase.mProtocolVersion >= 2)
891 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR);
892 REGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA);
893
894 do
895 {
896 rc = DnDDirDroppedFilesCreateAndOpenTemp(&pCtx->mURI.mDropDir);
897 if (RT_FAILURE(rc))
898 break;
899 LogFlowFunc(("strDropDir=%s\n", rc, DnDDirDroppedFilesGetDirAbs(&pCtx->mURI.mDropDir)));
900 if (RT_FAILURE(rc))
901 break;
902
903 /*
904 * Receive the URI list.
905 */
906 GuestDnDMsg Msg;
907 Msg.setType(DragAndDropSvc::HOST_DND_GH_EVT_DROPPED);
908 Msg.setNextPointer((void*)pCtx->mFormat.c_str(), (uint32_t)pCtx->mFormat.length() + 1);
909 Msg.setNextUInt32((uint32_t)pCtx->mFormat.length() + 1);
910 Msg.setNextUInt32(pCtx->mAction);
911
912 /* Make the initial call to the guest by telling that we initiated the "dropped" event on
913 * the host and therefore now waiting for the actual URI data. */
914 rc = pInst->hostCall(Msg.getType(), Msg.getCount(), Msg.getParms());
915 if (RT_SUCCESS(rc))
916 rc = waitForEvent(msTimeout, pCtx->mCallback, pCtx->mpResp);
917
918 } while (0);
919
920 /*
921 * Unregister callbacks.
922 */
923 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_EVT_ERROR);
924 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DATA);
925 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_DIR);
926 if (mDataBase.mProtocolVersion >= 2)
927 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR);
928 UNREGISTER_CALLBACK(DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA);
929
930#undef REGISTER_CALLBACK
931#undef UNREGISTER_CALLBACK
932
933 int rc2;
934
935 if (rc == VERR_CANCELLED)
936 {
937 rc2 = sendCancel();
938 AssertRC(rc2);
939 }
940
941 if (RT_FAILURE(rc))
942 {
943 rc2 = DnDDirDroppedFilesRollback(&pCtx->mURI.mDropDir); /** @todo Inform user on rollback failure? */
944 LogFlowFunc(("Rolling back ended with rc=%Rrc\n", rc2));
945 }
946
947 rc2 = DnDDirDroppedFilesClose(&pCtx->mURI.mDropDir, RT_FAILURE(rc) ? true : false /* fRemove */);
948 if (RT_SUCCESS(rc))
949 rc = rc2;
950
951 LogFlowFuncLeaveRC(rc);
952 return rc;
953}
954
955/* static */
956DECLCALLBACK(int) GuestDnDSource::i_receiveRawDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
957{
958 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
959 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
960
961 GuestDnDSource *pThis = pCtx->mpSource;
962 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
963
964 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
965
966 int rc = VINF_SUCCESS;
967
968 switch (uMsg)
969 {
970#ifdef VBOX_WITH_DRAG_AND_DROP_GH
971 case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
972 {
973 DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
974 AssertPtr(pCBData);
975 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
976 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
977
978 rc = pThis->i_onReceiveData(pCtx, pCBData->pvData, pCBData->cbData, pCBData->cbTotalSize);
979 break;
980 }
981 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
982 {
983 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
984 AssertPtr(pCBData);
985 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
986 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
987
988 pCtx->mpResp->reset();
989 rc = pCtx->mpResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
990 if (RT_SUCCESS(rc))
991 rc = pCBData->rc;
992 break;
993 }
994#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
995 default:
996 rc = VERR_NOT_SUPPORTED;
997 break;
998 }
999
1000 if (RT_FAILURE(rc))
1001 {
1002 int rc2 = pCtx->mCallback.Notify(rc);
1003 AssertRC(rc2);
1004 }
1005
1006 LogFlowFuncLeaveRC(rc);
1007 return rc; /* Tell the guest. */
1008}
1009
1010/* static */
1011DECLCALLBACK(int) GuestDnDSource::i_receiveURIDataCallback(uint32_t uMsg, void *pvParms, size_t cbParms, void *pvUser)
1012{
1013 PRECVDATACTX pCtx = (PRECVDATACTX)pvUser;
1014 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1015
1016 GuestDnDSource *pThis = pCtx->mpSource;
1017 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
1018
1019 LogFlowFunc(("pThis=%p, uMsg=%RU32\n", pThis, uMsg));
1020
1021 int rc = VINF_SUCCESS;
1022 bool fNotify = false;
1023
1024 switch (uMsg)
1025 {
1026#ifdef VBOX_WITH_DRAG_AND_DROP_GH
1027 case DragAndDropSvc::GUEST_DND_GH_SND_DATA:
1028 {
1029 DragAndDropSvc::PVBOXDNDCBSNDDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDATADATA>(pvParms);
1030 AssertPtr(pCBData);
1031 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1032 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1033
1034 rc = pThis->i_onReceiveData(pCtx, pCBData->pvData, pCBData->cbData, pCBData->cbTotalSize);
1035 break;
1036 }
1037 case DragAndDropSvc::GUEST_DND_GH_SND_DIR:
1038 {
1039 DragAndDropSvc::PVBOXDNDCBSNDDIRDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDDIRDATA>(pvParms);
1040 AssertPtr(pCBData);
1041 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDDIRDATA) == cbParms, VERR_INVALID_PARAMETER);
1042 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_DIR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1043
1044 rc = pThis->i_onReceiveDir(pCtx, pCBData->pszPath, pCBData->cbPath, pCBData->fMode);
1045 break;
1046 }
1047 case DragAndDropSvc::GUEST_DND_GH_SND_FILE_HDR:
1048 {
1049 DragAndDropSvc::PVBOXDNDCBSNDFILEHDRDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDFILEHDRDATA>(pvParms);
1050 AssertPtr(pCBData);
1051 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDFILEHDRDATA) == cbParms, VERR_INVALID_PARAMETER);
1052 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_FILE_HDR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1053
1054 rc = pThis->i_onReceiveFileHdr(pCtx, pCBData->pszFilePath, pCBData->cbFilePath,
1055 pCBData->cbSize, pCBData->fMode, pCBData->fFlags);
1056 break;
1057 }
1058 case DragAndDropSvc::GUEST_DND_GH_SND_FILE_DATA:
1059 {
1060 DragAndDropSvc::PVBOXDNDCBSNDFILEDATADATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBSNDFILEDATADATA>(pvParms);
1061 AssertPtr(pCBData);
1062 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBSNDFILEDATADATA) == cbParms, VERR_INVALID_PARAMETER);
1063 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_SND_FILE_DATA == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1064
1065 if (pThis->mDataBase.mProtocolVersion <= 1)
1066 {
1067 /**
1068 * Notes for protocol v1 (< VBox 5.0):
1069 * - Every time this command is being sent it includes the file header,
1070 * so just process both calls here.
1071 * - There was no information whatsoever about the total file size; the old code only
1072 * appended data to the desired file. So just pass 0 as cbSize.
1073 */
1074 rc = pThis->i_onReceiveFileHdr(pCtx,
1075 pCBData->u.v1.pszFilePath, pCBData->u.v1.cbFilePath,
1076 0 /* cbSize */, pCBData->u.v1.fMode, 0 /* fFlags */);
1077 if (RT_SUCCESS(rc))
1078 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1079 }
1080 else /* Protocol v2 and up. */
1081 rc = pThis->i_onReceiveFileData(pCtx, pCBData->pvData, pCBData->cbData);
1082 break;
1083 }
1084 case DragAndDropSvc::GUEST_DND_GH_EVT_ERROR:
1085 {
1086 DragAndDropSvc::PVBOXDNDCBEVTERRORDATA pCBData = reinterpret_cast<DragAndDropSvc::PVBOXDNDCBEVTERRORDATA>(pvParms);
1087 AssertPtr(pCBData);
1088 AssertReturn(sizeof(DragAndDropSvc::VBOXDNDCBEVTERRORDATA) == cbParms, VERR_INVALID_PARAMETER);
1089 AssertReturn(DragAndDropSvc::CB_MAGIC_DND_GH_EVT_ERROR == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
1090
1091 pCtx->mpResp->reset();
1092 rc = pCtx->mpResp->setProgress(100, DragAndDropSvc::DND_PROGRESS_ERROR, pCBData->rc);
1093 if (RT_SUCCESS(rc))
1094 rc = pCBData->rc;
1095 break;
1096 }
1097#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
1098 default:
1099 rc = VERR_NOT_SUPPORTED;
1100 break;
1101 }
1102
1103 if (RT_FAILURE(rc))
1104 fNotify = true;
1105
1106 /* All URI data processed? */
1107 if (pCtx->mData.cbProcessed >= pCtx->mData.cbToProcess)
1108 {
1109 Assert(pCtx->mData.cbProcessed == pCtx->mData.cbToProcess);
1110 fNotify = true;
1111 }
1112
1113 LogFlowFunc(("cbProcessed=%RU64, cbToProcess=%RU64, fNotify=%RTbool, rc=%Rrc\n",
1114 pCtx->mData.cbProcessed, pCtx->mData.cbToProcess, fNotify, rc));
1115
1116 if (fNotify)
1117 {
1118 int rc2 = pCtx->mCallback.Notify(rc);
1119 AssertRC(rc2);
1120 }
1121
1122 LogFlowFuncLeaveRC(rc);
1123 return rc; /* Tell the guest. */
1124}
1125
1126int GuestDnDSource::i_updateProcess(PRECVDATACTX pCtx, uint32_t cbDataAdd)
1127{
1128 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
1129
1130 pCtx->mData.cbProcessed += cbDataAdd;
1131 Assert(pCtx->mData.cbProcessed <= pCtx->mData.cbToProcess);
1132
1133 int64_t cbTotal = pCtx->mData.cbToProcess;
1134 uint8_t uPercent = pCtx->mData.cbProcessed * 100 / (cbTotal ? cbTotal : 1);
1135
1136 int rc = pCtx->mpResp->setProgress(uPercent,
1137 uPercent >= 100
1138 ? DragAndDropSvc::DND_PROGRESS_COMPLETE
1139 : DragAndDropSvc::DND_PROGRESS_RUNNING);
1140 return rc;
1141}
1142
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