feat(theseus): change parameter type of normalize_skin_texture Tauri command
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use crate::api::Result;
|
||||
|
||||
use theseus::minecraft_skins::{self, Bytes, Cape, MinecraftSkinVariant, Skin};
|
||||
use theseus::minecraft_skins::{
|
||||
self, Bytes, Cape, MinecraftSkinVariant, Skin, UrlOrBlob,
|
||||
};
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("minecraft-skins")
|
||||
@@ -86,6 +88,6 @@ pub async fn unequip_skin() -> Result<()> {
|
||||
///
|
||||
/// See also: [minecraft_skins::normalize_skin_texture]
|
||||
#[tauri::command]
|
||||
pub async fn normalize_skin_texture(skin: Skin) -> Result<Bytes> {
|
||||
Ok(minecraft_skins::normalize_skin_texture(&skin).await?)
|
||||
pub async fn normalize_skin_texture(texture: UrlOrBlob) -> Result<Bytes> {
|
||||
Ok(minecraft_skins::normalize_skin_texture(&texture).await?)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ use std::sync::{
|
||||
};
|
||||
|
||||
pub use bytes::Bytes;
|
||||
use data_url::DataUrl;
|
||||
use futures::{Stream, StreamExt, TryStreamExt, future::Either, stream};
|
||||
use futures::{StreamExt, TryStreamExt, stream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
@@ -21,7 +20,6 @@ use crate::{
|
||||
CustomMinecraftSkin, DefaultMinecraftCape, mojang_api,
|
||||
},
|
||||
},
|
||||
util::fetch::REQWEST_CLIENT,
|
||||
};
|
||||
|
||||
use super::data::Credentials;
|
||||
@@ -74,32 +72,6 @@ pub struct Skin {
|
||||
pub is_equipped: bool,
|
||||
}
|
||||
|
||||
impl Skin {
|
||||
/// Resolves the skin texture URL to a stream of bytes.
|
||||
pub async fn resolve_texture(
|
||||
&self,
|
||||
) -> crate::Result<impl Stream<Item = Result<Bytes, reqwest::Error>> + use<>>
|
||||
{
|
||||
if self.texture.scheme() == "data" {
|
||||
let data = DataUrl::process(self.texture.as_str())?
|
||||
.decode_to_vec()?
|
||||
.0
|
||||
.into();
|
||||
|
||||
Ok(Either::Left(stream::once(async { Ok(data) })))
|
||||
} else {
|
||||
let response = REQWEST_CLIENT
|
||||
.get(self.texture.as_str())
|
||||
.header("Accept", "image/png")
|
||||
.send()
|
||||
.await
|
||||
.and_then(|response| response.error_for_status())?;
|
||||
|
||||
Ok(Either::Right(response.bytes_stream()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SkinSource {
|
||||
@@ -111,6 +83,14 @@ pub enum SkinSource {
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// Represents either a URL or a blob for a Minecraft skin PNG texture.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum UrlOrBlob {
|
||||
Url(Url),
|
||||
Blob(Bytes),
|
||||
}
|
||||
|
||||
/// Retrieves the available capes for the currently selected Minecraft profile. At most one cape
|
||||
/// can be equipped at a time. Also, at most one cape can be set as the default cape.
|
||||
#[tracing::instrument]
|
||||
@@ -398,7 +378,7 @@ pub async fn equip_skin(skin: Skin) -> crate::Result<()> {
|
||||
|
||||
mojang_api::MinecraftSkinOperation::equip(
|
||||
&selected_credentials,
|
||||
skin.resolve_texture().await?,
|
||||
png_util::url_to_data_stream(&skin.texture).await?,
|
||||
skin.variant,
|
||||
)
|
||||
.await?;
|
||||
@@ -467,10 +447,12 @@ pub async fn unequip_skin() -> crate::Result<()> {
|
||||
/// PNG encoding speed over compression density, so the resulting textures are better
|
||||
/// suited for display purposes, not persistent storage or transmission.
|
||||
///
|
||||
/// Returns the normalized, processed texture as a byte array in PNG format.
|
||||
/// The normalized, processed is returned texture as a byte array in PNG format.
|
||||
#[tracing::instrument]
|
||||
pub async fn normalize_skin_texture(skin: &Skin) -> crate::Result<Bytes> {
|
||||
png_util::normalize_skin_texture(skin).await
|
||||
pub async fn normalize_skin_texture(
|
||||
texture: &UrlOrBlob,
|
||||
) -> crate::Result<Bytes> {
|
||||
png_util::normalize_skin_texture(texture).await
|
||||
}
|
||||
|
||||
/// Synchronizes the equipped cape with the selected cape if necessary, taking into
|
||||
@@ -526,8 +508,7 @@ async fn save_current_custom_external_skin(
|
||||
CustomMinecraftSkin::add(
|
||||
selected_credentials.offline_profile.id,
|
||||
¤t_external_skin.texture_key,
|
||||
¤t_external_skin
|
||||
.resolve_texture()
|
||||
&png_util::url_to_data_stream(¤t_external_skin.texture)
|
||||
.await?
|
||||
.try_fold(vec![], async |mut texture_blob, chunk| {
|
||||
texture_blob.extend_from_slice(&chunk);
|
||||
|
||||
@@ -5,13 +5,33 @@ use std::sync::Arc;
|
||||
use base64::Engine;
|
||||
use bytemuck::{AnyBitPattern, NoUninit};
|
||||
use bytes::Bytes;
|
||||
use futures::TryStreamExt;
|
||||
use data_url::DataUrl;
|
||||
use futures::{Stream, TryStreamExt, future::Either, stream};
|
||||
use tokio_util::{compat::FuturesAsyncReadCompatExt, io::SyncIoBridge};
|
||||
use url::Url;
|
||||
|
||||
use crate::ErrorKind;
|
||||
use crate::{
|
||||
ErrorKind, minecraft_skins::UrlOrBlob, util::fetch::REQWEST_CLIENT,
|
||||
};
|
||||
|
||||
use super::Skin;
|
||||
pub async fn url_to_data_stream(
|
||||
url: &Url,
|
||||
) -> crate::Result<impl Stream<Item = Result<Bytes, reqwest::Error>> + use<>> {
|
||||
if url.scheme() == "data" {
|
||||
let data = DataUrl::process(url.as_str())?.decode_to_vec()?.0.into();
|
||||
|
||||
Ok(Either::Left(stream::once(async { Ok(data) })))
|
||||
} else {
|
||||
let response = REQWEST_CLIENT
|
||||
.get(url.as_str())
|
||||
.header("Accept", "image/png")
|
||||
.send()
|
||||
.await
|
||||
.and_then(|response| response.error_for_status())?;
|
||||
|
||||
Ok(Either::Right(response.bytes_stream()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blob_to_data_url(png_data: impl AsRef<[u8]>) -> Option<Arc<Url>> {
|
||||
let png_data = png_data.as_ref();
|
||||
@@ -69,14 +89,27 @@ pub fn dimensions(png_data: &[u8]) -> crate::Result<(u32, u32)> {
|
||||
/// PNG encoding speed over compression density, so the resulting textures are better
|
||||
/// suited for display purposes, not persistent storage or transmission.
|
||||
///
|
||||
/// Returns the normalized, processed texture as a byte array in PNG format.
|
||||
pub async fn normalize_skin_texture(skin: &Skin) -> crate::Result<Bytes> {
|
||||
/// The normalized, processed is returned texture as a byte array in PNG format.
|
||||
pub async fn normalize_skin_texture(
|
||||
texture: &UrlOrBlob,
|
||||
) -> crate::Result<Bytes> {
|
||||
let texture_stream = SyncIoBridge::new(Box::pin(
|
||||
skin.resolve_texture()
|
||||
.await?
|
||||
.map_err(std::io::Error::other)
|
||||
.into_async_read()
|
||||
.compat(),
|
||||
match texture {
|
||||
UrlOrBlob::Url(url) => Either::Left(
|
||||
url_to_data_stream(url)
|
||||
.await?
|
||||
.map_err(std::io::Error::other)
|
||||
.into_async_read(),
|
||||
),
|
||||
UrlOrBlob::Blob(blob) => Either::Right(
|
||||
stream::once({
|
||||
let blob = Bytes::clone(blob);
|
||||
async { Ok(blob) }
|
||||
})
|
||||
.into_async_read(),
|
||||
),
|
||||
}
|
||||
.compat(),
|
||||
));
|
||||
|
||||
tokio::task::spawn_blocking(|| {
|
||||
@@ -253,23 +286,14 @@ fn copy_rect_mirror_horizontally<PixelType: NoUninit + AnyBitPattern>(
|
||||
#[cfg(test)]
|
||||
#[tokio::test]
|
||||
async fn normalize_skin_texture_works() {
|
||||
use crate::{minecraft_skins::SkinSource, state::MinecraftSkinVariant};
|
||||
|
||||
let legacy_png_data = &include_bytes!("assets/default/MissingNo.png")[..];
|
||||
let expected_normalized_png_data =
|
||||
&include_bytes!("assets/test/MissingNo_normalized.png")[..];
|
||||
|
||||
let normalized_png_data = normalize_skin_texture(&Skin {
|
||||
texture_key: "missingno".into(),
|
||||
name: None,
|
||||
variant: MinecraftSkinVariant::Classic,
|
||||
cape_id: None,
|
||||
texture: blob_to_data_url(legacy_png_data).unwrap(),
|
||||
source: SkinSource::Default,
|
||||
is_equipped: false,
|
||||
})
|
||||
.await
|
||||
.expect("Failed to normalize skin texture");
|
||||
let normalized_png_data =
|
||||
normalize_skin_texture(&UrlOrBlob::Blob(legacy_png_data.into()))
|
||||
.await
|
||||
.expect("Failed to normalize skin texture");
|
||||
|
||||
let decode_to_pixels = |png_data: &[u8]| {
|
||||
let decoder = png::Decoder::new(png_data);
|
||||
|
||||
Reference in New Issue
Block a user