read manifest.toml and add get_webxdc_info()

This commit is contained in:
B. Petersen
2022-01-03 22:24:35 +01:00
committed by bjoern
parent 5a77df7cc5
commit 8f715532cb
11 changed files with 378 additions and 17 deletions

BIN
assets/icon-webxdc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

96
assets/icon-webxdc.svg Normal file
View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="409.60001"
inkscape:export-xdpi="409.60001"
inkscape:export-filename="/Users/bpetersen/projects/deltachat-core-rust/assets/icon-webxdc.png"
version="1.0"
width="60"
height="60"
viewBox="0 0 45 45"
preserveAspectRatio="xMidYMid meet"
id="svg4344"
sodipodi:docname="icon-webxdc.svg"
inkscape:version="1.0.2 (e86c8708, 2021-01-15)">
<defs
id="defs4348" />
<sodipodi:namedview
inkscape:snap-global="false"
pagecolor="#ffffff"
bordercolor="#666666"
inkscape:document-rotation="0"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1864"
inkscape:window-height="1027"
id="namedview4346"
showgrid="false"
units="px"
inkscape:zoom="7.919596"
inkscape:cx="37.391123"
inkscape:cy="24.898474"
inkscape:window-x="45"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="svg4344" />
<metadata
id="metadata4336">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<rect
y="-4.4408921e-16"
x="0"
height="45"
width="45"
id="rect860"
style="opacity:1;fill:#b2bbbe;fill-opacity:1;stroke-width:0.819271" />
<g
fill="#000000"
stroke="none"
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.00255113,0,0,-0.00255113,5.586152,38.200477)"
id="g4342">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6271.73px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:293.987"
x="-254.26019"
y="-3819.2668"
id="text865"
transform="scale(1,-1)"><tspan
sodipodi:role="line"
id="tspan863"
x="-254.26019"
y="-3819.2668"
style="font-size:6271.73px;stroke-width:293.987" /></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:18px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.75"
x="1.125"
y="29.20166"
id="text869"><tspan
sodipodi:role="line"
id="tspan867"
x="1.125"
y="29.20166"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:18px;font-family:sans-serif;-inkscape-font-specification:'sans-serif Bold';stroke-width:0.75">.xdc</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -3689,6 +3689,27 @@ char* dc_msg_get_filemime (const dc_msg_t* msg);
char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char* filename, size_t* ret_bytes);
/**
* Get info a webxdc message, in JSON format.
* The returned JSON string has the following key/values:
*
* - name: The name of the app.
* Defaults to the filename if not set in the manifest.
* - icon: App icon file name.
* Defaults to an standard icon if nothing is set in the manifest.
* To get the file, use dc_msg_get_webxdc_blob().
* App icons should should be square,
* the implementations will add round corners etc. as needed.
*
* @memberof dc_msg_t
* @param msg The webxdc instance.
* @return a UTF8-encoded JSON string containing all requested info.
* Must be freed using dc_str_unref().
* NULL is never returned.
*/
char* dc_msg_get_webxdc_info (const dc_msg_t* msg);
/**
* Get the size of the file. Returns the size of the file associated with a
* message, if applicable.

View File

@@ -3090,7 +3090,6 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_blob(
});
match blob {
Ok(blob) => {
// TODO: introduce dc_blob_t to avoid malloc and returning size by pointer and to save copying data
*ret_bytes = blob.len();
let ptr = libc::malloc(*ret_bytes);
libc::memcpy(ptr, blob.as_ptr() as *mut libc::c_void, *ret_bytes);
@@ -3103,6 +3102,29 @@ pub unsafe extern "C" fn dc_msg_get_webxdc_blob(
}
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_get_webxdc_info(msg: *mut dc_msg_t) -> *mut libc::c_char {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_get_webxdc_info()");
return "".strdup();
}
let ffi_msg = &*msg;
let ctx = &*ffi_msg.context;
block_on(async move {
let info = match ffi_msg.message.get_webxdc_info(ctx).await {
Ok(info) => info,
Err(err) => {
error!(ctx, "dc_msg_get_webxdc_info() failed to get info: {}", err);
return "".strdup();
}
};
serde_json::to_string(&info)
.unwrap_or_log_default(ctx, "dc_msg_get_webxdc_info() failed to serialise to json")
.strdup()
})
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_get_filemime(msg: *mut dc_msg_t) -> *mut libc::c_char {
if msg.is_null() {

View File

@@ -16,6 +16,22 @@ use std::convert::TryFrom;
use std::io::Read;
pub const WEBXDC_SUFFIX: &str = "xdc";
const WEBXDC_DEFAULT_ICON: &str = "__webxdc__/default-icon.png";
/// Raw information read from manifest.toml
#[derive(Debug, Deserialize)]
#[non_exhaustive]
struct WebxdcManifest {
name: Option<String>,
icon: Option<String>,
}
/// Parsed information from WebxdcManifest and fallbacks.
#[derive(Debug, Serialize)]
pub struct WebxdcInfo {
pub name: String,
pub icon: String,
}
/// Status Update ID.
#[derive(
@@ -227,12 +243,21 @@ impl Context {
}
}
async fn parse_webxdc_manifest(bytes: &[u8]) -> Result<WebxdcManifest> {
let manifest: WebxdcManifest = toml::from_slice(bytes)?;
Ok(manifest)
}
impl Message {
/// Return file form inside an archive.
/// Currently, this works only if the message is an webxdc instance.
pub async fn get_webxdc_blob(&self, context: &Context, name: &str) -> Result<Vec<u8>> {
ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance.");
if name == WEBXDC_DEFAULT_ICON {
return Ok(include_bytes!("../assets/icon-webxdc.png").to_vec());
}
let archive = self
.get_file(context)
.ok_or_else(|| format_err!("No webxdc instance file."))?;
@@ -253,6 +278,58 @@ impl Message {
file.read_to_end(&mut buf)?;
Ok(buf)
}
/// Return info from manifest.toml or from fallbacks.
pub async fn get_webxdc_info(&self, context: &Context) -> Result<WebxdcInfo> {
ensure!(self.viewtype == Viewtype::Webxdc, "No webxdc instance.");
let mut manifest = if let Ok(bytes) = self.get_webxdc_blob(context, "manifest.toml").await {
if let Ok(manifest) = parse_webxdc_manifest(&bytes).await {
manifest
} else {
WebxdcManifest {
name: None,
icon: None,
}
}
} else {
WebxdcManifest {
name: None,
icon: None,
}
};
if let Some(ref name) = manifest.name {
let name = name.trim();
if name.is_empty() {
warn!(context, "empty name given in manifest");
manifest.name = None;
}
}
if let Some(ref icon) = manifest.icon {
if !icon.ends_with(".png") && !icon.ends_with(".jpg") {
warn!(context, "bad icon format \"{}\"; use .png or .jpg", icon);
manifest.icon = None;
} else if self.get_webxdc_blob(context, icon).await.is_err() {
warn!(context, "cannot find icon \"{}\"", icon);
manifest.icon = None;
}
}
Ok(WebxdcInfo {
name: if let Some(name) = manifest.name {
name
} else {
self.get_filename().unwrap_or_default()
},
icon: if let Some(icon) = manifest.icon {
icon
} else {
WEBXDC_DEFAULT_ICON.to_string()
},
})
}
}
#[cfg(test)]
@@ -305,19 +382,21 @@ mod tests {
Ok(())
}
async fn create_webxdc_instance(t: &TestContext) -> Result<Message> {
let file = t.get_blobdir().join("minimal.xdc");
File::create(&file)
.await?
.write_all(include_bytes!("../test-data/webxdc/minimal.xdc"))
.await?;
async fn create_webxdc_instance(t: &TestContext, name: &str, bytes: &[u8]) -> Result<Message> {
let file = t.get_blobdir().join(name);
File::create(&file).await?.write_all(bytes).await?;
let mut instance = Message::new(Viewtype::File);
instance.set_file(file.to_str().unwrap(), None);
Ok(instance)
}
async fn send_webxdc_instance(t: &TestContext, chat_id: ChatId) -> Result<Message> {
let mut instance = create_webxdc_instance(t).await?;
let mut instance = create_webxdc_instance(
t,
"minimal.xdc",
include_bytes!("../test-data/webxdc/minimal.xdc"),
)
.await?;
let instance_msg_id = send_msg(t, chat_id, &mut instance).await?;
Message::load_from_db(t, instance_msg_id).await
}
@@ -379,7 +458,12 @@ mod tests {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let mut instance = create_webxdc_instance(&t).await?;
let mut instance = create_webxdc_instance(
&t,
"minimal.xdc",
include_bytes!("../test-data/webxdc/minimal.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let instance = chat_id.get_draft(&t).await?.unwrap();
t.send_webxdc_status_update(instance.id, "descr", "42")
@@ -603,7 +687,12 @@ mod tests {
// prepare webxdc instance,
// status updates are not sent for drafts, therefore send_webxdc_status_update() returns Ok(None)
let mut alice_instance = create_webxdc_instance(&alice).await?;
let mut alice_instance = create_webxdc_instance(
&alice,
"minimal.xdc",
include_bytes!("../test-data/webxdc/minimal.xdc"),
)
.await?;
alice_chat_id
.set_draft(&alice, Some(&mut alice_instance))
.await?;
@@ -677,6 +766,18 @@ mod tests {
Ok(())
}
#[async_std::test]
async fn test_get_webxdc_blob_default_icon() -> Result<()> {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let instance = send_webxdc_instance(&t, chat_id).await?;
let buf = instance.get_webxdc_blob(&t, WEBXDC_DEFAULT_ICON).await?;
assert!(buf.len() > 100);
assert!(String::from_utf8_lossy(&buf).contains("PNG\r\n"));
Ok(())
}
#[async_std::test]
async fn test_get_webxdc_blob_with_absolute_paths() -> Result<()> {
let t = TestContext::new_alice().await;
@@ -694,13 +795,12 @@ mod tests {
async fn test_get_webxdc_blob_with_subdirs() -> Result<()> {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let file = t.get_blobdir().join("some-files.xdc");
File::create(&file)
.await?
.write_all(include_bytes!("../test-data/webxdc/some-files.xdc"))
.await?;
let mut instance = Message::new(Viewtype::Webxdc);
instance.set_file(file.to_str().unwrap(), None);
let mut instance = create_webxdc_instance(
&t,
"some-files.xdc",
include_bytes!("../test-data/webxdc/some-files.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let buf = instance.get_webxdc_blob(&t, "index.html").await?;
@@ -731,4 +831,126 @@ mod tests {
Ok(())
}
#[async_std::test]
async fn test_parse_webxdc_manifest() -> Result<()> {
let result = parse_webxdc_manifest(r#"key = syntax error"#.as_bytes()).await;
assert!(result.is_err());
let manifest = parse_webxdc_manifest(r#"no_name = "no name, no icon""#.as_bytes()).await?;
assert_eq!(manifest.name, None);
assert_eq!(manifest.icon, None);
let manifest = parse_webxdc_manifest(r#"name = "name, no icon""#.as_bytes()).await?;
assert_eq!(manifest.name, Some("name, no icon".to_string()));
assert_eq!(manifest.icon, None);
let manifest = parse_webxdc_manifest(
r#"name = "foo"
icon = "bar""#
.as_bytes(),
)
.await?;
assert_eq!(manifest.name, Some("foo".to_string()));
assert_eq!(manifest.icon, Some("bar".to_string()));
let manifest = parse_webxdc_manifest(
r#"name = "foz"
icon = "baz"
add_item = "that should be just ignored"
[section]
sth_for_the = "future""#
.as_bytes(),
)
.await?;
assert_eq!(manifest.name, Some("foz".to_string()));
assert_eq!(manifest.icon, Some("baz".to_string()));
Ok(())
}
#[async_std::test]
async fn test_get_webxdc_info() -> Result<()> {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?;
let instance = send_webxdc_instance(&t, chat_id).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "minimal.xdc");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string());
let mut instance = create_webxdc_instance(
&t,
"with-manifest-empty-name.xdc",
include_bytes!("../test-data/webxdc/with-manifest-empty-name.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "with-manifest-empty-name.xdc");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string());
let mut instance = create_webxdc_instance(
&t,
"with-manifest-no-name.xdc",
include_bytes!("../test-data/webxdc/with-manifest-no-name.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "with-manifest-no-name.xdc");
assert_eq!(info.icon, "some.png".to_string());
let mut instance = create_webxdc_instance(
&t,
"with-minimal-manifest.xdc",
include_bytes!("../test-data/webxdc/with-minimal-manifest.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "nice app!");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string());
let mut instance = create_webxdc_instance(
&t,
"with-manifest-icon-not-existent.xdc",
include_bytes!("../test-data/webxdc/with-manifest-icon-not-existent.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "with bad icon");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON.to_string());
let mut instance = create_webxdc_instance(
&t,
"with-manifest-and-icon.xdc",
include_bytes!("../test-data/webxdc/with-manifest-and-icon.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "with some icon");
assert_eq!(info.icon, "some.png");
let mut instance = create_webxdc_instance(
&t,
"with-manifest-and-unsupported-icon-format.xdc",
include_bytes!("../test-data/webxdc/with-manifest-and-unsupported-icon-format.xdc"),
)
.await?;
chat_id.set_draft(&t, Some(&mut instance)).await?;
let info = instance.get_webxdc_info(&t).await?;
assert_eq!(info.name, "with tiff icon");
assert_eq!(info.icon, WEBXDC_DEFAULT_ICON);
let msg_id = send_text_msg(&t, chat_id, "foo".to_string()).await?;
let msg = Message::load_from_db(&t, msg_id).await?;
let result = msg.get_webxdc_info(&t).await;
assert!(result.is_err());
Ok(())
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.