playdate icon indicating copy to clipboard operation
playdate copied to clipboard

this is needed too:

Open github-actions[bot] opened this issue 3 months ago • 0 comments

this is needed too: https://github.com/boozook/playdate/blob/242a336585c8e947d2fa8f6421ff22f902edae6b/support/device/src/mount/linux.rs#L322

pub fn volumes_for<'i, I: 'i>(

devs: I)

-> Result<impl Iterator<Item = (impl Future<Output = Result<PathBuf, Error>>, &'i Device)>, Error>

where I: IntoIterator<Item = &'i Device> {

//

Ok(vec![(futures::future::lazy(|_| todo!()).into_future(), &todo!())].into_iter())

}

Source of this issue

use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;
use std::future::Future;
use std::future::IntoFuture;

use futures::FutureExt;
use udev::Enumerator;

use crate::device::serial::SerialNumber;
use crate::error::Error;
use crate::device::Device;


#[derive(Debug, Clone)]
pub struct Volume {
	/// FS mount point.
	path: PathBuf,

	/// Partition node path, e.g.: `/dev/sda1`.
	part_node: PathBuf,

	/// Disk node path, e.g.: `/dev/sda`.
	disk_node: PathBuf,

	/// Device sysfs path.
	dev_sysfs: PathBuf,
}

impl Volume {
	fn new(path: PathBuf, part: PathBuf, disk: PathBuf, dev_sysfs: PathBuf) -> Self {
		Self { path,
		       part_node: part,
		       disk_node: disk,
		       dev_sysfs }
	}
}

impl std::fmt::Display for Volume {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.path.display().fmt(f) }
}

impl Volume {
	/// This volume's path.
	pub fn path(&self) -> Cow<'_, Path> { self.path.as_path().into() }
}


mod unmount {
	use futures::TryFutureExt;

	use super::*;
	use crate::mount::Unmount;
	use crate::mount::UnmountAsync;


	impl Unmount for Volume {
		#[cfg_attr(feature = "tracing", tracing::instrument())]
		fn unmount_blocking(&self) -> Result<(), Error> {
			use std::process::Command;


			let res = eject(self).status()
			                     .map_err(Error::from)
			                     .and_then(|res| res.exit_ok().map_err(Error::from))
			                     .or_else(|err| -> Result<(), Error> {
				                     unmount(self).status()
				                                  .map_err(Error::from)
				                                  .and_then(|res| res.exit_ok().map_err(Error::from))
				                                  .map_err(|err2| Error::chain(err2, [err]))
			                     })
			                     .or_else(move |err| -> Result<(), Error> {
				                     udisksctl_unmount(self).status()
				                                            .map_err(Error::from)
				                                            .and_then(|res| res.exit_ok().map_err(Error::from))
				                                            .map_err(|err2| Error::chain(err2, [err]))
			                     })
			                     .or_else(move |err| -> Result<(), Error> {
				                     udisks_unmount(self).status()
				                                         .map_err(Error::from)
				                                         .and_then(|res| res.exit_ok().map_err(Error::from))
				                                         .map_err(|err2| Error::chain(err2, [err]))
			                     })
			                     .inspect(|_| trace!("unmounted {self}"));

			// TODO: use `udisks_power_off` also as fallback for `udisksctl_power_off`:
			Command::from(udisksctl_power_off(self)).status()
			                                        .map_err(Error::from)
			                                        .and_then(|res| res.exit_ok().map_err(Error::from))
			                                        .map_err(move |err2| {
				                                        if let Some(err) = res.err() {
					                                        Error::chain(err2, [err])
				                                        } else {
					                                        err2
				                                        }
			                                        })
		}
	}

	#[cfg(feature = "tokio")]
	impl UnmountAsync for Volume {
		#[cfg_attr(feature = "tracing", tracing::instrument())]
		async fn unmount(&self) -> Result<(), Error> {
			use tokio::process::Command;
			use futures_lite::future::ready;


			Command::from(eject(self)).status()
			                          .map_err(Error::from)
			                          .and_then(|res| ready(res.exit_ok().map_err(Error::from)))
			                          .or_else(|err| {
				                          Command::from(unmount(self)).status()
				                                                      .map_err(|err2| Error::chain(err2, [err]))
				                                                      .and_then(|res| {
					                                                      ready(res.exit_ok().map_err(Error::from))
				                                                      })
			                          })
			                          .or_else(|err| {
				                          Command::from(udisksctl_unmount(self)).status()
				                                                                .map_err(|err2| {
					                                                                Error::chain(err2, [err])
				                                                                })
				                                                                .and_then(|res| {
					                                                                ready(
					                                                                      res.exit_ok()
					                                                                         .map_err(Error::from),
					)
				                                                                })
			                          })
			                          .or_else(|err| {
				                          Command::from(udisks_unmount(self)).status()
				                                                             .map_err(|err2| {
					                                                             Error::chain(err2, [err])
				                                                             })
				                                                             .and_then(|res| {
					                                                             ready(
					                                                                   res.exit_ok()
					                                                                      .map_err(Error::from),
					)
				                                                             })
			                          })
			                          .inspect_ok(|_| trace!("unmounted {self}"))
			                          .then(|res| {
				                          // TODO: use `udisks_power_off` also as fallback for `udisksctl_power_off`:
				                          Command::from(udisksctl_power_off(self)).status()
				                                                                  .map_err(Error::from)
				                                                                  .and_then(|res| {
					                                                                  ready(
					                                                                        res.exit_ok()
					                                                                           .map_err(Error::from),
					)
				                                                                  })
				                                                                  .map_err(|err2| {
					                                                                  if let Some(err) = res.err() {
						                                                                  Error::chain(err2, [err])
					                                                                  } else {
						                                                                  err2
					                                                                  }
				                                                                  })
			                          })
			                          .await
		}
	}


	fn eject(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("eject");
		cmd.arg(vol.path().as_ref());
		cmd
	}

	fn unmount(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("umount");
		cmd.arg(vol.path().as_ref());
		cmd
	}

	fn udisksctl_unmount(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("udisksctl");
		cmd.args(["unmount", "--no-user-interaction", "-b"]);
		cmd.arg(&vol.part_node);
		cmd
	}

	fn udisksctl_power_off(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("udisksctl");
		cmd.args(["power-off", "--no-user-interaction", "-b"]);
		cmd.arg(&vol.disk_node);
		cmd
	}

	fn udisks_unmount(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("udisks");
		cmd.arg("--unmount");
		cmd.arg(&vol.part_node);
		cmd
	}

	fn udisks_power_off(vol: &Volume) -> std::process::Command {
		let mut cmd = std::process::Command::new("udisks");
		cmd.arg("--detach");
		cmd.arg(&vol.disk_node);
		cmd
	}

	// NOTE: mb. try to use `udisks`, that's existing in Ubuntu.
	// udisksctl unmount -b /dev/sdc1 && udisksctl power-off -b /dev/sdc
	// udisks --unmount /dev/sdb1 && udisks --detach /dev/sdb
}


#[cfg_attr(feature = "tracing", tracing::instrument(fields(dev = dev.as_ref().serial_number())))]
pub async fn volume_for<Info>(dev: Info) -> Result<Volume, Error>
	where Info: AsRef<nusb::DeviceInfo> {
	let sysfs = dev.as_ref().sysfs_path();
	let mut enumerator = enumerator()?;
	enumerator.add_syspath(sysfs)?;

	if let Some(sn) = dev.as_ref().serial_number() {
		enumerator.match_property("ID_SERIAL_SHORT", sn)?;
	}

	let mounts = lfs_core::read_mountinfo()?;
	enumerator.scan_devices()?
	          .filter_map(|udev| {
		          udev.devtype()
		              .filter(|ty| *ty == OsStr::new("partition"))
		              .is_some()
		              .then(move || udev.devnode().map(Path::to_path_buf).map(|node| (udev, node)))
	          })
	          .flatten()
	          .find_map(|(udev, node)| {
		          mounts.iter()
		                .find(|inf| Path::new(inf.fs.as_str()) == node.as_path())
		                .map(|inf| (udev, node, inf))
	          })
	          .and_then(|(udev, node, minf)| {
		          let disk = udev.parent()
		                         .filter(is_disk)
		                         .or_else(|| udev.parent().map(|d| d.parent().filter(is_disk)).flatten())
		                         .and_then(|dev| dev.devnode().map(ToOwned::to_owned));
		          let sysfs = PathBuf::from(sysfs);
		          disk.map(move |disk| Volume::new(minf.mount_point.clone(), node, disk, sysfs))
	          })
	          .ok_or_else(|| Error::not_found())
}


#[cfg_attr(feature = "tracing", tracing::instrument(skip(devs)))]
pub async fn volumes_for_map<I>(devs: I) -> Result<HashMap<Device, Option<Volume>>, Error>
	where I: IntoIterator<Item = Device> {
	let mounts = lfs_core::read_mountinfo()?;

	if mounts.is_empty() {
		return Ok(devs.into_iter().map(|dev| (dev, None)).collect());
	}

	let mut enumerator = enumerator()?;

	let udevs: Vec<_> = enumerator.scan_devices()?
	                              .filter(is_partition)
	                              .filter_map(|dev| {
		                              if let Some(sn) = dev.property_value("ID_SERIAL_SHORT") {
			                              let sn = sn.to_string_lossy().to_string();
			                              Some((dev, sn))
		                              } else {
			                              if let Some(sn) = dev.property_value("ID_SERIAL") {
				                              let sn: Result<SerialNumber, _> =
					                              sn.to_string_lossy().as_ref().try_into();
				                              sn.ok().map(|sn| (dev, sn.to_string()))
			                              } else {
				                              None
			                              }
		                              }
	                              })
	                              .collect();

	if udevs.is_empty() {
		return Ok(devs.into_iter().map(|dev| (dev, None)).collect());
	}

	let mut devs = devs.into_iter().filter_map(|dev| {
		                               if let Some(sn) = dev.info().serial_number().map(ToOwned::to_owned) {
			                               Some((dev, sn))
		                               } else {
			                               None
		                               }
	                               });

	let result =
		devs.map(|(dev, ref sna)| {
			    let node =
				    udevs.iter()
				         .find_map(|(inf, snb)| {
					         (sna == snb).then(|| inf.devnode())
					                     .flatten()
					                     .map(ToOwned::to_owned)
					                     .map(|dn| (inf, dn))
				         })
				         .and_then(|(udev, node)| {
					         mounts.iter()
					               .find(|inf| Path::new(inf.fs.as_str()) == node)
					               .and_then(|inf| {
						               let disk = udev.parent()
						                              .filter(is_disk)
						                              .or_else(|| udev.parent().map(|d| d.parent().filter(is_disk)).flatten())
						                              .and_then(|dev| dev.devnode().map(ToOwned::to_owned));

						               let sysfs = dev.info().sysfs_path().to_owned();
						               disk.map(move |disk| Volume::new(inf.mount_point.clone(), node, disk, sysfs))
					               })
				         });
			    (dev, node)
		    })
		    .collect();
	Ok(result)
}


// TODO: this is needed too:
// pub fn volumes_for<'i, I: 'i>(
// 	devs: I)
// 	-> Result<impl Iterator<Item = (impl Future<Output = Result<PathBuf, Error>>, &'i Device)>, Error>
// 	where I: IntoIterator<Item = &'i Device> {
// 	//
// 	Ok(vec![(futures::future::lazy(|_| todo!()).into_future(), &todo!())].into_iter())
// }


fn enumerator() -> Result<Enumerator, Error> {
	let mut enumerator = udev::Enumerator::new()?;
	// filter only PD devices:
	enumerator.match_property("ID_VENDOR", "Panic")?;
	enumerator.match_property("ID_MODEL", "Playdate")?;
	Ok(enumerator)
}


fn is_partition(dev: &udev::Device) -> bool {
	dev.devtype()
	   .filter(|ty| *ty == OsStr::new("partition"))
	   .is_some()
}

fn is_disk(dev: &udev::Device) -> bool { dev.devtype().filter(|ty| *ty == OsStr::new("disk")).is_some() }

github-actions[bot] avatar Apr 01 '24 20:04 github-actions[bot]