Release Notes: - Added the ability to set settings per-release stage - Added a `"server_url"` setting
Unified · Split
| @@ -9025,6 +9025,7 @@ dependencies = [ · 9024 unmodified lines | ||
| 9025 | 9025 | "isahc", |
| 9026 | 9026 | "lazy_static", |
| 9027 | 9027 | "log", |
| 9028 | + "parking_lot 0.11.2", | |
| 9028 | 9029 | "rand 0.8.5", |
| 9029 | 9030 | "rust-embed", |
| 9030 | 9031 | "serde", |
| @@ -503,5 +503,28 @@ · 502 unmodified lines | ||
| 503 | 503 | // } |
| 504 | 504 | // } |
| 505 | 505 | // } |
| 506 | + }, | |
| 507 | + // The server to connect to. If the environment variable | |
| 508 | + // ZED_SERVER_URL is set, it will override this setting. | |
| 509 | + "server_url": "https://zed.dev", | |
| 510 | + // Settings overrides to use when using Zed Preview. | |
| 511 | + // Mostly useful for developers who are managing multiple instances of Zed. | |
| 512 | + "preview": { | |
| 513 | + // "theme": "Andromeda" | |
| 514 | + }, | |
| 515 | + // Settings overrides to use when using Zed Nightly. | |
| 516 | + // Mostly useful for developers who are managing multiple instances of Zed. | |
| 517 | + "nightly": { | |
| 518 | + // "theme": "Andromeda" | |
| 519 | + }, | |
| 520 | + // Settings overrides to use when using Zed Stable. | |
| 521 | + // Mostly useful for developers who are managing multiple instances of Zed. | |
| 522 | + "stable": { | |
| 523 | + // "theme": "Andromeda" | |
| 524 | + }, | |
| 525 | + // Settings overrides to use when using Zed Stable. | |
| 526 | + // Mostly useful for developers who are managing multiple instances of Zed. | |
| 527 | + "dev": { | |
| 528 | + // "theme": "Andromeda" | |
| 506 | 529 | } |
| 507 | 530 | } |
| @@ -25,8 +25,11 @@ use std::{ · 24 unmodified lines | ||
| 25 | 25 | time::Duration, |
| 26 | 26 | }; |
| 27 | 27 | use update_notification::UpdateNotification; |
| 28 | −use util::channel::{AppCommitSha, ReleaseChannel}; | |
| 29 | 28 | use util::http::HttpClient; |
| 29 | +use util::{ | |
| 30 | + channel::{AppCommitSha, ReleaseChannel}, | |
| 31 | + http::ZedHttpClient, | |
| 32 | +}; | |
| 30 | 33 | use workspace::Workspace; |
| 31 | 34 | |
| 32 | 35 | const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; |
| @@ -54,9 +57,8 @@ pub enum AutoUpdateStatus { · 21 unmodified lines | ||
| 54 | 57 | pub struct AutoUpdater { |
| 55 | 58 | status: AutoUpdateStatus, |
| 56 | 59 | current_version: SemanticVersion, |
| 57 | − http_client: Arc<dyn HttpClient>, | |
| 60 | + http_client: Arc<ZedHttpClient>, | |
| 58 | 61 | pending_poll: Option<Task<Option<()>>>, |
| 59 | − server_url: String, | |
| 60 | 62 | } |
| 61 | 63 | |
| 62 | 64 | #[derive(Deserialize)] |
| @@ -92,7 +94,7 @@ impl Settings for AutoUpdateSetting { · 29 unmodified lines | ||
| 92 | 94 | } |
| 93 | 95 | } |
| 94 | 96 | |
| 95 | −pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) { | |
| 97 | +pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) { | |
| 96 | 98 | AutoUpdateSetting::register(cx); |
| 97 | 99 | |
| 98 | 100 | cx.observe_new_views(|workspace: &mut Workspace, _cx| { |
| @@ -106,7 +108,7 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo · 7 unmodified lines | ||
| 106 | 108 | |
| 107 | 109 | if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) { |
| 108 | 110 | let auto_updater = cx.new_model(|cx| { |
| 109 | − let updater = AutoUpdater::new(version, http_client, server_url); | |
| 111 | + let updater = AutoUpdater::new(version, http_client); | |
| 110 | 112 | |
| 111 | 113 | let mut update_subscription = AutoUpdateSetting::get_global(cx) |
| 112 | 114 | .0 |
| @@ -151,10 +153,11 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( · 38 unmodified lines | ||
| 151 | 153 | ReleaseChannel::Stable | ReleaseChannel::Preview |
| 152 | 154 | ) { |
| 153 | 155 | let auto_updater = auto_updater.read(cx); |
| 154 | − let server_url = &auto_updater.server_url; | |
| 155 | 156 | let release_channel = release_channel.dev_name(); |
| 156 | 157 | let current_version = auto_updater.current_version; |
| 157 | − let url = format!("{server_url}/releases/{release_channel}/{current_version}"); | |
| 158 | + let url = &auto_updater | |
| 159 | + .http_client | |
| 160 | + .zed_url(&format!("/releases/{release_channel}/{current_version}")); | |
| 158 | 161 | cx.open_url(&url); |
| 159 | 162 | } |
| 160 | 163 | |
| @@ -191,16 +194,11 @@ impl AutoUpdater { · 30 unmodified lines | ||
| 191 | 194 | cx.default_global::<Option<Model<Self>>>().clone() |
| 192 | 195 | } |
| 193 | 196 | |
| 194 | − fn new( | |
| 195 | − current_version: SemanticVersion, | |
| 196 | − http_client: Arc<dyn HttpClient>, | |
| 197 | − server_url: String, | |
| 198 | − ) -> Self { | |
| 197 | + fn new(current_version: SemanticVersion, http_client: Arc<ZedHttpClient>) -> Self { | |
| 199 | 198 | Self { |
| 200 | 199 | status: AutoUpdateStatus::Idle, |
| 201 | 200 | current_version, |
| 202 | 201 | http_client, |
| 203 | − server_url, | |
| 204 | 202 | pending_poll: None, |
| 205 | 203 | } |
| 206 | 204 | } |
| @@ -246,18 +244,14 @@ impl AutoUpdater { · 39 unmodified lines | ||
| 246 | 244 | } |
| 247 | 245 | |
| 248 | 246 | async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> { |
| 249 | − let (client, server_url, current_version) = this.read_with(&cx, |this, _| { | |
| 250 | − ( | |
| 251 | − this.http_client.clone(), | |
| 252 | − this.server_url.clone(), | |
| 253 | − this.current_version, | |
| 254 | − ) | |
| 247 | + let (client, current_version) = this.read_with(&cx, |this, _| { | |
| 248 | + (this.http_client.clone(), this.current_version) | |
| 255 | 249 | })?; |
| 256 | 250 | |
| 257 | − let mut url_string = format!( | |
| 258 | − "{server_url}/api/releases/latest?asset=Zed.dmg&os={}&arch={}", | |
| 251 | + let mut url_string = client.zed_url(&format!( | |
| 252 | + "/api/releases/latest?asset=Zed.dmg&os={}&arch={}", | |
| 259 | 253 | OS, ARCH |
| 260 | − ); | |
| 254 | + )); | |
| 261 | 255 | cx.update(|cx| { |
| 262 | 256 | if let Some(param) = cx |
| 263 | 257 | .try_global::<ReleaseChannel>() |
| @@ -329,6 +329,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) { · 328 unmodified lines | ||
| 329 | 329 | fn init_test(cx: &mut AppContext) -> Model<ChannelStore> { |
| 330 | 330 | let settings_store = SettingsStore::test(cx); |
| 331 | 331 | cx.set_global(settings_store); |
| 332 | + client::init_settings(cx); | |
| 332 | 333 | |
| 333 | 334 | let http = FakeHttpClient::with_404_response(); |
| 334 | 335 | let client = Client::new(http.clone(), cx); |
| @@ -26,7 +26,7 @@ use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, Requ · 25 unmodified lines | ||
| 26 | 26 | use schemars::JsonSchema; |
| 27 | 27 | use serde::{Deserialize, Serialize}; |
| 28 | 28 | use serde_json; |
| 29 | −use settings::Settings; | |
| 29 | +use settings::{Settings, SettingsStore}; | |
| 30 | 30 | use std::{ |
| 31 | 31 | any::TypeId, |
| 32 | 32 | collections::HashMap, |
| @@ -41,8 +41,8 @@ use std::{ · 8 unmodified lines | ||
| 41 | 41 | use telemetry::Telemetry; |
| 42 | 42 | use thiserror::Error; |
| 43 | 43 | use url::Url; |
| 44 | −use util::channel::ReleaseChannel; | |
| 45 | 44 | use util::http::HttpClient; |
| 45 | +use util::{channel::ReleaseChannel, http::ZedHttpClient}; | |
| 46 | 46 | use util::{ResultExt, TryFutureExt}; |
| 47 | 47 | |
| 48 | 48 | pub use rpc::*; |
| @@ -50,9 +50,8 @@ pub use telemetry::Event; · 1 unmodified line | ||
| 50 | 50 | pub use user::*; |
| 51 | 51 | |
| 52 | 52 | lazy_static! { |
| 53 | − pub static ref ZED_SERVER_URL: String = | |
| 54 | − std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); | |
| 55 | − pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok(); | |
| 53 | + static ref ZED_SERVER_URL: Option<String> = std::env::var("ZED_SERVER_URL").ok(); | |
| 54 | + static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok(); | |
| 56 | 55 | pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE") |
| 57 | 56 | .ok() |
| 58 | 57 | .and_then(|s| if s.is_empty() { None } else { Some(s) }); |
| @@ -73,13 +72,45 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); · 14 unmodified lines | ||
| 73 | 72 | |
| 74 | 73 | actions!(client, [SignIn, SignOut, Reconnect]); |
| 75 | 74 | |
| 75 | +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] | |
| 76 | +pub struct ClientSettingsContent { | |
| 77 | + server_url: Option<String>, | |
| 78 | +} | |
| 79 | + | |
| 80 | +#[derive(Deserialize)] | |
| 81 | +pub struct ClientSettings { | |
| 82 | + pub server_url: String, | |
| 83 | +} | |
| 84 | + | |
| 85 | +impl Settings for ClientSettings { | |
| 86 | + const KEY: Option<&'static str> = None; | |
| 87 | + | |
| 88 | + type FileContent = ClientSettingsContent; | |
| 89 | + | |
| 90 | + fn load( | |
| 91 | + default_value: &Self::FileContent, | |
| 92 | + user_values: &[&Self::FileContent], | |
| 93 | + _: &mut AppContext, | |
| 94 | + ) -> Result<Self> | |
| 95 | + where | |
| 96 | + Self: Sized, | |
| 97 | + { | |
| 98 | + let mut result = Self::load_via_json_merge(default_value, user_values)?; | |
| 99 | + if let Some(server_url) = &*ZED_SERVER_URL { | |
| 100 | + result.server_url = server_url.clone() | |
| 101 | + } | |
| 102 | + Ok(result) | |
| 103 | + } | |
| 104 | +} | |
| 105 | + | |
| 76 | 106 | pub fn init_settings(cx: &mut AppContext) { |
| 77 | 107 | TelemetrySettings::register(cx); |
| 108 | + cx.update_global(|store: &mut SettingsStore, cx| { | |
| 109 | + store.register_setting::<ClientSettings>(cx); | |
| 110 | + }); | |
| 78 | 111 | } |
| 79 | 112 | |
| 80 | 113 | pub fn init(client: &Arc<Client>, cx: &mut AppContext) { |
| 81 | − init_settings(cx); | |
| 82 | − | |
| 83 | 114 | let client = Arc::downgrade(client); |
| 84 | 115 | cx.on_action({ |
| 85 | 116 | let client = client.clone(); |
| @@ -121,7 +152,7 @@ pub fn init(client: &Arc<Client>, cx: &mut AppContext) { · 35 unmodified lines | ||
| 121 | 152 | pub struct Client { |
| 122 | 153 | id: AtomicU64, |
| 123 | 154 | peer: Arc<Peer>, |
| 124 | − http: Arc<dyn HttpClient>, | |
| 155 | + http: Arc<ZedHttpClient>, | |
| 125 | 156 | telemetry: Arc<Telemetry>, |
| 126 | 157 | state: RwLock<ClientState>, |
| 127 | 158 | |
| @@ -390,8 +421,8 @@ impl settings::Settings for TelemetrySettings { · 262 unmodified lines | ||
| 390 | 421 | } |
| 391 | 422 | |
| 392 | 423 | impl Client { |
| 393 | − pub fn new(http: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { | |
| 394 | − Arc::new(Self { | |
| 424 | + pub fn new(http: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> { | |
| 425 | + let client = Arc::new(Self { | |
| 395 | 426 | id: AtomicU64::new(0), |
| 396 | 427 | peer: Peer::new(0), |
| 397 | 428 | telemetry: Telemetry::new(http.clone(), cx), |
| @@ -402,14 +433,16 @@ impl Client { · 4 unmodified lines | ||
| 402 | 433 | authenticate: Default::default(), |
| 403 | 434 | #[cfg(any(test, feature = "test-support"))] |
| 404 | 435 | establish_connection: Default::default(), |
| 405 | − }) | |
| 436 | + }); | |
| 437 | + | |
| 438 | + client | |
| 406 | 439 | } |
| 407 | 440 | |
| 408 | 441 | pub fn id(&self) -> u64 { |
| 409 | 442 | self.id.load(std::sync::atomic::Ordering::SeqCst) |
| 410 | 443 | } |
| 411 | 444 | |
| 412 | − pub fn http_client(&self) -> Arc<dyn HttpClient> { | |
| 445 | + pub fn http_client(&self) -> Arc<ZedHttpClient> { | |
| 413 | 446 | self.http.clone() |
| 414 | 447 | } |
| 415 | 448 | |
| @@ -925,14 +958,14 @@ impl Client { · 509 unmodified lines | ||
| 925 | 958 | } |
| 926 | 959 | |
| 927 | 960 | async fn get_rpc_url( |
| 928 | − http: Arc<dyn HttpClient>, | |
| 961 | + http: Arc<ZedHttpClient>, | |
| 929 | 962 | release_channel: Option<ReleaseChannel>, |
| 930 | 963 | ) -> Result<Url> { |
| 931 | 964 | if let Some(url) = &*ZED_RPC_URL { |
| 932 | 965 | return Url::parse(url).context("invalid rpc url"); |
| 933 | 966 | } |
| 934 | 967 | |
| 935 | − let mut url = format!("{}/rpc", *ZED_SERVER_URL); | |
| 968 | + let mut url = http.zed_url("/rpc"); | |
| 936 | 969 | if let Some(preview_param) = |
| 937 | 970 | release_channel.and_then(|channel| channel.release_query_param()) |
| 938 | 971 | { |
| @@ -1053,10 +1086,10 @@ impl Client { · 114 unmodified lines | ||
| 1053 | 1086 | |
| 1054 | 1087 | // Open the Zed sign-in page in the user's browser, with query parameters that indicate |
| 1055 | 1088 | // that the user is signing in from a Zed app running on the same device. |
| 1056 | − let mut url = format!( | |
| 1057 | − "{}/native_app_signin?native_app_port={}&native_app_public_key={}", | |
| 1058 | − *ZED_SERVER_URL, port, public_key_string | |
| 1059 | − ); | |
| 1089 | + let mut url = http.zed_url(&format!( | |
| 1090 | + "/native_app_signin?native_app_port={}&native_app_public_key={}", | |
| 1091 | + port, public_key_string | |
| 1092 | + )); | |
| 1060 | 1093 | |
| 1061 | 1094 | if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { |
| 1062 | 1095 | log::info!("impersonating user @{}", impersonate_login); |
| @@ -1088,7 +1121,7 @@ impl Client { · 25 unmodified lines | ||
| 1088 | 1121 | } |
| 1089 | 1122 | |
| 1090 | 1123 | let post_auth_url = |
| 1091 | − format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); | |
| 1124 | + http.zed_url("/native_app_signin_succeeded"); | |
| 1092 | 1125 | req.respond( |
| 1093 | 1126 | tiny_http::Response::empty(302).with_header( |
| 1094 | 1127 | tiny_http::Header::from_bytes( |
| @@ -1130,7 +1163,7 @@ impl Client { · 35 unmodified lines | ||
| 1130 | 1163 | } |
| 1131 | 1164 | |
| 1132 | 1165 | async fn authenticate_as_admin( |
| 1133 | − http: Arc<dyn HttpClient>, | |
| 1166 | + http: Arc<ZedHttpClient>, | |
| 1134 | 1167 | login: String, |
| 1135 | 1168 | mut api_token: String, |
| 1136 | 1169 | ) -> Result<Credentials> { |
| @@ -1351,7 +1384,7 @@ async fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credenti · 214 unmodified lines | ||
| 1351 | 1384 | } |
| 1352 | 1385 | |
| 1353 | 1386 | let (user_id, access_token) = cx |
| 1354 | − .update(|cx| cx.read_credentials(&ZED_SERVER_URL)) | |
| 1387 | + .update(|cx| cx.read_credentials(&ClientSettings::get_global(cx).server_url)) | |
| 1355 | 1388 | .log_err()? |
| 1356 | 1389 | .await |
| 1357 | 1390 | .log_err()??; |
| @@ -1368,7 +1401,7 @@ async fn write_credentials_to_keychain( · 10 unmodified lines | ||
| 1368 | 1401 | ) -> Result<()> { |
| 1369 | 1402 | cx.update(move |cx| { |
| 1370 | 1403 | cx.write_credentials( |
| 1371 | − &ZED_SERVER_URL, | |
| 1404 | + &ClientSettings::get_global(cx).server_url, | |
| 1372 | 1405 | &credentials.user_id.to_string(), |
| 1373 | 1406 | credentials.access_token.as_bytes(), |
| 1374 | 1407 | ) |
| @@ -1377,7 +1410,7 @@ async fn write_credentials_to_keychain( · 2 unmodified lines | ||
| 1377 | 1410 | } |
| 1378 | 1411 | |
| 1379 | 1412 | async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { |
| 1380 | − cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? | |
| 1413 | + cx.update(move |cx| cx.delete_credentials(&ClientSettings::get_global(cx).server_url))? | |
| 1381 | 1414 | .await |
| 1382 | 1415 | } |
| 1383 | 1416 | |
| @@ -1684,6 +1717,7 @@ mod tests { · 300 unmodified lines | ||
| 1684 | 1717 | cx.update(|cx| { |
| 1685 | 1718 | let settings_store = SettingsStore::test(cx); |
| 1686 | 1719 | cx.set_global(settings_store); |
| 1720 | + init_settings(cx); | |
| 1687 | 1721 | }); |
| 1688 | 1722 | } |
| 1689 | 1723 | } |
| @@ -1,10 +1,9 @@ | ||
| 1 | 1 | mod event_coalescer; |
| 2 | 2 | |
| 3 | −use crate::{TelemetrySettings, ZED_SERVER_URL}; | |
| 3 | +use crate::TelemetrySettings; | |
| 4 | 4 | use chrono::{DateTime, Utc}; |
| 5 | 5 | use futures::Future; |
| 6 | 6 | use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; |
| 7 | −use lazy_static::lazy_static; | |
| 8 | 7 | use parking_lot::Mutex; |
| 9 | 8 | use serde::Serialize; |
| 10 | 9 | use settings::{Settings, SettingsStore}; |
| @@ -13,7 +12,7 @@ use sysinfo::{ · 2 unmodified lines | ||
| 13 | 12 | CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt, |
| 14 | 13 | }; |
| 15 | 14 | use tempfile::NamedTempFile; |
| 16 | −use util::http::HttpClient; | |
| 15 | +use util::http::{HttpClient, ZedHttpClient}; | |
| 17 | 16 | #[cfg(not(debug_assertions))] |
| 18 | 17 | use util::ResultExt; |
| 19 | 18 | use util::{channel::ReleaseChannel, TryFutureExt}; |
| @@ -21,7 +20,7 @@ use util::{channel::ReleaseChannel, TryFutureExt}; · 1 unmodified line | ||
| 21 | 20 | use self::event_coalescer::EventCoalescer; |
| 22 | 21 | |
| 23 | 22 | pub struct Telemetry { |
| 24 | − http_client: Arc<dyn HttpClient>, | |
| 23 | + http_client: Arc<ZedHttpClient>, | |
| 25 | 24 | executor: BackgroundExecutor, |
| 26 | 25 | state: Arc<Mutex<TelemetryState>>, |
| 27 | 26 | } |
| @@ -43,12 +42,6 @@ struct TelemetryState { · 15 unmodified lines | ||
| 43 | 42 | max_queue_size: usize, |
| 44 | 43 | } |
| 45 | 44 | |
| 46 | −const EVENTS_URL_PATH: &'static str = "/api/events"; | |
| 47 | − | |
| 48 | −lazy_static! { | |
| 49 | − static ref EVENTS_URL: String = format!("{}{}", *ZED_SERVER_URL, EVENTS_URL_PATH); | |
| 50 | −} | |
| 51 | − | |
| 52 | 45 | #[derive(Serialize, Debug)] |
| 53 | 46 | struct EventRequestBody { |
| 54 | 47 | installation_id: Option<Arc<str>>, |
| @@ -149,7 +142,7 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1); · 94 unmodified lines | ||
| 149 | 142 | const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5); |
| 150 | 143 | |
| 151 | 144 | impl Telemetry { |
| 152 | − pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { | |
| 145 | + pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> { | |
| 153 | 146 | let release_channel = cx |
| 154 | 147 | .try_global::<ReleaseChannel>() |
| 155 | 148 | .map(|release_channel| release_channel.display_name()); |
| @@ -548,7 +541,7 @@ impl Telemetry { · 392 unmodified lines | ||
| 548 | 541 | } |
| 549 | 542 | |
| 550 | 543 | this.http_client |
| 551 | − .post_json(EVENTS_URL.as_str(), json_bytes.into()) | |
| 544 | + .post_json(&this.http_client.zed_url("/api/events"), json_bytes.into()) | |
| 552 | 545 | .await?; |
| 553 | 546 | anyhow::Ok(()) |
| 554 | 547 | } |
| @@ -153,6 +153,7 @@ impl TestServer { · 152 unmodified lines | ||
| 153 | 153 | } |
| 154 | 154 | let settings = SettingsStore::test(cx); |
| 155 | 155 | cx.set_global(settings); |
| 156 | + client::init_settings(cx); | |
| 156 | 157 | }); |
| 157 | 158 | |
| 158 | 159 | let http = FakeHttpClient::with_404_response(); |
| @@ -2,7 +2,7 @@ use std::{ops::RangeInclusive, sync::Arc, time::Duration}; · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | use anyhow::{anyhow, bail}; |
| 4 | 4 | use bitflags::bitflags; |
| 5 | −use client::{Client, ZED_SERVER_URL}; | |
| 5 | +use client::Client; | |
| 6 | 6 | use db::kvp::KEY_VALUE_STORE; |
| 7 | 7 | use editor::{Editor, EditorEvent}; |
| 8 | 8 | use futures::AsyncReadExt; |
| @@ -16,7 +16,7 @@ use project::Project; · 7 unmodified lines | ||
| 16 | 16 | use regex::Regex; |
| 17 | 17 | use serde_derive::Serialize; |
| 18 | 18 | use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; |
| 19 | −use util::ResultExt; | |
| 19 | +use util::{http::HttpClient, ResultExt}; | |
| 20 | 20 | use workspace::{ModalView, Toast, Workspace}; |
| 21 | 21 | |
| 22 | 22 | use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo}; |
| @@ -293,12 +293,12 @@ impl FeedbackModal { · 270 unmodified lines | ||
| 293 | 293 | } |
| 294 | 294 | } |
| 295 | 295 | |
| 296 | − let feedback_endpoint = format!("{}/api/feedback", *ZED_SERVER_URL); | |
| 297 | 296 | let telemetry = zed_client.telemetry(); |
| 298 | 297 | let metrics_id = telemetry.metrics_id(); |
| 299 | 298 | let installation_id = telemetry.installation_id(); |
| 300 | 299 | let is_staff = telemetry.is_staff(); |
| 301 | 300 | let http_client = zed_client.http_client(); |
| 301 | + let feedback_endpoint = http_client.zed_url("/api/feedback"); | |
| 302 | 302 | let request = FeedbackRequestBody { |
| 303 | 303 | feedback_text: &feedback_text, |
| 304 | 304 | email, |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | use anyhow::{anyhow, Context, Result}; |
| 2 | 2 | use collections::{btree_map, hash_map, BTreeMap, HashMap}; |
| 3 | −use gpui::AppContext; | |
| 3 | +use gpui::{AppContext, AsyncAppContext}; | |
| 4 | 4 | use lazy_static::lazy_static; |
| 5 | 5 | use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; |
| 6 | 6 | use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; |
| @@ -13,7 +13,9 @@ use std::{ · 6 unmodified lines | ||
| 13 | 13 | str, |
| 14 | 14 | sync::Arc, |
| 15 | 15 | }; |
| 16 | −use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; | |
| 16 | +use util::{ | |
| 17 | + channel::RELEASE_CHANNEL_NAME, merge_non_null_json_value_into, RangeExt, ResultExt as _, | |
| 18 | +}; | |
| 17 | 19 | |
| 18 | 20 | /// A value that can be defined as a user setting. |
| 19 | 21 | /// |
| @@ -102,6 +104,14 @@ pub trait Settings: 'static + Send + Sync { · 82 unmodified lines | ||
| 102 | 104 | cx.global::<SettingsStore>().get(None) |
| 103 | 105 | } |
| 104 | 106 | |
| 107 | + #[track_caller] | |
| 108 | + fn try_read_global<'a, R>(cx: &'a AsyncAppContext, f: impl FnOnce(&Self) -> R) -> Option<R> | |
| 109 | + where | |
| 110 | + Self: Sized, | |
| 111 | + { | |
| 112 | + cx.try_read_global(|s: &SettingsStore, _| f(s.get(None))) | |
| 113 | + } | |
| 114 | + | |
| 105 | 115 | #[track_caller] |
| 106 | 116 | fn override_global<'a>(settings: Self, cx: &'a mut AppContext) |
| 107 | 117 | where |
| @@ -197,6 +207,15 @@ impl SettingsStore { · 89 unmodified lines | ||
| 197 | 207 | user_values_stack = vec![user_settings]; |
| 198 | 208 | } |
| 199 | 209 | |
| 210 | + if let Some(release_settings) = &self.raw_user_settings.get(&*RELEASE_CHANNEL_NAME) { | |
| 211 | + if let Some(release_settings) = setting_value | |
| 212 | + .deserialize_setting(&release_settings) | |
| 213 | + .log_err() | |
| 214 | + { | |
| 215 | + user_values_stack.push(release_settings); | |
| 216 | + } | |
| 217 | + } | |
| 218 | + | |
| 200 | 219 | if let Some(setting) = setting_value |
| 201 | 220 | .load_setting(&default_settings, &user_values_stack, cx) |
| 202 | 221 | .context("A default setting must be added to the `default.json` file") |
| @@ -484,6 +503,15 @@ impl SettingsStore { · 281 unmodified lines | ||
| 484 | 503 | } |
| 485 | 504 | } |
| 486 | 505 | |
| 506 | + for release_stage in ["dev", "nightly", "stable", "preview"] { | |
| 507 | + let schema = combined_schema.schema.clone(); | |
| 508 | + combined_schema | |
| 509 | + .schema | |
| 510 | + .object() | |
| 511 | + .properties | |
| 512 | + .insert(release_stage.to_string(), schema.into()); | |
| 513 | + } | |
| 514 | + | |
| 487 | 515 | serde_json::to_value(&combined_schema).unwrap() |
| 488 | 516 | } |
| 489 | 517 | |
| @@ -509,6 +537,16 @@ impl SettingsStore { · 19 unmodified lines | ||
| 509 | 537 | paths_stack.push(None); |
| 510 | 538 | } |
| 511 | 539 | |
| 540 | + if let Some(release_settings) = &self.raw_user_settings.get(&*RELEASE_CHANNEL_NAME) { | |
| 541 | + if let Some(release_settings) = setting_value | |
| 542 | + .deserialize_setting(&release_settings) | |
| 543 | + .log_err() | |
| 544 | + { | |
| 545 | + user_settings_stack.push(release_settings); | |
| 546 | + paths_stack.push(None); | |
| 547 | + } | |
| 548 | + } | |
| 549 | + | |
| 512 | 550 | // If the global settings file changed, reload the global value for the field. |
| 513 | 551 | if changed_local_path.is_none() { |
| 514 | 552 | if let Some(value) = setting_value |
| @@ -30,6 +30,7 @@ serde_json.workspace = true · 29 unmodified lines | ||
| 30 | 30 | git2 = { workspace = true, optional = true } |
| 31 | 31 | dirs = "3.0" |
| 32 | 32 | take-until = "0.2.0" |
| 33 | +parking_lot.workspace = true | |
| 33 | 34 | |
| 34 | 35 | [target.'cfg(windows)'.dependencies] |
| 35 | 36 | tendril = "0.4.3" |
| @@ -6,12 +6,49 @@ pub use isahc::{ · 5 unmodified lines | ||
| 6 | 6 | Error, |
| 7 | 7 | }; |
| 8 | 8 | pub use isahc::{AsyncBody, Request, Response}; |
| 9 | +use parking_lot::Mutex; | |
| 9 | 10 | use smol::future::FutureExt; |
| 10 | 11 | #[cfg(feature = "test-support")] |
| 11 | 12 | use std::fmt; |
| 12 | 13 | use std::{sync::Arc, time::Duration}; |
| 13 | 14 | pub use url::Url; |
| 14 | 15 | |
| 16 | +pub struct ZedHttpClient { | |
| 17 | + pub zed_host: Mutex<String>, | |
| 18 | + client: Box<dyn HttpClient>, | |
| 19 | +} | |
| 20 | + | |
| 21 | +impl ZedHttpClient { | |
| 22 | + pub fn zed_url(&self, path: &str) -> String { | |
| 23 | + format!("{}{}", self.zed_host.lock(), path) | |
| 24 | + } | |
| 25 | +} | |
| 26 | + | |
| 27 | +impl HttpClient for Arc<ZedHttpClient> { | |
| 28 | + fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> { | |
| 29 | + self.client.send(req) | |
| 30 | + } | |
| 31 | +} | |
| 32 | + | |
| 33 | +impl HttpClient for ZedHttpClient { | |
| 34 | + fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>> { | |
| 35 | + self.client.send(req) | |
| 36 | + } | |
| 37 | +} | |
| 38 | + | |
| 39 | +pub fn zed_client(zed_host: &str) -> Arc<ZedHttpClient> { | |
| 40 | + Arc::new(ZedHttpClient { | |
| 41 | + zed_host: Mutex::new(zed_host.to_string()), | |
| 42 | + client: Box::new( | |
| 43 | + isahc::HttpClient::builder() | |
| 44 | + .connect_timeout(Duration::from_secs(5)) | |
| 45 | + .low_speed_timeout(100, Duration::from_secs(5)) | |
| 46 | + .build() | |
| 47 | + .unwrap(), | |
| 48 | + ), | |
| 49 | + }) | |
| 50 | +} | |
| 51 | + | |
| 15 | 52 | pub trait HttpClient: Send + Sync { |
| 16 | 53 | fn send(&self, req: Request<AsyncBody>) -> BoxFuture<Result<Response<AsyncBody>, Error>>; |
| 17 | 54 | |
| @@ -81,17 +118,20 @@ pub struct FakeHttpClient { · 63 unmodified lines | ||
| 81 | 118 | |
| 82 | 119 | #[cfg(feature = "test-support")] |
| 83 | 120 | impl FakeHttpClient { |
| 84 | − pub fn create<Fut, F>(handler: F) -> Arc<dyn HttpClient> | |
| 121 | + pub fn create<Fut, F>(handler: F) -> Arc<ZedHttpClient> | |
| 85 | 122 | where |
| 86 | 123 | Fut: 'static + Send + futures::Future<Output = Result<Response<AsyncBody>, Error>>, |
| 87 | 124 | F: 'static + Send + Sync + Fn(Request<AsyncBody>) -> Fut, |
| 88 | 125 | { |
| 89 | − Arc::new(Self { | |
| 90 | − handler: Box::new(move |req| Box::pin(handler(req))), | |
| 126 | + Arc::new(ZedHttpClient { | |
| 127 | + zed_host: Mutex::new("http://test.example".into()), | |
| 128 | + client: Box::new(Self { | |
| 129 | + handler: Box::new(move |req| Box::pin(handler(req))), | |
| 130 | + }), | |
| 91 | 131 | }) |
| 92 | 132 | } |
| 93 | 133 | |
| 94 | − pub fn with_404_response() -> Arc<dyn HttpClient> { | |
| 134 | + pub fn with_404_response() -> Arc<ZedHttpClient> { | |
| 95 | 135 | Self::create(|_| async move { |
| 96 | 136 | Ok(Response::builder() |
| 97 | 137 | .status(404) |
| @@ -100,7 +140,7 @@ impl FakeHttpClient { · 2 unmodified lines | ||
| 100 | 140 | }) |
| 101 | 141 | } |
| 102 | 142 | |
| 103 | − pub fn with_200_response() -> Arc<dyn HttpClient> { | |
| 143 | + pub fn with_200_response() -> Arc<ZedHttpClient> { | |
| 104 | 144 | Self::create(|_| async move { |
| 105 | 145 | Ok(Response::builder() |
| 106 | 146 | .status(200) |
| @@ -42,7 +42,7 @@ use theme::{ActiveTheme, ThemeRegistry, ThemeSettings}; · 41 unmodified lines | ||
| 42 | 42 | use util::{ |
| 43 | 43 | async_maybe, |
| 44 | 44 | channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}, |
| 45 | − http::{self, HttpClient}, | |
| 45 | + http::{self, HttpClient, ZedHttpClient}, | |
| 46 | 46 | paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR}, |
| 47 | 47 | ResultExt, |
| 48 | 48 | }; |
| @@ -59,7 +59,6 @@ fn main() { · 10 unmodified lines | ||
| 59 | 59 | menu::init(); |
| 60 | 60 | zed_actions::init(); |
| 61 | 61 | |
| 62 | − let http = http::client(); | |
| 63 | 62 | init_paths(); |
| 64 | 63 | init_logger(); |
| 65 | 64 | |
| @@ -132,6 +131,9 @@ fn main() { · 66 unmodified lines | ||
| 132 | 131 | cx.set_global(store); |
| 133 | 132 | handle_settings_file_changes(user_settings_file_rx, cx); |
| 134 | 133 | handle_keymap_file_changes(user_keymap_file_rx, cx); |
| 134 | + client::init_settings(cx); | |
| 135 | + | |
| 136 | + let http = http::zed_client(&client::ClientSettings::get_global(cx).server_url); | |
| 135 | 137 | |
| 136 | 138 | let client = client::Client::new(http.clone(), cx); |
| 137 | 139 | let mut languages = LanguageRegistry::new(login_shell_env_loaded); |
| @@ -201,7 +203,20 @@ fn main() { · 63 unmodified lines | ||
| 201 | 203 | languages.set_theme(cx.theme().clone()); |
| 202 | 204 | cx.observe_global::<SettingsStore>({ |
| 203 | 205 | let languages = languages.clone(); |
| 204 | − move |cx| languages.set_theme(cx.theme().clone()) | |
| 206 | + let http = http.clone(); | |
| 207 | + let client = client.clone(); | |
| 208 | + | |
| 209 | + move |cx| { | |
| 210 | + languages.set_theme(cx.theme().clone()); | |
| 211 | + let new_host = &client::ClientSettings::get_global(cx).server_url; | |
| 212 | + let mut host = http.zed_host.lock(); | |
| 213 | + if &*host != new_host { | |
| 214 | + *host = new_host.clone(); | |
| 215 | + if client.status().borrow().is_connected() { | |
| 216 | + client.reconnect(&cx.to_async()); | |
| 217 | + } | |
| 218 | + } | |
| 219 | + } | |
| 205 | 220 | }) |
| 206 | 221 | .detach(); |
| 207 | 222 | |
| @@ -230,7 +245,7 @@ fn main() { · 22 unmodified lines | ||
| 230 | 245 | cx.set_global(Arc::downgrade(&app_state)); |
| 231 | 246 | |
| 232 | 247 | audio::init(Assets, cx); |
| 233 | − auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx); | |
| 248 | + auto_update::init(http.clone(), cx); | |
| 234 | 249 | |
| 235 | 250 | workspace::init(app_state.clone(), cx); |
| 236 | 251 | recent_projects::init(cx); |
| @@ -634,7 +649,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin · 397 unmodified lines | ||
| 634 | 649 | })); |
| 635 | 650 | } |
| 636 | 651 | |
| 637 | −fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) { | |
| 652 | +fn upload_panics_and_crashes(http: Arc<ZedHttpClient>, cx: &mut AppContext) { | |
| 638 | 653 | let telemetry_settings = *client::TelemetrySettings::get_global(cx); |
| 639 | 654 | cx.background_executor() |
| 640 | 655 | .spawn(async move { |
| @@ -650,10 +665,10 @@ fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) { · 9 unmodified lines | ||
| 650 | 665 | |
| 651 | 666 | /// upload panics to us (via zed.dev) |
| 652 | 667 | async fn upload_previous_panics( |
| 653 | − http: Arc<dyn HttpClient>, | |
| 668 | + http: Arc<ZedHttpClient>, | |
| 654 | 669 | telemetry_settings: client::TelemetrySettings, |
| 655 | 670 | ) -> Result<()> { |
| 656 | − let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); | |
| 671 | + let panic_report_url = http.zed_url("/api/panic"); | |
| 657 | 672 | let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; |
| 658 | 673 | while let Some(child) = children.next().await { |
| 659 | 674 | let child = child?; |
| @@ -717,7 +732,7 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; · 57 unmodified lines | ||
| 717 | 732 | /// upload crashes from apple's diagnostic reports to our server. |
| 718 | 733 | /// (only if telemetry is enabled) |
| 719 | 734 | async fn upload_previous_crashes( |
| 720 | − http: Arc<dyn HttpClient>, | |
| 735 | + http: Arc<ZedHttpClient>, | |
| 721 | 736 | telemetry_settings: client::TelemetrySettings, |
| 722 | 737 | ) -> Result<()> { |
| 723 | 738 | if !telemetry_settings.diagnostics { |
| @@ -728,7 +743,7 @@ async fn upload_previous_crashes( · 4 unmodified lines | ||
| 728 | 743 | .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this. |
| 729 | 744 | let mut uploaded = last_uploaded.clone(); |
| 730 | 745 | |
| 731 | − let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL); | |
| 746 | + let crash_report_url = http.zed_url("/api/crash"); | |
| 732 | 747 | |
| 733 | 748 | for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] { |
| 734 | 749 | let mut children = smol::fs::read_dir(&dir).await?; |