Qt/QML edition

This commit is contained in:
Floris Bos 2020-03-04 16:55:40 +01:00
commit d7b361ba44
2168 changed files with 721948 additions and 0 deletions

View file

@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Version=1.0
Name=Imager
Comment=Raspberry Pi Imager
Icon=imagingutility
Exec=imagingutility
Categories=Utility
StartupNotify=false

127
linux/linuxdrivelist.cpp Normal file
View file

@ -0,0 +1,127 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
*/
#include "../dependencies/drivelist/src/drivelist.hpp"
#include <QProcess>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
/*
* Our third-party drivelist module does not provide a C++ implementation
* for listing drives on Linux (only Javascript)
* So roll our own for Linux using same function/data structure as the drivelist one
*/
namespace Drivelist
{
static void _walkStorageChildren(Drivelist::DeviceDescriptor &d, QStringList &labels, QJsonArray &ca)
{
for (auto j : ca)
{
QJsonObject child = j.toObject();
QString label = child["label"].toString();
QString mp = child["mountpoint"].toString();
if (!label.isEmpty())
{
labels.append(label);
}
if (!mp.isEmpty())
{
d.mountpoints.push_back(mp.toStdString());
d.mountpointLabels.push_back(label.toStdString());
}
QJsonArray subca = child["children"].toArray();
if (subca.count())
{
_walkStorageChildren(d, labels, subca);
}
}
}
std::vector<Drivelist::DeviceDescriptor> ListStorageDevices()
{
std::vector<DeviceDescriptor> deviceList;
QProcess p;
p.start("lsblk --bytes --json --paths --output-all");
p.waitForFinished(2000);
QByteArray output = p.readAll();
if (p.exitStatus() != QProcess::NormalExit || p.exitCode() || output.isEmpty())
{
qDebug() << "Error executing lsblk";
return deviceList;
}
QJsonDocument d = QJsonDocument::fromJson(output);
QJsonArray a = d.object()["blockdevices"].toArray();
for (auto i : a)
{
DeviceDescriptor d;
QJsonObject bdev = i.toObject();
QString name = bdev["kname"].toString();
QString subsystems = bdev["subsystems"].toString();
if (name.startsWith("/dev/loop") || name.startsWith("/dev/sr") || name.startsWith("/dev/ram") || name.isEmpty())
continue;
d.busType = bdev["busType"].toString().toStdString();
d.device = name.toStdString();
d.raw = true;
d.isVirtual = subsystems == "block";
if (bdev["ro"].isBool())
{
/* With some lsblk versions it is a bool in others a "0" or "1" string */
d.isReadOnly = bdev["ro"].toBool();
d.isRemovable= bdev["rm"].toBool() || bdev["hotplug"].toBool() || d.isVirtual;
}
else
{
d.isReadOnly = bdev["ro"].toString() == "1";
d.isRemovable= bdev["rm"].toString() == "1" || bdev["hotplug"].toString() == "1" || d.isVirtual;
}
if (bdev["size"].isString())
{
d.size = bdev["size"].toString().toULongLong();
}
else
{
d.size = bdev["size"].toDouble();
}
d.isSystem = !d.isRemovable && !d.isVirtual;
d.isUSB = subsystems.contains("usb");
d.isSCSI = subsystems.contains("scsi") && !d.isUSB;
d.blockSize = bdev["phy-sec"].toInt();
d.logicalBlockSize = bdev["log-sec"].toInt();
QStringList dp = {
bdev["label"].toString().trimmed(),
bdev["vendor"].toString().trimmed(),
bdev["model"].toString().trimmed()
};
QString mp = bdev["mountpoint"].toString();
if (!mp.isEmpty())
{
d.mountpoints.push_back(mp.toStdString());
d.mountpointLabels.push_back(bdev["label"].toString().toStdString());
}
QStringList labels;
QJsonArray ca = bdev["children"].toArray();
_walkStorageChildren(d, labels, ca);
if (labels.count()) {
dp.append("("+labels.join(", ")+")");
}
dp.removeAll("");
d.description = dp.join(" ").toStdString();
deviceList.push_back(d);
}
return deviceList;
}
}

172
linux/udisks2api.cpp Normal file
View file

@ -0,0 +1,172 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
*/
#include "udisks2api.h"
#include <unistd.h>
#include <fcntl.h>
#include <QDBusInterface>
#include <QDBusReply>
#include <QDBusUnixFileDescriptor>
#include <QDebug>
#include <QThread>
UDisks2Api::UDisks2Api(QObject *parent)
: QObject(parent)
{
}
int UDisks2Api::authOpen(const QString &device, const QString &mode)
{
QString devpath = _resolveDevice(device);
if (devpath.isEmpty())
return -1;
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpath,
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
QString drive = blockdevice.property("Drive").value<QDBusObjectPath>().path();
if (!drive.isEmpty() && drive != "/")
{
_unmountDrive(drive);
}
// User may need to enter password in authentication dialog so set long timeout
blockdevice.setTimeout(3600 * 1000);
QVariantMap options = {{"flags", O_EXCL}};
QDBusReply<QDBusUnixFileDescriptor> dbusfd = blockdevice.call("OpenDevice", mode, options);
if (!blockdevice.isValid() || !dbusfd.isValid() || !dbusfd.value().isValid())
return -1;
int fd = ::dup(dbusfd.value().fileDescriptor());
return fd;
}
QString UDisks2Api::_resolveDevice(const QString &device)
{
QDBusInterface manager("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/Manager",
"org.freedesktop.UDisks2.Manager", QDBusConnection::systemBus());
QVariantMap devspec = {{"path", device}};
QVariantMap options;
QDBusReply<QList<QDBusObjectPath>> list = manager.call("ResolveDevice", devspec, options);
if (!manager.isValid() || !list.isValid() || list.value().isEmpty())
return QString();
return list.value().first().path();
}
void UDisks2Api::_unmountDrive(const QString &driveDbusPath)
{
qDebug() << "Drive:" << driveDbusPath;
QDBusInterface manager("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2/Manager",
"org.freedesktop.UDisks2.Manager", QDBusConnection::systemBus());
QVariantMap options;
QDBusReply<QList<QDBusObjectPath>> list = manager.call("GetBlockDevices", options);
if (!manager.isValid() || !list.isValid())
return;
for (auto devpath : list.value())
{
QString devpathStr = devpath.path();
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpathStr,
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
QString driveOfDev = blockdevice.property("Drive").value<QDBusObjectPath>().path();
if (driveOfDev != driveDbusPath)
continue;
qDebug() << "Device:" << devpathStr << "belongs to same drive";
QDBusInterface filesystem("org.freedesktop.UDisks2", devpathStr,
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
QDBusReply<void> reply = filesystem.call("Unmount", options);
if (reply.isValid())
qDebug() << "Unmounted" << devpathStr << "successfully";
}
}
bool UDisks2Api::formatDrive(const QString &device, bool mountAfterwards)
{
QString devpath = _resolveDevice(device);
if (devpath.isEmpty())
return false;
QDBusInterface blockdevice("org.freedesktop.UDisks2", devpath,
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
QString drive = blockdevice.property("Drive").value<QDBusObjectPath>().path();
if (!drive.isEmpty() && drive != "/")
{
_unmountDrive(drive);
}
qDebug() << "Repartitioning drive";
QVariantMap options;
QDBusReply<void> reply = blockdevice.call("Format", "dos", options);
if (!reply.isValid())
{
qDebug() << "Error repartitioning device:" << reply.error().message();
return false;
}
QVariantMap partOptions, formatOptions;
QDBusInterface partitiontable("org.freedesktop.UDisks2", devpath,
"org.freedesktop.UDisks2.PartitionTable", QDBusConnection::systemBus());
/* The all-in-one CreatePartitionAndFormat udisks2 method seems to not always
work properly. Do seperate actions with sleep in between instead */
qDebug() << "Adding partition";
QDBusReply<QDBusObjectPath> newpartition = partitiontable.call("CreatePartition", QVariant((qulonglong) 4*1024*1024), QVariant((qulonglong) 0), "0x0e", "", partOptions);
if (!newpartition.isValid())
{
qDebug() << "Error adding partition:" << newpartition.error().message();
return false;
}
qDebug() << "New partition:" << newpartition.value().path();
QThread::sleep(1);
qDebug() << "Formatting drive as FAT32";
QDBusInterface newblockdevice("org.freedesktop.UDisks2", newpartition.value().path(),
"org.freedesktop.UDisks2.Block", QDBusConnection::systemBus());
newblockdevice.setTimeout(120 * 1000);
QDBusReply<void> fatformatreply = newblockdevice.call("Format", "vfat", formatOptions);
if (!fatformatreply.isValid())
{
qDebug() << "Error from udisks2 while performing FAT32 format:" << fatformatreply.error().message()
<< "(going to try to mount anyway, as errors are sometimes false positive)";
/* udisks2 sometimes report
"Error synchronizing after formatting with type `vfat': Timed out waiting for object"
when there is nothing actually wrong with the format */
}
if (mountAfterwards)
{
QDBusInterface filesystem("org.freedesktop.UDisks2", newpartition.value().path(),
"org.freedesktop.UDisks2.Filesystem", QDBusConnection::systemBus());
QVariantMap mountOptions;
for (int attempt = 0; attempt < 10; attempt++)
{
qDebug() << "Mounting partition";
QDBusReply<QString> mp = filesystem.call("Mount", mountOptions);
if (mp.isValid())
{
qDebug() << "Mounted new file system at:" << mp;
return true;
}
QThread::sleep(1);
}
qDebug() << "Failed to mount new file system.";
return false;
}
return true;
}

29
linux/udisks2api.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef UDISKS2API_H
#define UDISKS2API_H
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (C) 2020 Raspberry Pi (Trading) Limited
*/
#include <QObject>
#include <QFile>
class UDisks2Api : public QObject
{
Q_OBJECT
public:
explicit UDisks2Api(QObject *parent = nullptr);
int authOpen(const QString &device, const QString &mode = "rw");
bool formatDrive(const QString &device, bool mountAfterwards = true);
protected:
QString _resolveDevice(const QString &device);
void _unmountDrive(const QString &driveDbusPath);
signals:
public slots:
};
#endif // UDISKS2API_H