Many imorovments
This commit is contained in:
+121
-7
@@ -106,6 +106,28 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
// --- sign in with an app password -----------------------------------
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
let ctl = controller.clone();
|
||||
window.on_launch_sign_in_direct(move |server, login, password| {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
ctl.model
|
||||
.borrow_mut()
|
||||
.begin_direct_sign_in(server.to_string());
|
||||
render(&w, &ctl);
|
||||
|
||||
let server = ctl.model.borrow().server_url.clone();
|
||||
spawn_direct_login(
|
||||
w.as_weak(),
|
||||
ctl.clone(),
|
||||
server,
|
||||
login.to_string(),
|
||||
password.to_string(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// --- sign out ------------------------------------------------------
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
@@ -279,11 +301,18 @@ fn spawn_login(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, server:
|
||||
};
|
||||
step("thread started");
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
|
||||
// `enable_all()` also enables the signal driver, which wants to own
|
||||
// process-wide signal handling and is not something a worker thread
|
||||
// inside an Android app can safely claim — the flow needs only IO
|
||||
// (reqwest) and time (the poll interval), so ask for just those.
|
||||
let rt = match tokio::runtime::Builder::new_current_thread()
|
||||
// Multi-thread, not current-thread: a current-thread runtime drives
|
||||
// its reactor only while the thread sits inside `block_on`, and on
|
||||
// Android that left reqwest's connection future never polled — the
|
||||
// await never resolved, so the worker neither failed nor returned.
|
||||
// A multi-thread runtime owns worker threads that poll the reactor
|
||||
// regardless. One worker is plenty for a single login flow.
|
||||
//
|
||||
// Only IO and time are enabled; `enable_all()` would also start the
|
||||
// signal driver, which wants process-wide signal handling that an
|
||||
// Android app's runtime already owns.
|
||||
let rt = match tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
@@ -313,6 +342,85 @@ fn spawn_login(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, server:
|
||||
poll_channel(weak, ctl, rx);
|
||||
}
|
||||
|
||||
/// TRACES: FR-NC-1
|
||||
/// Verify an app password the user supplied, then keep it.
|
||||
///
|
||||
/// No browser and no polling: one authenticated request establishes both that
|
||||
/// the credential works and what the account's canonical user id is, which is
|
||||
/// what the DAV paths are built from. Reuses the same channel and drain loop as
|
||||
/// the browser flow, so success and failure land in the UI identically.
|
||||
fn spawn_direct_login(
|
||||
weak: slint::Weak<AppWindow>,
|
||||
ctl: Rc<LaunchController>,
|
||||
server: String,
|
||||
login: String,
|
||||
password: String,
|
||||
) {
|
||||
let (tx, rx) = std::sync::mpsc::channel::<LoginMessage>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let panic_tx = tx.clone();
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
|
||||
// Multi-thread for the reason the browser flow is: a current-thread
|
||||
// runtime left reqwest's future unpolled on Android.
|
||||
let rt = match tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build()
|
||||
{
|
||||
Ok(rt) => rt,
|
||||
Err(e) => {
|
||||
let _ = tx.send(LoginMessage::Failed(format!("tokio runtime: {e}")));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
rt.block_on(async {
|
||||
let client = match dr_sync_nextcloud::http_client("DarkRoom") {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let _ = tx.send(LoginMessage::Failed(format!("http client: {e}")));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let creds = dr_sync_nextcloud::AppCredentials {
|
||||
server,
|
||||
login_name: login,
|
||||
app_password: password,
|
||||
};
|
||||
|
||||
// The credential is only worth storing if it actually works,
|
||||
// and this is the cheapest request that proves it. A 401 comes
|
||||
// back as an error here rather than as a puzzling failure on
|
||||
// the first listing.
|
||||
match fetch_user_id(&client, &creds).await {
|
||||
Ok(user_id) => {
|
||||
let _ = tx.send(LoginMessage::Success(Box::new((creds, user_id))));
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = tx.send(LoginMessage::Failed(format!(
|
||||
"could not sign in with that app password: {e}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
if let Err(panic) = result {
|
||||
let detail = panic
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| (*s).to_string())
|
||||
.or_else(|| panic.downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "panicked with a non-string payload".to_string());
|
||||
let _ = panic_tx.send(LoginMessage::Failed(format!("internal error: {detail}")));
|
||||
}
|
||||
});
|
||||
|
||||
poll_channel(weak, ctl, rx);
|
||||
}
|
||||
|
||||
/// The body of the login flow, split out so the worker above can wrap it in
|
||||
/// `catch_unwind` without a deeply nested closure.
|
||||
fn run_login_flow(
|
||||
@@ -487,8 +595,14 @@ fn spawn_folder_list(weak: slint::Weak<AppWindow>, ctl: Rc<LaunchController>, pa
|
||||
let user_id = session.user_id.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
// Multi-thread for the same reason as the login worker: a
|
||||
// current-thread runtime left reqwest's connection future unpolled on
|
||||
// Android, so the await never resolved and the thread stopped without
|
||||
// failing or returning.
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(1)
|
||||
.enable_io()
|
||||
.enable_time()
|
||||
.build();
|
||||
let Ok(rt) = rt else {
|
||||
let _ = tx.send(Err("runtime".into()));
|
||||
|
||||
Reference in New Issue
Block a user