app version to server (#7130)

- Send app version and release stage to collab on connect
- Read the new header on the server

Release Notes:

- Added the ability to collaborate with users on different releases of
Zed.
This commit is contained in:
Conrad Irwin 2024-01-31 15:46:24 -07:00 committed by GitHub
parent 5b7b5bfea5
commit 2187513026
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 166 additions and 96 deletions

1
Cargo.lock generated
View File

@ -1486,6 +1486,7 @@ dependencies = [
"prometheus",
"prost 0.8.0",
"rand 0.8.5",
"release_channel",
"reqwest",
"rpc",
"scrypt",

View File

@ -1,7 +1,7 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
use client::{Client, TelemetrySettings, ZED_APP_PATH};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
@ -108,29 +108,28 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
})
.detach();
if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
let version = release_channel::AppVersion::global(cx);
let auto_updater = cx.new_model(|cx| {
let updater = AutoUpdater::new(version, http_client);
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
.then(|| updater.start_polling(cx));
let mut update_subscription = AutoUpdateSetting::get_global(cx)
.0
.then(|| updater.start_polling(cx));
cx.observe_global::<SettingsStore>(move |updater, cx| {
if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
}
} else {
update_subscription.take();
cx.observe_global::<SettingsStore>(move |updater, cx| {
if AutoUpdateSetting::get_global(cx).0 {
if update_subscription.is_none() {
update_subscription = Some(updater.start_polling(cx))
}
})
.detach();
} else {
update_subscription.take();
}
})
.detach();
updater
});
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
updater
});
cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
pub fn check(_: &Check, cx: &mut WindowContext) {

View File

@ -329,6 +329,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
let http = FakeHttpClient::with_404_response();

View File

@ -15,14 +15,13 @@ use futures::{
TryFutureExt as _, TryStreamExt,
};
use gpui::{
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, SemanticVersion,
Task, WeakModel,
actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
use release_channel::ReleaseChannel;
use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@ -58,9 +57,6 @@ lazy_static! {
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
.ok()
.and_then(|s| if s.is_empty() { None } else { Some(s) });
pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
.ok()
.and_then(|v| v.parse().ok());
pub static ref ZED_APP_PATH: Option<PathBuf> =
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
pub static ref ZED_ALWAYS_ACTIVE: bool =
@ -1011,13 +1007,22 @@ impl Client {
.update(|cx| ReleaseChannel::try_global(cx))
.ok()
.flatten();
let app_version = cx
.update(|cx| AppVersion::global(cx).to_string())
.ok()
.unwrap_or_default();
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
.header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
.header("x-zed-app-version", app_version)
.header(
"x-zed-release-channel",
release_channel.map(|r| r.dev_name()).unwrap_or("unknown"),
);
let http = self.http.clone();
cx.background_executor().spawn(async move {

View File

@ -61,6 +61,7 @@ util = { path = "../util" }
uuid.workspace = true
[dev-dependencies]
release_channel = { path = "../release_channel" }
async-trait.workspace = true
audio = { path = "../audio" }
call = { path = "../call", features = ["test-support"] }

View File

@ -64,6 +64,7 @@ use time::OffsetDateTime;
use tokio::sync::{watch, Semaphore};
use tower::ServiceBuilder;
use tracing::{field, info_span, instrument, Instrument};
use util::SemanticVersion;
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
@ -795,6 +796,7 @@ fn broadcast<F>(
lazy_static! {
static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
static ref ZED_APP_VERSION: HeaderName = HeaderName::from_static("x-zed-app-version");
}
pub struct ProtocolVersion(u32);
@ -824,6 +826,32 @@ impl Header for ProtocolVersion {
}
}
pub struct AppVersionHeader(SemanticVersion);
impl Header for AppVersionHeader {
fn name() -> &'static HeaderName {
&ZED_APP_VERSION
}
fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
where
Self: Sized,
I: Iterator<Item = &'i axum::http::HeaderValue>,
{
let version = values
.next()
.ok_or_else(axum::headers::Error::invalid)?
.to_str()
.map_err(|_| axum::headers::Error::invalid())?
.parse()
.map_err(|_| axum::headers::Error::invalid())?;
Ok(Self(version))
}
fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
values.extend([self.0.to_string().parse().unwrap()]);
}
}
pub fn routes(server: Arc<Server>) -> Router<Body> {
Router::new()
.route("/rpc", get(handle_websocket_request))
@ -838,6 +866,7 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
pub async fn handle_websocket_request(
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
_app_version_header: Option<TypedHeader<AppVersionHeader>>,
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
Extension(server): Extension<Arc<Server>>,
Extension(user): Extension<User>,
@ -851,6 +880,7 @@ pub async fn handle_websocket_request(
)
.into_response();
}
let socket_address = socket_address.to_string();
ws.on_upgrade(move |socket| {
use util::ResultExt;

View File

@ -153,6 +153,7 @@ impl TestServer {
}
let settings = SettingsStore::test(cx);
cx.set_global(settings);
release_channel::init("0.0.0", cx);
client::init_settings(cx);
});

View File

@ -1,14 +1,13 @@
use client::ZED_APP_VERSION;
use gpui::AppContext;
use human_bytes::human_bytes;
use release_channel::ReleaseChannel;
use release_channel::{AppVersion, ReleaseChannel};
use serde::Serialize;
use std::{env, fmt::Display};
use sysinfo::{RefreshKind, System, SystemExt};
#[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs {
app_version: Option<String>,
app_version: String,
release_channel: &'static str,
os_name: &'static str,
os_version: Option<String>,
@ -18,9 +17,7 @@ pub struct SystemSpecs {
impl SystemSpecs {
pub fn new(cx: &AppContext) -> Self {
let app_version = ZED_APP_VERSION
.or_else(|| cx.app_metadata().app_version)
.map(|v| v.to_string());
let app_version = AppVersion::global(cx).to_string();
let release_channel = ReleaseChannel::global(cx).display_name();
let os_name = cx.app_metadata().os_name;
let system = System::new_with_specifics(RefreshKind::new().with_memory());
@ -48,18 +45,15 @@ impl Display for SystemSpecs {
Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
None => format!("OS: {}", self.os_name),
};
let app_version_information = self
.app_version
.as_ref()
.map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel));
let app_version_information =
format!("Zed: v{} ({})", self.app_version, self.release_channel);
let system_specs = [
app_version_information,
Some(os_information),
Some(format!("Memory: {}", human_bytes(self.memory as f64))),
Some(format!("Architecture: {}", self.architecture)),
os_information,
format!("Memory: {}", human_bytes(self.memory as f64)),
format!("Architecture: {}", self.architecture),
]
.into_iter()
.flatten()
.collect::<Vec<String>>()
.join("\n");

View File

@ -11,7 +11,7 @@ use crate::{
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
SharedString, Size, Task, TaskLabel, WindowContext,
};
use anyhow::{anyhow, Result};
use anyhow::Result;
use async_task::Runnable;
use futures::channel::oneshot;
use parking::Unparker;
@ -23,11 +23,10 @@ use std::hash::{Hash, Hasher};
use std::time::Duration;
use std::{
any::Any,
fmt::{self, Debug, Display},
fmt::{self, Debug},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
str::FromStr,
sync::Arc,
};
use uuid::Uuid;
@ -39,6 +38,7 @@ pub(crate) use mac::*;
#[cfg(any(test, feature = "test-support"))]
pub(crate) use test::*;
use time::UtcOffset;
pub use util::SemanticVersion;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
@ -697,45 +697,6 @@ impl Default for CursorStyle {
}
}
/// A datastructure representing a semantic version number
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SemanticVersion {
major: usize,
minor: usize,
patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {

View File

@ -1,9 +1,9 @@
use gpui::{AppContext, Global};
use gpui::{AppContext, Global, SemanticVersion};
use once_cell::sync::Lazy;
use std::env;
#[doc(hidden)]
pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
Lazy::new(|| {
env::var("ZED_RELEASE_CHANNEL")
.unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
@ -11,6 +11,7 @@ pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
} else {
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
};
#[doc(hidden)]
pub static RELEASE_CHANNEL: Lazy<ReleaseChannel> =
Lazy::new(|| match RELEASE_CHANNEL_NAME.as_str() {
@ -39,6 +40,29 @@ impl AppCommitSha {
}
}
struct GlobalAppVersion(SemanticVersion);
impl Global for GlobalAppVersion {}
pub struct AppVersion;
impl AppVersion {
pub fn init(pkg_version: &str, cx: &mut AppContext) {
let version = if let Some(from_env) = env::var("ZED_APP_VERSION").ok() {
from_env.parse().expect("invalid ZED_APP_VERSION")
} else {
cx.app_metadata()
.app_version
.unwrap_or_else(|| pkg_version.parse().expect("invalid version in Cargo.toml"))
};
cx.set_global(GlobalAppVersion(version))
}
pub fn global(cx: &AppContext) -> SemanticVersion {
cx.global::<GlobalAppVersion>().0
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum ReleaseChannel {
#[default]
@ -52,11 +76,12 @@ struct GlobalReleaseChannel(ReleaseChannel);
impl Global for GlobalReleaseChannel {}
impl ReleaseChannel {
pub fn init(cx: &mut AppContext) {
cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
}
pub fn init(pkg_version: &str, cx: &mut AppContext) {
AppVersion::init(pkg_version, cx);
cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
}
impl ReleaseChannel {
pub fn global(cx: &AppContext) -> Self {
cx.global::<GlobalReleaseChannel>().0
}

View File

@ -210,7 +210,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
.get(&*release_channel::RELEASE_CHANNEL_NAME)
.get(&*release_channel::RELEASE_CHANNEL.dev_name())
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)
@ -543,7 +543,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
.get(&*release_channel::RELEASE_CHANNEL_NAME)
.get(&*release_channel::RELEASE_CHANNEL.dev_name())
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)

View File

@ -0,0 +1,46 @@
use std::{
fmt::{self, Display},
str::FromStr,
};
use anyhow::{anyhow, Result};
use serde::Serialize;
/// A datastructure representing a semantic version number
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SemanticVersion {
pub major: usize,
pub minor: usize,
pub patch: usize,
}
impl FromStr for SemanticVersion {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut components = s.trim().split('.');
let major = components
.next()
.ok_or_else(|| anyhow!("missing major version number"))?
.parse()?;
let minor = components
.next()
.ok_or_else(|| anyhow!("missing minor version number"))?
.parse()?;
let patch = components
.next()
.ok_or_else(|| anyhow!("missing patch version number"))?
.parse()?;
Ok(Self {
major,
minor,
patch,
})
}
}
impl Display for SemanticVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}

View File

@ -3,6 +3,7 @@ pub mod fs;
pub mod github;
pub mod http;
pub mod paths;
mod semantic_version;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@ -10,6 +11,7 @@ pub use backtrace::Backtrace;
use futures::Future;
use lazy_static::lazy_static;
use rand::{seq::SliceRandom, Rng};
pub use semantic_version::SemanticVersion;
use std::{
borrow::Cow,
cmp::{self, Ordering},

View File

@ -120,7 +120,7 @@ fn main() {
});
app.run(move |cx| {
ReleaseChannel::init(cx);
release_channel::init(env!("CARGO_PKG_VERSION"), cx);
if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
}
@ -608,9 +608,13 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
std::process::exit(-1);
}
let app_version = client::ZED_APP_VERSION
.or(app_metadata.app_version)
.map_or("dev".to_string(), |v| v.to_string());
let app_version = if let Some(version) = app_metadata.app_version {
version.to_string()
} else {
option_env!("CARGO_PKG_VERSION")
.unwrap_or("dev")
.to_string()
};
let backtrace = Backtrace::new();
let mut backtrace = backtrace
@ -639,7 +643,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
file: location.file().into(),
line: location.line(),
}),
app_version: app_version.clone(),
app_version: app_version.to_string(),
release_channel: RELEASE_CHANNEL.display_name().into(),
os_name: app_metadata.os_name.into(),
os_version: app_metadata