1

I have a QTreeView populated with a QFileSystemModel, and I'm setting the root path to '\' (as "\\"). This works very well, and sees local drives and USB drives. My problem is that, when I programmatically eject the USB drive (I'm programming for a Win7 application in kiosk mode, so it needs to handle windows-level activity) using DeviceIoControl(..., FSCTL_DISMOUNT_VOLUME, ...), followed by DeviceIoControl(..., IOCTL_STORAGE_EJECT_MEDIA, ...), although both functions return true, and the windows system service tray doesn't show a USB icon, and the windows explorer doesn't have a USB entry, yet my QFileSystemModel still sees the drive.

I do have a proxy model derived from QSortFilterProxyModel, and that is filtering what is being seen, because I really only want to see the USB drives. This is calling my USBController class to detect mounted removables. That function calls GetLogicalDrives(), which actually returns the drive letter of the USB I just programmatically ejected.

I've looked around for a solution to this dilemma, but haven't been able to figure this one out yet. Does anyone have any suggestions? Here's the relevant code:

Functions from UsbController.h:

bool UsbController::ejectDrive(char driveletter, QString &errmsg)
{
    char devicepath[7];
    char format[] = "\\\\.\\?:";
    strcpy_s(devicepath, format);
    devicepath[4] = driveletter;
    errmsg = "";

    DWORD dwRet = 0;
    wchar_t wtext[7];
    size_t textlen = 7;
    mbstowcs_s(&textlen, wtext, devicepath,strlen(devicepath)+1);
    HANDLE hVol = CreateFile(wtext, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hVol == INVALID_HANDLE_VALUE)
    {
        FormatErrorMsg("CreateFile: ", errmsg);
        return false;
    }

    if(!DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &dwRet, 0))
    {
        FormatErrorMsg("Lock Volume: ", errmsg);
        return false;
    }

    if(!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, &dwRet, 0))
    {
        FormatErrorMsg("Dismount Volume: ", errmsg);
        return false;
    }

    if (!DeviceIoControl(hVol, IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, &dwRet, 0))
    {
        FormatErrorMsg("Eject Media: ", errmsg);
        return false;
    }

    CloseHandle(hVol);
    return true;
}

QMap<QString,QString> UsbController::getMountedRemovables()
{
    DWORD test = GetLogicalDrives();
    DWORD mask = 1;
    UINT type = 0;
    WCHAR wdrive[] = L"?:\\"; // use as a drive letter template

    QMap<QString,QString> removables;
    for (int i = 0; i < 32; i++)
    {
        if (test & mask)
        {
            wdrive[0] = (char)('A' + i); // change letter in template
            type = GetDriveType(wdrive);
            switch (type) {
            case DRIVE_REMOVABLE:
            {
                QString qdrive = QString((char)('A' + i)) + ":";
                if (!removables.contains(qdrive))
                {
                    QString name = mountNameFromDriveLetter((char)('A' + i));
                    removables.insert(qdrive, name);
                }
                break;
            }
            default: break;
            }
        }
        mask = mask << 1;
    }

    return removables;
}

Functions in my proxy model (data makes use of the acceptedRemovables QMap, which is populated in detectMountedRemovables() by making a call to the UsbController:

QVariant DriveFilterProxyModel::data(const QModelIndex & index, int role) const
{
    QVariant data = QSortFilterProxyModel::data(index, role);

    if (role == Qt::DisplayRole)
    {
        QString source = data.toString();

        for (int i = 0; i < acceptList.size(); i++)
        {
            if (source.contains(acceptList[i]))
            {
                QString drivestring = source;
                if (drivestring.contains('('))
                {
                    drivestring = drivestring.remove(0, drivestring.indexOf('(')+1);
                    drivestring = drivestring.remove(drivestring.indexOf(')'), drivestring.size()-drivestring.indexOf(')')+1);
                }
                QMap<QString,QString>::const_iterator iter = acceptedNonRemovables.find(drivestring);
                if (iter != acceptedNonRemovables.end())
                {
                    if (source.contains("Data")) // no path as yet applied
                    {
                        QString newsource(source);
                        newsource.replace("Data", iter.value());
                        qDebug() << "DriveFilterProxyModel::filterAcceptsRow(): Mapped drive: should replace \"Data\" with: " << iter.value() << " to produce: " << newsource;
                        return newsource;
                    }
                }
                else
                {
                    iter = acceptedRemovables.find(drivestring);
                    if (iter != acceptedRemovables.end())
                    {
                        if (!source.contains('(')) // no drive name found
                        {
                            QString newsource = QString("Removable Disk (%1)").arg(source);
                            qDebug() << "DriveFilterProxyModel::filterAcceptsRow(): USB drive: should change simple drive letter to: " << newsource;
                            return newsource;
                        }
                    }
                }
            }
        }
    }

    return data;
}

void DriveFilterProxyModel::detectMountedRemovables()
{
    acceptedRemovables = UsbController::getMountedRemovables();
    resetAcceptList();
}
bmahf
  • 965
  • 2
  • 11
  • 27
  • Does the drive letter go away (from the point of view of your program) when you eject the drive using the GUI rather than using your code to eject it? – Harry Johnston Mar 17 '15 at 21:42

1 Answers1

0

I guess that the Drive is still recognized by the system but it stays in Ejected mode.

Look at the Enumerating all available drive letters in Windows question may be you find working solution there.

Community
  • 1
  • 1
Pavel Nazarov
  • 723
  • 6
  • 10