Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add download started and download completed callbacks #530

Merged
merged 54 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3e40ed5
Add download handler to webview attributes
atlanticaccent Mar 26, 2022
53d78f1
Potentially implement download event for windows
atlanticaccent Mar 26, 2022
cb59396
Add download event example
atlanticaccent Mar 26, 2022
5aa94a8
Implement download event for webkitgtk
atlanticaccent Mar 26, 2022
7094792
Attempt to write example writing to tempdir
atlanticaccent Mar 27, 2022
22a037b
Add download compete handler, fix example
atlanticaccent Mar 27, 2022
3dfdef6
Update doc
atlanticaccent Mar 27, 2022
d9d3c05
Fix webkitgtk implementation for download handlers
atlanticaccent Mar 27, 2022
11dcbb2
Attempt to implement download events on macOS
Mar 27, 2022
b7e9bce
Use more reliable URLs
atlanticaccent Mar 27, 2022
2a3d6fa
Improve gtk implementation
atlanticaccent Mar 27, 2022
004a497
Add more details to example
atlanticaccent Mar 27, 2022
61e44f2
Attempt to write tempfile to documents
Mar 28, 2022
b5fa4ba
Fix download delegate funcs to implement on navdelegate on macOS
Mar 28, 2022
eeab913
dummy commit
atlanticaccent Mar 28, 2022
d8dc749
Update webkit2gtk
atlanticaccent Apr 22, 2022
799da8e
Match changes on dev
atlanticaccent Apr 30, 2022
8062f0f
Split download handlers
atlanticaccent May 2, 2022
01b1326
Propagate split of download handlers to win impl
atlanticaccent May 2, 2022
cf93bea
Switch to mutable ref PathBuf instead of string
atlanticaccent May 2, 2022
cb4fc82
Wrap download_completed callback in Rc
atlanticaccent May 2, 2022
9274b12
Merge branch 'dev' into implement-download-event
atlanticaccent Jul 25, 2022
610d502
Windows formatting
atlanticaccent Jul 25, 2022
91f7010
Fix merge in linux implementation
atlanticaccent Aug 2, 2022
ba1084c
Fix macOS implementation + refactor
atlanticaccent Sep 14, 2022
32f72a5
Rework example
atlanticaccent Sep 14, 2022
218f0f0
Merge branch 'dev' into implement-download-event
atlanticaccent Sep 15, 2022
00c4a36
Ignore unused parameter
atlanticaccent Sep 15, 2022
7deaeab
Improve download example handling of empty path
atlanticaccent Sep 15, 2022
1e82b10
Formatting
atlanticaccent Sep 15, 2022
890518f
Attempt to improve linux behaviour
atlanticaccent Sep 15, 2022
5fd45a9
Attempt to fix Windows compile errors
atlanticaccent Sep 16, 2022
74e8822
Separate download complete handler from download started handler
atlanticaccent Sep 16, 2022
fa1bff1
Formatting
atlanticaccent Sep 16, 2022
e349368
Take closure by mutable reference (windows)
atlanticaccent Sep 16, 2022
caf757d
Workaround mutable borrow issues on windows
atlanticaccent Sep 16, 2022
23c1295
Separate download started handler from finished on linux
atlanticaccent Sep 16, 2022
1ff1e05
Potentially improve setting output path on linux
atlanticaccent Sep 16, 2022
5c62cbe
Add original download's url as parameter to download completed handler
atlanticaccent Sep 16, 2022
77365d0
Formatting
atlanticaccent Sep 16, 2022
94287fb
Standardise terminology (replace `callback` with `handler`)
atlanticaccent Sep 16, 2022
71528e9
Use dunce to attempt to remove UNC prefixes on windows
atlanticaccent Sep 16, 2022
b600926
Fix incorrect function signature on macOS
atlanticaccent Sep 16, 2022
ee9c898
Improve docs
atlanticaccent Sep 16, 2022
c33f31b
Enable devtools in example
atlanticaccent Sep 17, 2022
a205b78
Include blob download example
atlanticaccent Sep 17, 2022
f791741
Formatting
atlanticaccent Sep 17, 2022
1610f98
Address comment regarding passing pathbuf to completion handler
atlanticaccent Sep 19, 2022
7d4f6c3
Separate download completed from started handler on macOS
atlanticaccent Sep 19, 2022
99c08f6
Formatting
atlanticaccent Sep 19, 2022
371dab5
Merge branch 'dev' into implement-download-event
wusyong Oct 19, 2022
2867989
Move download ffi function to download module
wusyong Oct 19, 2022
da1f5cf
Add change file
wusyong Oct 19, 2022
fed64aa
Correct the name of download start method
wusyong Oct 19, 2022
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
5 changes: 5 additions & 0 deletions .changes/download.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wry": patch
---

On Desktop, add `download_started_handler` and `download_completed_handler`. See `blob_download` and `download_event` example for their usages.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ http = "0.2.8"
anyhow = "1.0"
tempfile = "3.3.0"
http-range = "0.1.5"
normpath = "0.3.2"
dirs = "4.0.0"
base64 = "0.13.0"

[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
webkit2gtk = { version = "0.18.2", features = [ "v2_36" ] }
Expand All @@ -61,6 +64,7 @@ soup2 = "0.2"
[target."cfg(target_os = \"windows\")".dependencies]
webview2-com = "0.19.1"
windows-implement = "0.39.0"
dunce = "1.0.2"

[target."cfg(target_os = \"windows\")".dependencies.windows]
version = "0.39.0"
Expand Down
198 changes: 198 additions & 0 deletions examples/blob_download.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
};

use base64::decode;
use tempfile::tempdir;

fn main() -> wry::Result<()> {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};

let html = r#"
<body>
<div>
<a download="hello.txt" href='#' id="link">Download</a>
<script>
const example = new Blob(["Hello, world!"], {type: 'text/plain'});
link.href = URL.createObjectURL(example);
</script>
</div>
</body>
"#;

enum UserEvent {
BlobReceived(String),
BlobChunk(Option<String>),
}

let init_script = r"
// Adds an URL.getFromObjectURL( <blob:// URI> ) method
// returns the original object (<Blob> or <MediaSource>) the URI points to or null
(() => {
// overrides URL methods to be able to retrieve the original blobs later on
const old_create = URL.createObjectURL;
const old_revoke = URL.revokeObjectURL;
Object.defineProperty(URL, 'createObjectURL', {
get: () => storeAndCreate
});
Object.defineProperty(URL, 'revokeObjectURL', {
get: () => forgetAndRevoke
});
Object.defineProperty(URL, 'getFromObjectURL', {
get: () => getBlob
});
Object.defineProperty(URL, 'getObjectURLDict', {
get: () => getDict
});
Object.defineProperty(URL, 'clearURLDict', {
get: () => clearDict
});
const dict = {};

function storeAndCreate(blob) {
const url = old_create(blob); // let it throw if it has to
dict[url] = blob;
console.log(url)
console.log(blob)
return url
}

function forgetAndRevoke(url) {
console.log(`revoke ${url}`)
old_revoke(url);
}

function getBlob(url) {
return dict[url] || null;
}

function getDict() {
return dict;
}

function clearDict() {
dict = {};
}
})();
";

let event_loop: EventLoop<UserEvent> = EventLoop::with_user_event();
let proxy = event_loop.create_proxy();
let window = WindowBuilder::new()
.with_title("Hello World")
.build(&event_loop)?;
let webview = WebViewBuilder::new(window)?
.with_html(html)?
.with_initialization_script(init_script)
.with_download_started_handler({
let proxy = proxy.clone();
move |uri: String, _: &mut PathBuf| {
if uri.starts_with("blob:") {
let _ = proxy.send_event(UserEvent::BlobReceived(dbg!(uri)));
}

false
}
})
.with_ipc_handler({
let proxy = proxy.clone();
move |_, string| match string.as_str() {
_ if string.starts_with("data:") => {
let _ = proxy.send_event(UserEvent::BlobChunk(Some(string)));
}
"#EOF" => {
let _ = proxy.send_event(UserEvent::BlobChunk(None));
}
_ => {}
}
})
.with_devtools(true)
.build()?;

#[cfg(debug_assertions)]
webview.open_devtools();

let mut blob_file = None;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::UserEvent(UserEvent::BlobReceived(uri)) => {
let temp_dir = tempdir().expect("Create temp dir");
blob_file = Some((File::create(&temp_dir.path().join("blob.txt")).expect("Create file"), temp_dir));
webview.evaluate_script(&format!(r#"
(() => {{
/**
* @type Blob
*/
let blob = URL.getObjectURLDict()['{}']
|| Object.values(URL.getObjectURLDict())[0] // For some reason macOS returns a completely random blob URL? Just grab the first one

var increment = 1024;
var index = 0;
var reader = new FileReader();
let func = function() {{
let res = reader.result;
window.ipc.postMessage(`${{res}}`);
index += increment;
if (index < blob.size) {{
let slice = blob.slice(index, index + increment);
reader = new FileReader();
reader.onloadend = func;
reader.readAsDataURL(slice);
}} else {{
window.ipc.postMessage('#EOF');
}}
}};
reader.onloadend = func;
reader.readAsDataURL(blob.slice(index, increment))
}})();
"#, uri)).expect("Eval script");
},
Event::UserEvent(UserEvent::BlobChunk(chunk)) => {
if let Some((file, path)) = blob_file.as_mut() {
match chunk {
Some(chunk) => {
let split = chunk.split(',').nth(1);
println!("{:?}", chunk.split(',').next());
if let Some(split) = split {
if let Ok(decoded) = decode(split) {
if file.write(&decoded).is_err() {
eprintln!("Failed to write bytes to temp file")
}
}
}
},
None => {
let mut file = File::open(&path.path().join("blob.txt")).expect("Open temp file");
let mut content = String::new();
file.read_to_string(&mut content).expect("Read contents of file");
println!("Contents of file:");
println!("{}", content);
blob_file = None;
}
}
}
},
_ => (),
}
});
}
132 changes: 132 additions & 0 deletions examples/download_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::{cell::RefCell, path::PathBuf, rc::Rc};

use normpath::PathExt;
use tempfile::tempdir;

fn main() -> wry::Result<()> {
use wry::{
application::{
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
},
webview::WebViewBuilder,
};

let html = r#"
<body>
<div>
<p> WRYYYYYYYYYYYYYYYYYYYYYY! </p>
<a download="allow.zip" href='https://github.com/tauri-apps/wry/archive/refs/tags/wry-v0.13.3.zip' id="link">Allowed Download</a>
<a download="deny.zip" href='https://github.com/tauri-apps/wry/archive/refs/tags/wry-v0.13.2.zip' id="link">Denied Download</a>
</div>
</body>
"#;

enum UserEvent {
DownloadStarted(String, String),
DownloadComplete(Option<PathBuf>, bool),
Rejected(String),
}

let temp_dir = Rc::new(RefCell::new(None));
let event_loop: EventLoop<UserEvent> = EventLoop::with_user_event();
let proxy = event_loop.create_proxy();
let window = WindowBuilder::new()
.with_title("Hello World")
.build(&event_loop)?;
let webview = WebViewBuilder::new(window)?
.with_html(html)?
.with_download_started_handler({
let proxy = proxy.clone();
let tempdir_cell = temp_dir.clone();
move |uri: String, default_path: &mut PathBuf| {
if uri.contains("wry-v0.13.3") {
if let Ok(tempdir) = tempdir() {
if let Ok(path) = tempdir.path().normalize() {
tempdir_cell.borrow_mut().replace(tempdir);

let path = path.join("example.zip").as_path().to_path_buf();

*default_path = path.clone();

let submitted = proxy
.send_event(UserEvent::DownloadStarted(
uri.clone(),
path.display().to_string(),
))
.is_ok();

return submitted;
}
}
}

let _ = proxy.send_event(UserEvent::Rejected(uri.clone()));

false
}
})
.with_download_completed_handler({
let proxy = proxy.clone();
move |_uri, path, success| {
let _ = proxy.send_event(UserEvent::DownloadComplete(path, success));
}
})
.with_devtools(true)
.build()?;

#[cfg(debug_assertions)]
webview.open_devtools();

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
Event::UserEvent(UserEvent::DownloadStarted(uri, temp_dir)) => {
println!("Download: {}", uri);
println!("Will write to: {:?}", temp_dir);
}
Event::UserEvent(UserEvent::DownloadComplete(mut path, success)) => {
let _temp_dir_guard = if path.is_none() && success {
let temp_dir = temp_dir.borrow_mut().take();
path = Some(
temp_dir
.as_ref()
.expect("Stored temp dir")
.path()
.join("example.zip"),
);
temp_dir
} else {
None
};
println!("Succeeded: {}", success);
if let Some(path) = path {
let metadata = path.metadata();
println!("Path: {}", path.to_string_lossy());
if let Ok(metadata) = metadata {
println!("Size of {}Mb", (metadata.len() / 1024) / 1024)
} else {
println!("Failed to retrieve file metadata - does it exist?")
}
} else {
println!("No output path")
}
}
Event::UserEvent(UserEvent::Rejected(uri)) => {
println!("Rejected download from: {}", uri)
}
_ => (),
}
});
}
Loading