mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
Improve ticket copy and parsing
This commit is contained in:
@@ -52,6 +52,26 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn metadata_ticket_round_trip_tolerates_wrapped_whitespace() {
|
||||||
|
let secret = SecretKey::generate();
|
||||||
|
let addr = iroh::EndpointAddr::new(secret.public());
|
||||||
|
let blob_ticket = BlobTicket::new(addr, Hash::new([9; 32]), BlobFormat::HashSeq);
|
||||||
|
let metadata = TransferMetadata::new(7, "Wrapped", None, blob_ticket.hash(), 1, 10);
|
||||||
|
let encoded = VnidropTicket::new(blob_ticket.clone(), metadata)
|
||||||
|
.encode()
|
||||||
|
.unwrap();
|
||||||
|
let wrapped = encoded
|
||||||
|
.as_bytes()
|
||||||
|
.chunks(8)
|
||||||
|
.map(|chunk| std::str::from_utf8(chunk).unwrap())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n ");
|
||||||
|
|
||||||
|
let parsed = parse_transfer_ticket(&wrapped).unwrap();
|
||||||
|
assert_eq!(parsed.blob_ticket.hash(), blob_ticket.hash());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_ticket_is_rejected() {
|
fn invalid_ticket_is_rejected() {
|
||||||
assert!(parse_transfer_ticket("not-a-ticket").is_err());
|
assert!(parse_transfer_ticket("not-a-ticket").is_err());
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ impl VnidropTicket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decode(value: &str) -> Result<Self> {
|
fn decode(value: &str) -> Result<Self> {
|
||||||
let encoded = value
|
let normalized = normalize_ticket_input(value);
|
||||||
|
let encoded = normalized
|
||||||
.strip_prefix(VNIDROP_TICKET_PREFIX)
|
.strip_prefix(VNIDROP_TICKET_PREFIX)
|
||||||
.context("not a VniDrop ticket")?;
|
.context("not a VniDrop ticket")?;
|
||||||
let bytes = BASE64URL_NOPAD
|
let bytes = BASE64URL_NOPAD
|
||||||
@@ -51,8 +52,9 @@ pub(crate) struct ParsedTransferTicket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_transfer_ticket(value: &str) -> Result<ParsedTransferTicket> {
|
pub(crate) fn parse_transfer_ticket(value: &str) -> Result<ParsedTransferTicket> {
|
||||||
if value.starts_with(VNIDROP_TICKET_PREFIX) {
|
let normalized = normalize_ticket_input(value);
|
||||||
let ticket = VnidropTicket::decode(value)?;
|
if normalized.starts_with(VNIDROP_TICKET_PREFIX) {
|
||||||
|
let ticket = VnidropTicket::decode(&normalized)?;
|
||||||
let blob_ticket = BlobTicket::from_str(&ticket.blob_ticket)
|
let blob_ticket = BlobTicket::from_str(&ticket.blob_ticket)
|
||||||
.context("invalid BlobTicket inside VniDrop ticket")?;
|
.context("invalid BlobTicket inside VniDrop ticket")?;
|
||||||
return Ok(ParsedTransferTicket {
|
return Ok(ParsedTransferTicket {
|
||||||
@@ -61,9 +63,13 @@ pub(crate) fn parse_transfer_ticket(value: &str) -> Result<ParsedTransferTicket>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let blob_ticket = BlobTicket::from_str(value).context("invalid BlobTicket")?;
|
let blob_ticket = BlobTicket::from_str(&normalized).context("invalid BlobTicket")?;
|
||||||
Ok(ParsedTransferTicket {
|
Ok(ParsedTransferTicket {
|
||||||
blob_ticket,
|
blob_ticket,
|
||||||
metadata: None,
|
metadata: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_ticket_input(value: &str) -> String {
|
||||||
|
value.chars().filter(|char| !char.is_whitespace()).collect()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ package com.vnidrop.app
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeContentPadding
|
import androidx.compose.foundation.layout.safeContentPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@@ -18,6 +20,8 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vnidrop.app.core.CoreRepository
|
import com.vnidrop.app.core.CoreRepository
|
||||||
@@ -33,6 +37,7 @@ fun App() {
|
|||||||
val repository = remember { CoreRepository() }
|
val repository = remember { CoreRepository() }
|
||||||
val state by repository.state.collectAsState()
|
val state by repository.state.collectAsState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
var appDataDir by remember { mutableStateOf(platform.defaultCoreDataDir) }
|
var appDataDir by remember { mutableStateOf(platform.defaultCoreDataDir) }
|
||||||
var sourcePath by remember { mutableStateOf("") }
|
var sourcePath by remember { mutableStateOf("") }
|
||||||
var selectedFile by remember { mutableStateOf<PickedShareFile?>(null) }
|
var selectedFile by remember { mutableStateOf<PickedShareFile?>(null) }
|
||||||
@@ -129,7 +134,19 @@ fun App() {
|
|||||||
}
|
}
|
||||||
state.lastShare?.let { share ->
|
state.lastShare?.let { share ->
|
||||||
Text("Ticket")
|
Text("Ticket")
|
||||||
Text(share.ticket, style = MaterialTheme.typography.bodySmall)
|
SelectionContainer {
|
||||||
|
Text(share.ticket, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Button(onClick = {
|
||||||
|
clipboardManager.setText(AnnotatedString(share.ticket))
|
||||||
|
}) {
|
||||||
|
Text("Copy ticket")
|
||||||
|
}
|
||||||
|
Button(onClick = { ticket = share.ticket }) {
|
||||||
|
Text("Use locally")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user