Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions crates/yoop-core/src/preview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,7 @@ impl PreviewGenerator {

let metadata = std::fs::metadata(path)?;

// Try to open the image
let Ok(img) = image::open(path) else {
// If image cannot be opened (e.g., unsupported format like JPEG),
// return a preview with just metadata
return Ok(Preview {
preview_type: PreviewType::Icon,
data: String::new(),
Expand All @@ -194,17 +191,14 @@ impl PreviewGenerator {
let (width, height) = img.dimensions();
let (max_w, max_h) = self.config.thumbnail_size;

// Generate thumbnail
let thumb = img.thumbnail(max_w, max_h);

// Encode as PNG to a buffer
let mut buf = Vec::new();
let mut cursor = Cursor::new(&mut buf);
thumb
.write_to(&mut cursor, image::ImageFormat::Png)
.map_err(|e| crate::error::Error::Io(std::io::Error::other(e.to_string())))?;

// Base64 encode the thumbnail
let encoded = base64::engine::general_purpose::STANDARD.encode(&buf);

Ok(Preview {
Expand Down Expand Up @@ -247,7 +241,6 @@ impl PreviewGenerator {

#[cfg(not(feature = "web"))]
{
// Without the web feature, zip crate is not available
let metadata = std::fs::metadata(path)?;
Ok(Preview {
preview_type: PreviewType::ArchiveListing,
Expand All @@ -269,9 +262,7 @@ impl PreviewGenerator {
let metadata = std::fs::metadata(path)?;
let file = File::open(path)?;

// Try to open as ZIP archive
let Ok(archive) = zip::ZipArchive::new(file) else {
// Not a valid ZIP file, return empty listing
return Ok(Preview {
preview_type: PreviewType::ArchiveListing,
data: "[]".to_string(),
Expand All @@ -286,7 +277,6 @@ impl PreviewGenerator {

let total_files = archive.len();

// Collect file names (up to 50 entries)
let entries: Vec<String> = archive.file_names().take(50).map(String::from).collect();

let data = serde_json::to_string(&entries)
Expand Down Expand Up @@ -338,7 +328,6 @@ mod tests {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("large.txt");

// Create a file larger than max_text_length
let content = "x".repeat(2000);
std::fs::write(&file_path, &content).unwrap();

Expand All @@ -359,7 +348,6 @@ mod tests {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.png");

// Create a simple 2x2 PNG image
let img = image::RgbImage::from_fn(100, 100, |x, y| {
if (x + y) % 2 == 0 {
image::Rgb([255, 0, 0])
Expand All @@ -379,7 +367,6 @@ mod tests {
"Thumbnail data should not be empty"
);

// Verify dimensions metadata
let meta = preview.metadata.unwrap();
assert_eq!(meta.dimensions, Some((100, 100)));
}
Expand All @@ -388,7 +375,6 @@ mod tests {
async fn test_preview_type_detection() {
let generator = PreviewGenerator::new();

// Test image detection
assert_eq!(
generator.determine_preview_type(Path::new("image.png"), None),
PreviewType::Thumbnail
Expand All @@ -398,7 +384,6 @@ mod tests {
PreviewType::Thumbnail
);

// Test text detection
assert_eq!(
generator.determine_preview_type(Path::new("file.txt"), None),
PreviewType::Text
Expand All @@ -412,7 +397,6 @@ mod tests {
PreviewType::Text
);

// Test archive detection
assert_eq!(
generator.determine_preview_type(Path::new("archive.zip"), None),
PreviewType::ArchiveListing
Expand All @@ -422,7 +406,6 @@ mod tests {
PreviewType::ArchiveListing
);

// Test icon fallback
assert_eq!(
generator.determine_preview_type(Path::new("unknown.xyz"), None),
PreviewType::Icon
Expand Down Expand Up @@ -451,7 +434,6 @@ mod tests {
let temp_dir = TempDir::new().unwrap();
let zip_path = temp_dir.path().join("test.zip");

// Create a simple ZIP file with some entries
let file = std::fs::File::create(&zip_path).unwrap();
let mut zip = zip::ZipWriter::new(file);

Expand Down
117 changes: 110 additions & 7 deletions crates/yoop-core/src/transfer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,9 @@ impl ShareSession {
return Err(Error::FileNotFound("no files to share".to_string()));
}

// Generate previews for files
let preview_generator = crate::preview::PreviewGenerator::new();
for file in &mut files {
if !file.is_directory {
// Find the absolute path for this file
for base_path in paths {
let absolute_path = if base_path.is_dir() {
base_path.join(&file.relative_path)
Expand Down Expand Up @@ -557,6 +555,23 @@ impl ShareSession {
};
let start_payload = protocol::encode_payload(&start)?;
protocol::write_frame(stream, MessageType::ChunkStart, &start_payload).await?;

let (header, ack_payload) = protocol::read_frame(stream).await?;
if header.message_type != MessageType::ChunkAck {
return Err(Error::UnexpectedMessage {
expected: "ChunkAck".to_string(),
actual: format!("{:?}", header.message_type),
});
}

let ack: ChunkAckPayload = protocol::decode_payload(&ack_payload)?;
if !ack.success {
return Err(Error::ProtocolError(format!(
"Receiver failed to create directory: {}",
file.file_name()
)));
}

tracing::debug!(
"Sent directory marker for file {}: {}",
file_index,
Expand All @@ -578,11 +593,29 @@ impl ShareSession {
};
let start_payload = protocol::encode_payload(&start)?;
protocol::write_frame(stream, MessageType::ChunkStart, &start_payload).await?;

let (header, ack_payload) = protocol::read_frame(stream).await?;
if header.message_type != MessageType::ChunkAck {
return Err(Error::UnexpectedMessage {
expected: "ChunkAck".to_string(),
actual: format!("{:?}", header.message_type),
});
}

let ack: ChunkAckPayload = protocol::decode_payload(&ack_payload)?;
if !ack.success {
return Err(Error::ProtocolError(format!(
"Receiver failed to create empty file: {}",
file.file_name()
)));
}

tracing::debug!(
"Sent empty file marker for file {}: {}",
file_index,
file.file_name()
);
continue;
}

for chunk in chunks {
Expand Down Expand Up @@ -1431,12 +1464,16 @@ impl ReceiveSession {
Ok(file_list.files)
}

async fn handle_chunk_start(
async fn handle_chunk_start<S>(
&self,
stream: &mut S,
start: ChunkStartPayload,
current_writer: &mut Option<FileWriter>,
current_file_index: &mut Option<usize>,
) -> Result<()> {
) -> Result<()>
where
S: AsyncRead + AsyncWrite + Unpin,
{
if *current_file_index != Some(start.file_index) {
if let Some(writer) = current_writer.take() {
let _sha256 = writer.finalize().await?;
Expand All @@ -1445,7 +1482,7 @@ impl ReceiveSession {
let file = &self.files[start.file_index];
let output_path = self.output_dir.join(&file.relative_path);

if start.total_chunks == 0 || file.is_directory {
if file.is_directory {
tokio::fs::create_dir_all(&output_path).await.map_err(|e| {
Error::Io(std::io::Error::new(
e.kind(),
Expand All @@ -1471,6 +1508,67 @@ impl ReceiveSession {
}

tracing::debug!("Created directory: {}", output_path.display());

let ack = ChunkAckPayload {
file_index: start.file_index,
chunk_index: 0,
success: true,
};
let ack_payload = protocol::encode_payload(&ack)?;
protocol::write_frame(stream, MessageType::ChunkAck, &ack_payload).await?;

*current_file_index = Some(start.file_index);
return Ok(());
}

if start.total_chunks == 0 {
if let Some(parent) = output_path.parent() {
tokio::fs::create_dir_all(parent).await.map_err(|e| {
Error::Io(std::io::Error::new(
e.kind(),
format!(
"Failed to create parent directory {}: {}",
parent.display(),
e
),
))
})?;
}

tokio::fs::File::create(&output_path).await.map_err(|e| {
Error::Io(std::io::Error::new(
e.kind(),
format!(
"Failed to create empty file {}: {}",
output_path.display(),
e
),
))
})?;

#[cfg(unix)]
if let Some(mode) = file.permissions {
use std::os::unix::fs::PermissionsExt;
let perms = std::fs::Permissions::from_mode(mode);
if let Err(e) = std::fs::set_permissions(&output_path, perms) {
tracing::warn!(
"Failed to set permissions on file {}: {}",
output_path.display(),
e
);
}
}

tracing::debug!("Created empty file: {}", output_path.display());

let ack = ChunkAckPayload {
file_index: start.file_index,
chunk_index: 0,
success: true,
};
let ack_payload = protocol::encode_payload(&ack)?;
protocol::write_frame(stream, MessageType::ChunkAck, &ack_payload).await?;

*current_file_index = Some(start.file_index);
return Ok(());
}
Expand Down Expand Up @@ -1559,8 +1657,13 @@ impl ReceiveSession {
match header.message_type {
MessageType::ChunkStart => {
let start: ChunkStartPayload = protocol::decode_payload(&payload)?;
self.handle_chunk_start(start, &mut current_writer, &mut current_file_index)
.await?;
self.handle_chunk_start(
stream,
start,
&mut current_writer,
&mut current_file_index,
)
.await?;
}
MessageType::ChunkData => {
self.handle_chunk_data(stream, &payload, &mut current_writer)
Expand Down
Loading
Loading