diff --git a/src/app.rs b/src/app.rs index 0b30c9a..3f83be0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,17 @@ +//! All of the data available from parsing [App] manifest files +//! +//! This contains the definition of [`App`] and all of the types used within it +//! +//! Fundamentally an [`App`] is contained within a [`Library`], but there are a variety of helpers +//! that make locating an app easier. Namely: +//! +//! - [SteamDir::find_app()][crate::SteamDir::find_app] +//! - Searches through all of the libraries to locate an app by ID +//! - [Library::app()] +//! - Searches this specific library for an app by ID +//! - [Library::apps()] +//! - Iterates over all of the apps contained in this library + use std::{ collections::BTreeMap, fs, @@ -12,6 +26,9 @@ use crate::{ use serde::{Deserialize, Deserializer}; +/// An [`Iterator`] over a [`Library`]'s [`App`]s +/// +/// Returned from calling [`Library::apps()`] pub struct Iter<'library> { library: &'library Library, app_ids: slice::Iter<'library, u32>, @@ -41,6 +58,55 @@ impl Iterator for Iter<'_> { } /// Metadata for an installed Steam app +/// +/// _See the [module level docs][self] for different ways to get an [`App`]_ +/// +/// All of the information contained within the `appmanifest_.acf` file. For instance +/// +/// ```vdf +/// "AppState" +/// { +/// "appid" "599140" +/// "installdir" "Graveyard Keeper" +/// "name" "Graveyard Keeper" +/// "LastOwner" "12312312312312312" +/// "Universe" "1" +/// "StateFlags" "6" +/// "LastUpdated" "1672176869" +/// "UpdateResult" "0" +/// "SizeOnDisk" "1805798572" +/// "buildid" "8559806" +/// "BytesToDownload" "24348080" +/// "BytesDownloaded" "0" +/// "TargetBuildID" "8559806" +/// "AutoUpdateBehavior" "1" +/// } +/// ``` +/// +/// gets parsed as +/// +/// ```ignore +/// App { +/// app_id: 599140, +/// install_dir: "Graveyard Keeper", +/// name: Some("Graveyard Keeper"), +/// last_user: Some(12312312312312312), +/// universe: Some(Public), +/// state_flags: Some(StateFlags(6)), +/// last_updated: Some(SystemTime { +/// tv_sec: 1672176869, +/// tv_nsec: 0, +/// }), +/// update_result: Some(0), +/// size_on_disk: Some(1805798572), +/// build_id: Some(8559806), +/// bytes_to_download: Some(24348080), +/// bytes_downloaded: Some(0), +/// target_build_id: Some(8559806), +/// auto_update_behavior: Some(OnlyUpdateOnLaunch), +/// // ... +/// } +/// ``` #[derive(Clone, Debug, Deserialize, PartialEq)] #[cfg_attr(test, derive(serde::Serialize))] #[non_exhaustive] diff --git a/src/lib.rs b/src/lib.rs index c4720e5..ac1ec41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,29 @@ impl SteamDir { library::parse_library_paths(&libraryfolders_vdf) } + /// Returns an [`Iterator`] over all the [`Library`]s believed to be part of this installation + /// + /// For reasons akin to [`std::fs::read_dir()`] this method both returns a [`Result`] and + /// returns [`Result`]s for the iterator's items. + /// + /// # Example + /// + /// ``` + /// # /* + /// let steam_dir = SteamDir::locate()?; + /// # */ + /// # use steamlocate::__private_tests::prelude::*; + /// # let temp_steam_dir = expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// let num_apps: usize = steam_dir + /// .libraries()? + /// .filter_map(Result::ok) + /// .map(|lib| lib.app_ids().len()) + /// .sum(); + /// println!("Wow you have {num_apps} installed!"); + /// # assert_eq!(num_apps, 3); + /// # Ok::<_, TestError>(()) + /// ``` pub fn libraries(&self) -> Result { let paths = self.library_paths()?; Ok(library::Iter::new(paths)) @@ -182,6 +205,7 @@ impl SteamDir { /// Convenient helper to look through all the libraries for a specific app /// /// # Example + /// /// ``` /// # use steamlocate::__private_tests::prelude::*; /// # let temp_steam_dir = expect_test_env(); @@ -206,6 +230,7 @@ impl SteamDir { .transpose() } + // TODO: `Iterator`ify this pub fn compat_tool_mapping(&self) -> Result> { let config_path = self.path.join("config").join("config.vdf"); let vdf_text = @@ -243,8 +268,25 @@ impl SteamDir { shortcut::Iter::new(&self.path) } - // TODO: rename to `from_dir()` and make consitent with similar constructors on other structs - pub fn from_dir(path: &Path) -> Result { + /// Attempt to create a [`SteamDir`] from its installation directory + /// + /// When possible you should prefer using [`SteamDir::locate()`] + /// + /// # Example + /// + /// ``` + /// # use steamlocate::SteamDir; + /// # use steamlocate::__private_tests::prelude::*; + /// # let temp_steam_dir = expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// # /* + /// let steam_dir = SteamDir::locate()?; + /// # */ + /// let steam_path = steam_dir.path(); + /// let still_steam_dir = SteamDir::from_dir(steam_path).expect("We just located it"); + /// assert_eq!(still_steam_dir.path(), steam_path); + /// ``` + pub fn from_dir(path: &Path) -> Result { if !path.is_dir() { return Err(Error::validation(ValidationError::missing_dir())); } @@ -257,8 +299,14 @@ impl SteamDir { } /// Attempts to locate the Steam installation directory on the system + /// + /// See the [example on the struct docs][Self#example] + /// + /// Uses platform specific operations to locate the Steam directory. Currently the supported + /// platforms are Windows, MacOS, and Linux while other platforms return an + /// [`LocateError::Unsupported`][error::LocateError::Unsupported] #[cfg(feature = "locate")] - pub fn locate() -> Result { + pub fn locate() -> Result { let path = locate::locate_steam_dir()?; Self::from_dir(&path) diff --git a/src/library.rs b/src/library.rs index 6a1c582..ff70af8 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,3 +1,9 @@ +//! Functionality related to Steam [`Library`]s and related types +//! +//! [`Library`]s are obtained from either [`SteamDir::libraries()`][super::SteamDir::libraries], +//! [`SteamDir::find_app()`][super::SteamDir::find_app], or located manually with +//! [`Library::from_dir()`]. + use std::{ fs, path::{Path, PathBuf}, @@ -74,6 +80,9 @@ pub(crate) fn parse_library_paths(path: &Path) -> Result> { Ok(paths) } +/// An [`Iterator`] over a Steam installation's [`Library`]s +/// +/// Returned from calling [`SteamDir::libraries()`][super::SteamDir::libraries] pub struct Iter { paths: std::vec::IntoIter, } @@ -100,6 +109,7 @@ impl ExactSizeIterator for Iter { } } +/// A steam library containing various installed [`App`]s #[derive(Clone, Debug)] pub struct Library { path: PathBuf, @@ -107,6 +117,11 @@ pub struct Library { } impl Library { + /// Attempt to create a [`Library`] directly from its installation directory + /// + /// You'll typically want to use methods that handle locating the library for you like + /// [`SteamDir::libraries()`][super::SteamDir::libraries] or + /// [`SteamDir::find_app()`][super::SteamDir::find_app]. pub fn from_dir(path: &Path) -> Result { // Read the manifest files at the library to get an up-to-date list of apps since the // values in `libraryfolders.vdf` may be stale @@ -131,14 +146,51 @@ impl Library { }) } + /// Returns the path to the library's installation directory + /// + /// # Example + /// + /// ``` + /// # use steamlocate::__private_tests::prelude::*; + /// # let temp_steam_dir = expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// # let library = steam_dir.libraries().unwrap().next().unwrap().unwrap(); + /// # /* + /// let library = /* Somehow get a library */; + /// # */ + /// let path = library.path(); + /// assert!(path.join("steamapps").is_dir()); + /// ``` pub fn path(&self) -> &Path { &self.path } + /// Returns the full list of Application IDs located within this library pub fn app_ids(&self) -> &[u32] { &self.apps } + /// Attempts to return the [`App`] identified by `app_id` + /// + /// Returns [`None`] if the app isn't located within this library. Otherwise it attempts to + /// return metadata for the installed app + /// + /// # Example + /// + /// ``` + /// # use steamlocate::__private_tests::prelude::*; + /// # let temp_steam_dir = expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// # let library = steam_dir.libraries()?.next().unwrap()?; + /// const GMOD: u32 = 4_000; + /// # /* + /// let library = /* Somehow get a library */; + /// # */ + /// let gmod = library.app(GMOD).expect("Of course we have gmod")?; + /// assert_eq!(gmod.app_id, GMOD); + /// assert_eq!(gmod.name.unwrap(), "Garry's Mod"); + /// # Ok::<_, TestError>(()) + /// ``` pub fn app(&self, app_id: u32) -> Option> { self.app_ids().iter().find(|&&id| id == app_id).map(|&id| { let manifest_path = self @@ -149,6 +201,30 @@ impl Library { }) } + /// Returns an [`Iterator`] over all of the [`App`]s contained in this library + /// + /// # Example + /// + /// ``` + /// # use steamlocate::__private_tests::prelude::*; + /// # let temp_steam_dir = expect_test_env(); + /// # let steam_dir = temp_steam_dir.steam_dir(); + /// # let library = steam_dir.libraries()?.next().unwrap()?; + /// # /* + /// let library = /* Somehow get a library */; + /// # */ + /// let total_size: u64 = library + /// .apps() + /// .filter_map(Result::ok) + /// .filter_map(|app| app.bytes_downloaded) + /// .sum(); + /// println!( + /// "Library {} takes up {} bytes", + /// library.path().display(), total_size, + /// ); + /// # assert_eq!(total_size, 30804429728); + /// # Ok::<_, TestError>(()) + /// ``` pub fn apps(&self) -> app::Iter { app::Iter::new(self) } diff --git a/src/shortcut.rs b/src/shortcut.rs index 53d2f3b..671cc09 100644 --- a/src/shortcut.rs +++ b/src/shortcut.rs @@ -12,7 +12,10 @@ use crate::{ Error, Result, }; -/// A added non-Steam game +// TODO: refactor this to remove storing the `steam_id` and instead make it a method that +// calculates on demand. That fixes some API issues and more directly represents the underlying +// data. This also means that `fn new()` can be removed +/// A non-Steam game that has been added to Steam /// /// Information is parsed from your `userdata//config/shortcuts.vdf` files #[derive(Clone, Debug, PartialEq, Eq)] @@ -58,6 +61,9 @@ impl Shortcut { } } +/// An [`Iterator`] over a Steam installation's [`Shortcut`]s +/// +/// Returned from calling [`SteamDir::shortcuts()`][super::SteamDir::shortcuts] pub struct Iter { dir: PathBuf, read_dir: fs::ReadDir,