File indexing completed on 2024-04-28 09:41:47
0001 // SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com> 0002 // 0003 // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "driveselection.h" 0006 #include "driveselectiondelegate.h" 0007 #include "backupplan.h" 0008 0009 #include <QItemSelectionModel> 0010 #include <QList> 0011 #include <QPainter> 0012 #include <QStandardItemModel> 0013 #include <QTimer> 0014 0015 #include <KConfigDialogManager> 0016 #include <QStorageInfo> 0017 #include <KLocalizedString> 0018 0019 #include <Solid/Device> 0020 #include <Solid/DeviceNotifier> 0021 #include <Solid/StorageAccess> 0022 #include <Solid/StorageDrive> 0023 #include <Solid/StorageVolume> 0024 0025 bool deviceLessThan(const Solid::Device &a, const Solid::Device &b) { 0026 return a.udi() < b.udi(); 0027 } 0028 0029 DriveSelection::DriveSelection(BackupPlan *pBackupPlan, QWidget *parent) 0030 : QListView(parent), mBackupPlan(pBackupPlan), mSelectedAndAccessible(false), mSyncedBackupType(false) 0031 { 0032 mDrivesModel = new QStandardItemModel(this); 0033 setModel(mDrivesModel); 0034 setItemDelegate(new DriveSelectionDelegate(this)); 0035 setSelectionMode(QAbstractItemView::SingleSelection); 0036 setWordWrap(true); 0037 0038 if(!mBackupPlan->mExternalUUID.isEmpty()) { 0039 auto *lItem = new QStandardItem(); 0040 lItem->setEditable(false); 0041 lItem->setData(QString(), DriveSelection::UDI); 0042 lItem->setData(mBackupPlan->mExternalUUID, DriveSelection::UUID); 0043 lItem->setData(0, DriveSelection::UsedSpace); 0044 lItem->setData(mBackupPlan->mExternalPartitionNumber, DriveSelection::PartitionNumber); 0045 lItem->setData(mBackupPlan->mExternalPartitionsOnDrive, DriveSelection::PartitionsOnDrive); 0046 lItem->setData(mBackupPlan->mExternalDeviceDescription, DriveSelection::DeviceDescription); 0047 lItem->setData(mBackupPlan->mExternalVolumeCapacity, DriveSelection::TotalSpace); 0048 lItem->setData(mBackupPlan->mExternalVolumeLabel, DriveSelection::Label); 0049 mDrivesModel->appendRow(lItem); 0050 } 0051 0052 QList<Solid::Device> lDeviceList = Solid::Device::listFromType(Solid::DeviceInterface::StorageDrive); 0053 foreach (const Solid::Device &lDevice, lDeviceList) { 0054 deviceAdded(lDevice.udi()); 0055 } 0056 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), SLOT(deviceAdded(QString))); 0057 connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), SLOT(deviceRemoved(QString))); 0058 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 0059 this, SLOT(updateSelection(QItemSelection,QItemSelection))); 0060 } 0061 0062 QString DriveSelection::mountPathOfSelectedDrive() const { 0063 if(mSelectedAndAccessible) { 0064 QStandardItem *lItem; 0065 findItem(DriveSelection::UUID, mSelectedUuid, &lItem); 0066 if(lItem != nullptr) { 0067 Solid::Device lDevice(lItem->data(DriveSelection::UDI).toString()); 0068 auto *lAccess = lDevice.as<Solid::StorageAccess>(); 0069 if(lAccess) { 0070 return lAccess->filePath(); 0071 } 0072 } 0073 } 0074 return QString(); 0075 } 0076 0077 void DriveSelection::deviceAdded(const QString &pUdi) { 0078 Solid::Device lDevice(pUdi); 0079 if(!lDevice.is<Solid::StorageDrive>()) { 0080 return; 0081 } 0082 auto *lDrive = lDevice.as<Solid::StorageDrive>(); 0083 if(!lDrive->isHotpluggable() && !lDrive->isRemovable()) { 0084 return; 0085 } 0086 if(mDrivesToAdd.contains(pUdi)) { 0087 return; 0088 } 0089 mDrivesToAdd.append(pUdi); 0090 QTimer::singleShot(2000, this, SLOT(delayedDeviceAdded())); 0091 } 0092 0093 void DriveSelection::delayedDeviceAdded() { 0094 if(mDrivesToAdd.isEmpty()) { 0095 return; 0096 } 0097 Solid::Device lParentDevice(mDrivesToAdd.takeFirst()); 0098 QList<Solid::Device> lDeviceList = Solid::Device::listFromType(Solid::DeviceInterface::StorageVolume, 0099 lParentDevice.udi()); 0100 // check for when there is no partitioning scheme, then the drive is also the storage volume 0101 if(lParentDevice.is<Solid::StorageVolume>()) { 0102 lDeviceList.append(lParentDevice); 0103 } 0104 0105 // filter out some volumes that should not be visible 0106 QList<Solid::Device> lVolumeDeviceList; 0107 foreach(Solid::Device lVolumeDevice, lDeviceList) { 0108 auto *lVolume = lVolumeDevice.as<Solid::StorageVolume>(); 0109 if(lVolume && !lVolume->isIgnored() && ( 0110 lVolume->usage() == Solid::StorageVolume::FileSystem || 0111 lVolume->usage() == Solid::StorageVolume::Encrypted)) { 0112 lVolumeDeviceList.append(lVolumeDevice); 0113 } 0114 } 0115 0116 // simplest attempt at getting the same partition numbering every time a device is plugged in 0117 std::sort(lVolumeDeviceList.begin(), lVolumeDeviceList.end(), deviceLessThan); 0118 0119 int lPartitionNumber = 1; 0120 foreach(Solid::Device lVolumeDevice, lVolumeDeviceList) { 0121 auto *lVolume = lVolumeDevice.as<Solid::StorageVolume>(); 0122 QString lUuid = lVolume->uuid(); 0123 if(lUuid.isEmpty()) { //seems to happen for vfat partitions 0124 lUuid += lParentDevice.description(); 0125 lUuid += QStringLiteral("|"); 0126 lUuid += lVolume->label(); 0127 } 0128 QStandardItem *lItem; 0129 bool lNeedsToBeAdded = false; 0130 findItem(DriveSelection::UUID, lUuid, &lItem); 0131 if(lItem == nullptr) { 0132 lItem = new QStandardItem(); 0133 lItem->setEditable(false); 0134 lItem->setData(lUuid, DriveSelection::UUID); 0135 lItem->setData(0, DriveSelection::TotalSpace); 0136 lItem->setData(0, DriveSelection::UsedSpace); 0137 lNeedsToBeAdded = true; 0138 } 0139 lItem->setData(lParentDevice.description(), DriveSelection::DeviceDescription); 0140 lItem->setData(lVolume->label(), DriveSelection::Label); 0141 lItem->setData(lVolumeDevice.udi(), DriveSelection::UDI); 0142 lItem->setData(lPartitionNumber, DriveSelection::PartitionNumber); 0143 lItem->setData(lVolumeDeviceList.count(), DriveSelection::PartitionsOnDrive); 0144 lItem->setData(lVolume->fsType(), DriveSelection::FileSystem); 0145 lItem->setData(mSyncedBackupType && (lVolume->fsType() == QStringLiteral("vfat") || 0146 lVolume->fsType() == QStringLiteral("ntfs")), 0147 DriveSelection::PermissionLossWarning); 0148 lItem->setData(mSyncedBackupType && lVolume->fsType() == QStringLiteral("vfat"), 0149 DriveSelection::SymlinkLossWarning); 0150 0151 auto *lAccess = lVolumeDevice.as<Solid::StorageAccess>(); 0152 connect(lAccess, SIGNAL(accessibilityChanged(bool,QString)), SLOT(accessabilityChanged(bool,QString))); 0153 if(lAccess->isAccessible()) { 0154 QStorageInfo storageInfo(lAccess->filePath()); 0155 if(storageInfo.isValid()) { 0156 lItem->setData(storageInfo.bytesTotal(), DriveSelection::TotalSpace); 0157 lItem->setData(storageInfo.bytesTotal() - storageInfo.bytesFree(), DriveSelection::UsedSpace); 0158 } 0159 if(lUuid == mSelectedUuid) { 0160 // Selected volume was just added, could not have been accessible before. 0161 mSelectedAndAccessible = true; 0162 emit selectedDriveIsAccessibleChanged(true); 0163 } 0164 } else if(lVolume->usage() != Solid::StorageVolume::Encrypted) { 0165 // Don't bother the user with password prompt just for sake of storage volume space info 0166 lAccess->setup(); 0167 } 0168 if(lNeedsToBeAdded) { 0169 mDrivesModel->appendRow(lItem); 0170 if(mDrivesModel->rowCount() == 1) { 0171 selectionModel()->select(mDrivesModel->index(0, 0), QItemSelectionModel::ClearAndSelect); 0172 } 0173 } 0174 lPartitionNumber++; 0175 } 0176 } 0177 0178 void DriveSelection::deviceRemoved(const QString &pUdi) { 0179 QStandardItem *lItem; 0180 int lRow = findItem(DriveSelection::UDI, pUdi, &lItem); 0181 if(lRow >= 0) { 0182 QString lUuid = lItem->data(DriveSelection::UUID).toString(); 0183 if(lUuid == mBackupPlan->mExternalUUID) { 0184 // let the selected and saved item stay in the list 0185 // just clear the UDI so that it will be shown as disconnected. 0186 lItem->setData(QString(), DriveSelection::UDI); 0187 } else { 0188 mDrivesModel->removeRow(lRow); 0189 } 0190 if(lUuid == mSelectedUuid && mSelectedAndAccessible) { 0191 mSelectedAndAccessible = false; 0192 emit selectedDriveIsAccessibleChanged(false); 0193 } 0194 } 0195 } 0196 0197 void DriveSelection::accessabilityChanged(bool pAccessible, const QString &pUdi) { 0198 QStandardItem *lItem; 0199 findItem(DriveSelection::UDI, pUdi, &lItem); 0200 if(lItem != nullptr) { 0201 if(pAccessible) { 0202 Solid::Device lDevice(pUdi); 0203 auto *lAccess = lDevice.as<Solid::StorageAccess>(); 0204 if(lAccess) { 0205 QStorageInfo storageInfo(lAccess->filePath()); 0206 if(storageInfo.isValid()) { 0207 lItem->setData(storageInfo.bytesTotal(), DriveSelection::TotalSpace); 0208 lItem->setData(storageInfo.bytesTotal() - storageInfo.bytesFree(), DriveSelection::UsedSpace); 0209 } 0210 } 0211 } 0212 bool lSelectedAndAccessible = (lItem->data(DriveSelection::UUID).toString() == mSelectedUuid && pAccessible); 0213 if(lSelectedAndAccessible != mSelectedAndAccessible) { 0214 mSelectedAndAccessible = lSelectedAndAccessible; 0215 emit selectedDriveIsAccessibleChanged(lSelectedAndAccessible); 0216 } 0217 } 0218 } 0219 0220 void DriveSelection::updateSelection(const QItemSelection &pSelected, const QItemSelection &pDeselected) { 0221 Q_UNUSED(pDeselected) 0222 if(!pSelected.indexes().isEmpty()) { 0223 QModelIndex lIndex = pSelected.indexes().first(); 0224 if(mSelectedUuid.isEmpty()) { 0225 emit driveIsSelectedChanged(true); 0226 } 0227 mSelectedUuid = lIndex.data(DriveSelection::UUID).toString(); 0228 emit selectedDriveChanged(mSelectedUuid); 0229 // check if the newly selected volume is accessible, compare to previous selection 0230 bool lIsAccessible = false; 0231 QString lUdiOfSelected = lIndex.data(DriveSelection::UDI).toString(); 0232 if(!lUdiOfSelected.isEmpty()) { 0233 Solid::Device lDevice(lUdiOfSelected); 0234 auto *lAccess = lDevice.as<Solid::StorageAccess>(); 0235 if(lAccess != nullptr) { 0236 lIsAccessible = lAccess->isAccessible(); 0237 } 0238 } 0239 if(mSelectedAndAccessible != lIsAccessible) { 0240 mSelectedAndAccessible = lIsAccessible; 0241 emit selectedDriveIsAccessibleChanged(mSelectedAndAccessible); 0242 } 0243 } else { 0244 mSelectedUuid.clear(); 0245 emit selectedDriveChanged(mSelectedUuid); 0246 emit driveIsSelectedChanged(false); 0247 mSelectedAndAccessible = false; 0248 emit selectedDriveIsAccessibleChanged(false); 0249 } 0250 } 0251 0252 void DriveSelection::paintEvent(QPaintEvent *pPaintEvent) { 0253 QListView::paintEvent(pPaintEvent); 0254 if(mDrivesModel->rowCount() == 0) { 0255 QPainter lPainter(viewport()); 0256 style()->drawItemText(&lPainter, rect(), Qt::AlignCenter, palette(), false, 0257 xi18nc("@label Only shown if no drives are detected", "Plug in the external " 0258 "storage you wish to use, then select it in this list."), QPalette::Text); 0259 } 0260 } 0261 0262 void DriveSelection::setSelectedDrive(const QString &pUuid) { 0263 if(pUuid == mSelectedUuid) { 0264 return; 0265 } 0266 if(pUuid.isEmpty()) { 0267 clearSelection(); 0268 } else { 0269 QStandardItem *lItem; 0270 findItem(DriveSelection::UUID, pUuid, &lItem); 0271 if(lItem != nullptr) { 0272 setCurrentIndex(mDrivesModel->indexFromItem(lItem)); 0273 } 0274 } 0275 } 0276 0277 void DriveSelection::saveExtraData() { 0278 QStandardItem *lItem; 0279 findItem(DriveSelection::UUID, mSelectedUuid, &lItem); 0280 if(lItem != nullptr) { 0281 mBackupPlan->mExternalDeviceDescription = lItem->data(DriveSelection::DeviceDescription).toString(); 0282 mBackupPlan->mExternalPartitionNumber = lItem->data(DriveSelection::PartitionNumber).toInt(); 0283 mBackupPlan->mExternalPartitionsOnDrive = lItem->data(DriveSelection::PartitionsOnDrive).toInt(); 0284 mBackupPlan->mExternalVolumeCapacity = lItem->data(DriveSelection::TotalSpace).toULongLong(); 0285 mBackupPlan->mExternalVolumeLabel = lItem->data(DriveSelection::Label).toString(); 0286 } 0287 } 0288 0289 void DriveSelection::updateSyncWarning(bool pSyncBackupSelected) { 0290 mSyncedBackupType = pSyncBackupSelected; 0291 for(int i = 0; i < mDrivesModel->rowCount(); ++i) { 0292 QString lFsType = mDrivesModel->item(i)->data(DriveSelection::FileSystem).toString(); 0293 mDrivesModel->item(i)->setData(mSyncedBackupType && (lFsType == QStringLiteral("vfat") || 0294 lFsType == QStringLiteral("ntfs")), 0295 DriveSelection::PermissionLossWarning); 0296 mDrivesModel->item(i)->setData(mSyncedBackupType && lFsType == QStringLiteral("vfat"), 0297 DriveSelection::SymlinkLossWarning); 0298 } 0299 } 0300 0301 int DriveSelection::findItem(const DriveSelection::DataType pField, const QString &pSearchString, 0302 QStandardItem **pReturnedItem) const { 0303 for(int lRow = 0; lRow < mDrivesModel->rowCount(); ++lRow) { 0304 QStandardItem *lItem = mDrivesModel->item(lRow); 0305 if(lItem->data(pField).toString() == pSearchString) { 0306 if(pReturnedItem != nullptr) { 0307 *pReturnedItem = lItem; 0308 } 0309 return lRow; 0310 } 0311 } 0312 if(pReturnedItem != nullptr) { 0313 *pReturnedItem = nullptr; 0314 } 0315 return -1; 0316 }