VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/medium/UIMedium.cpp@ 102493

Last change on this file since 102493 was 98103, checked in by vboxsync, 20 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.2 KB
Line 
1/* $Id: UIMedium.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMedium class implementation.
4 */
5
6/*
7 * Copyright (C) 2009-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QApplication>
30#include <QDir>
31
32/* GUI includes: */
33#include "UICommon.h"
34#include "UIConverter.h"
35#include "UIErrorString.h"
36#include "UIExtraDataManager.h"
37#include "UIIconPool.h"
38#include "UIMedium.h"
39#include "UITranslator.h"
40
41/* COM includes: */
42#include "CMachine.h"
43#include "CSnapshot.h"
44
45QUuid UIMedium::m_uNullID;
46QString UIMedium::m_sstrTable = QString("<table>%1</table>");
47QString UIMedium::m_sstrRow = QString("<tr><td>%1</td></tr>");
48
49UIMedium::UIMedium()
50 : m_type(UIMediumDeviceType_Invalid)
51 , m_medium(CMedium())
52 , m_state(KMediumState_NotCreated)
53 , m_enmMediumType(KMediumType_Max)
54 , m_enmMediumVariant(KMediumVariant_Max)
55{
56 refresh();
57}
58
59UIMedium::UIMedium(const CMedium &medium, UIMediumDeviceType type)
60 : m_type(type)
61 , m_medium(medium)
62 , m_state(KMediumState_NotCreated)
63 , m_enmMediumType(KMediumType_Max)
64 , m_enmMediumVariant(KMediumVariant_Max)
65{
66 refresh();
67}
68
69UIMedium::UIMedium(const CMedium &medium, UIMediumDeviceType type, KMediumState state)
70 : m_type(type)
71 , m_medium(medium)
72 , m_state(state)
73 , m_enmMediumType(KMediumType_Max)
74 , m_enmMediumVariant(KMediumVariant_Max)
75{
76 refresh();
77}
78
79UIMedium::UIMedium(const UIMedium &other)
80{
81 *this = other;
82}
83
84UIMedium& UIMedium::operator=(const UIMedium &other)
85{
86 m_type = other.type();
87
88 m_medium = other.medium();
89
90 m_state = other.state();
91 m_result = other.result();
92 m_strLastAccessError = other.lastAccessError();
93
94 m_uId = other.id();
95 m_uRootId = other.rootID();
96 m_uParentId = other.parentID();
97
98 m_uKey = other.key();
99
100 m_strName = other.name();
101 m_strLocation = other.location();
102 m_strDescription = other.description();
103
104 m_uSize = other.sizeInBytes();
105 m_uLogicalSize = other.logicalSizeInBytes();
106 m_strSize = other.size();
107 m_strLogicalSize = other.logicalSize();
108
109 m_enmMediumType = other.mediumType();
110 m_enmMediumVariant = other.mediumVariant();
111
112 m_strHardDiskType = other.hardDiskType();
113 m_strHardDiskFormat = other.hardDiskFormat();
114 m_fHasChildren = other.hasChildren();
115 m_strStorageDetails = other.storageDetails();
116 m_strEncryptionPasswordID = other.encryptionPasswordID();
117
118 m_strUsage = other.usage();
119 m_strToolTip = other.tip();
120 m_machineIds = other.machineIds();
121 m_curStateMachineIds = other.curStateMachineIds();
122
123 m_noDiffs = other.cache();
124
125 m_fHidden = other.m_fHidden;
126 m_fUsedByHiddenMachinesOnly = other.m_fUsedByHiddenMachinesOnly;
127 m_fReadOnly = other.isReadOnly();
128 m_fUsedInSnapshots = other.isUsedInSnapshots();
129 m_fHostDrive = other.isHostDrive();
130 m_fEncrypted = other.isEncrypted();
131
132 return *this;
133}
134
135void UIMedium::blockAndQueryState()
136{
137 /* Ignore for NULL medium: */
138 if (m_medium.isNull())
139 return;
140
141 /* Acquire actual medium state: */
142 m_state = m_medium.RefreshState();
143
144 /* Save the result to distinguish between
145 * inaccessible and e.g. uninitialized objects: */
146 m_result = COMResult(m_medium);
147 if (!m_result.isOk())
148 {
149 m_state = KMediumState_Inaccessible;
150 m_strLastAccessError = QString();
151 }
152 else
153 m_strLastAccessError = m_medium.GetLastAccessError();
154
155 /* Refresh finally: */
156 refresh();
157}
158
159void UIMedium::refresh()
160{
161 /* Reset ID parameters: */
162 m_uId = nullID();
163 m_uRootId = nullID();
164 m_uParentId = nullID();
165
166 /* Reset cache parameters: */
167 //m_strKey = nullID();
168
169 /* Reset name/location/description/size parameters: */
170 m_strName = QApplication::translate("UICommon", "Empty", "medium");
171 m_strLocation = m_strSize = m_strLogicalSize = QString("--");
172 m_strDescription = QString();
173 m_uSize = m_uLogicalSize = 0;
174
175 /* Reset medium type & variant parameter: */
176 m_enmMediumType = KMediumType_Max;
177 m_enmMediumVariant = KMediumVariant_Max;
178
179 /* Reset hard drive related parameters: */
180 m_strHardDiskType = QString();
181 m_strHardDiskFormat = QString();
182 m_fHasChildren = false;
183 m_strStorageDetails = QString();
184 m_strEncryptionPasswordID = QString();
185
186 /* Reset data parameters: */
187 m_strUsage = QString();
188 m_strToolTip = QString();
189 m_machineIds.clear();
190 m_curStateMachineIds.clear();
191
192 /* Reset m_noDiffs: */
193 m_noDiffs.isSet = false;
194
195 /* Reset flags: */
196 m_fHidden = false;
197 m_fUsedByHiddenMachinesOnly = false;
198 m_fReadOnly = false;
199 m_fUsedInSnapshots = false;
200 m_fHostDrive = false;
201 m_fEncrypted = false;
202
203 /* For non NULL medium: */
204 if (!m_medium.isNull())
205 {
206 /* Refresh medium ID: */
207 m_uId = normalizedID(m_medium.GetId());
208 /* Refresh root medium ID: */
209 m_uRootId = m_uId;
210
211 /* Init medium key if necessary: */
212 if (m_uKey.isNull())
213 m_uKey = m_uId;
214
215 /* Check whether this is host-drive medium: */
216 m_fHostDrive = m_medium.GetHostDrive();
217
218 /* Refresh medium description: */
219 m_strDescription = m_medium.GetDescription();
220
221 /* Refresh medium name: */
222 if (!m_fHostDrive)
223 m_strName = m_medium.GetName();
224 else if (m_strDescription.isEmpty())
225 m_strName = QApplication::translate("UICommon", "Host Drive '%1'", "medium").arg(QDir::toNativeSeparators(m_medium.GetLocation()));
226 else
227 m_strName = QApplication::translate("UICommon", "Host Drive %1 (%2)", "medium").arg(m_strDescription, m_medium.GetName());
228 /* Refresh medium location: */
229 if (!m_fHostDrive)
230 m_strLocation = QDir::toNativeSeparators(m_medium.GetLocation());
231
232 /* Refresh medium size and logical size: */
233 if (!m_fHostDrive)
234 {
235 /* Only for created and accessible media: */
236 if (m_state != KMediumState_Inaccessible && m_state != KMediumState_NotCreated)
237 {
238 m_uSize = m_medium.GetSize();
239 m_strSize = UITranslator::formatSize(m_uSize);
240 if (m_type == UIMediumDeviceType_HardDisk)
241 {
242 m_uLogicalSize = m_medium.GetLogicalSize();
243 m_strLogicalSize = UITranslator::formatSize(m_uLogicalSize);
244 }
245 else
246 {
247 m_uLogicalSize = m_uSize;
248 m_strLogicalSize = m_strSize;
249 }
250 }
251 }
252
253 /* Refresh medium type & variant: */
254 m_enmMediumType = m_medium.GetType();
255 qlonglong iMediumVariant = 0;
256 foreach (const KMediumVariant &enmVariant, m_medium.GetVariant())
257 iMediumVariant |= enmVariant;
258 m_enmMediumVariant = (KMediumVariant)iMediumVariant;
259
260 /* For hard drive medium: */
261 if (m_type == UIMediumDeviceType_HardDisk)
262 {
263 /* Refresh hard drive disk type: */
264 m_strHardDiskType = mediumTypeToString(m_medium);
265 /* Refresh hard drive format: */
266 m_strHardDiskFormat = m_medium.GetFormat();
267
268 /* Refresh hard drive parental status: */
269 m_fHasChildren = m_medium.GetChildren().size();
270
271 /* Refresh hard drive storage details: */
272 m_strStorageDetails = gpConverter->toString(m_enmMediumVariant);
273
274 /* Check whether this is read-only hard drive: */
275 m_fReadOnly = m_medium.GetReadOnly();
276
277 /* Refresh parent hard drive ID: */
278 CMedium parentMedium = m_medium.GetParent();
279 if (!parentMedium.isNull())
280 m_uParentId = normalizedID(parentMedium.GetId());
281
282 /* Only for created and accessible media: */
283 if (m_state != KMediumState_Inaccessible && m_state != KMediumState_NotCreated)
284 {
285 /* Refresh root hard drive ID: */
286 while (!parentMedium.isNull())
287 {
288 m_uRootId = normalizedID(parentMedium.GetId());
289 parentMedium = parentMedium.GetParent();
290 }
291
292 /* Refresh encryption attributes: */
293 if (m_uRootId != m_uId)
294 {
295 m_strEncryptionPasswordID = root().encryptionPasswordID();
296 m_fEncrypted = root().isEncrypted();
297 }
298 else
299 {
300 QString strCipher;
301 CMedium medium(m_medium);
302 const QString strEncryptionPasswordID = medium.GetEncryptionSettings(strCipher);
303 if (medium.isOk())
304 {
305 m_strEncryptionPasswordID = strEncryptionPasswordID;
306 m_fEncrypted = true;
307 }
308 }
309 }
310 }
311
312 /* Check whether this is hidden medium: */
313 QString strHints = m_medium.GetProperty("Special/GUI/Hints");
314 if (!strHints.isEmpty())
315 {
316 QStringList hints(strHints.split(','));
317 if (hints.contains("Hide", Qt::CaseInsensitive))
318 m_fHidden = true;
319 }
320
321 /* Refresh usage data: */
322 m_curStateMachineIds.clear();
323 m_machineIds = m_medium.GetMachineIds().toList();
324 if (m_machineIds.size() > 0)
325 {
326 /* Get CVirtualBox object: */
327 CVirtualBox vbox = uiCommon().virtualBox();
328
329 /* By default we assuming that this medium is attached
330 * to 'hidden' machines only, if at least one machine present: */
331 m_fUsedByHiddenMachinesOnly = true;
332
333 /* Prepare machine usage: */
334 QString strMachineUsage;
335 /* Walk through all the machines this medium attached to: */
336 foreach (const QUuid &uMachineID, m_machineIds)
337 {
338 /* Look for the corresponding machine: */
339 CMachine machine = vbox.FindMachine(uMachineID.toString());
340
341 /* UIMedium object can wrap newly created CMedium object
342 * which belongs to not yet registered machine, like while creating VM clone.
343 * We can skip such a machines in usage string. */
344 if (machine.isNull())
345 {
346 /* Since we can't precisely check 'hidden' status for that machine in such case,
347 * we have to assume that medium attached not only to 'hidden' machines: */
348 m_fUsedByHiddenMachinesOnly = false;
349 continue;
350 }
351
352 /* Finally we can precisely check if current machine is 'hidden': */
353 if (gEDataManager->showMachineInVirtualBoxManagerChooser(uMachineID))
354 m_fUsedByHiddenMachinesOnly = false;
355
356 /* Prepare snapshot usage: */
357 QString strSnapshotUsage;
358 /* Walk through all the snapshots this medium attached to: */
359 foreach (const QUuid &uSnapshotID, m_medium.GetSnapshotIds(uMachineID))
360 {
361 if (uSnapshotID == uMachineID)
362 {
363 /* The medium is attached to the machine in the current
364 * state, we don't distinguish this for now by always
365 * giving the VM name in front of snapshot names. */
366 m_curStateMachineIds.push_back(uSnapshotID);
367 continue;
368 }
369
370 /* Look for the corresponding snapshot: */
371 CSnapshot snapshot = machine.FindSnapshot(uSnapshotID.toString());
372
373 /* Snapshot can be NULL while takeSnaphot is in progress: */
374 if (snapshot.isNull())
375 continue;
376
377 /* Refresh snapshot usage flag: */
378 m_fUsedInSnapshots = true;
379
380 /* Append snapshot usage: */
381 if (!strSnapshotUsage.isNull())
382 strSnapshotUsage += ", ";
383 strSnapshotUsage += snapshot.GetName();
384 }
385
386 /* Append machine usage: */
387 if (!strMachineUsage.isNull())
388 strMachineUsage += ", ";
389 strMachineUsage += machine.GetName();
390
391 /* Append snapshot usage: */
392 if (!strSnapshotUsage.isNull())
393 strMachineUsage += QString(" (%2)").arg(strSnapshotUsage);
394 }
395
396 /* Append machine usage: */
397 if (!strMachineUsage.isEmpty())
398 m_strUsage += strMachineUsage;
399 }
400
401 /* Refresh tool-tip: */
402 m_strToolTip = m_sstrRow.arg(QString("<p style=white-space:pre><b>%1</b></p>").arg(m_fHostDrive ? m_strName : m_strLocation));
403 if (m_type == UIMediumDeviceType_HardDisk)
404 {
405 m_strToolTip += m_sstrRow.arg(QApplication::translate("UICommon", "<p style=white-space:pre>Type (Format): %1 (%2)</p>", "medium")
406 .arg(m_strHardDiskType).arg(m_strHardDiskFormat));
407 }
408 m_strToolTip += m_sstrRow.arg(QApplication::translate("UICommon", "<p>Attached to: %1</p>", "image")
409 .arg(m_strUsage.isNull() ? QApplication::translate("UICommon", "<i>Not Attached</i>", "image") : m_strUsage));
410 switch (m_state)
411 {
412 case KMediumState_NotCreated:
413 {
414 m_strToolTip += m_sstrRow.arg(QApplication::translate("UICommon", "<i>Checking accessibility...</i>", "medium"));
415 break;
416 }
417 case KMediumState_Inaccessible:
418 {
419 if (m_result.isOk())
420 {
421 /* Not Accessible: */
422 m_strToolTip += m_sstrRow.arg("<hr>") + m_sstrRow.arg(UITranslator::highlight(m_strLastAccessError, true /* aToolTip */));
423 }
424 else
425 {
426 /* Accessibility check (eg GetState()) itself failed: */
427 m_strToolTip += m_sstrRow.arg("<hr>") + m_sstrRow.arg(QApplication::translate("UICommon", "Failed to check accessibility of disk image files.", "medium")) +
428 m_sstrRow.arg(UIErrorString::formatErrorInfo(m_result) + ".");
429 }
430 break;
431 }
432 default:
433 break;
434 }
435 }
436}
437
438void UIMedium::updateParentID()
439{
440 m_uParentId = nullID();
441 if (m_type == UIMediumDeviceType_HardDisk)
442 {
443 CMedium parentMedium = m_medium.GetParent();
444 if (!parentMedium.isNull())
445 m_uParentId = normalizedID(parentMedium.GetId());
446 }
447}
448
449QString UIMedium::toolTip(bool fNoDiffs /* = false */, bool fCheckRO /* = false */, bool fNullAllowed /* = false */) const
450{
451 QString strTip;
452
453 if (m_medium.isNull())
454 {
455 strTip = fNullAllowed ? m_sstrRow.arg(QApplication::translate("UICommon", "<b>No disk image file selected</b>", "medium")) +
456 m_sstrRow.arg(QApplication::translate("UICommon", "You can also change this while the machine is running.")) :
457 m_sstrRow.arg(QApplication::translate("UICommon", "<b>No disk image files available</b>", "medium")) +
458 m_sstrRow.arg(QApplication::translate("UICommon", "You can create or add disk image files in the virtual machine settings."));
459 }
460 else
461 {
462 unconst(this)->checkNoDiffs(fNoDiffs);
463
464 strTip = fNoDiffs ? m_noDiffs.toolTip : m_strToolTip;
465
466 if (fCheckRO && m_fReadOnly)
467 strTip += m_sstrRow.arg("<hr>") +
468 m_sstrRow.arg(QApplication::translate("UICommon",
469 "Attaching this hard disk will be performed indirectly using "
470 "a newly created differencing hard disk.", "medium"));
471 }
472
473 return m_sstrTable.arg(strTip);
474}
475
476QPixmap UIMedium::icon(bool fNoDiffs /* = false */, bool fCheckRO /* = false */) const
477{
478 QPixmap pixmap;
479
480 if (state(fNoDiffs) == KMediumState_Inaccessible)
481 pixmap = result(fNoDiffs).isOk() ? generalIconPool().warningIcon() : generalIconPool().errorIcon();
482
483 if (fCheckRO && m_fReadOnly)
484 {
485 QIcon icon = UIIconPool::iconSet(":/hd_create_16px.png");
486 pixmap = UIIconPool::joinPixmaps(pixmap, icon.pixmap(icon.availableSizes().value(0, QSize(16, 16))));
487 }
488
489 return pixmap;
490}
491
492QString UIMedium::details(bool fNoDiffs /* = false */,
493 bool fPredictDiff /* = false */,
494 bool fUseHTML /* = false */) const
495{
496 /// @todo the below check is rough; if m_medium becomes uninitialized, any
497 // of getters called afterwards will also fail. The same relates to the
498 // root hard drive object (that will be the hard drive itself in case of
499 // non-differencing disks). However, this check was added to fix a
500 // particular use case: when the hard drive is a differencing hard drive and
501 // it happens to be discarded (and uninitialized) after this method is
502 // called but before we read all its properties (yes, it's possible!), the
503 // root object will be null and calling methods on it will assert in the
504 // debug builds. This check seems to be enough as a quick solution (fresh
505 // hard drive attachments will be re-read by a machine state change signal
506 // after the discard operation is finished, so the user will eventually see
507 // correct data), but in order to solve the problem properly we need to use
508 // exceptions everywhere (or check the result after every method call). See
509 // @bugref{2149}.
510
511 if (m_medium.isNull() || m_fHostDrive)
512 return m_strName;
513
514 if (!m_medium.isOk())
515 return QString();
516
517 QString strDetails, strText;
518
519 /* Note: root accessible only if medium enumerated: */
520 UIMedium rootMedium = root();
521 KMediumState eState = m_state;
522
523 if (m_type == UIMediumDeviceType_HardDisk)
524 {
525 if (fNoDiffs)
526 {
527 bool isDiff = (!fPredictDiff && parentID() != nullID()) || (fPredictDiff && m_fReadOnly);
528
529 strDetails = isDiff && fUseHTML ?
530 QString("<i>%1</i>, ").arg(rootMedium.m_strHardDiskType) :
531 QString("%1, ").arg(rootMedium.m_strHardDiskType);
532
533 eState = this->state(true /* fNoDiffs */);
534
535 if (rootMedium.m_state == KMediumState_NotCreated)
536 eState = KMediumState_NotCreated;
537 }
538 else
539 {
540 strDetails = QString("%1, ").arg(rootMedium.m_strHardDiskType);
541 }
542
543 /* Add encryption status: */
544 if (m_fEncrypted)
545 strDetails += QString("%1, ").arg(QApplication::translate("UICommon", "Encrypted", "medium"));
546 }
547
548 /// @todo prepend the details with the warning/error icon when not accessible
549
550 switch (eState)
551 {
552 case KMediumState_NotCreated:
553 strText = QApplication::translate("UICommon", "Checking...", "medium");
554 strDetails += fUseHTML ? QString("<i>%1</i>").arg(strText) : strText;
555 break;
556 case KMediumState_Inaccessible:
557 strText = QApplication::translate("UICommon", "Inaccessible", "medium");
558 strDetails += fUseHTML ? QString("<b>%1</b>").arg(strText) : strText;
559 break;
560 default:
561 strDetails += m_type == UIMediumDeviceType_HardDisk ? rootMedium.m_strLogicalSize : rootMedium.m_strSize;
562 break;
563 }
564
565 strDetails = fUseHTML ?
566 QString("%1 (<nobr>%2</nobr>)").arg(QFileInfo(rootMedium.m_strName).fileName(), strDetails) :
567 QString("%1 (%2)").arg(QFileInfo(rootMedium.m_strName).fileName(), strDetails);
568
569 return strDetails;
570}
571
572/* static */
573QUuid UIMedium::nullID()
574{
575 return m_uNullID;
576}
577
578/* static */
579QUuid UIMedium::normalizedID(const QUuid &uID)
580{
581 /// @todo wipe out!
582 return uID;
583}
584
585/* static */
586bool UIMedium::isMediumAttachedToHiddenMachinesOnly(const UIMedium &medium)
587{
588 /* Iterate till the root: */
589 UIMedium mediumIterator = medium;
590 do
591 {
592 /* Ignore medium if its hidden
593 * or attached to hidden machines only: */
594 if (mediumIterator.isHidden())
595 return true;
596 /* Move iterator to parent: */
597 mediumIterator = mediumIterator.parent();
598 }
599 while (!mediumIterator.isNull());
600 /* False by default: */
601 return false;
602}
603
604UIMedium UIMedium::root() const
605{
606 /* Redirect call to UICommon: */
607 return uiCommon().medium(m_uRootId);
608}
609
610UIMedium UIMedium::parent() const
611{
612 /* Redirect call to UICommon: */
613 return uiCommon().medium(m_uParentId);
614}
615
616void UIMedium::checkNoDiffs(bool fNoDiffs)
617{
618 if (!fNoDiffs || m_noDiffs.isSet)
619 return;
620
621 m_noDiffs.toolTip = QString();
622
623 m_noDiffs.state = m_state;
624 for (UIMedium parentMedium = parent(); !parentMedium.isNull(); parentMedium = parentMedium.parent())
625 {
626 if (parentMedium.m_state == KMediumState_Inaccessible)
627 {
628 m_noDiffs.state = parentMedium.m_state;
629
630 if (m_noDiffs.toolTip.isNull())
631 m_noDiffs.toolTip = m_sstrRow.arg(QApplication::translate("UICommon",
632 "Some of the files in this hard disk chain "
633 "are inaccessible. Please use the Virtual Medium "
634 "Manager to inspect these files.", "medium"));
635
636 if (!parentMedium.m_result.isOk())
637 {
638 m_noDiffs.result = parentMedium.m_result;
639 break;
640 }
641 }
642 }
643
644 if (parentID() != nullID() && !m_fReadOnly)
645 {
646 m_noDiffs.toolTip = root().tip() +
647 m_sstrRow.arg("<hr>") +
648 m_sstrRow.arg(QApplication::translate("UICommon",
649 "This base hard disk is indirectly attached using "
650 "the following differencing hard disk:", "medium")) +
651 m_strToolTip + m_noDiffs.toolTip;
652 }
653
654 if (m_noDiffs.toolTip.isNull())
655 m_noDiffs.toolTip = m_strToolTip;
656
657 m_noDiffs.isSet = true;
658}
659
660/* static */
661QString UIMedium::mediumTypeToString(const CMedium &comMedium)
662{
663 if (!comMedium.GetParent().isNull())
664 {
665 Assert(comMedium.GetType() == KMediumType_Normal);
666 return QApplication::translate("UICommon", "Differencing", "MediumType");
667 }
668 return gpConverter->toString(comMedium.GetType());
669}
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