cat_dev/fsemul/pcfs/sata/proto/
remove.rs

1//! Definitions for the `Remove` packet type, and it's response types.
2//!
3//! This does destructive things, and removes files from your filesystem. You
4//! can disable the behavior of truly "removing" items from your filesystem,
5//! and configure your PCFS client to just move files to `.rm`
6
7use crate::{errors::NetworkParseError, fsemul::pcfs::errors::PcfsApiError};
8use bytes::{Bytes, BytesMut};
9use std::ffi::CStr;
10use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
11
12/// A packet to remove a file/directory.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SataRemovePacketBody {
15	/// The path to remove, note that this is not the 'resolved' path which is
16	/// the path to actual read from.
17	///
18	/// Interpolation has a few known ways of being replaced:
19	///
20	/// - `%MLC_EMU_DIR`: `<cafe_sdk>/data/mlc/`
21	/// - `%SLC_EMU_DIR`: `<cafe_sdk>/data/slc/`
22	/// - `%DISC_EMU_DIR`: `<cafe_sdk>/data/disc/`
23	/// - `%SAVE_EMU_DIR`: `<cafe_sdk>/data/save/`
24	/// - `%NETWORK`: <mounted network share path>
25	path: String,
26}
27
28impl SataRemovePacketBody {
29	/// Attempt to construct a new remove file packet.
30	///
31	/// ## Errors
32	///
33	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
34	/// but because we need to encode our data as a C-String with a NUL
35	/// terminator we cannot be longer than 511 bytes.
36	///
37	/// Consider using relative/mapped paths if possible when dealing with long
38	/// paths.
39	pub fn new(path: String) -> Result<Self, PcfsApiError> {
40		if path.len() > 511 {
41			return Err(PcfsApiError::PathTooLong(path));
42		}
43
44		Ok(Self { path })
45	}
46
47	#[must_use]
48	pub fn path(&self) -> &str {
49		self.path.as_str()
50	}
51
52	/// Update the path to send in this particular remove file packet.
53	///
54	/// ## Errors
55	///
56	/// If the path is longer than 511 bytes. Normally the max path is 512 bytes,
57	/// but because we need to encode our data as a C-String with a NUL
58	/// terminator we cannot be longer than 511 bytes.
59	///
60	/// Consider using relative/mapped paths if possible when dealing with long
61	/// paths.
62	pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
63		if new_path.len() > 511 {
64			return Err(PcfsApiError::PathTooLong(new_path));
65		}
66
67		self.path = new_path;
68		Ok(())
69	}
70}
71
72impl From<&SataRemovePacketBody> for Bytes {
73	fn from(value: &SataRemovePacketBody) -> Self {
74		let mut result = BytesMut::with_capacity(0x200);
75		result.extend_from_slice(value.path.as_bytes());
76		// These are C Strings so we need a NUL terminator.
77		// Pad with `0`, til we get a full path with a nul terminator.
78		result.extend(BytesMut::zeroed(0x200 - result.len()));
79		result.freeze()
80	}
81}
82
83impl From<SataRemovePacketBody> for Bytes {
84	fn from(value: SataRemovePacketBody) -> Self {
85		Self::from(&value)
86	}
87}
88
89impl TryFrom<Bytes> for SataRemovePacketBody {
90	type Error = NetworkParseError;
91
92	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
93		if value.len() < 0x200 {
94			return Err(NetworkParseError::FieldNotLongEnough(
95				"SataRemove",
96				"Body",
97				0x200,
98				value.len(),
99				value,
100			));
101		}
102		if value.len() > 0x200 {
103			return Err(NetworkParseError::UnexpectedTrailer(
104				"SataRemove",
105				value.slice(0x200..),
106			));
107		}
108
109		let path_c_str =
110			CStr::from_bytes_until_nul(&value).map_err(NetworkParseError::BadCString)?;
111
112		Ok(Self {
113			path: path_c_str.to_str()?.to_owned(),
114		})
115	}
116}
117
118const SATA_REMOVE_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
119
120impl Structable for SataRemovePacketBody {
121	fn definition(&self) -> StructDef<'_> {
122		StructDef::new_static(
123			"SataRemovePacketBody",
124			Fields::Named(SATA_REMOVE_PACKET_BODY_FIELDS),
125		)
126	}
127}
128
129impl Valuable for SataRemovePacketBody {
130	fn as_value(&self) -> Value<'_> {
131		Value::Structable(self)
132	}
133
134	fn visit(&self, visitor: &mut dyn Visit) {
135		visitor.visit_named_fields(&NamedValues::new(
136			SATA_REMOVE_PACKET_BODY_FIELDS,
137			&[Valuable::as_value(&self.path)],
138		));
139	}
140}