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
19 changes: 19 additions & 0 deletions apps/studio/src/agent_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ pub struct AgentPanel {
/// here (the app's data was reset): the sign-in being restored, tried once per session.
restore_rx: Option<mpsc::Receiver<Result<sf_license::Activation, String>>>,
restore_tried: bool,
/// `sf_license::license_generation` when the plan was last fetched: a key saved or removed
/// elsewhere (Export activated Pro, a renewal) fetches it again.
license_seen: Option<u64>,
/// "Signed in." and the like: shown above the suggestions until the first message.
notice: Option<String>,
}
Expand Down Expand Up @@ -683,6 +686,8 @@ impl AgentPanel {
self.log.clear();
self.notice = note;
self.license_changed = true;
// this panel's own sign-in: no second fetch for the key it just saved
self.license_seen = Some(sf_license::license_generation());
self.fetch_credits();
}

Expand Down Expand Up @@ -1043,6 +1048,20 @@ impl AgentPanel {
if std::mem::take(&mut self.license_changed) {
ed.export.license = sf_license::load_saved().map(|k| sf_license::verify(&k)).unwrap_or_else(|| sf_license::Status::Invalid("no key".into()));
}
// the license changed outside this panel (bought or signed in from Export): fetch the
// plan again, keeping the chat
// (a fetch already running is left to finish; the next frame asks again)
let generation = sf_license::license_generation();
match self.license_seen {
None => self.license_seen = Some(generation),
Some(seen) if seen != generation && self.credits_rx.is_none() => {
self.license_seen = Some(generation);
self.credits = None;
self.credits_error = None;
self.fetch_credits();
}
_ => {}
}
if self.credits.is_none() && self.credits_error.is_none() {
self.fetch_credits();
}
Expand Down
12 changes: 10 additions & 2 deletions apps/studio/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub struct ExportDialog {
free_rx: Option<Receiver<Result<u32, String>>>,
/// The dialog was open last frame (opening it refreshes the free exports).
was_open: bool,
/// `sf_license::license_generation` the license was last read at: a key saved or removed
/// elsewhere (the agent panel's upgrade) reads it again.
license_seen: u64,
/// The export running now is a free one: counted once it finishes.
free_job: bool,
progress: Option<sf_export::Progress>,
Expand All @@ -79,7 +82,7 @@ pub struct ExportDialog {
/// that loads the license and talks to the backend.
impl Default for ExportDialog {
fn default() -> Self {
ExportDialog { open: false, license: sf_license::Status::Invalid("no key".into()), email: String::new(), activation: None, check_in: None, activation_msg: None, code_sent_to: None, code: String::new(), signing_in: false, promo: String::new(), promo_open: false, checkout: None, claim_until: None, next_claim: None, claim: None, free_left: None, free_rx: None, was_open: false, free_job: false, format: Format::Mp4, fps: 60.0, height: 1080, quality: QualityPreset::SocialMedia, codec: VideoCodec::H264, audio: true, job: None, progress: None, result: None, started: None }
ExportDialog { open: false, license: sf_license::Status::Invalid("no key".into()), email: String::new(), activation: None, check_in: None, activation_msg: None, code_sent_to: None, code: String::new(), signing_in: false, promo: String::new(), promo_open: false, checkout: None, claim_until: None, next_claim: None, claim: None, free_left: None, free_rx: None, was_open: false, license_seen: 0, free_job: false, format: Format::Mp4, fps: 60.0, height: 1080, quality: QualityPreset::SocialMedia, codec: VideoCodec::H264, audio: true, job: None, progress: None, result: None, started: None }
}
}

Expand All @@ -103,7 +106,7 @@ impl ExportDialog {
None
};
// a checkout from an earlier session, paid after quitting: look for it a while
let mut d = ExportDialog { license, email, check_in, ..Default::default() };
let mut d = ExportDialog { license, email, check_in, license_seen: sf_license::license_generation(), ..Default::default() };
// known before the dialog first opens
if !d.license.is_valid() {
d.fetch_free_exports();
Expand Down Expand Up @@ -256,6 +259,11 @@ impl ExportDialog {

/// Drain background messages (export progress, activation) and handle Escape.
pub fn poll(&mut self, ctx: &egui::Context) {
let generation = sf_license::license_generation();
if generation != self.license_seen {
self.license_seen = generation;
self.license = sf_license::load_saved().map(|k| sf_license::verify(&k)).unwrap_or_else(|| sf_license::Status::Invalid("no key".into()));
}
self.poll_checkout(ctx);
self.poll_free_exports(ctx);
if let Some(rx) = &self.check_in {
Expand Down
23 changes: 21 additions & 2 deletions crates/sf-license/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,18 +488,37 @@ pub fn billing_portal_url() -> Result<String, String> {
v.get("url").and_then(|u| u.as_str()).map(String::from).ok_or_else(|| "no portal url".to_string())
}

/// Bumped whenever the key on this computer is saved or removed (activated, renewed, signed
/// out, moved away), so every part of the app that shows the plan can refresh: the agent
/// panel and Export each check [`license_generation`] and re-read when it changed.
static LICENSE_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);

pub fn license_generation() -> u64 {
LICENSE_GENERATION.load(std::sync::atomic::Ordering::Relaxed)
}

fn license_changed() {
LICENSE_GENERATION.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}

/// Remove the saved key (the license moved to another computer).
pub fn forget() -> std::io::Result<()> {
match key_file() {
Some(p) if p.exists() => std::fs::remove_file(p),
Some(p) if p.exists() => {
std::fs::remove_file(p)?;
license_changed();
Ok(())
}
_ => Ok(()),
}
}

pub fn save(key: &str) -> std::io::Result<()> {
let p = key_file().ok_or_else(|| std::io::Error::other("no home dir"))?;
std::fs::create_dir_all(p.parent().unwrap())?;
std::fs::write(p, key.trim())
std::fs::write(p, key.trim())?;
license_changed();
Ok(())
}

#[cfg(test)]
Expand Down
Loading