odilia_cache/
lib.rs

1#![deny(
2	clippy::all,
3	clippy::pedantic,
4	clippy::cargo,
5	clippy::map_unwrap_or,
6	clippy::unwrap_used,
7	unsafe_code
8)]
9
10use std::{
11	collections::HashMap,
12	sync::{Arc, RwLock, Weak},
13};
14
15use async_trait::async_trait;
16use atspi::{
17	accessible::{Accessible, AccessibleProxy, RelationType, Role},
18	accessible_id::{AccessibleId, HasAccessibleId},
19	convertable::Convertable,
20	events::GenericEvent,
21	signify::Signified,
22	text::{ClipType, Granularity, Text, TextProxy},
23	text_ext::TextExt,
24	CoordType, InterfaceSet, StateSet,
25};
26use dashmap::DashMap;
27use fxhash::FxBuildHasher;
28use odilia_common::{
29	errors::{AccessiblePrimitiveConversionError, CacheError, OdiliaError},
30	result::OdiliaResult,
31};
32use serde::{Deserialize, Serialize};
33use zbus::{
34	names::OwnedUniqueName,
35	zvariant::{ObjectPath, OwnedObjectPath},
36	CacheProperties, ProxyBuilder,
37};
38
39type CacheKey = AccessiblePrimitive;
40type InnerCache = DashMap<CacheKey, Arc<RwLock<CacheItem>>, FxBuildHasher>;
41type ThreadSafeCache = Arc<InnerCache>;
42
43#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
44/// A struct which represents the bare minimum of an accessible for purposes of caching.
45/// This makes some *possibly eronious* assumptions about what the sender is.
46pub struct AccessiblePrimitive {
47	/// The accessible ID in /org/a11y/atspi/accessible/XYZ; note that XYZ may be equal to any positive number, 0, "null", or "root".
48	pub id: AccessibleId,
49	/// Assuming that the sender is ":x.y", this stores the (x,y) portion of this sender.
50	pub sender: smartstring::alias::String,
51}
52impl AccessiblePrimitive {
53	/// Convert into an [`atspi::accessible::AccessibleProxy`]. Must be async because the creation of an async proxy requires async itself.
54	/// # Errors
55	/// Will return a [`zbus::Error`] in the case of an invalid destination, path, or failure to create a `Proxy` from those properties.
56	pub async fn into_accessible<'a>(
57		self,
58		conn: &zbus::Connection,
59	) -> zbus::Result<AccessibleProxy<'a>> {
60		let id = self.id;
61		let sender = self.sender.clone();
62		let path: ObjectPath<'a> = id.try_into()?;
63		ProxyBuilder::new(conn)
64			.path(path)?
65			.destination(sender.as_str().to_owned())?
66			.cache_properties(CacheProperties::No)
67			.build()
68			.await
69	}
70	/// Convert into an [`atspi::text::TextProxy`]. Must be async because the creation of an async proxy requires async itself.
71	/// # Errors
72	/// Will return a [`zbus::Error`] in the case of an invalid destination, path, or failure to create a `Proxy` from those properties.
73	pub async fn into_text<'a>(self, conn: &zbus::Connection) -> zbus::Result<TextProxy<'a>> {
74		let id = self.id;
75		let sender = self.sender.clone();
76		let path: ObjectPath<'a> = id.try_into()?;
77		ProxyBuilder::new(conn)
78			.path(path)?
79			.destination(sender.as_str().to_owned())?
80			.cache_properties(CacheProperties::No)
81			.build()
82			.await
83	}
84	/// Turns any `atspi::event` type into an `AccessiblePrimtive`, the basic type which is used for keys in the cache.
85	/// # Errors
86	/// The errors are self-explanitory variants of the [`odilia_common::errors::AccessiblePrimitiveConversionError`].
87	pub fn from_event<T: GenericEvent>(
88		event: &T,
89	) -> Result<Self, AccessiblePrimitiveConversionError> {
90		let sender = event
91			.sender()
92			.map_err(|_| AccessiblePrimitiveConversionError::ErrSender)?
93			.ok_or(AccessiblePrimitiveConversionError::NoSender)?;
94		let path = event.path().ok_or(AccessiblePrimitiveConversionError::NoPathId)?;
95		let id: AccessibleId = path
96			.try_into()
97			.map_err(|_| AccessiblePrimitiveConversionError::InvalidPath)?;
98		Ok(Self { id, sender: sender.as_str().into() })
99	}
100}
101impl TryFrom<atspi::events::Accessible> for AccessiblePrimitive {
102	type Error = AccessiblePrimitiveConversionError;
103
104	fn try_from(
105		atspi_accessible: atspi::events::Accessible,
106	) -> Result<AccessiblePrimitive, Self::Error> {
107		let tuple_converter = (atspi_accessible.name, atspi_accessible.path);
108		tuple_converter.try_into()
109	}
110}
111impl TryFrom<(OwnedUniqueName, OwnedObjectPath)> for AccessiblePrimitive {
112	type Error = AccessiblePrimitiveConversionError;
113
114	fn try_from(
115		so: (OwnedUniqueName, OwnedObjectPath),
116	) -> Result<AccessiblePrimitive, Self::Error> {
117		let accessible_id: AccessibleId = so.1.try_into()?;
118		Ok(AccessiblePrimitive { id: accessible_id, sender: so.0.as_str().into() })
119	}
120}
121impl TryFrom<(String, OwnedObjectPath)> for AccessiblePrimitive {
122	type Error = AccessiblePrimitiveConversionError;
123
124	fn try_from(so: (String, OwnedObjectPath)) -> Result<AccessiblePrimitive, Self::Error> {
125		let accessible_id: AccessibleId = so.1.try_into()?;
126		Ok(AccessiblePrimitive { id: accessible_id, sender: so.0.into() })
127	}
128}
129impl TryFrom<(String, AccessibleId)> for AccessiblePrimitive {
130	type Error = AccessiblePrimitiveConversionError;
131
132	fn try_from(so: (String, AccessibleId)) -> Result<AccessiblePrimitive, Self::Error> {
133		Ok(AccessiblePrimitive { id: so.1, sender: so.0.into() })
134	}
135}
136impl<'a> TryFrom<(String, ObjectPath<'a>)> for AccessiblePrimitive {
137	type Error = OdiliaError;
138
139	fn try_from(so: (String, ObjectPath<'a>)) -> Result<AccessiblePrimitive, Self::Error> {
140		let accessible_id: AccessibleId = so.1.try_into()?;
141		Ok(AccessiblePrimitive { id: accessible_id, sender: so.0.into() })
142	}
143}
144impl<'a> TryFrom<&AccessibleProxy<'a>> for AccessiblePrimitive {
145	type Error = AccessiblePrimitiveConversionError;
146
147	fn try_from(accessible: &AccessibleProxy<'_>) -> Result<AccessiblePrimitive, Self::Error> {
148		let sender = accessible.destination().as_str().into();
149		let Ok(id) = accessible.id() else {
150			return Err(AccessiblePrimitiveConversionError::NoPathId);
151		};
152		Ok(AccessiblePrimitive { id, sender })
153	}
154}
155impl<'a> TryFrom<AccessibleProxy<'a>> for AccessiblePrimitive {
156	type Error = AccessiblePrimitiveConversionError;
157
158	fn try_from(accessible: AccessibleProxy<'_>) -> Result<AccessiblePrimitive, Self::Error> {
159		let sender = accessible.destination().as_str().into();
160		let Ok(id) = accessible.id() else {
161      return Err(AccessiblePrimitiveConversionError::NoPathId);
162    };
163		Ok(AccessiblePrimitive { id, sender })
164	}
165}
166
167#[derive(Clone, Debug, Deserialize, Serialize)]
168/// A struct representing an accessible. To get any information from the cache other than the stored information like role, interfaces, and states, you will need to instantiate an [`atspi::accessible::AccessibleProxy`] or other `*Proxy` type from atspi to query further info.
169pub struct CacheItem {
170	// The accessible object (within the application)	(so)
171	pub object: AccessiblePrimitive,
172	// The application (root object(?)	  (so)
173	pub app: AccessiblePrimitive,
174	// The parent object.  (so)
175	pub parent: CacheRef,
176	// The accessbile index in parent.	i
177	pub index: i32,
178	// Child count of the accessible  i
179	pub children_num: i32,
180	// The exposed interfece(s) set.  as
181	pub interfaces: InterfaceSet,
182	// Accessible role. u
183	pub role: Role,
184	// The states applicable to the accessible.  au
185	pub states: StateSet,
186	// The text of the accessible.
187	pub text: String,
188	// The children (ids) of the accessible.
189	pub children: Vec<CacheRef>,
190
191	#[serde(skip)]
192	pub cache: Weak<Cache>,
193}
194impl CacheItem {
195	/// Return a *reference* to a parent. This is *much* cheaper than getting the parent element outright via [`Self::parent`].
196	/// # Errors
197	/// This method will return a [`CacheError::NoItem`] if no item is found within the cache.
198	pub fn parent_ref(&mut self) -> OdiliaResult<Arc<std::sync::RwLock<CacheItem>>> {
199		let parent_ref = Weak::upgrade(&self.parent.item);
200		if let Some(p) = parent_ref {
201			Ok(p)
202		} else {
203			let cache = strong_cache(&self.cache)?;
204			let arc_mut_parent = cache
205				.get_ref(&self.parent.key.clone())
206				.ok_or(CacheError::NoItem)?;
207			self.parent.item = Arc::downgrade(&arc_mut_parent);
208			Ok(arc_mut_parent)
209		}
210	}
211	/// Creates a `CacheItem` from an [`atspi::Event`] type.
212	/// # Errors
213	/// This can fail under three possible conditions:
214	///
215	/// 1. We are unable to convert information from the event into an [`AccessiblePrimitive`] hashmap key. This should never happen.
216	/// 2. We are unable to convert the [`AccessiblePrimitive`] to an [`atspi::accessible::AccessibleProxy`].
217	/// 3. The `accessible_to_cache_item` function fails for any reason. This also shouldn't happen.
218	pub async fn from_atspi_event<T: Signified>(
219		event: &T,
220		cache: Weak<Cache>,
221		connection: &zbus::Connection,
222	) -> OdiliaResult<Self> {
223		let a11y_prim = AccessiblePrimitive::from_event(event)?;
224		accessible_to_cache_item(&a11y_prim.into_accessible(connection).await?, cache).await
225	}
226	/// Convert an [`atspi::cache::CacheItem`] into a [`crate::CacheItem`].
227	/// This requires calls to `DBus`, which is quite expensive. Beware calling this too often.
228	/// # Errors
229	/// This function can fail under the following conditions:
230	///
231	/// 1. The [`atspi::cache::CacheItem`] can not be turned into a [`crate::AccessiblePrimitive`]. This should never happen.
232	/// 2. The [`crate::AccessiblePrimitive`] can not be turned into a [`atspi::accessible::AccessibleProxy`]. This should never happen.
233	/// 3. Getting children from the `AccessibleProxy` fails. This should never happen.
234	///
235	/// The only time these can fail is if the item is removed on the application side before the conversion to `AccessibleProxy`.
236	pub async fn from_atspi_cache_item(
237		atspi_cache_item: atspi::cache::CacheItem,
238		cache: Weak<Cache>,
239		connection: &zbus::Connection,
240	) -> OdiliaResult<Self> {
241		let children: Vec<CacheRef> =
242			AccessiblePrimitive::try_from(atspi_cache_item.object.clone())?
243				.into_accessible(connection)
244				.await?
245				.get_children()
246				.await?
247				.into_iter()
248				.map(|child_object_pair| {
249					Ok(CacheRef::new(child_object_pair.try_into()?))
250				})
251				.collect::<Result<_, AccessiblePrimitiveConversionError>>()?;
252		Ok(Self {
253			object: atspi_cache_item.object.try_into()?,
254			app: atspi_cache_item.app.try_into()?,
255			parent: CacheRef::new(atspi_cache_item.parent.try_into()?),
256			index: atspi_cache_item.index,
257			children_num: atspi_cache_item.children,
258			interfaces: atspi_cache_item.ifaces,
259			role: atspi_cache_item.role,
260			states: atspi_cache_item.states,
261			text: atspi_cache_item.name,
262			cache,
263			children,
264		})
265	}
266	// Same as [`Accessible::get_children`], just offered as a non-async version.
267	/// Get a `Vec` of children with the same type as `Self`.
268	/// # Errors
269	/// 1. Will return an `Err` variant if `self.cache` does not reference an active cache. This should never happen, but it is technically possible.
270	/// 2. Any children keys' values are not found in the cache itself.
271	pub fn get_children(&self) -> OdiliaResult<Vec<Self>> {
272		let derefed_cache: Arc<Cache> = strong_cache(&self.cache)?;
273		let children = self
274			.children
275			.iter()
276			.map(|child_ref| {
277				child_ref
278					.clone_inner()
279					.or_else(|| derefed_cache.get(&child_ref.key))
280					.ok_or(CacheError::NoItem)
281			})
282			.collect::<Result<Vec<_>, _>>()?;
283		Ok(children)
284	}
285}
286
287/// A composition of an accessible ID and (possibly) a reference
288/// to its `CacheItem`, if the item has not been dropped from the cache yet.
289/// TODO if desirable, we could make one direction strong references (e.g. have
290/// the parent be an Arc, xor have the children be Arcs). Might even be possible to have both.
291/// BUT - is it even desirable to keep an item pinned in an Arc from its
292/// releatives after it has been removed from the cache?
293#[derive(Debug, Clone, Deserialize, Serialize)]
294pub struct CacheRef {
295	pub key: CacheKey,
296	#[serde(skip)]
297	item: Weak<RwLock<CacheItem>>,
298}
299
300impl CacheRef {
301	#[must_use]
302	pub fn new(key: AccessiblePrimitive) -> Self {
303		Self { key, item: Weak::new() }
304	}
305
306	#[must_use]
307	pub fn clone_inner(&self) -> Option<CacheItem> {
308		Some(self.item.upgrade().as_ref()?.read().ok()?.clone())
309	}
310}
311
312impl From<AccessiblePrimitive> for CacheRef {
313	fn from(value: AccessiblePrimitive) -> Self {
314		Self::new(value)
315	}
316}
317
318#[inline]
319async fn as_accessible(cache_item: &CacheItem) -> OdiliaResult<AccessibleProxy<'_>> {
320	let cache = strong_cache(&cache_item.cache)?;
321	Ok(cache_item.object.clone().into_accessible(&cache.connection).await?)
322}
323#[inline]
324async fn as_text(cache_item: &CacheItem) -> OdiliaResult<TextProxy<'_>> {
325	let cache = strong_cache(&cache_item.cache)?;
326	Ok(cache_item.object.clone().into_text(&cache.connection).await?)
327}
328
329#[inline]
330fn strong_cache(weak_cache: &Weak<Cache>) -> OdiliaResult<Arc<Cache>> {
331	Weak::upgrade(weak_cache).ok_or(OdiliaError::Cache(CacheError::NotAvailable))
332}
333
334#[async_trait]
335impl Accessible for CacheItem {
336	type Error = OdiliaError;
337
338	async fn get_application(&self) -> Result<Self, Self::Error> {
339		let derefed_cache: Arc<Cache> = strong_cache(&self.cache)?;
340		derefed_cache.get(&self.app).ok_or(CacheError::NoItem.into())
341	}
342	async fn parent(&self) -> Result<Self, Self::Error> {
343		let parent_item = self
344			.parent
345			.clone_inner()
346			.or_else(|| self.cache.upgrade()?.get(&self.parent.key));
347		parent_item.ok_or(CacheError::NoItem.into())
348	}
349	async fn get_children(&self) -> Result<Vec<Self>, Self::Error> {
350		self.get_children()
351	}
352	async fn child_count(&self) -> Result<i32, Self::Error> {
353		Ok(self.children_num)
354	}
355	async fn get_index_in_parent(&self) -> Result<i32, Self::Error> {
356		Ok(self.index)
357	}
358	async fn get_role(&self) -> Result<Role, Self::Error> {
359		Ok(self.role)
360	}
361	async fn get_interfaces(&self) -> Result<InterfaceSet, Self::Error> {
362		Ok(self.interfaces)
363	}
364	async fn get_attributes(&self) -> Result<HashMap<String, String>, Self::Error> {
365		Ok(as_accessible(self).await?.get_attributes().await?)
366	}
367	async fn name(&self) -> Result<String, Self::Error> {
368		Ok(as_accessible(self).await?.name().await?)
369	}
370	async fn locale(&self) -> Result<String, Self::Error> {
371		Ok(as_accessible(self).await?.locale().await?)
372	}
373	async fn description(&self) -> Result<String, Self::Error> {
374		Ok(as_accessible(self).await?.description().await?)
375	}
376	async fn get_relation_set(&self) -> Result<Vec<(RelationType, Vec<Self>)>, Self::Error> {
377		let cache = strong_cache(&self.cache)?;
378		as_accessible(self)
379			.await?
380			.get_relation_set()
381			.await?
382			.into_iter()
383			.map(|(relation, object_pairs)| {
384				(
385					relation,
386					object_pairs
387						.into_iter()
388						.map(|object_pair| {
389							cache.get(&object_pair.try_into()?).ok_or(
390								OdiliaError::Cache(
391									CacheError::NoItem,
392								),
393							)
394						})
395						.collect::<Result<Vec<Self>, OdiliaError>>(),
396				)
397			})
398			.map(|(relation, result_selfs)| Ok((relation, result_selfs?)))
399			.collect::<Result<Vec<(RelationType, Vec<Self>)>, OdiliaError>>()
400	}
401	async fn get_role_name(&self) -> Result<String, Self::Error> {
402		Ok(as_accessible(self).await?.get_role_name().await?)
403	}
404	async fn get_state(&self) -> Result<StateSet, Self::Error> {
405		Ok(self.states)
406	}
407	async fn get_child_at_index(&self, idx: i32) -> Result<Self, Self::Error> {
408		<Self as Accessible>::get_children(self)
409			.await?
410			.get(usize::try_from(idx)?)
411			.ok_or(CacheError::NoItem.into())
412			.cloned()
413	}
414	async fn get_localized_role_name(&self) -> Result<String, Self::Error> {
415		Ok(as_accessible(self).await?.get_localized_role_name().await?)
416	}
417	async fn accessible_id(&self) -> Result<AccessibleId, Self::Error> {
418		Ok(self.object.id)
419	}
420}
421#[async_trait]
422impl Text for CacheItem {
423	type Error = OdiliaError;
424
425	async fn add_selection(
426		&self,
427		start_offset: i32,
428		end_offset: i32,
429	) -> Result<bool, Self::Error> {
430		Ok(as_text(self).await?.add_selection(start_offset, end_offset).await?)
431	}
432	async fn get_attribute_run(
433		&self,
434		offset: i32,
435		include_defaults: bool,
436	) -> Result<(std::collections::HashMap<String, String>, i32, i32), Self::Error> {
437		Ok(as_text(self)
438			.await?
439			.get_attribute_run(offset, include_defaults)
440			.await?)
441	}
442	async fn get_attribute_value(
443		&self,
444		offset: i32,
445		attribute_name: &str,
446	) -> Result<String, Self::Error> {
447		Ok(as_text(self)
448			.await?
449			.get_attribute_value(offset, attribute_name)
450			.await?)
451	}
452	async fn get_attributes(
453		&self,
454		offset: i32,
455	) -> Result<(std::collections::HashMap<String, String>, i32, i32), Self::Error> {
456		Ok(as_text(self).await?.get_attributes(offset).await?)
457	}
458	async fn get_bounded_ranges(
459		&self,
460		x: i32,
461		y: i32,
462		width: i32,
463		height: i32,
464		coord_type: CoordType,
465		x_clip_type: ClipType,
466		y_clip_type: ClipType,
467	) -> Result<Vec<(i32, i32, String, zbus::zvariant::OwnedValue)>, Self::Error> {
468		Ok(as_text(self)
469			.await?
470			.get_bounded_ranges(
471				x,
472				y,
473				width,
474				height,
475				coord_type,
476				x_clip_type,
477				y_clip_type,
478			)
479			.await?)
480	}
481	async fn get_character_at_offset(&self, offset: i32) -> Result<i32, Self::Error> {
482		Ok(as_text(self).await?.get_character_at_offset(offset).await?)
483	}
484	async fn get_character_extents(
485		&self,
486		offset: i32,
487		coord_type: CoordType,
488	) -> Result<(i32, i32, i32, i32), Self::Error> {
489		Ok(as_text(self).await?.get_character_extents(offset, coord_type).await?)
490	}
491	async fn get_default_attribute_set(
492		&self,
493	) -> Result<std::collections::HashMap<String, String>, Self::Error> {
494		Ok(as_text(self).await?.get_default_attribute_set().await?)
495	}
496	async fn get_default_attributes(
497		&self,
498	) -> Result<std::collections::HashMap<String, String>, Self::Error> {
499		Ok(as_text(self).await?.get_default_attributes().await?)
500	}
501	async fn get_nselections(&self) -> Result<i32, Self::Error> {
502		Ok(as_text(self).await?.get_nselections().await?)
503	}
504	async fn get_offset_at_point(
505		&self,
506		x: i32,
507		y: i32,
508		coord_type: CoordType,
509	) -> Result<i32, Self::Error> {
510		Ok(as_text(self).await?.get_offset_at_point(x, y, coord_type).await?)
511	}
512	async fn get_range_extents(
513		&self,
514		start_offset: i32,
515		end_offset: i32,
516		coord_type: CoordType,
517	) -> Result<(i32, i32, i32, i32), Self::Error> {
518		Ok(as_text(self)
519			.await?
520			.get_range_extents(start_offset, end_offset, coord_type)
521			.await?)
522	}
523	async fn get_selection(&self, selection_num: i32) -> Result<(i32, i32), Self::Error> {
524		Ok(as_text(self).await?.get_selection(selection_num).await?)
525	}
526	async fn get_string_at_offset(
527		&self,
528		offset: i32,
529		granularity: Granularity,
530	) -> Result<(String, i32, i32), Self::Error> {
531		let uoffset = usize::try_from(offset)?;
532		// optimisations that don't call out to DBus.
533		if granularity == Granularity::Paragraph {
534			return Ok((self.text.clone(), 0, self.text.len().try_into()?));
535		} else if granularity == Granularity::Char {
536			let range = uoffset..=uoffset;
537			return Ok((
538				self.text
539					.get(range)
540					.ok_or(CacheError::TextBoundsError)?
541					.to_string(),
542				offset,
543				offset + 1,
544			));
545		} else if granularity == Granularity::Word {
546			return Ok(self
547				.text
548				// [char]
549				.split_whitespace()
550				// [(idx, char)]
551				.enumerate()
552				// [(word, start, end)]
553				.filter_map(|(_, word)| {
554					let start = self
555						.text
556						// [(idx, char)]
557						.char_indices()
558						// [(idx, char)]: uses pointer arithmatic to find start index
559						.find(|&(idx, _)| {
560							idx == word.as_ptr() as usize
561								- self.text.as_ptr() as usize
562						})
563						// [idx]
564						.map(|(idx, _)| idx)?;
565					// calculate based on start
566					let end = start + word.len();
567					let i_start = i32::try_from(start).ok()?;
568					let i_end = i32::try_from(end).ok()?;
569					// if the offset if within bounds
570					if uoffset >= start && uoffset <= end {
571						Some((word.to_string(), i_start, i_end))
572					} else {
573						None
574					}
575				})
576				// get "all" words that match; there should be only one result
577				.collect::<Vec<_>>()
578				// get the first result
579				.get(0)
580				// if there's no matching word (out of bounds)
581				.ok_or_else(|| OdiliaError::Generic("Out of bounds".to_string()))?
582				// clone the reference into a value
583				.clone());
584		}
585		// any other variations, in particular, Granularity::Line, will need to call out to DBus. It's just too complex to calculate, get updates for bounding boxes, etc.
586		// this variation does NOT get a semantic line. It gets a visual line.
587		Ok(as_text(self).await?.get_string_at_offset(offset, granularity).await?)
588	}
589	async fn get_text(
590		&self,
591		start_offset: i32,
592		end_offset: i32,
593	) -> Result<String, Self::Error> {
594		self.text
595			.get(usize::try_from(start_offset)?..usize::try_from(end_offset)?)
596			.map(std::borrow::ToOwned::to_owned)
597			.ok_or(OdiliaError::Generic("Type is None, not Some".to_string()))
598	}
599	async fn get_text_after_offset(
600		&self,
601		offset: i32,
602		type_: u32,
603	) -> Result<(String, i32, i32), Self::Error> {
604		Ok(as_text(self).await?.get_text_after_offset(offset, type_).await?)
605	}
606	async fn get_text_at_offset(
607		&self,
608		offset: i32,
609		type_: u32,
610	) -> Result<(String, i32, i32), Self::Error> {
611		Ok(as_text(self).await?.get_text_at_offset(offset, type_).await?)
612	}
613	async fn get_text_before_offset(
614		&self,
615		offset: i32,
616		type_: u32,
617	) -> Result<(String, i32, i32), Self::Error> {
618		Ok(as_text(self).await?.get_text_before_offset(offset, type_).await?)
619	}
620	async fn remove_selection(&self, selection_num: i32) -> Result<bool, Self::Error> {
621		Ok(as_text(self).await?.remove_selection(selection_num).await?)
622	}
623	async fn scroll_substring_to(
624		&self,
625		start_offset: i32,
626		end_offset: i32,
627		type_: u32,
628	) -> Result<bool, Self::Error> {
629		Ok(as_text(self)
630			.await?
631			.scroll_substring_to(start_offset, end_offset, type_)
632			.await?)
633	}
634	async fn scroll_substring_to_point(
635		&self,
636		start_offset: i32,
637		end_offset: i32,
638		type_: u32,
639		x: i32,
640		y: i32,
641	) -> Result<bool, Self::Error> {
642		Ok(as_text(self)
643			.await?
644			.scroll_substring_to_point(start_offset, end_offset, type_, x, y)
645			.await?)
646	}
647	async fn set_caret_offset(&self, offset: i32) -> Result<bool, Self::Error> {
648		Ok(as_text(self).await?.set_caret_offset(offset).await?)
649	}
650	async fn set_selection(
651		&self,
652		selection_num: i32,
653		start_offset: i32,
654		end_offset: i32,
655	) -> Result<bool, Self::Error> {
656		Ok(as_text(self)
657			.await?
658			.set_selection(selection_num, start_offset, end_offset)
659			.await?)
660	}
661	async fn caret_offset(&self) -> Result<i32, Self::Error> {
662		Ok(as_text(self).await?.caret_offset().await?)
663	}
664	async fn character_count(&self) -> Result<i32, Self::Error> {
665		Ok(i32::try_from(self.text.len())?)
666	}
667}
668
669/// An internal cache used within Odilia.
670///
671/// This contains (mostly) all accessibles in the entire accessibility tree, and
672/// they are referenced by their IDs. If you are having issues with incorrect or
673/// invalid accessibles trying to be accessed, this is code is probably the issue.
674#[derive(Clone, Debug)]
675pub struct Cache {
676	pub by_id: ThreadSafeCache,
677	pub connection: zbus::Connection,
678}
679
680// N.B.: we are using std RwLockes internally here, within the cache hashmap
681// entries. When adding async methods, take care not to hold these mutexes
682// across .await points.
683impl Cache {
684	/// create a new, fresh cache
685	#[must_use]
686	pub fn new(conn: zbus::Connection) -> Self {
687		Self {
688			by_id: Arc::new(DashMap::with_capacity_and_hasher(
689				10_000,
690				FxBuildHasher::default(),
691			)),
692			connection: conn,
693		}
694	}
695	/// add a single new item to the cache. Note that this will empty the bucket
696	/// before inserting the `CacheItem` into the cache (this is so there is
697	/// never two items with the same ID stored in the cache at the same time).
698	/// # Errors
699	/// Fails if the internal call to [`Self::add_ref`] fails.
700	pub fn add(&self, cache_item: CacheItem) -> OdiliaResult<()> {
701		let id = cache_item.object.clone();
702		self.add_ref(id, &Arc::new(RwLock::new(cache_item)))
703	}
704
705	/// Add an item via a reference instead of creating the reference.
706	/// # Errors
707	/// Can error if [`Cache::populate_references`] errors. The insertion is guarenteed to succeed.
708	pub fn add_ref(
709		&self,
710		id: CacheKey,
711		cache_item: &Arc<RwLock<CacheItem>>,
712	) -> OdiliaResult<()> {
713		self.by_id.insert(id, Arc::clone(cache_item));
714		Self::populate_references(&self.by_id, cache_item)
715	}
716
717	/// Remove a single cache item. This function can not fail.
718	pub fn remove(&self, id: &CacheKey) {
719		self.by_id.remove(id);
720	}
721
722	/// Get a single item from the cache, this only gets a reference to an item, not the item itself.
723	/// You will need to either get a read or a write lock on any item returned from this function.
724	/// It also may return `None` if a value is not matched to the key.
725	#[must_use]
726	pub fn get_ref(&self, id: &CacheKey) -> Option<Arc<RwLock<CacheItem>>> {
727		self.by_id.get(id).as_deref().cloned()
728	}
729
730	/// Get a single item from the cache.
731	///
732	/// This will allow you to get the item without holding any locks to it,
733	/// at the cost of (1) a clone and (2) no guarantees that the data is kept up-to-date.
734	#[must_use]
735	pub fn get(&self, id: &CacheKey) -> Option<CacheItem> {
736		Some(self.by_id.get(id).as_deref()?.read().ok()?.clone())
737	}
738
739	/// get a many items from the cache; this only creates one read handle (note that this will copy all data you would like to access)
740	#[must_use]
741	pub fn get_all(&self, ids: &[CacheKey]) -> Vec<Option<CacheItem>> {
742		ids.iter().map(|id| self.get(id)).collect()
743	}
744
745	/// Bulk add many items to the cache; only one accessible should ever be
746	/// associated with an id.
747	/// # Errors
748	/// An `Err(_)` variant may be returned if the [`Cache::populate_references`] function fails.
749	pub fn add_all(&self, cache_items: Vec<CacheItem>) -> OdiliaResult<()> {
750		cache_items
751			.into_iter()
752			.map(|cache_item| {
753				let id = cache_item.object.clone();
754				let arc = Arc::new(RwLock::new(cache_item));
755				self.by_id.insert(id, Arc::clone(&arc));
756				arc
757			})
758			.collect::<Vec<_>>() // Insert all items before populating
759			.into_iter()
760			.try_for_each(|item| Self::populate_references(&self.by_id, &item))
761	}
762	/// Bulk remove all ids in the cache; this only refreshes the cache after removing all items.
763	pub fn remove_all(&self, ids: &Vec<CacheKey>) {
764		for id in ids {
765			self.by_id.remove(id);
766		}
767	}
768
769	/// Edit a mutable `CacheItem`. Returns true if the update was successful.
770	///
771	/// Note: an exclusive lock for the given cache item will be placed for the
772	/// entire length of the passed function, so try to avoid any compute in it.
773	///
774	/// # Errors
775	///
776	/// An [`odilia_common::errors::OdiliaError::PoisoningError`] may be returned if a write lock can not be aquired on the `CacheItem` being modified.
777	pub fn modify_item<F>(&self, id: &CacheKey, modify: F) -> OdiliaResult<bool>
778	where
779		F: FnOnce(&mut CacheItem),
780	{
781		// I wonder if `get_mut` vs `get` makes any difference here? I suppose
782		// it will just rely on the dashmap write access vs mutex lock access.
783		// Let's default to the fairness of the mutex.
784		let entry = if let Some(i) = self.by_id.get(id) {
785			// Drop the dashmap reference immediately, at the expense of an Arc clone.
786			(*i).clone()
787		} else {
788			tracing::trace!("The cache does not contain the requested item: {:?}", id);
789			return Ok(false);
790		};
791		let mut cache_item = entry.write()?;
792		modify(&mut cache_item);
793		Ok(true)
794	}
795
796	/// Get a single item from the cache (note that this copies some integers to a new struct).
797	/// If the `CacheItem` is not found, create one, add it to the cache, and return it.
798	/// # Errors
799	/// The function will return an error if:
800	/// 1. The `accessible` can not be turned into an `AccessiblePrimitive`. This should never happen, but is technically possible.
801	/// 2. The [`Self::add`] function fails.
802	/// 3. The [`accessible_to_cache_item`] function fails.
803	pub async fn get_or_create(
804		&self,
805		accessible: &AccessibleProxy<'_>,
806		cache: Weak<Self>,
807	) -> OdiliaResult<CacheItem> {
808		// if the item already exists in the cache, return it
809		let primitive = accessible.try_into()?;
810		if let Some(cache_item) = self.get(&primitive) {
811			return Ok(cache_item);
812		}
813		// otherwise, build a cache item
814		let start = std::time::Instant::now();
815		let cache_item = accessible_to_cache_item(accessible, cache).await?;
816		let end = std::time::Instant::now();
817		let diff = end - start;
818		tracing::debug!("Time to create cache item: {:?}", diff);
819		// add a clone of it to the cache
820		self.add(cache_item.clone())?;
821		// return that same cache item
822		Ok(cache_item)
823	}
824
825	/// Populate children and parent references given a cache and an `Arc<RwLock<CacheItem>>`.
826	/// This will unlock the `RwLock<_>`, update the references for children and parents, then go to the parent and children and do the same: update the parent for the children, then update the children referneces for the parent.
827	/// # Errors
828	/// If any references, either the ones passed in through the `item_ref` parameter, any children references, or the parent reference are unable to be unlocked, an `Err(_)` variant will be returned.
829	/// Technically it can also fail if the index of the `item_ref` in its parent exceeds `usize` on the given platform, but this is highly improbable.
830	pub fn populate_references(
831		cache: &ThreadSafeCache,
832		item_ref: &Arc<RwLock<CacheItem>>,
833	) -> Result<(), OdiliaError> {
834		let item_wk_ref = Arc::downgrade(item_ref);
835
836		let mut item = item_ref.write()?;
837		let item_key = item.object.clone();
838
839		let parent_key = item.parent.key.clone();
840		let parent_ref_opt = cache.get(&parent_key);
841
842		// Update this item's parent reference
843		let ix_opt = usize::try_from(item.index).ok();
844
845		// Update this item's children references
846		for child_ref in &mut item.children {
847			if let Some(child_arc) = cache.get(&child_ref.key).as_ref() {
848				child_ref.item = Arc::downgrade(child_arc);
849				child_arc.write()?.parent.item = Weak::clone(&item_wk_ref);
850			}
851		}
852
853		// TODO: Should there be errors for the non let bindings?
854		if let Some(parent_ref) = parent_ref_opt {
855			item.parent.item = Arc::downgrade(&parent_ref);
856			if let Some(ix) = ix_opt {
857				if let Some(cache_ref) = parent_ref
858					.write()?
859					.children
860					.get_mut(ix)
861					.filter(|i| i.key == item_key)
862				{
863					cache_ref.item = Weak::clone(&item_wk_ref);
864				}
865			}
866		}
867		Ok(())
868	}
869}
870
871/// Convert an [`atspi::accessible::AccessibleProxy`] into a [`crate::CacheItem`].
872/// This runs a bunch of long-awaiting code and can take quite some time; use this sparingly.
873/// This takes most properties and some function calls through the `AccessibleProxy` structure and generates a new `CacheItem`, which will be written to cache before being sent back.
874///
875/// # Errors
876///
877/// Will return an `Err(_)` variant when:
878///
879/// 1. The `cache` parameter does not reference an active cache once the `Weak` is upgraded to an `Option<Arc<_>>`.
880/// 2. Any of the function calls on the `accessible` fail.
881/// 3. Any `(String, OwnedObjectPath) -> AccessiblePrimitive` conversions fail. This *should* never happen, but technically it is possible.
882pub async fn accessible_to_cache_item(
883	accessible: &AccessibleProxy<'_>,
884	cache: Weak<Cache>,
885) -> OdiliaResult<CacheItem> {
886	let (app, parent, index, children_num, interfaces, role, states, children) = tokio::try_join!(
887		accessible.get_application(),
888		accessible.parent(),
889		accessible.get_index_in_parent(),
890		accessible.child_count(),
891		accessible.get_interfaces(),
892		accessible.get_role(),
893		accessible.get_state(),
894		accessible.get_children(),
895	)?;
896	// if it implements the Text interface
897	let text = match accessible.to_text().await {
898		// get *all* the text
899		Ok(text_iface) => text_iface.get_all_text().await,
900		// otherwise, use the name instaed
901		Err(_) => Ok(accessible.name().await?),
902	}?;
903	Ok(CacheItem {
904		object: accessible.try_into()?,
905		app: app.try_into()?,
906		parent: CacheRef::new(parent.try_into()?),
907		index,
908		children_num,
909		interfaces,
910		role,
911		states,
912		text,
913		children: children
914			.into_iter()
915			.map(|k| Ok(CacheRef::new(k.try_into()?)))
916			.collect::<Result<_, AccessiblePrimitiveConversionError>>()?,
917		cache,
918	})
919}