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
1 change: 1 addition & 0 deletions app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tauri-build = { workspace = true }
winreg = "0.55"
windows = { version = "0.62", features = [
"ApplicationModel",
"Management_Deployment",
"Services_Store",
"Win32_Foundation",
"Win32_System_Com",
Expand Down
56 changes: 52 additions & 4 deletions app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2551,7 +2551,7 @@ async fn update_ready(
if !asked && !update::due(last, now) {
return Ok(last_said());
}
let shelf = tauri::async_runtime::spawn_blocking(move || shop::asked(window))
let shelf = tauri::async_runtime::spawn_blocking(move || shop::asked(window, asked))
.await
.unwrap_or(shop::Shelf::Silent);
return match shelf {
Expand All @@ -2564,6 +2564,20 @@ async fn update_ready(
})?;
Ok(seen)
}
// Already down and waiting for this window to close: there is nothing to fetch, so
// it is announced without the button that would ask the Store for it again.
shop::Shelf::Landed(version) => {
let seen = update::from_the_shop(&version, HERE).map(|one| update::Ready {
installs: false,
..one
});
held(&session).keep(|c| {
c.checked_at = Some(now);
c.found_version = seen.as_ref().map(|one| one.version.clone());
c.found_in_the_shop = seen.as_ref().map(|_| true);
})?;
Ok(seen)
}
shop::Shelf::Current => {
held(&session).keep(|c| {
c.checked_at = Some(now);
Expand Down Expand Up @@ -3088,6 +3102,19 @@ struct Filed {
#[serde(skip_serializing_if = "Option::is_none")]
guest: Option<String>,
page_of: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
flagged: Option<Marked>,
#[serde(skip_serializing_if = "Option::is_none")]
folder_was: Option<Vec<String>>,
}

#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct Marked {
at: String,
said: String,
#[serde(skip_serializing_if = "Option::is_none")]
via: Option<String>,
}

const CATCHING_UP_AT_ONCE: usize = 500;
Expand Down Expand Up @@ -3188,6 +3215,12 @@ fn gathered(session: &Session) -> Vec<Filed> {
guest: kept.guest.then(|| kept.by.clone().unwrap_or_default()),
page_of: kept.page_of.map(|up| up.to_string()),
tags: kept.tags.iter().map(|one| one.to_string()).collect(),
flagged: kept.flagged.as_ref().map(|mark| Marked {
at: mark.at.to_string(),
said: mark.body.clone(),
via: mark.via.clone(),
}),
folder_was: kept.folder_was.clone(),
})
.collect()
}
Expand Down Expand Up @@ -3265,7 +3298,7 @@ fn folder_add(
parent: Option<String>,
icon: Option<String>,
color: Option<String>,
) -> Answer<()> {
) -> Answer<String> {
let name = named_folder(&name)?;
let parent = parent
.map(|at| at.parse().map_err(|_| Refusal::of("noSuchFolder")))
Expand Down Expand Up @@ -3296,8 +3329,9 @@ fn folder_add(
.iter()
.map(|one| one.order.as_str()),
);
let id = ulid::Ulid::generate();
session.commit(Op::FolderAdd {
id: ulid::Ulid::generate(),
id,
d: tisty_core::event::FolderAdd {
name,
order,
Expand All @@ -3306,7 +3340,7 @@ fn folder_add(
color: painted,
},
})?;
Ok(())
Ok(id.to_string())
}

#[tauri::command]
Expand Down Expand Up @@ -4283,6 +4317,19 @@ fn doc_away(session: tauri::State<'_, Mutex<Session>>, id: String, away: bool) -
Ok(())
}

#[tauri::command]
fn doc_unflag(session: tauri::State<'_, Mutex<Session>>, id: String) -> Answer<()> {
let id = id.parse().map_err(|_| Refusal::of("noSuchDoc"))?;
let mut session = held(&session);
match session.state.docs.get(&id) {
None => return Err(Refusal::of("noSuchDoc")),
Some(one) if one.flagged.is_none() => return Ok(()),
Some(_) => {}
}
session.commit(Op::DocUnflag { id })?;
Ok(())
}

#[tauri::command]
fn folder_away(session: tauri::State<'_, Mutex<Session>>, id: String, away: bool) -> Answer<()> {
let id = id.parse().map_err(|_| Refusal::of("noSuchFolder"))?;
Expand Down Expand Up @@ -7354,6 +7401,7 @@ pub fn run() {
doc_let_go,
retire_attachments,
doc_away,
doc_unflag,
folder_away,
doc_lock,
parted,
Expand Down
72 changes: 68 additions & 4 deletions app/src-tauri/src/shop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Shelf {
Waiting(String),
/// Already on this machine, staged beside the copy in use, and it takes over when this one
/// closes. Nothing left to download, so nothing to offer but the closing.
Landed(String),
Current,
Silent,
}
Expand All @@ -27,8 +30,8 @@ pub fn step(far: f64) -> (&'static str, u64) {
("getting", (far.max(0.0) / DOWNLOADED * 100.0) as u64)
}

pub fn asked(window: isize) -> Shelf {
there::asked(window)
pub fn asked(window: isize, forced: bool) -> Shelf {
there::asked(window, forced)
}

pub fn take(
Expand All @@ -53,6 +56,11 @@ mod there {
use windows_future::AsyncOperationProgressHandler;

const PATIENCE: std::time::Duration = std::time::Duration::from_secs(10);
/// Asking wakes the Store's own errand, and its answer is the state from before it looked:
/// at 15:06 it says nothing and at 15:07 it is installing. A person who pressed the button
/// is owed the answer that comes out of that errand, not the one that preceded it.
const AGAIN: std::time::Duration = std::time::Duration::from_secs(6);
const UNTIL: std::time::Duration = std::time::Duration::from_secs(75);
/// Long past the point where the process should already have been taken down by the install.
const AT_LENGTH: std::time::Duration = std::time::Duration::from_secs(30 * 60);

Expand All @@ -78,7 +86,7 @@ mod there {
.is_ok_and(|kind| kind == PackageSignatureKind::Store)
}

pub fn asked(window: isize) -> Shelf {
pub fn asked(window: isize, forced: bool) -> Shelf {
if !sold_here() {
witness::warn(
channel::WINDOW,
Expand All @@ -87,8 +95,12 @@ mod there {
);
return Shelf::Silent;
}
if let Some(version) = beside_us() {
return Shelf::Landed(version);
}
match apart(PATIENCE, move || waiting(window)) {
Some(Ok(Some(version))) => Shelf::Waiting(version),
Some(Ok(None)) if forced => waited_out(window),
Some(Ok(None)) => Shelf::Current,
Some(Err(why)) => {
witness::warn(
Expand All @@ -109,6 +121,58 @@ mod there {
}
}

/// The Store was just asked and said nothing, which is what it says while its own errand is
/// still running. Waiting it out turns «nothing for you» into what it actually found.
fn waited_out(window: isize) -> Shelf {
let since = std::time::Instant::now();
while since.elapsed() < UNTIL {
std::thread::sleep(AGAIN);
if let Some(version) = beside_us() {
witness::note(
channel::WINDOW,
"the Store brought the update down while the person waited on the answer",
&[("version", Fact::Id(version.clone()))],
);
return Shelf::Landed(version);
}
if let Some(Ok(Some(version))) = apart(PATIENCE, move || waiting(window)) {
witness::note(
channel::WINDOW,
"the Store named an update only after its own errand had run",
&[
("version", Fact::Id(version.clone())),
("waited", Fact::Count(since.elapsed().as_secs() as usize)),
],
);
return Shelf::Waiting(version);
}
}
Shelf::Current
}

/// A newer package of our own family already registered for this user is one the Store has
/// finished bringing down: it cannot replace a copy that is running, so it waits for the
/// window to close. The Store's own answer never mentions it — there is nothing left to get.
fn beside_us() -> Option<String> {
let ours = Package::Current().ok()?;
let family = ours.Id().ok()?.FamilyName().ok()?;
let here = numbered(&ours.Id().ok()?.Version().ok()?);
let shelf = windows::Management::Deployment::PackageManager::new().ok()?;
let mut newest: Option<semver::Version> = None;
let mine: semver::Version = here.parse().ok()?;
for one in shelf.FindPackagesByPackageFamilyName(&family).ok()? {
let Ok(id) = one.Id() else { continue };
let Ok(version) = id.Version() else { continue };
let Ok(said) = numbered(&version).parse::<semver::Version>() else {
continue;
};
if said > mine && newest.as_ref().is_none_or(|had| said > *had) {
newest = Some(said);
}
}
newest.map(|one| one.to_string())
}

/// The errand covers optional packages too, and the version of one of those is not a version
/// of Tisty to put in front of anybody.
fn waiting(window: isize) -> windows::core::Result<Option<String>> {
Expand Down Expand Up @@ -234,7 +298,7 @@ mod there {
mod there {
use super::{Shelf, Trouble};

pub fn asked(_window: isize) -> Shelf {
pub fn asked(_window: isize, _forced: bool) -> Shelf {
Shelf::Silent
}

Expand Down
Loading
Loading