diff --git a/Cargo.lock b/Cargo.lock index e99018c..16ef0d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "account" -version = "0.0.0" +version = "0.1.4" dependencies = [ "anyhow", "common", @@ -429,6 +429,21 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto_update" +version = "0.1.4" +dependencies = [ + "anyhow", + "common", + "global", + "gpui", + "log", + "nostr-sdk", + "reqwest 0.12.15", + "smol", + "tempfile", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -944,7 +959,7 @@ dependencies = [ [[package]] name = "chats" -version = "0.0.0" +version = "0.1.4" dependencies = [ "account", "anyhow", @@ -1058,7 +1073,7 @@ dependencies = [ "cocoa-foundation 0.1.2", "core-foundation 0.9.4", "core-graphics 0.23.2", - "foreign-types", + "foreign-types 0.5.0", "libc", "objc", ] @@ -1074,7 +1089,7 @@ dependencies = [ "cocoa-foundation 0.2.0", "core-foundation 0.10.0", "core-graphics 0.24.0", - "foreign-types", + "foreign-types 0.5.0", "libc", "objc", ] @@ -1151,7 +1166,7 @@ dependencies = [ [[package]] name = "common" -version = "0.0.0" +version = "0.1.4" dependencies = [ "anyhow", "chrono", @@ -1205,6 +1220,7 @@ version = "0.1.4" dependencies = [ "account", "anyhow", + "auto_update", "chats", "common", "dirs 5.0.1", @@ -1262,7 +1278,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1275,7 +1291,7 @@ dependencies = [ "bitflags 2.9.0", "core-foundation 0.10.0", "core-graphics-types 0.2.0", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1288,7 +1304,7 @@ dependencies = [ "bitflags 2.9.0", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1335,7 +1351,7 @@ checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" dependencies = [ "core-foundation 0.10.0", "core-graphics 0.24.0", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -1946,6 +1962,15 @@ dependencies = [ "ttf-parser 0.25.1", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1953,7 +1978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -1967,6 +1992,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -2197,7 +2228,7 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "global" -version = "0.0.0" +version = "0.1.4" dependencies = [ "dirs 5.0.1", "nostr-sdk", @@ -2305,7 +2336,7 @@ dependencies = [ "filedescriptor", "flume", "font-kit", - "foreign-types", + "foreign-types 0.5.0", "futures", "gpui_macros", "http_client", @@ -2659,6 +2690,22 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.11" @@ -3339,7 +3386,7 @@ dependencies = [ "core-foundation 0.10.0", "core-video", "ctor", - "foreign-types", + "foreign-types 0.5.0", "metal", "objc", "workspace-hack", @@ -3378,7 +3425,7 @@ dependencies = [ "bitflags 2.9.0", "block", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -3463,6 +3510,23 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "negentropy" version = "0.3.1" @@ -4048,12 +4112,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -4801,19 +4903,23 @@ checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4825,14 +4931,18 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-socks", + "tokio-util", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "windows-registry 0.4.0", @@ -5013,7 +5123,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -5049,7 +5159,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs", "windows-sys 0.59.0", @@ -5266,6 +5376,19 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -6071,6 +6194,16 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.2" @@ -6311,7 +6444,7 @@ dependencies = [ [[package]] name = "ui" -version = "0.0.0" +version = "0.1.4" dependencies = [ "anyhow", "chrono", @@ -6601,6 +6734,12 @@ dependencies = [ "sval_serde", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 8a0f2e9..0a5036e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,9 @@ members = ["crates/*"] default-members = ["crates/app"] [workspace.package] -publish = false +version = "0.1.4" edition = "2021" +publish = false [workspace.dependencies] coop = { path = "crates/*" } diff --git a/Packager.toml b/Packager.toml index 51351db..95d05f0 100644 --- a/Packager.toml +++ b/Packager.toml @@ -2,7 +2,6 @@ name = "coop" description = "Coop is a cross-platform Nostr client designed for secure communication focus on simplicity and customizability." product-name = "Coop" identifier = "su.reya.coop" -version = "0.1.4" resources = ["assets/*/*", "Cargo.toml", "./LICENSE", "./README.md"] icons = [ "assets/brand/32x32.png", diff --git a/crates/account/Cargo.toml b/crates/account/Cargo.toml index 9ce40a7..2630f3f 100644 --- a/crates/account/Cargo.toml +++ b/crates/account/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "account" -version = "0.0.0" +version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 5e920a1..0a847c2 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "coop" -version = "0.1.4" +version.workspace = true edition.workspace = true publish.workspace = true @@ -14,6 +14,7 @@ common = { path = "../common" } global = { path = "../global" } chats = { path = "../chats" } account = { path = "../account" } +auto_update = { path = "../auto_update" } gpui.workspace = true reqwest_client.workspace = true diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 4a1ecba..3310215 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -1,11 +1,12 @@ use anyhow::{anyhow, Error}; use asset::Assets; +use auto_update::AutoUpdater; use chats::ChatRegistry; use futures::{select, FutureExt}; #[cfg(not(target_os = "linux"))] use global::constants::APP_NAME; use global::{ - constants::{ALL_MESSAGES_SUB_ID, APP_ID, BOOTSTRAP_RELAYS, NEW_MESSAGE_SUB_ID}, + constants::{ALL_MESSAGES_SUB_ID, APP_ID, APP_PUBKEY, BOOTSTRAP_RELAYS, NEW_MESSAGE_SUB_ID}, get_client, }; use gpui::{ @@ -17,9 +18,9 @@ use gpui::{point, SharedString, TitlebarOptions}; #[cfg(target_os = "linux")] use gpui::{WindowBackgroundAppearance, WindowDecorations}; use nostr_sdk::{ - pool::prelude::ReqExitPolicy, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind, - Metadata, PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions, - SubscriptionId, Tag, + nips::nip01::Coordinate, pool::prelude::ReqExitPolicy, Client, Event, EventBuilder, EventId, + Filter, JsonUtil, Keys, Kind, Metadata, PublicKey, RelayMessage, RelayPoolNotification, + SubscribeAutoCloseOptions, SubscriptionId, Tag, }; use smol::Timer; use std::{collections::HashSet, mem, sync::Arc, time::Duration}; @@ -37,8 +38,10 @@ enum Signal { Event(Event), /// Receive metadata Metadata(Box<(PublicKey, Option)>), - /// Receive EOSE + /// Receive eose Eose, + /// Receive app updates + AppUpdates(Event), } fn main() { @@ -47,11 +50,12 @@ fn main() { // Fix crash on startup _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); - let (event_tx, event_rx) = smol::channel::bounded::(1024); + let (event_tx, event_rx) = smol::channel::bounded::(2048); let (batch_tx, batch_rx) = smol::channel::bounded::>(500); // Initialize nostr client let client = get_client(); + let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); // Initialize application let app = Application::new() @@ -60,12 +64,36 @@ fn main() { // Connect to default relays app.background_executor() - .spawn(async { + .spawn(async move { for relay in BOOTSTRAP_RELAYS.into_iter() { - _ = client.add_relay(relay).await; + if let Err(e) = client.add_relay(relay).await { + log::error!("Failed to add relay {}: {}", relay, e); + } } - _ = client.connect().await + // Establish connection to bootstrap relays + client.connect().await; + + log::info!("Connected to bootstrap relays"); + log::info!("Subscribing to app updates..."); + + let coordinate = Coordinate { + kind: Kind::Custom(32267), + public_key: PublicKey::from_hex(APP_PUBKEY).expect("App Pubkey is invalid"), + identifier: APP_ID.into(), + }; + + let filter = Filter::new() + .kind(Kind::ArticlesCurationSet) + .coordinate(&coordinate) + .limit(1); + + if let Err(e) = client + .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) + .await + { + log::error!("Failed to subscribe for app updates: {}", e); + } }) .detach(); @@ -86,7 +114,7 @@ fn main() { Ok(keys) => { batch.extend(keys); if batch.len() >= BATCH_SIZE { - sync_metadata(mem::take(&mut batch)).await; + sync_metadata(mem::take(&mut batch), client, opts).await; } } Err(_) => break, @@ -94,7 +122,7 @@ fn main() { } _ = timeout => { if !batch.is_empty() { - sync_metadata(mem::take(&mut batch)).await; + sync_metadata(mem::take(&mut batch), client, opts).await; } } } @@ -175,6 +203,23 @@ fn main() { } } } + Kind::ReleaseArtifactSet => { + let filter = Filter::new() + .ids(event.tags.event_ids().copied()) + .kind(Kind::FileMetadata); + + if let Err(e) = client + .subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) + .await + { + log::error!("Failed to subscribe for file metadata: {}", e); + } else { + event_tx + .send(Signal::AppUpdates(event.into_owned())) + .await + .ok(); + } + } _ => {} } } @@ -241,13 +286,20 @@ fn main() { cx.new(|cx| { // Initialize components ui::init(cx); + + // Initialize auto update + auto_update::init(cx); + // Initialize chat state chats::init(cx); + // Initialize account state account::init(cx); + // Spawn a task to handle events from nostr channel cx.spawn_in(window, async move |_, cx| { let chats = cx.update(|_, cx| ChatRegistry::global(cx)).unwrap(); + let auto_updater = cx.update(|_, cx| AutoUpdater::global(cx)).unwrap(); while let Ok(signal) = event_rx.recv().await { cx.update(|window, cx| { @@ -269,6 +321,12 @@ fn main() { this.load_rooms(window, cx) }); } + Signal::AppUpdates(event) => { + // TODO: add settings for auto updates + auto_updater.update(cx, |this, cx| { + this.update(event, cx); + }) + } }; }) .ok(); @@ -310,10 +368,11 @@ async fn get_unwrapped(gift_wrap: EventId) -> Result { } } -async fn sync_metadata(buffer: HashSet) { - let client = get_client(); - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); - +async fn sync_metadata( + buffer: HashSet, + client: &Client, + opts: SubscribeAutoCloseOptions, +) { let kinds = vec![ Kind::Metadata, Kind::ContactList, diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml new file mode 100644 index 0000000..6779137 --- /dev/null +++ b/crates/auto_update/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "auto_update" +version.workspace = true +edition.workspace = true +publish.workspace = true + +[dependencies] +common = { path = "../common" } +global = { path = "../global" } + +gpui.workspace = true +nostr-sdk.workspace = true +anyhow.workspace = true +smol.workspace = true +log.workspace = true + +tempfile = "3.19.1" +reqwest = { version = "0.12", features = ["stream"] } diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs new file mode 100644 index 0000000..44966f3 --- /dev/null +++ b/crates/auto_update/src/lib.rs @@ -0,0 +1,350 @@ +use std::{ + env::{self, consts::OS}, + ffi::OsString, + path::PathBuf, +}; + +use anyhow::{anyhow, Context as _, Error}; +use global::get_client; +use gpui::{App, AppContext, Context, Entity, Global, SemanticVersion, Task}; +use nostr_sdk::prelude::*; +use smol::{ + fs::{self, File}, + io::AsyncWriteExt, + process::Command, +}; +use tempfile::TempDir; + +struct GlobalAutoUpdate(Entity); + +impl Global for GlobalAutoUpdate {} + +pub fn init(cx: &mut App) { + let env = env!("CARGO_PKG_VERSION"); + let current_version: SemanticVersion = env.parse().expect("Invalid version in Cargo.toml"); + + AutoUpdater::set_global( + cx.new(|_| AutoUpdater { + current_version, + status: AutoUpdateStatus::Idle, + }), + cx, + ); +} + +struct MacOsUnmounter { + mount_path: PathBuf, +} + +impl Drop for MacOsUnmounter { + fn drop(&mut self) { + let unmount_output = std::process::Command::new("hdiutil") + .args(["detach", "-force"]) + .arg(&self.mount_path) + .output(); + + match unmount_output { + Ok(output) if output.status.success() => { + log::info!("Successfully unmounted the disk image"); + } + Ok(output) => { + log::error!( + "Failed to unmount disk image: {:?}", + String::from_utf8_lossy(&output.stderr) + ); + } + Err(error) => { + log::error!("Error while trying to unmount disk image: {:?}", error); + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AutoUpdateStatus { + Idle, + Checking, + Downloading, + Installing, + Updated { binary_path: PathBuf }, + Errored, +} + +impl AutoUpdateStatus { + pub fn is_updated(&self) -> bool { + matches!(self, Self::Updated { .. }) + } +} + +#[derive(Debug)] +pub struct AutoUpdater { + status: AutoUpdateStatus, + current_version: SemanticVersion, +} + +impl AutoUpdater { + pub fn global(cx: &App) -> Entity { + cx.global::().0.clone() + } + + pub fn set_global(auto_updater: Entity, cx: &mut App) { + cx.set_global(GlobalAutoUpdate(auto_updater)); + } + + pub fn current_version(&self) -> SemanticVersion { + self.current_version + } + + pub fn status(&self) -> AutoUpdateStatus { + self.status.clone() + } + + pub fn set_status(&mut self, status: AutoUpdateStatus, cx: &mut Context) { + self.status = status; + cx.notify(); + } + + pub fn update(&mut self, event: Event, cx: &mut Context) { + self.set_status(AutoUpdateStatus::Checking, cx); + + // Extract the version from the identifier tag + let ident = match event.tags.identifier() { + Some(i) => match i.split('@').next_back() { + Some(i) => i, + None => return, + }, + None => return, + }; + + // Convert the version string to a SemanticVersion + let new_version: SemanticVersion = ident.parse().expect("Invalid version"); + + // Check if the new version is the same as the current version + if self.current_version == new_version { + self.set_status(AutoUpdateStatus::Idle, cx); + return; + }; + + // Download the new version + self.set_status(AutoUpdateStatus::Downloading, cx); + + let task: Task> = cx.background_spawn(async move { + let client = get_client(); + let ids = event.tags.event_ids().copied(); + let filter = Filter::new().ids(ids).kind(Kind::FileMetadata); + let events = client.database().query(filter).await?; + + if let Some(event) = events.into_iter().find(|event| event.content == OS) { + let tag = event.tags.find(TagKind::Url).context("url not found")?; + let url = Url::parse(tag.content().context("invalid")?)?; + + let temp_dir = tempfile::Builder::new().prefix("coop-update").tempdir()?; + let filename = match OS { + "macos" => Ok("Coop.dmg"), + "linux" => Ok("Coop.tar.gz"), + "windows" => Ok("CoopUpdateInstaller.exe"), + _ => Err(anyhow!("not supported: {:?}", OS)), + }?; + + let downloaded_asset = temp_dir.path().join(filename); + let mut target_file = File::create(&downloaded_asset).await?; + + let response = reqwest::get(url).await?; + let mut stream = response.bytes_stream(); + + while let Some(item) = stream.next().await { + let chunk = item?; + target_file.write_all(&chunk).await?; + } + + log::info!("downloaded update. path:{:?}", downloaded_asset); + + Ok((temp_dir, downloaded_asset)) + } else { + Err(anyhow!("Not found")) + } + }); + + cx.spawn(async move |this, cx| { + if let Ok((temp_dir, downloaded_asset)) = task.await { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.set_status(AutoUpdateStatus::Installing, cx); + + match OS { + "macos" => this.install_release_macos(temp_dir, downloaded_asset, cx), + "linux" => this.install_release_linux(temp_dir, downloaded_asset, cx), + "windows" => this.install_release_windows(downloaded_asset, cx), + _ => {} + } + }) + .ok(); + }) + .ok(); + } + }) + .detach(); + } + + fn install_release_macos(&mut self, temp_dir: TempDir, asset: PathBuf, cx: &mut Context) { + let running_app_path = cx.app_path().unwrap(); + let running_app_filename = running_app_path.file_name().unwrap(); + + let mount_path = temp_dir.path().join("Coop"); + + let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into(); + mounted_app_path.push("/"); + + let task: Task> = cx.background_spawn(async move { + let output = Command::new("hdiutil") + .args(["attach", "-nobrowse"]) + .arg(&asset) + .arg("-mountroot") + .arg(temp_dir.path()) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "failed to mount: {:?}", + String::from_utf8_lossy(&output.stderr) + ); + + // Create an MacOsUnmounter that will be dropped (and thus unmount the disk) when this function exits + let _unmounter = MacOsUnmounter { + mount_path: mount_path.clone(), + }; + + let output = Command::new("rsync") + .args(["-av", "--delete"]) + .arg(&mounted_app_path) + .arg(&running_app_path) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "failed to copy app: {:?}", + String::from_utf8_lossy(&output.stderr) + ); + + Ok(running_app_path) + }); + + cx.spawn(async move |this, cx| { + if let Ok(binary_path) = task.await { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.status = AutoUpdateStatus::Updated { binary_path }; + cx.notify(); + }) + .ok(); + }) + .ok(); + } + }) + .detach(); + } + + fn install_release_linux(&mut self, temp_dir: TempDir, asset: PathBuf, cx: &mut Context) { + let home_dir = PathBuf::from(env::var("HOME").unwrap()); + let running_app_path = cx.app_path().unwrap(); + let extracted = temp_dir.path().join("coop"); + + let task: Task> = cx.background_spawn(async move { + fs::create_dir_all(&extracted).await?; + + let output = Command::new("tar") + .arg("-xzf") + .arg(&asset) + .arg("-C") + .arg(&extracted) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "failed to extract {:?} to {:?}: {:?}", + asset, + extracted, + String::from_utf8_lossy(&output.stderr) + ); + + let app_folder_name: String = "coop.app".into(); + let from = extracted.join(&app_folder_name); + let mut to = home_dir.join(".local"); + + let expected_suffix = format!("{}/libexec/coop", app_folder_name); + + if let Some(prefix) = running_app_path + .to_str() + .and_then(|str| str.strip_suffix(&expected_suffix)) + { + to = PathBuf::from(prefix); + } + + let output = Command::new("rsync") + .args(["-av", "--delete"]) + .arg(&from) + .arg(&to) + .output() + .await?; + + anyhow::ensure!( + output.status.success(), + "failed to copy Coop update from {:?} to {:?}: {:?}", + from, + to, + String::from_utf8_lossy(&output.stderr) + ); + + Ok(to.join(expected_suffix)) + }); + + cx.spawn(async move |this, cx| { + if let Ok(binary_path) = task.await { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.status = AutoUpdateStatus::Updated { binary_path }; + cx.notify(); + }) + .ok(); + }) + .ok(); + } + }) + .detach(); + } + + fn install_release_windows(&mut self, asset: PathBuf, cx: &mut Context) { + let task: Task> = cx.background_spawn(async move { + let output = Command::new(asset) + .arg("/verysilent") + .arg("/update=true") + .arg("!desktopicon") + .arg("!quicklaunchicon") + .output() + .await?; + anyhow::ensure!( + output.status.success(), + "failed to start installer: {:?}", + String::from_utf8_lossy(&output.stderr) + ); + Ok(std::env::current_exe()?) + }); + + cx.spawn(async move |this, cx| { + if let Ok(binary_path) = task.await { + cx.update(|cx| { + this.update(cx, |this, cx| { + this.status = AutoUpdateStatus::Updated { binary_path }; + cx.notify(); + }) + .ok(); + }) + .ok(); + } + }) + .detach(); + } +} diff --git a/crates/chats/Cargo.toml b/crates/chats/Cargo.toml index 66ad5a2..990edec 100644 --- a/crates/chats/Cargo.toml +++ b/crates/chats/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chats" -version = "0.0.0" +version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index ca4eed3..2b3d50a 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "common" -version = "0.0.0" +version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/global/Cargo.toml b/crates/global/Cargo.toml index 46aa9c3..02801ed 100644 --- a/crates/global/Cargo.toml +++ b/crates/global/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "global" -version = "0.0.0" +version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/global/src/constants.rs b/crates/global/src/constants.rs index 8d914b9..55bea6f 100644 --- a/crates/global/src/constants.rs +++ b/crates/global/src/constants.rs @@ -1,5 +1,6 @@ pub const APP_NAME: &str = "Coop"; pub const APP_ID: &str = "su.reya.coop"; +pub const APP_PUBKEY: &str = "b1813fb01274b32cc5db6d1198e7c79dda0fb430899f63c7064f651a41d44f2b"; /// Bootstrap relays pub const BOOTSTRAP_RELAYS: [&str; 5] = [ diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 899f302..0e94842 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ui" -version = "0.0.0" +version.workspace = true edition.workspace = true publish.workspace = true diff --git a/crates/ui/README.md b/crates/ui/README.md deleted file mode 100644 index e69de29..0000000