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,93 @@
/*
* Copyright 2018 Balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { execFile } from 'mz/child_process';
import { readdir, readlink } from 'mz/fs';
import { join, resolve } from 'path';
import { Drive } from '..';
import { parse as parseJSON } from './json';
import { parse as parsePairs } from './pairs';
const DISK_PATH_DIR = '/dev/disk/by-path/';
let SUPPORTS_JSON = true;
async function getDevicePaths(): Promise<Map<string, string>> {
const mapping = new Map();
for (const filename of await readdir(DISK_PATH_DIR)) {
const linkPath = join(DISK_PATH_DIR, filename);
let link: string;
try {
link = await readlink(linkPath);
} catch (error) {
continue;
}
const devicePath = resolve(DISK_PATH_DIR, link);
mapping.set(devicePath, linkPath);
}
return mapping;
}
async function addDevicePaths(devices: Drive[]): Promise<void> {
const devicePaths = await getDevicePaths();
for (const device of devices) {
device.devicePath = devicePaths.get(device.device) || null;
}
}
async function getOutput(command: string, ...args: string[]) {
const [stdout] = await execFile(command, args);
return stdout;
}
async function lsblkJSON(): Promise<Drive[]> {
return parseJSON(
await getOutput(
'lsblk',
'--bytes',
'--all',
'--json',
'--paths',
'--output-all',
),
);
}
async function lsblkPairs(): Promise<Drive[]> {
return parsePairs(await getOutput('lsblk', '--bytes', '--all', '--pairs'));
}
async function $lsblk(): Promise<Drive[]> {
if (SUPPORTS_JSON) {
try {
return await lsblkJSON();
} catch (error) {
SUPPORTS_JSON = false;
}
}
return await lsblkPairs();
}
export async function lsblk(): Promise<Drive[]> {
const drives = await $lsblk();
try {
await addDevicePaths(drives);
} catch (error) {
// Couldn't add device paths
}
return drives;
}

155
dependencies/drivelist/lib/lsblk/json.ts vendored Normal file
View file

@ -0,0 +1,155 @@
/*
* Copyright 2018 Balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { posix } from 'path';
import { Drive, Mountpoint } from '..';
interface LsblkJsonOutput {
blockdevices: LsblkJsonOutputDevice[];
}
interface LsblkJsonOutputDevice {
children: LsblkJsonOutputDeviceChild[];
hotplug?: string;
kname?: string;
label: string | null;
'log-sec'?: string;
model: string | null;
mountpoint: string | null;
name: string;
partlabel: string | null;
'phy-sec'?: string;
rm?: string;
ro?: string;
size?: string;
subsystems?: string;
tran?: string;
vendor: string | null;
}
interface LsblkJsonOutputDeviceChild {
label: string | null;
mountpoint?: string;
partlabel: string | null;
}
function getMountpoints(
children: Array<LsblkJsonOutputDeviceChild | LsblkJsonOutputDevice>,
): Mountpoint[] {
return children
.filter(child => {
return child.mountpoint;
})
.map(child => {
return {
path: child.mountpoint!,
label: child.label || child.partlabel,
};
});
}
function getDescription(device: LsblkJsonOutputDevice): string {
const description = [
device.label || '',
device.vendor || '',
device.model || '',
];
if (device.children) {
let subLabels = device.children
.filter(c => (c.label && c.label !== device.label) || c.mountpoint)
.map(c => c.label || c.mountpoint);
subLabels = Array.from(new Set(subLabels));
if (subLabels.length) {
description.push(`(${subLabels.join(', ')})`);
}
}
return description
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
function resolveDeviceName(name?: string): string | null {
if (!name) {
return null;
}
if (!posix.isAbsolute(name)) {
return posix.resolve('/dev', name);
}
return name;
}
export function transform(data: LsblkJsonOutput): Drive[] {
return data.blockdevices
.map(device =>
Object.assign({}, device, {
name: resolveDeviceName(device.name),
kname: resolveDeviceName(device.kname),
}),
)
.filter(
device =>
// Omit loop devices, CD/DVD drives, and RAM
!device.name.startsWith('/dev/loop') &&
!device.name.startsWith('/dev/sr') &&
!device.name.startsWith('/dev/ram'),
)
.map(
(device: LsblkJsonOutputDevice): Drive => {
const isVirtual = device.subsystems
? /^(block)$/i.test(device.subsystems)
: null;
const isSCSI = device.tran
? /^(sata|scsi|ata|ide|pci)$/i.test(device.tran)
: null;
const isUSB = device.tran ? /^(usb)$/i.test(device.tran) : null;
const isReadOnly = Number(device.ro) === 1;
const isRemovable =
Number(device.rm) === 1 ||
Number(device.hotplug) === 1 ||
Boolean(isVirtual);
return {
enumerator: 'lsblk:json',
busType: (device.tran || 'UNKNOWN').toUpperCase(),
busVersion: null,
device: device.name,
devicePath: null,
raw: device.kname || device.name,
description: getDescription(device),
error: null,
size: Number(device.size) || null,
blockSize: Number(device['phy-sec']) || 512,
logicalBlockSize: Number(device['log-sec']) || 512,
mountpoints: device.children
? getMountpoints(device.children)
: getMountpoints([device]),
isReadOnly,
isSystem: !isRemovable && !isVirtual,
isVirtual,
isRemovable,
isCard: null,
isSCSI,
isUSB,
isUAS: null,
};
},
);
}
export function parse(stdout: string): Drive[] {
return transform(JSON.parse(stdout));
}

View file

@ -0,0 +1,182 @@
/*
* Copyright 2018 Balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Drive, Mountpoint } from '..';
interface Dict<T> {
[K: string]: T;
}
function parseLsblkLine(line: string): Dict<string> {
const data: Dict<string> = {};
let offset = 0;
let key = '';
let value = '';
const keyChar = /[^"=]/;
const whitespace = /\s+/;
const escape = '\\';
let state = 'key';
while (offset < line.length) {
if (state === 'key') {
while (keyChar.test(line[offset])) {
key += line[offset];
offset += 1;
}
if (line[offset] === '=') {
state = 'value';
offset += 1;
}
} else if (state === 'value') {
if (line[offset] !== '"') {
throw new Error(`Expected '"', saw "${line[offset]}"`);
}
offset += 1;
while (line[offset] !== '"' && line[offset - 1] !== escape) {
value += line[offset];
offset += 1;
}
if (line[offset] !== '"') {
throw new Error(`Expected '"', saw "${line[offset]}"`);
}
offset += 1;
data[key.toLowerCase()] = value.trim();
key = '';
value = '';
state = 'space';
} else if (state === 'space') {
while (whitespace.test(line[offset])) {
offset += 1;
}
state = 'key';
} else {
throw new Error(`Undefined state "${state}"`);
}
}
return data;
}
function parseLsblk(output: string): Array<Dict<string>> {
return output
.trim()
.split(/\r?\n/g)
.map(parseLsblkLine);
}
function consolidate(
devices: Array<Dict<string>>,
): Array<Dict<string> & { mountpoints: Mountpoint[] }> {
const primaries = devices.filter(device => {
return (
device.type === 'disk' &&
!device.name.startsWith('ram') &&
!device.name.startsWith('sr')
);
});
return primaries.map(device => {
return Object.assign({}, device, {
mountpoints: devices
.filter(child => {
return (
['disk', 'part'].includes(child.type) &&
child.mountpoint &&
child.name.startsWith(device.name)
);
})
.map(
(child): Mountpoint => {
return {
path: child.mountpoint,
label: child.label,
};
},
),
});
});
}
function getDescription(
device: Dict<string> & { mountpoints: Mountpoint[] },
): string {
const description = [
device.label || '',
device.vendor || '',
device.model || '',
];
if (device.mountpoints.length) {
let subLabels = device.mountpoints
.filter(c => {
return (c.label && c.label !== device.label) || c.path;
})
.map(c => {
return c.label || c.path;
});
subLabels = Array.from(new Set(subLabels));
if (subLabels.length) {
description.push(`(${subLabels.join(', ')})`);
}
}
return description
.join(' ')
.replace(/\s+/g, ' ')
.trim();
}
export function parse(stdout: string): Drive[] {
const devices = consolidate(parseLsblk(stdout));
return devices.map(
(device: Dict<string> & { mountpoints: Mountpoint[] }): Drive => {
const isVirtual = device.subsystems
? /^block$/i.test(device.subsystems)
: null;
const isSCSI = device.tran
? /^(?:sata|scsi|ata|ide|pci)$/i.test(device.tran)
: null;
const isUSB = device.tran ? /^usb$/i.test(device.tran) : null;
const isReadOnly = Number(device.ro) === 1;
const isRemovable =
Number(device.rm) === 1 ||
Number(device.hotplug) === 1 ||
Boolean(isVirtual);
return {
enumerator: 'lsblk:pairs',
busType: (device.tran || 'UNKNOWN').toUpperCase(),
busVersion: null,
device: '/dev/' + device.name,
devicePath: null,
raw: '/dev/' + device.name,
description: getDescription(device) || device.name,
error: null,
size: Number(device.size) || null,
blockSize: Number(device['phy-sec']) || 512,
logicalBlockSize: Number(device['log-sec']) || 512,
mountpoints: device.mountpoints,
isReadOnly,
isSystem: !isRemovable && !isVirtual,
isVirtual,
isRemovable,
isCard: null,
isSCSI,
isUSB,
isUAS: null,
};
},
);
}