Files
MPF/MPF.Frontend/Drive.cs

317 lines
11 KiB
C#
Raw Permalink Normal View History

using System.Collections.Generic;
2021-09-29 16:37:28 -07:00
using System.IO;
using System.Linq;
#if NET462_OR_GREATER || NETCOREAPP
2023-02-23 22:29:26 -05:00
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
#endif
2023-09-05 00:08:09 -04:00
using SabreTools.RedumpLib.Data;
2024-05-28 13:15:31 -04:00
namespace MPF.Frontend
2019-05-20 22:14:53 -07:00
{
/// <summary>
/// Represents information for a single drive
/// </summary>
2022-04-20 12:33:47 -07:00
/// <remarks>
/// TODO: Can the Aaru models be used instead of the ones I've created here?
/// </remarks>
2019-05-20 22:14:53 -07:00
public class Drive
{
2022-04-20 13:47:55 -07:00
#region Fields
2019-05-20 22:14:53 -07:00
/// <summary>
/// Represents drive type
2019-05-20 22:14:53 -07:00
/// </summary>
public InternalDriveType? InternalDriveType { get; set; }
2019-05-20 22:14:53 -07:00
/// <summary>
2021-02-04 21:38:57 -08:00
/// Drive partition format
2019-05-20 22:14:53 -07:00
/// </summary>
2023-10-07 01:02:21 -04:00
public string? DriveFormat { get; private set; } = null;
2021-09-27 21:38:35 -07:00
/// <summary>
/// Windows drive path
/// </summary>
2023-10-07 01:02:21 -04:00
public string? Name { get; private set; } = null;
2021-02-04 21:38:57 -08:00
/// <summary>
/// Represents if Windows has marked the drive as active
/// </summary>
2022-04-20 13:47:55 -07:00
public bool MarkedActive { get; private set; } = false;
2019-05-20 22:14:53 -07:00
/// <summary>
/// Represents the total size of the drive
/// </summary>
2022-04-20 13:47:55 -07:00
public long TotalSize { get; private set; } = default;
2019-05-20 22:14:53 -07:00
/// <summary>
/// Media label as read by Windows
2019-05-20 22:14:53 -07:00
/// </summary>
2021-12-29 10:00:28 -08:00
/// <remarks>The try/catch is needed because Windows will throw an exception if the drive is not marked as active</remarks>
2023-10-07 01:02:21 -04:00
public string? VolumeLabel { get; private set; } = null;
2022-04-20 13:47:55 -07:00
#endregion
#region Derived Fields
2023-10-18 02:30:38 -04:00
/// <summary>
/// Read-only access to the drive letter
/// </summary>
/// <remarks>Should only be used in UI applications</remarks>
2024-05-22 15:26:55 -04:00
public char? Letter => Name?[0] ?? '\0';
2023-10-18 02:30:38 -04:00
2022-04-20 13:47:55 -07:00
#endregion
/// <summary>
/// Protected constructor
/// </summary>
protected Drive() { }
/// <summary>
/// Create a new Drive object from a drive type and device path
2019-05-20 22:14:53 -07:00
/// </summary>
2022-04-20 13:47:55 -07:00
/// <param name="driveType">InternalDriveType value representing the drive type</param>
/// <param name="devicePath">Path to the device according to the local machine</param>
2023-10-07 01:02:21 -04:00
public static Drive? Create(InternalDriveType? driveType, string devicePath)
2022-04-20 13:47:55 -07:00
{
// Create a new, empty drive object
var drive = new Drive()
{
InternalDriveType = driveType,
};
// If we have an invalid device path, return null
if (string.IsNullOrEmpty(devicePath))
2022-04-20 13:47:55 -07:00
return null;
// Sanitize a Windows-formatted long device path
if (devicePath.StartsWith("\\\\.\\"))
2023-11-20 13:15:06 -05:00
devicePath = devicePath.Substring("\\\\.\\".Length);
2022-04-20 13:47:55 -07:00
// Create and validate the drive info object
var driveInfo = new DriveInfo(devicePath);
if (driveInfo == null || driveInfo == default)
return null;
// Fill in the rest of the data
drive.PopulateFromDriveInfo(driveInfo);
return drive;
}
2022-04-20 13:47:55 -07:00
/// <summary>
/// Populate all fields from a DriveInfo object
/// </summary>
/// <param name="driveInfo">DriveInfo object to populate from</param>
2023-10-07 01:02:21 -04:00
private void PopulateFromDriveInfo(DriveInfo? driveInfo)
{
2022-04-20 13:47:55 -07:00
// If we have an invalid DriveInfo, just return
if (driveInfo == null || driveInfo == default)
return;
// Populate the data fields
2024-05-22 15:26:55 -04:00
Name = driveInfo.Name;
MarkedActive = driveInfo.IsReady;
if (MarkedActive)
2022-04-20 13:47:55 -07:00
{
2024-05-22 15:26:55 -04:00
DriveFormat = driveInfo.DriveFormat;
TotalSize = driveInfo.TotalSize;
VolumeLabel = driveInfo.VolumeLabel;
2022-04-20 13:47:55 -07:00
}
else
{
2024-05-22 15:26:55 -04:00
DriveFormat = string.Empty;
TotalSize = default;
VolumeLabel = string.Empty;
}
}
2022-04-20 13:47:55 -07:00
#region Public Functionality
2021-09-29 16:37:28 -07:00
/// <summary>
/// Create a list of active drives matched to their volume labels
/// </summary>
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
2021-09-29 16:37:28 -07:00
/// <returns>Active drives, matched to labels, if possible</returns>
public static List<Drive> CreateListOfDrives(bool ignoreFixedDrives)
{
2022-04-19 20:19:22 -07:00
var drives = GetDriveList(ignoreFixedDrives);
2023-11-14 23:40:41 -05:00
drives = [.. drives.OrderBy(i => i == null ? "\0" : i.Name)];
2021-09-29 16:37:28 -07:00
return drives;
}
/// <summary>
/// Get the current media type from drive letter
/// </summary>
/// <param name="system"></param>
2021-09-29 16:37:28 -07:00
/// <returns></returns>
2023-10-07 01:02:21 -04:00
public (MediaType?, string?) GetMediaType(RedumpSystem? system)
{
// Take care of the non-optical stuff first
2024-05-22 15:26:55 -04:00
switch (InternalDriveType)
{
2024-05-28 13:15:31 -04:00
case Frontend.InternalDriveType.Floppy:
return (MediaType.FloppyDisk, null);
2024-05-28 13:15:31 -04:00
case Frontend.InternalDriveType.HardDisk:
return (MediaType.HardDisk, null);
2024-05-28 13:15:31 -04:00
case Frontend.InternalDriveType.Removable:
return (MediaType.FlashDrive, null);
}
// Some systems should default to certain media types
switch (system)
{
// CD
case RedumpSystem.Panasonic3DOInteractiveMultiplayer:
case RedumpSystem.PhilipsCDi:
case RedumpSystem.SegaDreamcast:
case RedumpSystem.SegaSaturn:
case RedumpSystem.SonyPlayStation:
case RedumpSystem.VideoCD:
return (MediaType.CDROM, null);
// DVD
case RedumpSystem.DVDAudio:
case RedumpSystem.DVDVideo:
case RedumpSystem.MicrosoftXbox:
case RedumpSystem.MicrosoftXbox360:
return (MediaType.DVD, null);
// HD-DVD
case RedumpSystem.HDDVDVideo:
return (MediaType.HDDVD, null);
// Blu-ray
case RedumpSystem.BDVideo:
case RedumpSystem.MicrosoftXboxOne:
case RedumpSystem.MicrosoftXboxSeriesXS:
case RedumpSystem.SonyPlayStation3:
case RedumpSystem.SonyPlayStation4:
case RedumpSystem.SonyPlayStation5:
return (MediaType.BluRay, null);
// GameCube
case RedumpSystem.NintendoGameCube:
return (MediaType.NintendoGameCubeGameDisc, null);
// Wii
case RedumpSystem.NintendoWii:
return (MediaType.NintendoWiiOpticalDisc, null);
// WiiU
case RedumpSystem.NintendoWiiU:
return (MediaType.NintendoWiiUOpticalDisc, null);
// PSP
case RedumpSystem.SonyPlayStationPortable:
return (MediaType.UMD, null);
}
// Handle optical media by size and filesystem
2024-05-22 15:26:55 -04:00
if (TotalSize >= 0 && TotalSize <= 800_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
return (MediaType.CDROM, null);
2024-05-22 15:26:55 -04:00
else if (TotalSize > 800_000_000 && TotalSize <= 8_540_000_000 && (DriveFormat == "CDFS" || DriveFormat == "UDF"))
return (MediaType.DVD, null);
2024-05-22 15:26:55 -04:00
else if (TotalSize > 8_540_000_000)
return (MediaType.BluRay, null);
return (null, "Could not determine media type!");
}
2021-09-29 16:37:28 -07:00
2022-03-01 16:41:54 -08:00
/// <summary>
/// Refresh the current drive information based on path
/// </summary>
public void RefreshDrive()
2022-04-20 13:47:55 -07:00
{
2024-05-22 15:26:55 -04:00
var driveInfo = DriveInfo.GetDrives().FirstOrDefault(d => d?.Name == Name);
PopulateFromDriveInfo(driveInfo);
2022-04-20 13:47:55 -07:00
}
#endregion
2022-04-20 13:47:55 -07:00
#region Helpers
2022-04-19 20:19:22 -07:00
/// <summary>
/// Get all current attached Drives
/// </summary>
/// <param name="ignoreFixedDrives">True to ignore fixed drives from population, false otherwise</param>
/// <returns>List of drives, null on error</returns>
/// <remarks>
/// https://stackoverflow.com/questions/3060796/how-to-distinguish-between-usb-and-floppy-devices?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
/// https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx
/// </remarks>
private static List<Drive> GetDriveList(bool ignoreFixedDrives)
{
var desiredDriveTypes = new List<DriveType>() { DriveType.CDRom };
if (!ignoreFixedDrives)
{
desiredDriveTypes.Add(DriveType.Fixed);
desiredDriveTypes.Add(DriveType.Removable);
}
2023-02-24 00:17:53 -05:00
// TODO: Reduce reliance on `DriveInfo`
// https://github.com/aaru-dps/Aaru/blob/5164a154e2145941472f2ee0aeb2eff3338ecbb3/Aaru.Devices/Windows/ListDevices.cs#L66
2023-03-20 15:16:35 -04:00
// Create an output drive list
2023-09-27 00:38:00 -04:00
var drives = new List<Drive>();
2023-03-20 15:16:35 -04:00
// Get all standard supported drive types
2022-04-19 20:19:22 -07:00
try
{
2023-03-20 15:16:35 -04:00
drives = DriveInfo.GetDrives()
.Where(d => desiredDriveTypes.Contains(d.DriveType))
2024-05-23 14:01:47 -04:00
.Select(d => Create(ToInternalDriveType(d.DriveType), d.Name) ?? new Drive())
.ToList();
2023-03-20 15:16:35 -04:00
}
catch
{
return drives;
}
2023-03-20 15:16:35 -04:00
// Find and update all floppy drives
#if NET462_OR_GREATER || NETCOREAPP
2023-03-20 15:16:35 -04:00
try
{
2023-02-23 22:29:26 -05:00
CimSession session = CimSession.Create(null);
var collection = session.QueryInstances("root\\CIMV2", "WQL", "SELECT * FROM Win32_LogicalDisk");
2022-04-19 20:19:22 -07:00
2023-02-23 22:29:26 -05:00
foreach (CimInstance instance in collection)
2022-04-19 20:19:22 -07:00
{
2023-02-23 22:29:26 -05:00
CimKeyedCollection<CimProperty> properties = instance.CimInstanceProperties;
uint? mediaType = properties["MediaType"]?.Value as uint?;
2022-04-19 20:19:22 -07:00
if (mediaType != null && ((mediaType > 0 && mediaType < 11) || (mediaType > 12 && mediaType < 22)))
{
2023-10-07 01:02:21 -04:00
char devId = (properties["Caption"].Value as string ?? string.Empty)[0];
2024-05-28 13:15:31 -04:00
drives.ForEach(d => { if (d?.Name != null && d.Name[0] == devId) { d.InternalDriveType = Frontend.InternalDriveType.Floppy; } });
2022-04-19 20:19:22 -07:00
}
}
}
catch
{
2023-03-20 15:16:35 -04:00
// No-op
2022-04-19 20:19:22 -07:00
}
#endif
2023-03-20 15:16:35 -04:00
return drives;
2022-04-19 20:19:22 -07:00
}
2024-05-23 14:01:47 -04:00
/// <summary>
/// Convert drive type to internal version, if possible
/// </summary>
/// <param name="driveType">DriveType value to check</param>
/// <returns>InternalDriveType, if possible, null on error</returns>
internal static InternalDriveType? ToInternalDriveType(DriveType driveType)
{
return driveType switch
{
2024-05-28 13:15:31 -04:00
DriveType.CDRom => (InternalDriveType?)Frontend.InternalDriveType.Optical,
DriveType.Fixed => (InternalDriveType?)Frontend.InternalDriveType.HardDisk,
DriveType.Removable => (InternalDriveType?)Frontend.InternalDriveType.Removable,
2024-05-23 14:01:47 -04:00
_ => null,
};
}
2022-04-20 13:47:55 -07:00
#endregion
2019-05-20 22:14:53 -07:00
}
}