mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Remove 2 suffix for client, call, channel
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
9f99e58834
commit
53bdf6beb3
160
Cargo.lock
generated
160
Cargo.lock
generated
@ -305,7 +305,7 @@ dependencies = [
|
|||||||
"ai",
|
"ai",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"editor",
|
"editor",
|
||||||
@ -687,7 +687,7 @@ name = "auto_update"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"db2",
|
"db2",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
"isahc",
|
"isahc",
|
||||||
@ -1116,37 +1116,11 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "call"
|
name = "call"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-broadcast",
|
|
||||||
"audio",
|
|
||||||
"client",
|
|
||||||
"collections",
|
|
||||||
"fs",
|
|
||||||
"futures 0.3.28",
|
|
||||||
"gpui",
|
|
||||||
"language",
|
|
||||||
"live_kit_client",
|
|
||||||
"log",
|
|
||||||
"media",
|
|
||||||
"postage",
|
|
||||||
"project",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"settings",
|
|
||||||
"util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "call2"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"audio2",
|
"audio2",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"fs2",
|
"fs2",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -1236,43 +1210,6 @@ dependencies = [
|
|||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"db",
|
|
||||||
"feature_flags",
|
|
||||||
"futures 0.3.28",
|
|
||||||
"gpui",
|
|
||||||
"image",
|
|
||||||
"language",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"postage",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"rpc",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"settings",
|
|
||||||
"smallvec",
|
|
||||||
"smol",
|
|
||||||
"sum_tree",
|
|
||||||
"tempfile",
|
|
||||||
"text",
|
|
||||||
"thiserror",
|
|
||||||
"time",
|
|
||||||
"tiny_http",
|
|
||||||
"url",
|
|
||||||
"util",
|
|
||||||
"uuid 1.4.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "channel2"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"client2",
|
|
||||||
"clock",
|
|
||||||
"collections",
|
|
||||||
"db2",
|
"db2",
|
||||||
"feature_flags",
|
"feature_flags",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -1441,43 +1378,6 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client"
|
name = "client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-recursion 0.3.2",
|
|
||||||
"async-tungstenite",
|
|
||||||
"chrono",
|
|
||||||
"collections",
|
|
||||||
"db",
|
|
||||||
"feature_flags",
|
|
||||||
"futures 0.3.28",
|
|
||||||
"gpui",
|
|
||||||
"image",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"parking_lot 0.11.2",
|
|
||||||
"postage",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"rpc",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"settings",
|
|
||||||
"smol",
|
|
||||||
"sum_tree",
|
|
||||||
"sysinfo",
|
|
||||||
"tempfile",
|
|
||||||
"text",
|
|
||||||
"thiserror",
|
|
||||||
"time",
|
|
||||||
"tiny_http",
|
|
||||||
"url",
|
|
||||||
"util",
|
|
||||||
"uuid 1.4.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "client2"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion 0.3.2",
|
"async-recursion 0.3.2",
|
||||||
@ -1641,10 +1541,10 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"call2",
|
"call",
|
||||||
"channel2",
|
"channel",
|
||||||
"clap 3.2.25",
|
"clap 3.2.25",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collab_ui",
|
"collab_ui",
|
||||||
"collections",
|
"collections",
|
||||||
@ -1709,9 +1609,9 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"auto_update",
|
"auto_update",
|
||||||
"call2",
|
"call",
|
||||||
"channel2",
|
"channel",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"db2",
|
"db2",
|
||||||
@ -2433,7 +2333,7 @@ name = "diagnostics"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -2594,7 +2494,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
@ -2823,7 +2723,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"client2",
|
"client",
|
||||||
"db2",
|
"db2",
|
||||||
"editor",
|
"editor",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -4187,7 +4087,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
@ -4257,7 +4157,7 @@ name = "language_tools"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -4856,7 +4756,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"convert_case 0.6.0",
|
"convert_case 0.6.0",
|
||||||
@ -5071,8 +4971,8 @@ name = "notifications2"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"channel2",
|
"channel",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"db2",
|
"db2",
|
||||||
@ -5903,7 +5803,7 @@ name = "prettier2"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"fs2",
|
"fs2",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -6010,7 +5910,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"client2",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"copilot",
|
"copilot",
|
||||||
@ -6062,7 +5962,7 @@ name = "project_panel"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"db2",
|
"db2",
|
||||||
"editor",
|
"editor",
|
||||||
@ -7265,7 +7165,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"editor",
|
"editor",
|
||||||
"futures 0.3.28",
|
"futures 0.3.28",
|
||||||
@ -7373,7 +7273,7 @@ dependencies = [
|
|||||||
"ai",
|
"ai",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"ctor",
|
"ctor",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -8553,7 +8453,7 @@ name = "terminal_view"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"db2",
|
"db2",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"editor",
|
"editor",
|
||||||
@ -8701,7 +8601,7 @@ dependencies = [
|
|||||||
name = "theme_selector"
|
name = "theme_selector"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"client2",
|
"client",
|
||||||
"editor",
|
"editor",
|
||||||
"feature_flags",
|
"feature_flags",
|
||||||
"fs2",
|
"fs2",
|
||||||
@ -10215,7 +10115,7 @@ name = "welcome"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client2",
|
"client",
|
||||||
"db2",
|
"db2",
|
||||||
"editor",
|
"editor",
|
||||||
"fs2",
|
"fs2",
|
||||||
@ -10482,8 +10382,8 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion 1.0.5",
|
"async-recursion 1.0.5",
|
||||||
"bincode",
|
"bincode",
|
||||||
"call2",
|
"call",
|
||||||
"client2",
|
"client",
|
||||||
"collections",
|
"collections",
|
||||||
"db2",
|
"db2",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@ -10607,11 +10507,11 @@ dependencies = [
|
|||||||
"auto_update",
|
"auto_update",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"breadcrumbs",
|
"breadcrumbs",
|
||||||
"call2",
|
"call",
|
||||||
"channel2",
|
"channel",
|
||||||
"chrono",
|
"chrono",
|
||||||
"cli",
|
"cli",
|
||||||
"client2",
|
"client",
|
||||||
"collab_ui",
|
"collab_ui",
|
||||||
"collections",
|
"collections",
|
||||||
"command_palette",
|
"command_palette",
|
||||||
|
@ -8,12 +8,9 @@ members = [
|
|||||||
"crates/auto_update",
|
"crates/auto_update",
|
||||||
"crates/breadcrumbs",
|
"crates/breadcrumbs",
|
||||||
"crates/call",
|
"crates/call",
|
||||||
"crates/call2",
|
|
||||||
"crates/channel",
|
"crates/channel",
|
||||||
"crates/channel2",
|
|
||||||
"crates/cli",
|
"crates/cli",
|
||||||
"crates/client",
|
"crates/client",
|
||||||
"crates/client2",
|
|
||||||
"crates/clock",
|
"crates/clock",
|
||||||
"crates/collab",
|
"crates/collab",
|
||||||
"crates/collab2",
|
"crates/collab2",
|
||||||
|
@ -10,7 +10,7 @@ doctest = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ai = { path = "../ai" }
|
ai = { path = "../ai" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections"}
|
collections = { path = "../collections"}
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
|
@ -10,7 +10,7 @@ doctest = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
menu = { package = "menu2", path = "../menu2" }
|
menu = { package = "menu2", path = "../menu2" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
|
@ -19,34 +19,36 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
audio = { path = "../audio" }
|
audio = { package = "audio2", path = "../audio2" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
live_kit_client = { path = "../live_kit_client" }
|
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
|
||||||
fs = { path = "../fs" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
media = { path = "../media" }
|
media = { path = "../media" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
settings = { path = "../settings" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-broadcast = "0.4"
|
async-broadcast = "0.4"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
image = "0.23"
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
fs = { path = "../fs", features = ["test-support"] }
|
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
live_kit_client = { path = "../live_kit_client", features = ["test-support"] }
|
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
|
||||||
project = { path = "../project", features = ["test-support"] }
|
project = { path = "../project", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
@ -9,31 +9,25 @@ use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, Z
|
|||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Subscription, Task,
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
||||||
WeakModelHandle,
|
WeakModel,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
use room::Event;
|
||||||
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use participant::ParticipantLocation;
|
pub use participant::ParticipantLocation;
|
||||||
pub use room::Room;
|
pub use room::Room;
|
||||||
|
|
||||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||||
settings::register::<CallSettings>(cx);
|
CallSettings::register(cx);
|
||||||
|
|
||||||
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
|
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||||
cx.set_global(active_call);
|
cx.set_global(active_call);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IncomingCall {
|
|
||||||
pub room_id: u64,
|
|
||||||
pub calling_user: Arc<User>,
|
|
||||||
pub participants: Vec<Arc<User>>,
|
|
||||||
pub initial_project: Option<proto::ParticipantProject>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OneAtATime {
|
pub struct OneAtATime {
|
||||||
cancel: Option<oneshot::Sender<()>>,
|
cancel: Option<oneshot::Sender<()>>,
|
||||||
}
|
}
|
||||||
@ -65,43 +59,44 @@ impl OneAtATime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct IncomingCall {
|
||||||
|
pub room_id: u64,
|
||||||
|
pub calling_user: Arc<User>,
|
||||||
|
pub participants: Vec<Arc<User>>,
|
||||||
|
pub initial_project: Option<proto::ParticipantProject>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Singleton global maintaining the user's participation in a room across workspaces.
|
/// Singleton global maintaining the user's participation in a room across workspaces.
|
||||||
pub struct ActiveCall {
|
pub struct ActiveCall {
|
||||||
room: Option<(ModelHandle<Room>, Vec<Subscription>)>,
|
room: Option<(Model<Room>, Vec<Subscription>)>,
|
||||||
pending_room_creation: Option<Shared<Task<Result<ModelHandle<Room>, Arc<anyhow::Error>>>>>,
|
pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
|
||||||
|
location: Option<WeakModel<Project>>,
|
||||||
_join_debouncer: OneAtATime,
|
_join_debouncer: OneAtATime,
|
||||||
location: Option<WeakModelHandle<Project>>,
|
|
||||||
pending_invites: HashSet<u64>,
|
pending_invites: HashSet<u64>,
|
||||||
incoming_call: (
|
incoming_call: (
|
||||||
watch::Sender<Option<IncomingCall>>,
|
watch::Sender<Option<IncomingCall>>,
|
||||||
watch::Receiver<Option<IncomingCall>>,
|
watch::Receiver<Option<IncomingCall>>,
|
||||||
),
|
),
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
_subscriptions: Vec<client::Subscription>,
|
_subscriptions: Vec<client::Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ActiveCall {
|
impl EventEmitter<Event> for ActiveCall {}
|
||||||
type Event = room::Event;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveCall {
|
impl ActiveCall {
|
||||||
fn new(
|
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
client: Arc<Client>,
|
|
||||||
user_store: ModelHandle<UserStore>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
room: None,
|
room: None,
|
||||||
pending_room_creation: None,
|
pending_room_creation: None,
|
||||||
location: None,
|
location: None,
|
||||||
pending_invites: Default::default(),
|
pending_invites: Default::default(),
|
||||||
incoming_call: watch::channel(),
|
incoming_call: watch::channel(),
|
||||||
|
|
||||||
_join_debouncer: OneAtATime { cancel: None },
|
_join_debouncer: OneAtATime { cancel: None },
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
client.add_request_handler(cx.handle(), Self::handle_incoming_call),
|
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||||
client.add_message_handler(cx.handle(), Self::handle_call_canceled),
|
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
||||||
],
|
],
|
||||||
client,
|
client,
|
||||||
user_store,
|
user_store,
|
||||||
@ -113,35 +108,35 @@ impl ActiveCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_incoming_call(
|
async fn handle_incoming_call(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<proto::Ack> {
|
) -> Result<proto::Ack> {
|
||||||
let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
let call = IncomingCall {
|
let call = IncomingCall {
|
||||||
room_id: envelope.payload.room_id,
|
room_id: envelope.payload.room_id,
|
||||||
participants: user_store
|
participants: user_store
|
||||||
.update(&mut cx, |user_store, cx| {
|
.update(&mut cx, |user_store, cx| {
|
||||||
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
||||||
})
|
})?
|
||||||
.await?,
|
.await?,
|
||||||
calling_user: user_store
|
calling_user: user_store
|
||||||
.update(&mut cx, |user_store, cx| {
|
.update(&mut cx, |user_store, cx| {
|
||||||
user_store.get_user(envelope.payload.calling_user_id, cx)
|
user_store.get_user(envelope.payload.calling_user_id, cx)
|
||||||
})
|
})?
|
||||||
.await?,
|
.await?,
|
||||||
initial_project: envelope.payload.initial_project,
|
initial_project: envelope.payload.initial_project,
|
||||||
};
|
};
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
*this.incoming_call.0.borrow_mut() = Some(call);
|
*this.incoming_call.0.borrow_mut() = Some(call);
|
||||||
});
|
})?;
|
||||||
|
|
||||||
Ok(proto::Ack {})
|
Ok(proto::Ack {})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_call_canceled(
|
async fn handle_call_canceled(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
envelope: TypedEnvelope<proto::CallCanceled>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -154,18 +149,18 @@ impl ActiveCall {
|
|||||||
{
|
{
|
||||||
incoming_call.take();
|
incoming_call.take();
|
||||||
}
|
}
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn global(cx: &AppContext) -> ModelHandle<Self> {
|
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||||
cx.global::<ModelHandle<Self>>().clone()
|
cx.global::<Model<Self>>().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invite(
|
pub fn invite(
|
||||||
&mut self,
|
&mut self,
|
||||||
called_user_id: u64,
|
called_user_id: u64,
|
||||||
initial_project: Option<ModelHandle<Project>>,
|
initial_project: Option<Model<Project>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if !self.pending_invites.insert(called_user_id) {
|
if !self.pending_invites.insert(called_user_id) {
|
||||||
@ -184,21 +179,21 @@ impl ActiveCall {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let invite = if let Some(room) = room {
|
let invite = if let Some(room) = room {
|
||||||
cx.spawn_weak(|_, mut cx| async move {
|
cx.spawn(move |_, mut cx| async move {
|
||||||
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||||
|
|
||||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||||
Some(
|
Some(
|
||||||
room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))
|
room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
room.update(&mut cx, |room, cx| {
|
room.update(&mut cx, move |room, cx| {
|
||||||
room.call(called_user_id, initial_project_id, cx)
|
room.call(called_user_id, initial_project_id, cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
@ -207,7 +202,7 @@ impl ActiveCall {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let room = cx
|
let room = cx
|
||||||
.spawn(|this, mut cx| async move {
|
.spawn(move |this, mut cx| async move {
|
||||||
let create_room = async {
|
let create_room = async {
|
||||||
let room = cx
|
let room = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
@ -218,31 +213,31 @@ impl ActiveCall {
|
|||||||
user_store,
|
user_store,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))
|
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
anyhow::Ok(room)
|
anyhow::Ok(room)
|
||||||
};
|
};
|
||||||
|
|
||||||
let room = create_room.await;
|
let room = create_room.await;
|
||||||
this.update(&mut cx, |this, _| this.pending_room_creation = None);
|
this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
|
||||||
room.map_err(Arc::new)
|
room.map_err(Arc::new)
|
||||||
})
|
})
|
||||||
.shared();
|
.shared();
|
||||||
self.pending_room_creation = Some(room.clone());
|
self.pending_room_creation = Some(room.clone());
|
||||||
cx.foreground().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
room.await.map_err(|err| anyhow!("{:?}", err))?;
|
room.await.map_err(|err| anyhow!("{:?}", err))?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let result = invite.await;
|
let result = invite.await;
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx));
|
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
||||||
} else {
|
} else {
|
||||||
// TODO: Resport collaboration error
|
// TODO: Resport collaboration error
|
||||||
}
|
}
|
||||||
@ -250,7 +245,7 @@ impl ActiveCall {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.pending_invites.remove(&called_user_id);
|
this.pending_invites.remove(&called_user_id);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
result
|
result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -267,7 +262,7 @@ impl ActiveCall {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.foreground().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
client
|
client
|
||||||
.request(proto::CancelCall {
|
.request(proto::CancelCall {
|
||||||
room_id,
|
room_id,
|
||||||
@ -306,11 +301,11 @@ impl ActiveCall {
|
|||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let room = join.await?;
|
let room = join.await?;
|
||||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))
|
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.report_call_event("accept incoming", cx)
|
this.report_call_event("accept incoming", cx)
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -333,7 +328,7 @@ impl ActiveCall {
|
|||||||
&mut self,
|
&mut self,
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Option<ModelHandle<Room>>>> {
|
) -> Task<Result<Option<Model<Room>>>> {
|
||||||
if let Some(room) = self.room().cloned() {
|
if let Some(room) = self.room().cloned() {
|
||||||
if room.read(cx).channel_id() == Some(channel_id) {
|
if room.read(cx).channel_id() == Some(channel_id) {
|
||||||
return Task::ready(Ok(Some(room)));
|
return Task::ready(Ok(Some(room)));
|
||||||
@ -352,13 +347,13 @@ impl ActiveCall {
|
|||||||
Room::join_channel(channel_id, client, user_store, cx).await
|
Room::join_channel(channel_id, client, user_store, cx).await
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
let room = join.await?;
|
let room = join.await?;
|
||||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))
|
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.report_call_event("join channel", cx)
|
this.report_call_event("join channel", cx)
|
||||||
});
|
})?;
|
||||||
Ok(room)
|
Ok(room)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -366,6 +361,7 @@ impl ActiveCall {
|
|||||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
self.report_call_event("hang up", cx);
|
self.report_call_event("hang up", cx);
|
||||||
|
|
||||||
Audio::end_call(cx);
|
Audio::end_call(cx);
|
||||||
if let Some((room, _)) = self.room.take() {
|
if let Some((room, _)) = self.room.take() {
|
||||||
room.update(cx, |room, cx| room.leave(cx))
|
room.update(cx, |room, cx| room.leave(cx))
|
||||||
@ -376,7 +372,7 @@ impl ActiveCall {
|
|||||||
|
|
||||||
pub fn share_project(
|
pub fn share_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: ModelHandle<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<u64>> {
|
) -> Task<Result<u64>> {
|
||||||
if let Some((room, _)) = self.room.as_ref() {
|
if let Some((room, _)) = self.room.as_ref() {
|
||||||
@ -389,7 +385,7 @@ impl ActiveCall {
|
|||||||
|
|
||||||
pub fn unshare_project(
|
pub fn unshare_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: ModelHandle<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some((room, _)) = self.room.as_ref() {
|
if let Some((room, _)) = self.room.as_ref() {
|
||||||
@ -400,13 +396,13 @@ impl ActiveCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn location(&self) -> Option<&WeakModelHandle<Project>> {
|
pub fn location(&self) -> Option<&WeakModel<Project>> {
|
||||||
self.location.as_ref()
|
self.location.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_location(
|
pub fn set_location(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&ModelHandle<Project>>,
|
project: Option<&Model<Project>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if project.is_some() || !*ZED_ALWAYS_ACTIVE {
|
if project.is_some() || !*ZED_ALWAYS_ACTIVE {
|
||||||
@ -420,7 +416,7 @@ impl ActiveCall {
|
|||||||
|
|
||||||
fn set_room(
|
fn set_room(
|
||||||
&mut self,
|
&mut self,
|
||||||
room: Option<ModelHandle<Room>>,
|
room: Option<Model<Room>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
|
if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
|
||||||
@ -441,7 +437,10 @@ impl ActiveCall {
|
|||||||
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
||||||
];
|
];
|
||||||
self.room = Some((room.clone(), subscriptions));
|
self.room = Some((room.clone(), subscriptions));
|
||||||
let location = self.location.and_then(|location| location.upgrade(cx));
|
let location = self
|
||||||
|
.location
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|location| location.upgrade());
|
||||||
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -453,7 +452,7 @@ impl ActiveCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn room(&self) -> Option<&ModelHandle<Room>> {
|
pub fn room(&self) -> Option<&Model<Room>> {
|
||||||
self.room.as_ref().map(|(room, _)| room)
|
self.room.as_ref().map(|(room, _)| room)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -465,7 +464,7 @@ impl ActiveCall {
|
|||||||
&self.pending_invites
|
&self.pending_invites
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_call_event(&self, operation: &'static str, cx: &AppContext) {
|
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
|
||||||
if let Some(room) = self.room() {
|
if let Some(room) = self.room() {
|
||||||
let room = room.read(cx);
|
let room = room.read(cx);
|
||||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
|
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
|
||||||
@ -478,10 +477,10 @@ pub fn report_call_event_for_room(
|
|||||||
room_id: u64,
|
room_id: u64,
|
||||||
channel_id: Option<u64>,
|
channel_id: Option<u64>,
|
||||||
client: &Arc<Client>,
|
client: &Arc<Client>,
|
||||||
cx: &AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let telemetry = client.telemetry();
|
let telemetry = client.telemetry();
|
||||||
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
let telemetry_settings = *TelemetrySettings::get_global(cx);
|
||||||
|
|
||||||
telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
|
telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
|
||||||
}
|
}
|
||||||
@ -495,7 +494,8 @@ pub fn report_call_event_for_channel(
|
|||||||
let room = ActiveCall::global(cx).read(cx).room();
|
let room = ActiveCall::global(cx).read(cx).room();
|
||||||
|
|
||||||
let telemetry = client.telemetry();
|
let telemetry = client.telemetry();
|
||||||
let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
|
|
||||||
|
let telemetry_settings = *TelemetrySettings::get_global(cx);
|
||||||
|
|
||||||
telemetry.report_call_event(
|
telemetry.report_call_event(
|
||||||
telemetry_settings,
|
telemetry_settings,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use gpui::AppContext;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::Setting;
|
use settings::Settings;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct CallSettings {
|
pub struct CallSettings {
|
||||||
@ -12,7 +14,7 @@ pub struct CallSettingsContent {
|
|||||||
pub mute_on_join: Option<bool>,
|
pub mute_on_join: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Setting for CallSettings {
|
impl Settings for CallSettings {
|
||||||
const KEY: Option<&'static str> = Some("calls");
|
const KEY: Option<&'static str> = Some("calls");
|
||||||
|
|
||||||
type FileContent = CallSettingsContent;
|
type FileContent = CallSettingsContent;
|
||||||
@ -20,8 +22,11 @@ impl Setting for CallSettings {
|
|||||||
fn load(
|
fn load(
|
||||||
default_value: &Self::FileContent,
|
default_value: &Self::FileContent,
|
||||||
user_values: &[&Self::FileContent],
|
user_values: &[&Self::FileContent],
|
||||||
_: &gpui::AppContext,
|
_cx: &mut AppContext,
|
||||||
) -> anyhow::Result<Self> {
|
) -> Result<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
Self::load_via_json_merge(default_value, user_values)
|
Self::load_via_json_merge(default_value, user_values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ use anyhow::{anyhow, Result};
|
|||||||
use client::ParticipantIndex;
|
use client::ParticipantIndex;
|
||||||
use client::{proto, User};
|
use client::{proto, User};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::WeakModelHandle;
|
use gpui::WeakModel;
|
||||||
pub use live_kit_client::Frame;
|
pub use live_kit_client::Frame;
|
||||||
use live_kit_client::RemoteAudioTrack;
|
pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::{fmt, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ParticipantLocation {
|
pub enum ParticipantLocation {
|
||||||
@ -35,7 +35,7 @@ impl ParticipantLocation {
|
|||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct LocalParticipant {
|
pub struct LocalParticipant {
|
||||||
pub projects: Vec<proto::ParticipantProject>,
|
pub projects: Vec<proto::ParticipantProject>,
|
||||||
pub active_project: Option<WeakModelHandle<Project>>,
|
pub active_project: Option<WeakModel<Project>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@ -50,20 +50,3 @@ pub struct RemoteParticipant {
|
|||||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
||||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RemoteVideoTrack {
|
|
||||||
pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for RemoteVideoTrack {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("RemoteVideoTrack").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RemoteVideoTrack {
|
|
||||||
pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
|
|
||||||
self.live_kit_track.frames()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
call_settings::CallSettings,
|
call_settings::CallSettings,
|
||||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
|
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant},
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use audio::{Audio, Sound};
|
use audio::{Audio, Sound};
|
||||||
@ -11,7 +11,9 @@ use client::{
|
|||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
|
use gpui::{
|
||||||
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
|
||||||
|
};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use live_kit_client::{
|
use live_kit_client::{
|
||||||
LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
|
LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate,
|
||||||
@ -19,7 +21,8 @@ use live_kit_client::{
|
|||||||
};
|
};
|
||||||
use postage::{sink::Sink, stream::Stream, watch};
|
use postage::{sink::Sink, stream::Stream, watch};
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use std::{future::Future, mem, pin::Pin, sync::Arc, time::Duration};
|
use settings::Settings as _;
|
||||||
|
use std::{future::Future, mem, sync::Arc, time::Duration};
|
||||||
use util::{post_inc, ResultExt, TryFutureExt};
|
use util::{post_inc, ResultExt, TryFutureExt};
|
||||||
|
|
||||||
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
@ -54,11 +57,11 @@ pub enum Event {
|
|||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
id: u64,
|
id: u64,
|
||||||
pub channel_id: Option<u64>,
|
channel_id: Option<u64>,
|
||||||
live_kit: Option<LiveKitRoom>,
|
live_kit: Option<LiveKitRoom>,
|
||||||
status: RoomStatus,
|
status: RoomStatus,
|
||||||
shared_projects: HashSet<WeakModelHandle<Project>>,
|
shared_projects: HashSet<WeakModel<Project>>,
|
||||||
joined_projects: HashSet<WeakModelHandle<Project>>,
|
joined_projects: HashSet<WeakModel<Project>>,
|
||||||
local_participant: LocalParticipant,
|
local_participant: LocalParticipant,
|
||||||
remote_participants: BTreeMap<u64, RemoteParticipant>,
|
remote_participants: BTreeMap<u64, RemoteParticipant>,
|
||||||
pending_participants: Vec<Arc<User>>,
|
pending_participants: Vec<Arc<User>>,
|
||||||
@ -66,39 +69,17 @@ pub struct Room {
|
|||||||
pending_call_count: usize,
|
pending_call_count: usize,
|
||||||
leave_when_empty: bool,
|
leave_when_empty: bool,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
|
follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
|
||||||
subscriptions: Vec<client::Subscription>,
|
client_subscriptions: Vec<client::Subscription>,
|
||||||
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
room_update_completed_tx: watch::Sender<Option<()>>,
|
room_update_completed_tx: watch::Sender<Option<()>>,
|
||||||
room_update_completed_rx: watch::Receiver<Option<()>>,
|
room_update_completed_rx: watch::Receiver<Option<()>>,
|
||||||
pending_room_update: Option<Task<()>>,
|
pending_room_update: Option<Task<()>>,
|
||||||
maintain_connection: Option<Task<Option<()>>>,
|
maintain_connection: Option<Task<Option<()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Room {
|
impl EventEmitter<Event> for Room {}
|
||||||
type Event = Event;
|
|
||||||
|
|
||||||
fn release(&mut self, cx: &mut AppContext) {
|
|
||||||
if self.status.is_online() {
|
|
||||||
self.leave_internal(cx).detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app_will_quit(&mut self, cx: &mut AppContext) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
|
|
||||||
if self.status.is_online() {
|
|
||||||
let leave = self.leave_internal(cx);
|
|
||||||
Some(
|
|
||||||
cx.background()
|
|
||||||
.spawn(async move {
|
|
||||||
leave.await.log_err();
|
|
||||||
})
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
pub fn channel_id(&self) -> Option<u64> {
|
pub fn channel_id(&self) -> Option<u64> {
|
||||||
@ -121,16 +102,12 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_publish(&self) -> bool {
|
|
||||||
self.live_kit.as_ref().is_some_and(|room| room.can_publish)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
id: u64,
|
id: u64,
|
||||||
channel_id: Option<u64>,
|
channel_id: Option<u64>,
|
||||||
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
let live_kit_room = if let Some(connection_info) = live_kit_connection_info {
|
||||||
@ -138,69 +115,75 @@ impl Room {
|
|||||||
let mut status = room.status();
|
let mut status = room.status();
|
||||||
// Consume the initial status of the room.
|
// Consume the initial status of the room.
|
||||||
let _ = status.try_recv();
|
let _ = status.try_recv();
|
||||||
let _maintain_room = cx.spawn_weak(|this, mut cx| async move {
|
let _maintain_room = cx.spawn(|this, mut cx| async move {
|
||||||
while let Some(status) = status.next().await {
|
while let Some(status) = status.next().await {
|
||||||
let this = if let Some(this) = this.upgrade(&cx) {
|
let this = if let Some(this) = this.upgrade() {
|
||||||
this
|
this
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
|
|
||||||
if status == live_kit_client::ConnectionState::Disconnected {
|
if status == live_kit_client::ConnectionState::Disconnected {
|
||||||
this.update(&mut cx, |this, cx| this.leave(cx).log_err());
|
this.update(&mut cx, |this, cx| this.leave(cx).log_err())
|
||||||
|
.ok();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut track_video_changes = room.remote_video_track_updates();
|
let _maintain_video_tracks = cx.spawn({
|
||||||
let _maintain_video_tracks = cx.spawn_weak(|this, mut cx| async move {
|
let room = room.clone();
|
||||||
while let Some(track_change) = track_video_changes.next().await {
|
move |this, mut cx| async move {
|
||||||
let this = if let Some(this) = this.upgrade(&cx) {
|
let mut track_video_changes = room.remote_video_track_updates();
|
||||||
this
|
while let Some(track_change) = track_video_changes.next().await {
|
||||||
} else {
|
let this = if let Some(this) = this.upgrade() {
|
||||||
break;
|
this
|
||||||
};
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.remote_video_track_updated(track_change, cx).log_err()
|
this.remote_video_track_updated(track_change, cx).log_err()
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut track_audio_changes = room.remote_audio_track_updates();
|
let _maintain_audio_tracks = cx.spawn({
|
||||||
let _maintain_audio_tracks = cx.spawn_weak(|this, mut cx| async move {
|
let room = room.clone();
|
||||||
while let Some(track_change) = track_audio_changes.next().await {
|
|this, mut cx| async move {
|
||||||
let this = if let Some(this) = this.upgrade(&cx) {
|
let mut track_audio_changes = room.remote_audio_track_updates();
|
||||||
this
|
while let Some(track_change) = track_audio_changes.next().await {
|
||||||
} else {
|
let this = if let Some(this) = this.upgrade() {
|
||||||
break;
|
this
|
||||||
};
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.remote_audio_track_updated(track_change, cx).log_err()
|
this.remote_audio_track_updated(track_change, cx).log_err()
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let connect = room.connect(&connection_info.server_url, &connection_info.token);
|
let connect = room.connect(&connection_info.server_url, &connection_info.token);
|
||||||
if connection_info.can_publish {
|
cx.spawn(|this, mut cx| async move {
|
||||||
cx.spawn(|this, mut cx| async move {
|
connect.await?;
|
||||||
connect.await?;
|
|
||||||
|
|
||||||
if !cx.read(Self::mute_on_join) {
|
if !cx.update(|cx| Self::mute_on_join(cx))? {
|
||||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))
|
this.update(&mut cx, |this, cx| this.share_microphone(cx))?
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
|
||||||
|
|
||||||
Some(LiveKitRoom {
|
Some(LiveKitRoom {
|
||||||
room,
|
room,
|
||||||
can_publish: connection_info.can_publish,
|
|
||||||
screen_track: LocalTrack::None,
|
screen_track: LocalTrack::None,
|
||||||
microphone_track: LocalTrack::None,
|
microphone_track: LocalTrack::None,
|
||||||
next_publish_id: 0,
|
next_publish_id: 0,
|
||||||
@ -214,8 +197,10 @@ impl Room {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let maintain_connection =
|
let maintain_connection = cx.spawn({
|
||||||
cx.spawn_weak(|this, cx| Self::maintain_connection(this, client.clone(), cx).log_err());
|
let client = client.clone();
|
||||||
|
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||||
|
});
|
||||||
|
|
||||||
Audio::play_sound(Sound::Joined, cx);
|
Audio::play_sound(Sound::Joined, cx);
|
||||||
|
|
||||||
@ -233,7 +218,13 @@ impl Room {
|
|||||||
remote_participants: Default::default(),
|
remote_participants: Default::default(),
|
||||||
pending_participants: Default::default(),
|
pending_participants: Default::default(),
|
||||||
pending_call_count: 0,
|
pending_call_count: 0,
|
||||||
subscriptions: vec![client.add_message_handler(cx.handle(), Self::handle_room_updated)],
|
client_subscriptions: vec![
|
||||||
|
client.add_message_handler(cx.weak_model(), Self::handle_room_updated)
|
||||||
|
],
|
||||||
|
_subscriptions: vec![
|
||||||
|
cx.on_release(Self::released),
|
||||||
|
cx.on_app_quit(Self::app_will_quit),
|
||||||
|
],
|
||||||
leave_when_empty: false,
|
leave_when_empty: false,
|
||||||
pending_room_update: None,
|
pending_room_update: None,
|
||||||
client,
|
client,
|
||||||
@ -247,15 +238,15 @@ impl Room {
|
|||||||
|
|
||||||
pub(crate) fn create(
|
pub(crate) fn create(
|
||||||
called_user_id: u64,
|
called_user_id: u64,
|
||||||
initial_project: Option<ModelHandle<Project>>,
|
initial_project: Option<Model<Project>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<ModelHandle<Self>>> {
|
) -> Task<Result<Model<Self>>> {
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(move |mut cx| async move {
|
||||||
let response = client.request(proto::CreateRoom {}).await?;
|
let response = client.request(proto::CreateRoom {}).await?;
|
||||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||||
let room = cx.add_model(|cx| {
|
let room = cx.new_model(|cx| {
|
||||||
Self::new(
|
Self::new(
|
||||||
room_proto.id,
|
room_proto.id,
|
||||||
None,
|
None,
|
||||||
@ -264,13 +255,13 @@ impl Room {
|
|||||||
user_store,
|
user_store,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
let initial_project_id = if let Some(initial_project) = initial_project {
|
||||||
let initial_project_id = room
|
let initial_project_id = room
|
||||||
.update(&mut cx, |room, cx| {
|
.update(&mut cx, |room, cx| {
|
||||||
room.share_project(initial_project.clone(), cx)
|
room.share_project(initial_project.clone(), cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Some(initial_project_id)
|
Some(initial_project_id)
|
||||||
} else {
|
} else {
|
||||||
@ -281,7 +272,7 @@ impl Room {
|
|||||||
.update(&mut cx, |room, cx| {
|
.update(&mut cx, |room, cx| {
|
||||||
room.leave_when_empty = true;
|
room.leave_when_empty = true;
|
||||||
room.call(called_user_id, initial_project_id, cx)
|
room.call(called_user_id, initial_project_id, cx)
|
||||||
})
|
})?
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => Ok(room),
|
Ok(()) => Ok(room),
|
||||||
@ -293,9 +284,9 @@ impl Room {
|
|||||||
pub(crate) async fn join_channel(
|
pub(crate) async fn join_channel(
|
||||||
channel_id: u64,
|
channel_id: u64,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
Self::from_join_response(
|
Self::from_join_response(
|
||||||
client.request(proto::JoinChannel { channel_id }).await?,
|
client.request(proto::JoinChannel { channel_id }).await?,
|
||||||
client,
|
client,
|
||||||
@ -307,9 +298,9 @@ impl Room {
|
|||||||
pub(crate) async fn join(
|
pub(crate) async fn join(
|
||||||
room_id: u64,
|
room_id: u64,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
Self::from_join_response(
|
Self::from_join_response(
|
||||||
client.request(proto::JoinRoom { id: room_id }).await?,
|
client.request(proto::JoinRoom { id: room_id }).await?,
|
||||||
client,
|
client,
|
||||||
@ -318,18 +309,41 @@ impl Room {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn released(&mut self, cx: &mut AppContext) {
|
||||||
|
if self.status.is_online() {
|
||||||
|
self.leave_internal(cx).detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_will_quit(&mut self, cx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
|
||||||
|
let task = if self.status.is_online() {
|
||||||
|
let leave = self.leave_internal(cx);
|
||||||
|
Some(cx.background_executor().spawn(async move {
|
||||||
|
leave.await.log_err();
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
async move {
|
||||||
|
if let Some(task) = task {
|
||||||
|
task.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mute_on_join(cx: &AppContext) -> bool {
|
pub fn mute_on_join(cx: &AppContext) -> bool {
|
||||||
settings::get::<CallSettings>(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
CallSettings::get_global(cx).mute_on_join || client::IMPERSONATE_LOGIN.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_join_response(
|
fn from_join_response(
|
||||||
response: proto::JoinRoomResponse,
|
response: proto::JoinRoomResponse,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
|
||||||
let room = cx.add_model(|cx| {
|
let room = cx.new_model(|cx| {
|
||||||
Self::new(
|
Self::new(
|
||||||
room_proto.id,
|
room_proto.id,
|
||||||
response.channel_id,
|
response.channel_id,
|
||||||
@ -338,12 +352,12 @@ impl Room {
|
|||||||
user_store,
|
user_store,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
})?;
|
||||||
room.update(&mut cx, |room, cx| {
|
room.update(&mut cx, |room, cx| {
|
||||||
room.leave_when_empty = room.channel_id.is_none();
|
room.leave_when_empty = room.channel_id.is_none();
|
||||||
room.apply_room_update(room_proto, cx)?;
|
room.apply_room_update(room_proto, cx)?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})?;
|
})??;
|
||||||
Ok(room)
|
Ok(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +386,7 @@ impl Room {
|
|||||||
self.clear_state(cx);
|
self.clear_state(cx);
|
||||||
|
|
||||||
let leave_room = self.client.request(proto::LeaveRoom {});
|
let leave_room = self.client.request(proto::LeaveRoom {});
|
||||||
cx.background().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
leave_room.await?;
|
leave_room.await?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
@ -380,14 +394,14 @@ impl Room {
|
|||||||
|
|
||||||
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
|
pub(crate) fn clear_state(&mut self, cx: &mut AppContext) {
|
||||||
for project in self.shared_projects.drain() {
|
for project in self.shared_projects.drain() {
|
||||||
if let Some(project) = project.upgrade(cx) {
|
if let Some(project) = project.upgrade() {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.unshare(cx).log_err();
|
project.unshare(cx).log_err();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for project in self.joined_projects.drain() {
|
for project in self.joined_projects.drain() {
|
||||||
if let Some(project) = project.upgrade(cx) {
|
if let Some(project) = project.upgrade() {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.disconnected_from_host(cx);
|
project.disconnected_from_host(cx);
|
||||||
project.close(cx);
|
project.close(cx);
|
||||||
@ -399,14 +413,14 @@ impl Room {
|
|||||||
self.remote_participants.clear();
|
self.remote_participants.clear();
|
||||||
self.pending_participants.clear();
|
self.pending_participants.clear();
|
||||||
self.participant_user_ids.clear();
|
self.participant_user_ids.clear();
|
||||||
self.subscriptions.clear();
|
self.client_subscriptions.clear();
|
||||||
self.live_kit.take();
|
self.live_kit.take();
|
||||||
self.pending_room_update.take();
|
self.pending_room_update.take();
|
||||||
self.maintain_connection.take();
|
self.maintain_connection.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn maintain_connection(
|
async fn maintain_connection(
|
||||||
this: WeakModelHandle<Self>,
|
this: WeakModel<Self>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@ -418,32 +432,33 @@ impl Room {
|
|||||||
if !is_connected || client_status.next().await.is_some() {
|
if !is_connected || client_status.next().await.is_some() {
|
||||||
log::info!("detected client disconnection");
|
log::info!("detected client disconnection");
|
||||||
|
|
||||||
this.upgrade(&cx)
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
this.status = RoomStatus::Rejoining;
|
this.status = RoomStatus::Rejoining;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
|
|
||||||
// Wait for client to re-establish a connection to the server.
|
// Wait for client to re-establish a connection to the server.
|
||||||
{
|
{
|
||||||
let mut reconnection_timeout = cx.background().timer(RECONNECT_TIMEOUT).fuse();
|
let mut reconnection_timeout =
|
||||||
|
cx.background_executor().timer(RECONNECT_TIMEOUT).fuse();
|
||||||
let client_reconnection = async {
|
let client_reconnection = async {
|
||||||
let mut remaining_attempts = 3;
|
let mut remaining_attempts = 3;
|
||||||
while remaining_attempts > 0 {
|
while remaining_attempts > 0 {
|
||||||
if client_status.borrow().is_connected() {
|
if client_status.borrow().is_connected() {
|
||||||
log::info!("client reconnected, attempting to rejoin room");
|
log::info!("client reconnected, attempting to rejoin room");
|
||||||
|
|
||||||
let Some(this) = this.upgrade(&cx) else { break };
|
let Some(this) = this.upgrade() else { break };
|
||||||
if this
|
match this.update(&mut cx, |this, cx| this.rejoin(cx)) {
|
||||||
.update(&mut cx, |this, cx| this.rejoin(cx))
|
Ok(task) => {
|
||||||
.await
|
if task.await.log_err().is_some() {
|
||||||
.log_err()
|
return true;
|
||||||
.is_some()
|
} else {
|
||||||
{
|
remaining_attempts -= 1;
|
||||||
return true;
|
}
|
||||||
} else {
|
}
|
||||||
remaining_attempts -= 1;
|
Err(_app_dropped) => return false,
|
||||||
}
|
}
|
||||||
} else if client_status.borrow().is_signed_out() {
|
} else if client_status.borrow().is_signed_out() {
|
||||||
return false;
|
return false;
|
||||||
@ -482,9 +497,9 @@ impl Room {
|
|||||||
// The client failed to re-establish a connection to the server
|
// The client failed to re-establish a connection to the server
|
||||||
// or an error occurred while trying to re-join the room. Either way
|
// or an error occurred while trying to re-join the room. Either way
|
||||||
// we leave the room and return an error.
|
// we leave the room and return an error.
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade() {
|
||||||
log::info!("reconnection failed, leaving room");
|
log::info!("reconnection failed, leaving room");
|
||||||
let _ = this.update(&mut cx, |this, cx| this.leave(cx));
|
let _ = this.update(&mut cx, |this, cx| this.leave(cx))?;
|
||||||
}
|
}
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"can't reconnect to room: client failed to re-establish connection"
|
"can't reconnect to room: client failed to re-establish connection"
|
||||||
@ -496,7 +511,7 @@ impl Room {
|
|||||||
let mut reshared_projects = Vec::new();
|
let mut reshared_projects = Vec::new();
|
||||||
let mut rejoined_projects = Vec::new();
|
let mut rejoined_projects = Vec::new();
|
||||||
self.shared_projects.retain(|project| {
|
self.shared_projects.retain(|project| {
|
||||||
if let Some(handle) = project.upgrade(cx) {
|
if let Some(handle) = project.upgrade() {
|
||||||
let project = handle.read(cx);
|
let project = handle.read(cx);
|
||||||
if let Some(project_id) = project.remote_id() {
|
if let Some(project_id) = project.remote_id() {
|
||||||
projects.insert(project_id, handle.clone());
|
projects.insert(project_id, handle.clone());
|
||||||
@ -510,14 +525,14 @@ impl Room {
|
|||||||
false
|
false
|
||||||
});
|
});
|
||||||
self.joined_projects.retain(|project| {
|
self.joined_projects.retain(|project| {
|
||||||
if let Some(handle) = project.upgrade(cx) {
|
if let Some(handle) = project.upgrade() {
|
||||||
let project = handle.read(cx);
|
let project = handle.read(cx);
|
||||||
if let Some(project_id) = project.remote_id() {
|
if let Some(project_id) = project.remote_id() {
|
||||||
projects.insert(project_id, handle.clone());
|
projects.insert(project_id, handle.clone());
|
||||||
rejoined_projects.push(proto::RejoinProject {
|
rejoined_projects.push(proto::RejoinProject {
|
||||||
id: project_id,
|
id: project_id,
|
||||||
worktrees: project
|
worktrees: project
|
||||||
.worktrees(cx)
|
.worktrees()
|
||||||
.map(|worktree| {
|
.map(|worktree| {
|
||||||
let worktree = worktree.read(cx);
|
let worktree = worktree.read(cx);
|
||||||
proto::RejoinWorktree {
|
proto::RejoinWorktree {
|
||||||
@ -565,7 +580,7 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,7 +658,7 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_room_updated(
|
async fn handle_room_updated(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
envelope: TypedEnvelope<proto::RoomUpdated>,
|
envelope: TypedEnvelope<proto::RoomUpdated>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -652,7 +667,7 @@ impl Room {
|
|||||||
.payload
|
.payload
|
||||||
.room
|
.room
|
||||||
.ok_or_else(|| anyhow!("invalid room"))?;
|
.ok_or_else(|| anyhow!("invalid room"))?;
|
||||||
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))
|
this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_room_update(
|
fn apply_room_update(
|
||||||
@ -733,7 +748,7 @@ impl Room {
|
|||||||
|
|
||||||
for unshared_project_id in old_projects.difference(&new_projects) {
|
for unshared_project_id in old_projects.difference(&new_projects) {
|
||||||
this.joined_projects.retain(|project| {
|
this.joined_projects.retain(|project| {
|
||||||
if let Some(project) = project.upgrade(cx) {
|
if let Some(project) = project.upgrade() {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
if project.remote_id() == Some(*unshared_project_id) {
|
if project.remote_id() == Some(*unshared_project_id) {
|
||||||
project.disconnected_from_host(cx);
|
project.disconnected_from_host(cx);
|
||||||
@ -876,7 +891,8 @@ impl Room {
|
|||||||
this.check_invariants();
|
this.check_invariants();
|
||||||
this.room_update_completed_tx.try_send(Some(())).ok();
|
this.room_update_completed_tx.try_send(Some(())).ok();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@ -907,12 +923,7 @@ impl Room {
|
|||||||
.remote_participants
|
.remote_participants
|
||||||
.get_mut(&user_id)
|
.get_mut(&user_id)
|
||||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||||
participant.video_tracks.insert(
|
participant.video_tracks.insert(track_id.clone(), track);
|
||||||
track_id.clone(),
|
|
||||||
Arc::new(RemoteVideoTrack {
|
|
||||||
live_kit_track: track,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
cx.emit(Event::RemoteVideoTracksChanged {
|
cx.emit(Event::RemoteVideoTracksChanged {
|
||||||
participant_id: participant.peer_id,
|
participant_id: participant.peer_id,
|
||||||
});
|
});
|
||||||
@ -991,7 +1002,6 @@ impl Room {
|
|||||||
.remote_participants
|
.remote_participants
|
||||||
.get_mut(&user_id)
|
.get_mut(&user_id)
|
||||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||||
|
|
||||||
participant.audio_tracks.insert(track_id.clone(), track);
|
participant.audio_tracks.insert(track_id.clone(), track);
|
||||||
participant.muted = publication.is_muted();
|
participant.muted = publication.is_muted();
|
||||||
|
|
||||||
@ -1053,7 +1063,7 @@ impl Room {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let room_id = self.id;
|
let room_id = self.id;
|
||||||
self.pending_call_count += 1;
|
self.pending_call_count += 1;
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let result = client
|
let result = client
|
||||||
.request(proto::Call {
|
.request(proto::Call {
|
||||||
room_id,
|
room_id,
|
||||||
@ -1066,7 +1076,7 @@ impl Room {
|
|||||||
if this.should_leave() {
|
if this.should_leave() {
|
||||||
this.leave(cx).detach_and_log_err(cx);
|
this.leave(cx).detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
});
|
})?;
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -1078,31 +1088,31 @@ impl Room {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<ModelHandle<Project>>> {
|
) -> Task<Result<Model<Project>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let project =
|
let project =
|
||||||
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.joined_projects.retain(|project| {
|
this.joined_projects.retain(|project| {
|
||||||
if let Some(project) = project.upgrade(cx) {
|
if let Some(project) = project.upgrade() {
|
||||||
!project.read(cx).is_read_only()
|
!project.read(cx).is_read_only()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.joined_projects.insert(project.downgrade());
|
this.joined_projects.insert(project.downgrade());
|
||||||
});
|
})?;
|
||||||
Ok(project)
|
Ok(project)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn share_project(
|
pub(crate) fn share_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: ModelHandle<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<u64>> {
|
) -> Task<Result<u64>> {
|
||||||
if let Some(project_id) = project.read(cx).remote_id() {
|
if let Some(project_id) = project.read(cx).remote_id() {
|
||||||
@ -1118,7 +1128,7 @@ impl Room {
|
|||||||
|
|
||||||
project.update(&mut cx, |project, cx| {
|
project.update(&mut cx, |project, cx| {
|
||||||
project.shared(response.project_id, cx)
|
project.shared(response.project_id, cx)
|
||||||
})?;
|
})??;
|
||||||
|
|
||||||
// If the user's location is in this project, it changes from UnsharedProject to SharedProject.
|
// If the user's location is in this project, it changes from UnsharedProject to SharedProject.
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
@ -1129,7 +1139,7 @@ impl Room {
|
|||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
}
|
}
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(response.project_id)
|
Ok(response.project_id)
|
||||||
@ -1138,7 +1148,7 @@ impl Room {
|
|||||||
|
|
||||||
pub(crate) fn unshare_project(
|
pub(crate) fn unshare_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: ModelHandle<Project>,
|
project: Model<Project>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let project_id = match project.read(cx).remote_id() {
|
let project_id = match project.read(cx).remote_id() {
|
||||||
@ -1152,7 +1162,7 @@ impl Room {
|
|||||||
|
|
||||||
pub(crate) fn set_location(
|
pub(crate) fn set_location(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Option<&ModelHandle<Project>>,
|
project: Option<&Model<Project>>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
if self.status.is_offline() {
|
if self.status.is_offline() {
|
||||||
@ -1178,7 +1188,7 @@ impl Room {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
cx.foreground().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
client
|
client
|
||||||
.request(proto::UpdateParticipantLocation {
|
.request(proto::UpdateParticipantLocation {
|
||||||
room_id,
|
room_id,
|
||||||
@ -1244,22 +1254,21 @@ impl Room {
|
|||||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let publish_track = async {
|
let publish_track = async {
|
||||||
let track = LocalAudioTrack::create();
|
let track = LocalAudioTrack::create();
|
||||||
this.upgrade(&cx)
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.read_with(&cx, |this, _| {
|
.update(&mut cx, |this, _| {
|
||||||
this.live_kit
|
this.live_kit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|live_kit| live_kit.room.publish_audio_track(track))
|
.map(|live_kit| live_kit.room.publish_audio_track(track))
|
||||||
})
|
})?
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
let publication = publish_track.await;
|
let publication = publish_track.await;
|
||||||
this.upgrade(&cx)
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
let live_kit = this
|
let live_kit = this
|
||||||
@ -1283,7 +1292,9 @@ impl Room {
|
|||||||
live_kit.room.unpublish_track(publication);
|
live_kit.room.unpublish_track(publication);
|
||||||
} else {
|
} else {
|
||||||
if muted {
|
if muted {
|
||||||
cx.background().spawn(publication.set_mute(muted)).detach();
|
cx.background_executor()
|
||||||
|
.spawn(publication.set_mute(muted))
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
live_kit.microphone_track = LocalTrack::Published {
|
live_kit.microphone_track = LocalTrack::Published {
|
||||||
track_publication: publication,
|
track_publication: publication,
|
||||||
@ -1303,7 +1314,7 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1326,26 +1337,26 @@ impl Room {
|
|||||||
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
return Task::ready(Err(anyhow!("live-kit was not initialized")));
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let publish_track = async {
|
let publish_track = async {
|
||||||
let displays = displays.await?;
|
let displays = displays.await?;
|
||||||
let display = displays
|
let display = displays
|
||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| anyhow!("no display found"))?;
|
.ok_or_else(|| anyhow!("no display found"))?;
|
||||||
let track = LocalVideoTrack::screen_share_for_display(&display);
|
let track = LocalVideoTrack::screen_share_for_display(&display);
|
||||||
this.upgrade(&cx)
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.read_with(&cx, |this, _| {
|
.update(&mut cx, |this, _| {
|
||||||
this.live_kit
|
this.live_kit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|live_kit| live_kit.room.publish_video_track(track))
|
.map(|live_kit| live_kit.room.publish_video_track(track))
|
||||||
})
|
})?
|
||||||
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
.ok_or_else(|| anyhow!("live-kit was not initialized"))?
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|
||||||
let publication = publish_track.await;
|
let publication = publish_track.await;
|
||||||
this.upgrade(&cx)
|
this.upgrade()
|
||||||
.ok_or_else(|| anyhow!("room was dropped"))?
|
.ok_or_else(|| anyhow!("room was dropped"))?
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
let live_kit = this
|
let live_kit = this
|
||||||
@ -1369,7 +1380,9 @@ impl Room {
|
|||||||
live_kit.room.unpublish_track(publication);
|
live_kit.room.unpublish_track(publication);
|
||||||
} else {
|
} else {
|
||||||
if muted {
|
if muted {
|
||||||
cx.background().spawn(publication.set_mute(muted)).detach();
|
cx.background_executor()
|
||||||
|
.spawn(publication.set_mute(muted))
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
live_kit.screen_track = LocalTrack::Published {
|
live_kit.screen_track = LocalTrack::Published {
|
||||||
track_publication: publication,
|
track_publication: publication,
|
||||||
@ -1392,7 +1405,7 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1435,11 +1448,12 @@ impl Room {
|
|||||||
.room
|
.room
|
||||||
.remote_audio_track_publications(&participant.user.id.to_string())
|
.remote_audio_track_publications(&participant.user.id.to_string())
|
||||||
{
|
{
|
||||||
tasks.push(cx.foreground().spawn(track.set_enabled(!live_kit.deafened)));
|
let deafened = live_kit.deafened;
|
||||||
|
tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(cx.foreground().spawn(async move {
|
Ok(cx.foreground_executor().spawn(async move {
|
||||||
if let Some(mute_task) = mute_task {
|
if let Some(mute_task) = mute_task {
|
||||||
mute_task.await?;
|
mute_task.await?;
|
||||||
}
|
}
|
||||||
@ -1499,7 +1513,6 @@ struct LiveKitRoom {
|
|||||||
deafened: bool,
|
deafened: bool,
|
||||||
speaking: bool,
|
speaking: bool,
|
||||||
next_publish_id: usize,
|
next_publish_id: usize,
|
||||||
can_publish: bool,
|
|
||||||
_maintain_room: Task<()>,
|
_maintain_room: Task<()>,
|
||||||
_maintain_tracks: [Task<()>; 2],
|
_maintain_tracks: [Task<()>; 2],
|
||||||
}
|
}
|
||||||
@ -1531,7 +1544,8 @@ impl LiveKitRoom {
|
|||||||
*muted = should_mute;
|
*muted = should_mute;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Ok((
|
Ok((
|
||||||
cx.background().spawn(track_publication.set_mute(*muted)),
|
cx.background_executor()
|
||||||
|
.spawn(track_publication.set_mute(*muted)),
|
||||||
old_muted,
|
old_muted,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "call2"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/call2.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test-support = [
|
|
||||||
"client/test-support",
|
|
||||||
"collections/test-support",
|
|
||||||
"gpui/test-support",
|
|
||||||
"live_kit_client/test-support",
|
|
||||||
"project/test-support",
|
|
||||||
"util/test-support"
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
audio = { package = "audio2", path = "../audio2" }
|
|
||||||
client = { package = "client2", path = "../client2" }
|
|
||||||
collections = { path = "../collections" }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
|
||||||
log.workspace = true
|
|
||||||
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
|
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
|
||||||
language = { path = "../language" }
|
|
||||||
media = { path = "../media" }
|
|
||||||
project = { path = "../project" }
|
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
|
||||||
async-broadcast = "0.4"
|
|
||||||
futures.workspace = true
|
|
||||||
image = "0.23"
|
|
||||||
postage.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
smallvec.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
|
||||||
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
|
||||||
language = { path = "../language", features = ["test-support"] }
|
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
|
||||||
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
|
|
||||||
project = { path = "../project", features = ["test-support"] }
|
|
||||||
util = { path = "../util", features = ["test-support"] }
|
|
@ -1,543 +0,0 @@
|
|||||||
pub mod call_settings;
|
|
||||||
pub mod participant;
|
|
||||||
pub mod room;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use audio::Audio;
|
|
||||||
use call_settings::CallSettings;
|
|
||||||
use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
|
|
||||||
use collections::HashSet;
|
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt};
|
|
||||||
use gpui::{
|
|
||||||
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
|
|
||||||
WeakModel,
|
|
||||||
};
|
|
||||||
use postage::watch;
|
|
||||||
use project::Project;
|
|
||||||
use room::Event;
|
|
||||||
use settings::Settings;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use participant::ParticipantLocation;
|
|
||||||
pub use room::Room;
|
|
||||||
|
|
||||||
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
|
||||||
CallSettings::register(cx);
|
|
||||||
|
|
||||||
let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx));
|
|
||||||
cx.set_global(active_call);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OneAtATime {
|
|
||||||
cancel: Option<oneshot::Sender<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OneAtATime {
|
|
||||||
/// spawn a task in the given context.
|
|
||||||
/// if another task is spawned before that resolves, or if the OneAtATime itself is dropped, the first task will be cancelled and return Ok(None)
|
|
||||||
/// otherwise you'll see the result of the task.
|
|
||||||
fn spawn<F, Fut, R>(&mut self, cx: &mut AppContext, f: F) -> Task<Result<Option<R>>>
|
|
||||||
where
|
|
||||||
F: 'static + FnOnce(AsyncAppContext) -> Fut,
|
|
||||||
Fut: Future<Output = Result<R>>,
|
|
||||||
R: 'static,
|
|
||||||
{
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
self.cancel.replace(tx);
|
|
||||||
cx.spawn(|cx| async move {
|
|
||||||
futures::select_biased! {
|
|
||||||
_ = rx.fuse() => Ok(None),
|
|
||||||
result = f(cx).fuse() => result.map(Some),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn running(&self) -> bool {
|
|
||||||
self.cancel
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|cancel| !cancel.is_canceled())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IncomingCall {
|
|
||||||
pub room_id: u64,
|
|
||||||
pub calling_user: Arc<User>,
|
|
||||||
pub participants: Vec<Arc<User>>,
|
|
||||||
pub initial_project: Option<proto::ParticipantProject>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Singleton global maintaining the user's participation in a room across workspaces.
|
|
||||||
pub struct ActiveCall {
|
|
||||||
room: Option<(Model<Room>, Vec<Subscription>)>,
|
|
||||||
pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
|
|
||||||
location: Option<WeakModel<Project>>,
|
|
||||||
_join_debouncer: OneAtATime,
|
|
||||||
pending_invites: HashSet<u64>,
|
|
||||||
incoming_call: (
|
|
||||||
watch::Sender<Option<IncomingCall>>,
|
|
||||||
watch::Receiver<Option<IncomingCall>>,
|
|
||||||
),
|
|
||||||
client: Arc<Client>,
|
|
||||||
user_store: Model<UserStore>,
|
|
||||||
_subscriptions: Vec<client::Subscription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<Event> for ActiveCall {}
|
|
||||||
|
|
||||||
impl ActiveCall {
|
|
||||||
fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
room: None,
|
|
||||||
pending_room_creation: None,
|
|
||||||
location: None,
|
|
||||||
pending_invites: Default::default(),
|
|
||||||
incoming_call: watch::channel(),
|
|
||||||
_join_debouncer: OneAtATime { cancel: None },
|
|
||||||
_subscriptions: vec![
|
|
||||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
|
||||||
client.add_message_handler(cx.weak_model(), Self::handle_call_canceled),
|
|
||||||
],
|
|
||||||
client,
|
|
||||||
user_store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channel_id(&self, cx: &AppContext) -> Option<u64> {
|
|
||||||
self.room()?.read(cx).channel_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_incoming_call(
|
|
||||||
this: Model<Self>,
|
|
||||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<proto::Ack> {
|
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
|
||||||
let call = IncomingCall {
|
|
||||||
room_id: envelope.payload.room_id,
|
|
||||||
participants: user_store
|
|
||||||
.update(&mut cx, |user_store, cx| {
|
|
||||||
user_store.get_users(envelope.payload.participant_user_ids, cx)
|
|
||||||
})?
|
|
||||||
.await?,
|
|
||||||
calling_user: user_store
|
|
||||||
.update(&mut cx, |user_store, cx| {
|
|
||||||
user_store.get_user(envelope.payload.calling_user_id, cx)
|
|
||||||
})?
|
|
||||||
.await?,
|
|
||||||
initial_project: envelope.payload.initial_project,
|
|
||||||
};
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
*this.incoming_call.0.borrow_mut() = Some(call);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(proto::Ack {})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_call_canceled(
|
|
||||||
this: Model<Self>,
|
|
||||||
envelope: TypedEnvelope<proto::CallCanceled>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
let mut incoming_call = this.incoming_call.0.borrow_mut();
|
|
||||||
if incoming_call
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |call| call.room_id == envelope.payload.room_id)
|
|
||||||
{
|
|
||||||
incoming_call.take();
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn global(cx: &AppContext) -> Model<Self> {
|
|
||||||
cx.global::<Model<Self>>().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invite(
|
|
||||||
&mut self,
|
|
||||||
called_user_id: u64,
|
|
||||||
initial_project: Option<Model<Project>>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
if !self.pending_invites.insert(called_user_id) {
|
|
||||||
return Task::ready(Err(anyhow!("user was already invited")));
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
if self._join_debouncer.running() {
|
|
||||||
return Task::ready(Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let room = if let Some(room) = self.room().cloned() {
|
|
||||||
Some(Task::ready(Ok(room)).shared())
|
|
||||||
} else {
|
|
||||||
self.pending_room_creation.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let invite = if let Some(room) = room {
|
|
||||||
cx.spawn(move |_, mut cx| async move {
|
|
||||||
let room = room.await.map_err(|err| anyhow!("{:?}", err))?;
|
|
||||||
|
|
||||||
let initial_project_id = if let Some(initial_project) = initial_project {
|
|
||||||
Some(
|
|
||||||
room.update(&mut cx, |room, cx| room.share_project(initial_project, cx))?
|
|
||||||
.await?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
room.update(&mut cx, move |room, cx| {
|
|
||||||
room.call(called_user_id, initial_project_id, cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let client = self.client.clone();
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let room = cx
|
|
||||||
.spawn(move |this, mut cx| async move {
|
|
||||||
let create_room = async {
|
|
||||||
let room = cx
|
|
||||||
.update(|cx| {
|
|
||||||
Room::create(
|
|
||||||
called_user_id,
|
|
||||||
initial_project,
|
|
||||||
client,
|
|
||||||
user_store,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| this.set_room(Some(room.clone()), cx))?
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
anyhow::Ok(room)
|
|
||||||
};
|
|
||||||
|
|
||||||
let room = create_room.await;
|
|
||||||
this.update(&mut cx, |this, _| this.pending_room_creation = None)?;
|
|
||||||
room.map_err(Arc::new)
|
|
||||||
})
|
|
||||||
.shared();
|
|
||||||
self.pending_room_creation = Some(room.clone());
|
|
||||||
cx.background_executor().spawn(async move {
|
|
||||||
room.await.map_err(|err| anyhow!("{:?}", err))?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
|
||||||
let result = invite.await;
|
|
||||||
if result.is_ok() {
|
|
||||||
this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?;
|
|
||||||
} else {
|
|
||||||
// TODO: Resport collaboration error
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.pending_invites.remove(&called_user_id);
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
result
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cancel_invite(
|
|
||||||
&mut self,
|
|
||||||
called_user_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
let room_id = if let Some(room) = self.room() {
|
|
||||||
room.read(cx).id()
|
|
||||||
} else {
|
|
||||||
return Task::ready(Err(anyhow!("no active call")));
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = self.client.clone();
|
|
||||||
cx.background_executor().spawn(async move {
|
|
||||||
client
|
|
||||||
.request(proto::CancelCall {
|
|
||||||
room_id,
|
|
||||||
called_user_id,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn incoming(&self) -> watch::Receiver<Option<IncomingCall>> {
|
|
||||||
self.incoming_call.1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn accept_incoming(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
|
||||||
if self.room.is_some() {
|
|
||||||
return Task::ready(Err(anyhow!("cannot join while on another call")));
|
|
||||||
}
|
|
||||||
|
|
||||||
let call = if let Some(call) = self.incoming_call.1.borrow().clone() {
|
|
||||||
call
|
|
||||||
} else {
|
|
||||||
return Task::ready(Err(anyhow!("no incoming call")));
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.pending_room_creation.is_some() {
|
|
||||||
return Task::ready(Ok(()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let room_id = call.room_id.clone();
|
|
||||||
let client = self.client.clone();
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let join = self
|
|
||||||
._join_debouncer
|
|
||||||
.spawn(cx, move |cx| Room::join(room_id, client, user_store, cx));
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let room = join.await?;
|
|
||||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
|
||||||
.await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.report_call_event("accept incoming", cx)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decline_incoming(&mut self, cx: &mut ModelContext<Self>) -> Result<()> {
|
|
||||||
let call = self
|
|
||||||
.incoming_call
|
|
||||||
.0
|
|
||||||
.borrow_mut()
|
|
||||||
.take()
|
|
||||||
.ok_or_else(|| anyhow!("no incoming call"))?;
|
|
||||||
report_call_event_for_room("decline incoming", call.room_id, None, &self.client, cx);
|
|
||||||
self.client.send(proto::DeclineCall {
|
|
||||||
room_id: call.room_id,
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn join_channel(
|
|
||||||
&mut self,
|
|
||||||
channel_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<Option<Model<Room>>>> {
|
|
||||||
if let Some(room) = self.room().cloned() {
|
|
||||||
if room.read(cx).channel_id() == Some(channel_id) {
|
|
||||||
return Task::ready(Ok(Some(room)));
|
|
||||||
} else {
|
|
||||||
room.update(cx, |room, cx| room.clear_state(cx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.pending_room_creation.is_some() {
|
|
||||||
return Task::ready(Ok(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = self.client.clone();
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let join = self._join_debouncer.spawn(cx, move |cx| async move {
|
|
||||||
Room::join_channel(channel_id, client, user_store, cx).await
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
let room = join.await?;
|
|
||||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
|
||||||
.await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.report_call_event("join channel", cx)
|
|
||||||
})?;
|
|
||||||
Ok(room)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hang_up(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
|
||||||
cx.notify();
|
|
||||||
self.report_call_event("hang up", cx);
|
|
||||||
|
|
||||||
Audio::end_call(cx);
|
|
||||||
if let Some((room, _)) = self.room.take() {
|
|
||||||
room.update(cx, |room, cx| room.leave(cx))
|
|
||||||
} else {
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn share_project(
|
|
||||||
&mut self,
|
|
||||||
project: Model<Project>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<u64>> {
|
|
||||||
if let Some((room, _)) = self.room.as_ref() {
|
|
||||||
self.report_call_event("share project", cx);
|
|
||||||
room.update(cx, |room, cx| room.share_project(project, cx))
|
|
||||||
} else {
|
|
||||||
Task::ready(Err(anyhow!("no active call")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unshare_project(
|
|
||||||
&mut self,
|
|
||||||
project: Model<Project>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if let Some((room, _)) = self.room.as_ref() {
|
|
||||||
self.report_call_event("unshare project", cx);
|
|
||||||
room.update(cx, |room, cx| room.unshare_project(project, cx))
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("no active call"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn location(&self) -> Option<&WeakModel<Project>> {
|
|
||||||
self.location.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_location(
|
|
||||||
&mut self,
|
|
||||||
project: Option<&Model<Project>>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
if project.is_some() || !*ZED_ALWAYS_ACTIVE {
|
|
||||||
self.location = project.map(|project| project.downgrade());
|
|
||||||
if let Some((room, _)) = self.room.as_ref() {
|
|
||||||
return room.update(cx, |room, cx| room.set_location(project, cx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_room(
|
|
||||||
&mut self,
|
|
||||||
room: Option<Model<Room>>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
|
|
||||||
cx.notify();
|
|
||||||
if let Some(room) = room {
|
|
||||||
if room.read(cx).status().is_offline() {
|
|
||||||
self.room = None;
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
} else {
|
|
||||||
let subscriptions = vec![
|
|
||||||
cx.observe(&room, |this, room, cx| {
|
|
||||||
if room.read(cx).status().is_offline() {
|
|
||||||
this.set_room(None, cx).detach_and_log_err(cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}),
|
|
||||||
cx.subscribe(&room, |_, _, event, cx| cx.emit(event.clone())),
|
|
||||||
];
|
|
||||||
self.room = Some((room.clone(), subscriptions));
|
|
||||||
let location = self
|
|
||||||
.location
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|location| location.upgrade());
|
|
||||||
room.update(cx, |room, cx| room.set_location(location.as_ref(), cx))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.room = None;
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn room(&self) -> Option<&Model<Room>> {
|
|
||||||
self.room.as_ref().map(|(room, _)| room)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client(&self) -> Arc<Client> {
|
|
||||||
self.client.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending_invites(&self) -> &HashSet<u64> {
|
|
||||||
&self.pending_invites
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_call_event(&self, operation: &'static str, cx: &mut AppContext) {
|
|
||||||
if let Some(room) = self.room() {
|
|
||||||
let room = room.read(cx);
|
|
||||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &self.client, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_call_event_for_room(
|
|
||||||
operation: &'static str,
|
|
||||||
room_id: u64,
|
|
||||||
channel_id: Option<u64>,
|
|
||||||
client: &Arc<Client>,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) {
|
|
||||||
let telemetry = client.telemetry();
|
|
||||||
let telemetry_settings = *TelemetrySettings::get_global(cx);
|
|
||||||
|
|
||||||
telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_call_event_for_channel(
|
|
||||||
operation: &'static str,
|
|
||||||
channel_id: u64,
|
|
||||||
client: &Arc<Client>,
|
|
||||||
cx: &AppContext,
|
|
||||||
) {
|
|
||||||
let room = ActiveCall::global(cx).read(cx).room();
|
|
||||||
|
|
||||||
let telemetry = client.telemetry();
|
|
||||||
|
|
||||||
let telemetry_settings = *TelemetrySettings::get_global(cx);
|
|
||||||
|
|
||||||
telemetry.report_call_event(
|
|
||||||
telemetry_settings,
|
|
||||||
operation,
|
|
||||||
room.map(|r| r.read(cx).id()),
|
|
||||||
Some(channel_id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use gpui::TestAppContext;
|
|
||||||
|
|
||||||
use crate::OneAtATime;
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_one_at_a_time(cx: &mut TestAppContext) {
|
|
||||||
let mut one_at_a_time = OneAtATime { cancel: None };
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(1) }))
|
|
||||||
.await
|
|
||||||
.unwrap(),
|
|
||||||
Some(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
let (a, b) = cx.update(|cx| {
|
|
||||||
(
|
|
||||||
one_at_a_time.spawn(cx, |_| async {
|
|
||||||
assert!(false);
|
|
||||||
Ok(2)
|
|
||||||
}),
|
|
||||||
one_at_a_time.spawn(cx, |_| async { Ok(3) }),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
assert_eq!(a.await.unwrap(), None);
|
|
||||||
assert_eq!(b.await.unwrap(), Some(3));
|
|
||||||
|
|
||||||
let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));
|
|
||||||
drop(one_at_a_time);
|
|
||||||
|
|
||||||
assert_eq!(promise.await.unwrap(), None);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use gpui::AppContext;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
|
||||||
use settings::Settings;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct CallSettings {
|
|
||||||
pub mute_on_join: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
|
||||||
pub struct CallSettingsContent {
|
|
||||||
pub mute_on_join: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Settings for CallSettings {
|
|
||||||
const KEY: Option<&'static str> = Some("calls");
|
|
||||||
|
|
||||||
type FileContent = CallSettingsContent;
|
|
||||||
|
|
||||||
fn load(
|
|
||||||
default_value: &Self::FileContent,
|
|
||||||
user_values: &[&Self::FileContent],
|
|
||||||
_cx: &mut AppContext,
|
|
||||||
) -> Result<Self>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
Self::load_via_json_merge(default_value, user_values)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use client::ParticipantIndex;
|
|
||||||
use client::{proto, User};
|
|
||||||
use collections::HashMap;
|
|
||||||
use gpui::WeakModel;
|
|
||||||
pub use live_kit_client::Frame;
|
|
||||||
pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
|
|
||||||
use project::Project;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum ParticipantLocation {
|
|
||||||
SharedProject { project_id: u64 },
|
|
||||||
UnsharedProject,
|
|
||||||
External,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParticipantLocation {
|
|
||||||
pub fn from_proto(location: Option<proto::ParticipantLocation>) -> Result<Self> {
|
|
||||||
match location.and_then(|l| l.variant) {
|
|
||||||
Some(proto::participant_location::Variant::SharedProject(project)) => {
|
|
||||||
Ok(Self::SharedProject {
|
|
||||||
project_id: project.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Some(proto::participant_location::Variant::UnsharedProject(_)) => {
|
|
||||||
Ok(Self::UnsharedProject)
|
|
||||||
}
|
|
||||||
Some(proto::participant_location::Variant::External(_)) => Ok(Self::External),
|
|
||||||
None => Err(anyhow!("participant location was not provided")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct LocalParticipant {
|
|
||||||
pub projects: Vec<proto::ParticipantProject>,
|
|
||||||
pub active_project: Option<WeakModel<Project>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct RemoteParticipant {
|
|
||||||
pub user: Arc<User>,
|
|
||||||
pub peer_id: proto::PeerId,
|
|
||||||
pub projects: Vec<proto::ParticipantProject>,
|
|
||||||
pub location: ParticipantLocation,
|
|
||||||
pub participant_index: ParticipantIndex,
|
|
||||||
pub muted: bool,
|
|
||||||
pub speaking: bool,
|
|
||||||
pub video_tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
|
|
||||||
pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -14,13 +14,13 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
db = { path = "../db" }
|
db = { package = "db2", path = "../db2" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { package = "rpc2", path = "../rpc2" }
|
||||||
text = { path = "../text" }
|
text = { package = "text2", path = "../text2" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
settings = { path = "../settings" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
feature_flags = { path = "../feature_flags" }
|
feature_flags = { path = "../feature_flags" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
@ -47,8 +47,8 @@ tempfile = "3"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
rpc = { path = "../rpc", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
@ -3,7 +3,7 @@ mod channel_chat;
|
|||||||
mod channel_store;
|
mod channel_store;
|
||||||
|
|
||||||
use client::{Client, UserStore};
|
use client::{Client, UserStore};
|
||||||
use gpui::{AppContext, ModelHandle};
|
use gpui::{AppContext, Model};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
|
pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
|
||||||
@ -16,7 +16,7 @@ pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, Cha
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod channel_store_tests;
|
mod channel_store_tests;
|
||||||
|
|
||||||
pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||||
channel_store::init(client, user_store, cx);
|
channel_store::init(client, user_store, cx);
|
||||||
channel_buffer::init(client);
|
channel_buffer::init(client);
|
||||||
channel_chat::init(client);
|
channel_chat::init(client);
|
||||||
|
@ -2,7 +2,7 @@ use crate::{Channel, ChannelId, ChannelStore};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use client::{Client, Collaborator, UserStore};
|
use client::{Client, Collaborator, UserStore};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||||
use language::proto::serialize_version;
|
use language::proto::serialize_version;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, PeerId},
|
proto::{self, PeerId},
|
||||||
@ -22,9 +22,9 @@ pub struct ChannelBuffer {
|
|||||||
pub channel_id: ChannelId,
|
pub channel_id: ChannelId,
|
||||||
connected: bool,
|
connected: bool,
|
||||||
collaborators: HashMap<PeerId, Collaborator>,
|
collaborators: HashMap<PeerId, Collaborator>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
buffer: ModelHandle<language::Buffer>,
|
buffer: Model<language::Buffer>,
|
||||||
buffer_epoch: u64,
|
buffer_epoch: u64,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
subscription: Option<client::Subscription>,
|
subscription: Option<client::Subscription>,
|
||||||
@ -38,31 +38,16 @@ pub enum ChannelBufferEvent {
|
|||||||
ChannelChanged,
|
ChannelChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ChannelBuffer {
|
impl EventEmitter<ChannelBufferEvent> for ChannelBuffer {}
|
||||||
type Event = ChannelBufferEvent;
|
|
||||||
|
|
||||||
fn release(&mut self, _: &mut AppContext) {
|
|
||||||
if self.connected {
|
|
||||||
if let Some(task) = self.acknowledge_task.take() {
|
|
||||||
task.detach();
|
|
||||||
}
|
|
||||||
self.client
|
|
||||||
.send(proto::LeaveChannelBuffer {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelBuffer {
|
impl ChannelBuffer {
|
||||||
pub(crate) async fn new(
|
pub(crate) async fn new(
|
||||||
channel: Arc<Channel>,
|
channel: Arc<Channel>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::JoinChannelBuffer {
|
.request(proto::JoinChannelBuffer {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
@ -76,16 +61,16 @@ impl ChannelBuffer {
|
|||||||
.map(language::proto::deserialize_operation)
|
.map(language::proto::deserialize_operation)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let buffer = cx.add_model(|_| {
|
let buffer = cx.new_model(|_| {
|
||||||
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
|
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
|
||||||
});
|
})?;
|
||||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
|
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
|
||||||
|
|
||||||
let subscription = client.subscribe_to_entity(channel.id)?;
|
let subscription = client.subscribe_to_entity(channel.id)?;
|
||||||
|
|
||||||
anyhow::Ok(cx.add_model(|cx| {
|
anyhow::Ok(cx.new_model(|cx| {
|
||||||
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
||||||
|
cx.on_release(Self::release).detach();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
buffer,
|
buffer,
|
||||||
buffer_epoch: response.epoch,
|
buffer_epoch: response.epoch,
|
||||||
@ -100,14 +85,27 @@ impl ChannelBuffer {
|
|||||||
};
|
};
|
||||||
this.replace_collaborators(response.collaborators, cx);
|
this.replace_collaborators(response.collaborators, cx);
|
||||||
this
|
this
|
||||||
}))
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&mut self, _: &mut AppContext) {
|
||||||
|
if self.connected {
|
||||||
|
if let Some(task) = self.acknowledge_task.take() {
|
||||||
|
task.detach();
|
||||||
|
}
|
||||||
|
self.client
|
||||||
|
.send(proto::LeaveChannelBuffer {
|
||||||
|
channel_id: self.channel_id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remote_id(&self, cx: &AppContext) -> u64 {
|
pub fn remote_id(&self, cx: &AppContext) -> u64 {
|
||||||
self.buffer.read(cx).remote_id()
|
self.buffer.read(cx).remote_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_store(&self) -> &ModelHandle<UserStore> {
|
pub fn user_store(&self) -> &Model<UserStore> {
|
||||||
&self.user_store
|
&self.user_store
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +134,7 @@ impl ChannelBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_channel_buffer(
|
async fn handle_update_channel_buffer(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -152,13 +150,13 @@ impl ChannelBuffer {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
this.buffer
|
this.buffer
|
||||||
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
||||||
})?;
|
})??;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_channel_buffer_collaborators(
|
async fn handle_update_channel_buffer_collaborators(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -167,14 +165,12 @@ impl ChannelBuffer {
|
|||||||
this.replace_collaborators(message.payload.collaborators, cx);
|
this.replace_collaborators(message.payload.collaborators, cx);
|
||||||
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_buffer_update(
|
fn on_buffer_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: ModelHandle<language::Buffer>,
|
_: Model<language::Buffer>,
|
||||||
event: &language::Event,
|
event: &language::Event,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
@ -202,8 +198,10 @@ impl ChannelBuffer {
|
|||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let epoch = self.epoch();
|
let epoch = self.epoch();
|
||||||
|
|
||||||
self.acknowledge_task = Some(cx.spawn_weak(|_, cx| async move {
|
self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
|
||||||
cx.background().timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL).await;
|
cx.background_executor()
|
||||||
|
.timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
|
||||||
|
.await;
|
||||||
client
|
client
|
||||||
.send(proto::AckBufferOperation {
|
.send(proto::AckBufferOperation {
|
||||||
buffer_id,
|
buffer_id,
|
||||||
@ -219,7 +217,7 @@ impl ChannelBuffer {
|
|||||||
self.buffer_epoch
|
self.buffer_epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer(&self) -> ModelHandle<language::Buffer> {
|
pub fn buffer(&self) -> Model<language::Buffer> {
|
||||||
self.buffer.clone()
|
self.buffer.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use client::{
|
|||||||
Client, Subscription, TypedEnvelope, UserId,
|
Client, Subscription, TypedEnvelope, UserId,
|
||||||
};
|
};
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
@ -22,11 +22,11 @@ pub struct ChannelChat {
|
|||||||
pub channel_id: ChannelId,
|
pub channel_id: ChannelId,
|
||||||
messages: SumTree<ChannelMessage>,
|
messages: SumTree<ChannelMessage>,
|
||||||
acknowledged_message_ids: HashSet<u64>,
|
acknowledged_message_ids: HashSet<u64>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
loaded_all_messages: bool,
|
loaded_all_messages: bool,
|
||||||
last_acknowledged_id: Option<u64>,
|
last_acknowledged_id: Option<u64>,
|
||||||
next_pending_message_id: usize,
|
next_pending_message_id: usize,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
rpc: Arc<Client>,
|
rpc: Arc<Client>,
|
||||||
outgoing_messages_lock: Arc<Mutex<()>>,
|
outgoing_messages_lock: Arc<Mutex<()>>,
|
||||||
rng: StdRng,
|
rng: StdRng,
|
||||||
@ -76,31 +76,20 @@ pub enum ChannelChatEvent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ChannelChatEvent> for ChannelChat {}
|
||||||
pub fn init(client: &Arc<Client>) {
|
pub fn init(client: &Arc<Client>) {
|
||||||
client.add_model_message_handler(ChannelChat::handle_message_sent);
|
client.add_model_message_handler(ChannelChat::handle_message_sent);
|
||||||
client.add_model_message_handler(ChannelChat::handle_message_removed);
|
client.add_model_message_handler(ChannelChat::handle_message_removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ChannelChat {
|
|
||||||
type Event = ChannelChatEvent;
|
|
||||||
|
|
||||||
fn release(&mut self, _: &mut AppContext) {
|
|
||||||
self.rpc
|
|
||||||
.send(proto::LeaveChannelChat {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelChat {
|
impl ChannelChat {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
channel: Arc<Channel>,
|
channel: Arc<Channel>,
|
||||||
channel_store: ModelHandle<ChannelStore>,
|
channel_store: Model<ChannelStore>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<ModelHandle<Self>> {
|
) -> Result<Model<Self>> {
|
||||||
let channel_id = channel.id;
|
let channel_id = channel.id;
|
||||||
let subscription = client.subscribe_to_entity(channel_id).unwrap();
|
let subscription = client.subscribe_to_entity(channel_id).unwrap();
|
||||||
|
|
||||||
@ -110,7 +99,8 @@ impl ChannelChat {
|
|||||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
||||||
let loaded_all_messages = response.done;
|
let loaded_all_messages = response.done;
|
||||||
|
|
||||||
Ok(cx.add_model(|cx| {
|
Ok(cx.new_model(|cx| {
|
||||||
|
cx.on_release(Self::release).detach();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
user_store,
|
user_store,
|
||||||
@ -127,7 +117,15 @@ impl ChannelChat {
|
|||||||
};
|
};
|
||||||
this.insert_messages(messages, cx);
|
this.insert_messages(messages, cx);
|
||||||
this
|
this
|
||||||
}))
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&mut self, _: &mut AppContext) {
|
||||||
|
self.rpc
|
||||||
|
.send(proto::LeaveChannelChat {
|
||||||
|
channel_id: self.channel_id,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
||||||
@ -176,7 +174,7 @@ impl ChannelChat {
|
|||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let rpc = self.rpc.clone();
|
let rpc = self.rpc.clone();
|
||||||
let outgoing_messages_lock = self.outgoing_messages_lock.clone();
|
let outgoing_messages_lock = self.outgoing_messages_lock.clone();
|
||||||
Ok(cx.spawn(|this, mut cx| async move {
|
Ok(cx.spawn(move |this, mut cx| async move {
|
||||||
let outgoing_message_guard = outgoing_messages_lock.lock().await;
|
let outgoing_message_guard = outgoing_messages_lock.lock().await;
|
||||||
let request = rpc.request(proto::SendChannelMessage {
|
let request = rpc.request(proto::SendChannelMessage {
|
||||||
channel_id,
|
channel_id,
|
||||||
@ -191,8 +189,8 @@ impl ChannelChat {
|
|||||||
let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
|
let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
this.insert_messages(SumTree::from_item(message, &()), cx);
|
||||||
Ok(id)
|
})?;
|
||||||
})
|
Ok(id)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,13 +199,12 @@ impl ChannelChat {
|
|||||||
channel_id: self.channel_id,
|
channel_id: self.channel_id,
|
||||||
message_id: id,
|
message_id: id,
|
||||||
});
|
});
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
response.await?;
|
response.await?;
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.message_removed(id, cx);
|
this.message_removed(id, cx);
|
||||||
Ok(())
|
})?;
|
||||||
})
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +217,7 @@ impl ChannelChat {
|
|||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let channel_id = self.channel_id;
|
let channel_id = self.channel_id;
|
||||||
let before_message_id = self.first_loaded_message_id()?;
|
let before_message_id = self.first_loaded_message_id()?;
|
||||||
Some(cx.spawn(|this, mut cx| {
|
Some(cx.spawn(move |this, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
let response = rpc
|
let response = rpc
|
||||||
.request(proto::GetChannelMessages {
|
.request(proto::GetChannelMessages {
|
||||||
@ -233,7 +230,7 @@ impl ChannelChat {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.loaded_all_messages = loaded_all_messages;
|
this.loaded_all_messages = loaded_all_messages;
|
||||||
this.insert_messages(messages, cx);
|
this.insert_messages(messages, cx);
|
||||||
});
|
})?;
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
.log_err()
|
||||||
@ -251,31 +248,33 @@ impl ChannelChat {
|
|||||||
///
|
///
|
||||||
/// For now, we always maintain a suffix of the channel's messages.
|
/// For now, we always maintain a suffix of the channel's messages.
|
||||||
pub async fn load_history_since_message(
|
pub async fn load_history_since_message(
|
||||||
chat: ModelHandle<Self>,
|
chat: Model<Self>,
|
||||||
message_id: u64,
|
message_id: u64,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
loop {
|
loop {
|
||||||
let step = chat.update(&mut cx, |chat, cx| {
|
let step = chat
|
||||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
.update(&mut cx, |chat, cx| {
|
||||||
if first_id <= message_id {
|
if let Some(first_id) = chat.first_loaded_message_id() {
|
||||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
|
if first_id <= message_id {
|
||||||
let message_id = ChannelMessageId::Saved(message_id);
|
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
|
||||||
cursor.seek(&message_id, Bias::Left, &());
|
let message_id = ChannelMessageId::Saved(message_id);
|
||||||
return ControlFlow::Break(
|
cursor.seek(&message_id, Bias::Left, &());
|
||||||
if cursor
|
return ControlFlow::Break(
|
||||||
.item()
|
if cursor
|
||||||
.map_or(false, |message| message.id == message_id)
|
.item()
|
||||||
{
|
.map_or(false, |message| message.id == message_id)
|
||||||
Some(cursor.start().1 .0)
|
{
|
||||||
} else {
|
Some(cursor.start().1 .0)
|
||||||
None
|
} else {
|
||||||
},
|
None
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ControlFlow::Continue(chat.load_more_messages(cx))
|
||||||
ControlFlow::Continue(chat.load_more_messages(cx))
|
})
|
||||||
});
|
.log_err()?;
|
||||||
match step {
|
match step {
|
||||||
ControlFlow::Break(ix) => return ix,
|
ControlFlow::Break(ix) => return ix,
|
||||||
ControlFlow::Continue(task) => task?.await?,
|
ControlFlow::Continue(task) => task?.await?,
|
||||||
@ -307,7 +306,7 @@ impl ChannelChat {
|
|||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let rpc = self.rpc.clone();
|
let rpc = self.rpc.clone();
|
||||||
let channel_id = self.channel_id;
|
let channel_id = self.channel_id;
|
||||||
cx.spawn(|this, mut cx| {
|
cx.spawn(move |this, mut cx| {
|
||||||
async move {
|
async move {
|
||||||
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
|
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
|
||||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
||||||
@ -333,7 +332,7 @@ impl ChannelChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.pending_messages().cloned().collect::<Vec<_>>()
|
this.pending_messages().cloned().collect::<Vec<_>>()
|
||||||
});
|
})?;
|
||||||
|
|
||||||
for pending_message in pending_messages {
|
for pending_message in pending_messages {
|
||||||
let request = rpc.request(proto::SendChannelMessage {
|
let request = rpc.request(proto::SendChannelMessage {
|
||||||
@ -351,7 +350,7 @@ impl ChannelChat {
|
|||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
this.insert_messages(SumTree::from_item(message, &()), cx);
|
||||||
});
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
@ -399,12 +398,12 @@ impl ChannelChat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_message_sent(
|
async fn handle_message_sent(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
message: TypedEnvelope<proto::ChannelMessageSent>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
let message = message
|
let message = message
|
||||||
.payload
|
.payload
|
||||||
.message
|
.message
|
||||||
@ -418,20 +417,20 @@ impl ChannelChat {
|
|||||||
channel_id: this.channel_id,
|
channel_id: this.channel_id,
|
||||||
message_id,
|
message_id,
|
||||||
})
|
})
|
||||||
});
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_message_removed(
|
async fn handle_message_removed(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.message_removed(message.payload.message_id, cx)
|
this.message_removed(message.payload.message_id, cx)
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +514,7 @@ impl ChannelChat {
|
|||||||
|
|
||||||
async fn messages_from_proto(
|
async fn messages_from_proto(
|
||||||
proto_messages: Vec<proto::ChannelMessage>,
|
proto_messages: Vec<proto::ChannelMessage>,
|
||||||
user_store: &ModelHandle<UserStore>,
|
user_store: &Model<UserStore>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<SumTree<ChannelMessage>> {
|
) -> Result<SumTree<ChannelMessage>> {
|
||||||
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
||||||
@ -527,13 +526,13 @@ async fn messages_from_proto(
|
|||||||
impl ChannelMessage {
|
impl ChannelMessage {
|
||||||
pub async fn from_proto(
|
pub async fn from_proto(
|
||||||
message: proto::ChannelMessage,
|
message: proto::ChannelMessage,
|
||||||
user_store: &ModelHandle<UserStore>,
|
user_store: &Model<UserStore>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let sender = user_store
|
let sender = user_store
|
||||||
.update(cx, |user_store, cx| {
|
.update(cx, |user_store, cx| {
|
||||||
user_store.get_user(message.sender_id, cx)
|
user_store.get_user(message.sender_id, cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Ok(ChannelMessage {
|
Ok(ChannelMessage {
|
||||||
id: ChannelMessageId::Saved(message.id),
|
id: ChannelMessageId::Saved(message.id),
|
||||||
@ -561,7 +560,7 @@ impl ChannelMessage {
|
|||||||
|
|
||||||
pub async fn from_proto_vec(
|
pub async fn from_proto_vec(
|
||||||
proto_messages: Vec<proto::ChannelMessage>,
|
proto_messages: Vec<proto::ChannelMessage>,
|
||||||
user_store: &ModelHandle<UserStore>,
|
user_store: &Model<UserStore>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Vec<Self>> {
|
) -> Result<Vec<Self>> {
|
||||||
let unique_user_ids = proto_messages
|
let unique_user_ids = proto_messages
|
||||||
@ -573,7 +572,7 @@ impl ChannelMessage {
|
|||||||
user_store
|
user_store
|
||||||
.update(cx, |user_store, cx| {
|
.update(cx, |user_store, cx| {
|
||||||
user_store.get_users(unique_user_ids, cx)
|
user_store.get_users(unique_user_ids, cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut messages = Vec::with_capacity(proto_messages.len());
|
let mut messages = Vec::with_capacity(proto_messages.len());
|
||||||
|
@ -7,17 +7,20 @@ use client::{Client, Subscription, User, UserId, UserStore};
|
|||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use db::RELEASE_CHANNEL;
|
use db::RELEASE_CHANNEL;
|
||||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||||
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
|
use gpui::{
|
||||||
|
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task,
|
||||||
|
WeakModel,
|
||||||
|
};
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, ChannelVisibility},
|
proto::{self, ChannelVisibility},
|
||||||
TypedEnvelope,
|
TypedEnvelope,
|
||||||
};
|
};
|
||||||
use std::{mem, sync::Arc, time::Duration};
|
use std::{mem, sync::Arc, time::Duration};
|
||||||
use util::ResultExt;
|
use util::{async_maybe, ResultExt};
|
||||||
|
|
||||||
pub fn init(client: &Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
||||||
let channel_store =
|
let channel_store =
|
||||||
cx.add_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
|
cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx));
|
||||||
cx.set_global(channel_store);
|
cx.set_global(channel_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +37,7 @@ pub struct ChannelStore {
|
|||||||
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
opened_buffers: HashMap<ChannelId, OpenedModelHandle<ChannelBuffer>>,
|
||||||
opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
|
opened_chats: HashMap<ChannelId, OpenedModelHandle<ChannelChat>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
_rpc_subscription: Subscription,
|
_rpc_subscription: Subscription,
|
||||||
_watch_connection_status: Task<Option<()>>,
|
_watch_connection_status: Task<Option<()>>,
|
||||||
disconnect_channel_buffers_task: Option<Task<()>>,
|
disconnect_channel_buffers_task: Option<Task<()>>,
|
||||||
@ -44,7 +47,7 @@ pub struct ChannelStore {
|
|||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub id: ChannelId,
|
pub id: ChannelId,
|
||||||
pub name: String,
|
pub name: SharedString,
|
||||||
pub visibility: proto::ChannelVisibility,
|
pub visibility: proto::ChannelVisibility,
|
||||||
pub role: proto::ChannelRole,
|
pub role: proto::ChannelRole,
|
||||||
pub unseen_note_version: Option<(u64, clock::Global)>,
|
pub unseen_note_version: Option<(u64, clock::Global)>,
|
||||||
@ -112,44 +115,45 @@ pub enum ChannelEvent {
|
|||||||
ChannelRenamed(ChannelId),
|
ChannelRenamed(ChannelId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for ChannelStore {
|
impl EventEmitter<ChannelEvent> for ChannelStore {}
|
||||||
type Event = ChannelEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum OpenedModelHandle<E: Entity> {
|
enum OpenedModelHandle<E> {
|
||||||
Open(WeakModelHandle<E>),
|
Open(WeakModel<E>),
|
||||||
Loading(Shared<Task<Result<ModelHandle<E>, Arc<anyhow::Error>>>>),
|
Loading(Shared<Task<Result<Model<E>, Arc<anyhow::Error>>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelStore {
|
impl ChannelStore {
|
||||||
pub fn global(cx: &AppContext) -> ModelHandle<Self> {
|
pub fn global(cx: &AppContext) -> Model<Self> {
|
||||||
cx.global::<ModelHandle<Self>>().clone()
|
cx.global::<Model<Self>>().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: Model<UserStore>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let rpc_subscription =
|
let rpc_subscription =
|
||||||
client.add_message_handler(cx.handle(), Self::handle_update_channels);
|
client.add_message_handler(cx.weak_model(), Self::handle_update_channels);
|
||||||
|
|
||||||
let mut connection_status = client.status();
|
let mut connection_status = client.status();
|
||||||
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
let (update_channels_tx, mut update_channels_rx) = mpsc::unbounded();
|
||||||
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
|
let watch_connection_status = cx.spawn(|this, mut cx| async move {
|
||||||
while let Some(status) = connection_status.next().await {
|
while let Some(status) = connection_status.next().await {
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade()?;
|
||||||
match status {
|
match status {
|
||||||
client::Status::Connected { .. } => {
|
client::Status::Connected { .. } => {
|
||||||
this.update(&mut cx, |this, cx| this.handle_connect(cx))
|
this.update(&mut cx, |this, cx| this.handle_connect(cx))
|
||||||
|
.ok()?
|
||||||
.await
|
.await
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
}
|
}
|
||||||
client::Status::SignedOut | client::Status::UpgradeRequired => {
|
client::Status::SignedOut | client::Status::UpgradeRequired => {
|
||||||
this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx));
|
this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx));
|
this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx))
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,17 +173,22 @@ impl ChannelStore {
|
|||||||
_rpc_subscription: rpc_subscription,
|
_rpc_subscription: rpc_subscription,
|
||||||
_watch_connection_status: watch_connection_status,
|
_watch_connection_status: watch_connection_status,
|
||||||
disconnect_channel_buffers_task: None,
|
disconnect_channel_buffers_task: None,
|
||||||
_update_channels: cx.spawn_weak(|this, mut cx| async move {
|
_update_channels: cx.spawn(|this, mut cx| async move {
|
||||||
while let Some(update_channels) = update_channels_rx.next().await {
|
async_maybe!({
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
while let Some(update_channels) = update_channels_rx.next().await {
|
||||||
let update_task = this.update(&mut cx, |this, cx| {
|
if let Some(this) = this.upgrade() {
|
||||||
this.update_channels(update_channels, cx)
|
let update_task = this.update(&mut cx, |this, cx| {
|
||||||
});
|
this.update_channels(update_channels, cx)
|
||||||
if let Some(update_task) = update_task {
|
})?;
|
||||||
update_task.await.log_err();
|
if let Some(update_task) = update_task {
|
||||||
|
update_task.await.log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,10 +249,10 @@ impl ChannelStore {
|
|||||||
self.channel_index.by_id().get(&channel_id)
|
self.channel_index.by_id().get(&channel_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, cx: &AppContext) -> bool {
|
pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool {
|
||||||
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
if let Some(buffer) = self.opened_buffers.get(&channel_id) {
|
||||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||||
return buffer.upgrade(cx).is_some();
|
return buffer.upgrade().is_some();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@ -253,7 +262,7 @@ impl ChannelStore {
|
|||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<ModelHandle<ChannelBuffer>>> {
|
) -> Task<Result<Model<ChannelBuffer>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let channel_store = cx.handle();
|
let channel_store = cx.handle();
|
||||||
@ -278,13 +287,13 @@ impl ChannelStore {
|
|||||||
.request(proto::GetChannelMessagesById { message_ids }),
|
.request(proto::GetChannelMessagesById { message_ids }),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
if let Some(request) = request {
|
if let Some(request) = request {
|
||||||
let response = request.await?;
|
let response = request.await?;
|
||||||
let this = this
|
let this = this
|
||||||
.upgrade(&cx)
|
.upgrade()
|
||||||
.ok_or_else(|| anyhow!("channel store dropped"))?;
|
.ok_or_else(|| anyhow!("channel store dropped"))?;
|
||||||
let user_store = this.read_with(&cx, |this, _| this.user_store.clone());
|
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
||||||
ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
|
ChannelMessage::from_proto_vec(response.messages, &user_store, &mut cx).await
|
||||||
} else {
|
} else {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
@ -354,7 +363,7 @@ impl ChannelStore {
|
|||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<ModelHandle<ChannelChat>>> {
|
) -> Task<Result<Model<ChannelChat>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.clone();
|
let user_store = self.user_store.clone();
|
||||||
let this = cx.handle();
|
let this = cx.handle();
|
||||||
@ -371,22 +380,23 @@ impl ChannelStore {
|
|||||||
/// Make sure that the resource is only opened once, even if this method
|
/// Make sure that the resource is only opened once, even if this method
|
||||||
/// is called multiple times with the same channel id while the first task
|
/// is called multiple times with the same channel id while the first task
|
||||||
/// is still running.
|
/// is still running.
|
||||||
fn open_channel_resource<T: Entity, F, Fut>(
|
fn open_channel_resource<T, F, Fut>(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel_id: ChannelId,
|
channel_id: ChannelId,
|
||||||
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
|
get_map: fn(&mut Self) -> &mut HashMap<ChannelId, OpenedModelHandle<T>>,
|
||||||
load: F,
|
load: F,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<ModelHandle<T>>>
|
) -> Task<Result<Model<T>>>
|
||||||
where
|
where
|
||||||
F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
|
F: 'static + FnOnce(Arc<Channel>, AsyncAppContext) -> Fut,
|
||||||
Fut: Future<Output = Result<ModelHandle<T>>>,
|
Fut: Future<Output = Result<Model<T>>>,
|
||||||
|
T: 'static,
|
||||||
{
|
{
|
||||||
let task = loop {
|
let task = loop {
|
||||||
match get_map(self).entry(channel_id) {
|
match get_map(self).entry(channel_id) {
|
||||||
hash_map::Entry::Occupied(e) => match e.get() {
|
hash_map::Entry::Occupied(e) => match e.get() {
|
||||||
OpenedModelHandle::Open(model) => {
|
OpenedModelHandle::Open(model) => {
|
||||||
if let Some(model) = model.upgrade(cx) {
|
if let Some(model) = model.upgrade() {
|
||||||
break Task::ready(Ok(model)).shared();
|
break Task::ready(Ok(model)).shared();
|
||||||
} else {
|
} else {
|
||||||
get_map(self).remove(&channel_id);
|
get_map(self).remove(&channel_id);
|
||||||
@ -399,12 +409,12 @@ impl ChannelStore {
|
|||||||
},
|
},
|
||||||
hash_map::Entry::Vacant(e) => {
|
hash_map::Entry::Vacant(e) => {
|
||||||
let task = cx
|
let task = cx
|
||||||
.spawn(|this, cx| async move {
|
.spawn(move |this, mut cx| async move {
|
||||||
let channel = this.read_with(&cx, |this, _| {
|
let channel = this.update(&mut cx, |this, _| {
|
||||||
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
|
this.channel_for_id(channel_id).cloned().ok_or_else(|| {
|
||||||
Arc::new(anyhow!("no channel for id: {}", channel_id))
|
Arc::new(anyhow!("no channel for id: {}", channel_id))
|
||||||
})
|
})
|
||||||
})?;
|
})??;
|
||||||
|
|
||||||
load(channel, cx).await.map_err(Arc::new)
|
load(channel, cx).await.map_err(Arc::new)
|
||||||
})
|
})
|
||||||
@ -413,7 +423,7 @@ impl ChannelStore {
|
|||||||
e.insert(OpenedModelHandle::Loading(task.clone()));
|
e.insert(OpenedModelHandle::Loading(task.clone()));
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
let task = task.clone();
|
let task = task.clone();
|
||||||
|this, mut cx| async move {
|
move |this, mut cx| async move {
|
||||||
let result = task.await;
|
let result = task.await;
|
||||||
this.update(&mut cx, |this, _| match result {
|
this.update(&mut cx, |this, _| match result {
|
||||||
Ok(model) => {
|
Ok(model) => {
|
||||||
@ -425,7 +435,8 @@ impl ChannelStore {
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
get_map(this).remove(&channel_id);
|
get_map(this).remove(&channel_id);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
@ -433,7 +444,7 @@ impl ChannelStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cx.foreground()
|
cx.background_executor()
|
||||||
.spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
|
.spawn(async move { task.await.map_err(|error| anyhow!("{}", error)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +469,7 @@ impl ChannelStore {
|
|||||||
) -> Task<Result<ChannelId>> {
|
) -> Task<Result<ChannelId>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let name = name.trim_start_matches("#").to_owned();
|
let name = name.trim_start_matches("#").to_owned();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::CreateChannel { name, parent_id })
|
.request(proto::CreateChannel { name, parent_id })
|
||||||
.await?;
|
.await?;
|
||||||
@ -468,15 +479,6 @@ impl ChannelStore {
|
|||||||
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
.ok_or_else(|| anyhow!("missing channel in response"))?;
|
||||||
let channel_id = channel.id;
|
let channel_id = channel.id;
|
||||||
|
|
||||||
// let parent_edge = if let Some(parent_id) = parent_id {
|
|
||||||
// vec![ChannelEdge {
|
|
||||||
// channel_id: channel.id,
|
|
||||||
// parent_id,
|
|
||||||
// }]
|
|
||||||
// } else {
|
|
||||||
// vec![]
|
|
||||||
// };
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let task = this.update_channels(
|
let task = this.update_channels(
|
||||||
proto::UpdateChannels {
|
proto::UpdateChannels {
|
||||||
@ -492,7 +494,7 @@ impl ChannelStore {
|
|||||||
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
||||||
// ensures that the collab panel will observe this creation before the frame completes
|
// ensures that the collab panel will observe this creation before the frame completes
|
||||||
cx.emit(ChannelEvent::ChannelCreated(channel_id));
|
cx.emit(ChannelEvent::ChannelCreated(channel_id));
|
||||||
});
|
})?;
|
||||||
|
|
||||||
Ok(channel_id)
|
Ok(channel_id)
|
||||||
})
|
})
|
||||||
@ -505,7 +507,7 @@ impl ChannelStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.spawn(|_, _| async move {
|
cx.spawn(move |_, _| async move {
|
||||||
let _ = client
|
let _ = client
|
||||||
.request(proto::MoveChannel { channel_id, to })
|
.request(proto::MoveChannel { channel_id, to })
|
||||||
.await?;
|
.await?;
|
||||||
@ -521,7 +523,7 @@ impl ChannelStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.spawn(|_, _| async move {
|
cx.spawn(move |_, _| async move {
|
||||||
let _ = client
|
let _ = client
|
||||||
.request(proto::SetChannelVisibility {
|
.request(proto::SetChannelVisibility {
|
||||||
channel_id,
|
channel_id,
|
||||||
@ -546,7 +548,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let result = client
|
let result = client
|
||||||
.request(proto::InviteChannelMember {
|
.request(proto::InviteChannelMember {
|
||||||
channel_id,
|
channel_id,
|
||||||
@ -558,7 +560,7 @@ impl ChannelStore {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
|
|
||||||
@ -578,7 +580,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let result = client
|
let result = client
|
||||||
.request(proto::RemoveChannelMember {
|
.request(proto::RemoveChannelMember {
|
||||||
channel_id,
|
channel_id,
|
||||||
@ -589,7 +591,7 @@ impl ChannelStore {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -608,7 +610,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let result = client
|
let result = client
|
||||||
.request(proto::SetChannelMemberRole {
|
.request(proto::SetChannelMemberRole {
|
||||||
channel_id,
|
channel_id,
|
||||||
@ -620,7 +622,7 @@ impl ChannelStore {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.outgoing_invites.remove(&(channel_id, user_id));
|
this.outgoing_invites.remove(&(channel_id, user_id));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
|
|
||||||
result?;
|
result?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -635,7 +637,7 @@ impl ChannelStore {
|
|||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let name = new_name.to_string();
|
let name = new_name.to_string();
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let channel = client
|
let channel = client
|
||||||
.request(proto::RenameChannel { channel_id, name })
|
.request(proto::RenameChannel { channel_id, name })
|
||||||
.await?
|
.await?
|
||||||
@ -656,7 +658,7 @@ impl ChannelStore {
|
|||||||
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
// will resolve before this flush_effects finishes. Synchronously emitting this event
|
||||||
// ensures that the collab panel will observe this creation before the frame complete
|
// ensures that the collab panel will observe this creation before the frame complete
|
||||||
cx.emit(ChannelEvent::ChannelRenamed(channel_id))
|
cx.emit(ChannelEvent::ChannelRenamed(channel_id))
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -668,7 +670,7 @@ impl ChannelStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
cx.background().spawn(async move {
|
cx.background_executor().spawn(async move {
|
||||||
client
|
client
|
||||||
.request(proto::RespondToChannelInvite { channel_id, accept })
|
.request(proto::RespondToChannelInvite { channel_id, accept })
|
||||||
.await?;
|
.await?;
|
||||||
@ -683,17 +685,17 @@ impl ChannelStore {
|
|||||||
) -> Task<Result<Vec<ChannelMembership>>> {
|
) -> Task<Result<Vec<ChannelMembership>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let user_store = self.user_store.downgrade();
|
let user_store = self.user_store.downgrade();
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(move |_, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.request(proto::GetChannelMembers { channel_id })
|
.request(proto::GetChannelMembers { channel_id })
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user_ids = response.members.iter().map(|m| m.user_id).collect();
|
let user_ids = response.members.iter().map(|m| m.user_id).collect();
|
||||||
let user_store = user_store
|
let user_store = user_store
|
||||||
.upgrade(&cx)
|
.upgrade()
|
||||||
.ok_or_else(|| anyhow!("user store dropped"))?;
|
.ok_or_else(|| anyhow!("user store dropped"))?;
|
||||||
let users = user_store
|
let users = user_store
|
||||||
.update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))
|
.update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))?
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(users
|
Ok(users
|
||||||
@ -727,7 +729,7 @@ impl ChannelStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_channels(
|
async fn handle_update_channels(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateChannels>,
|
message: TypedEnvelope<proto::UpdateChannels>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -736,7 +738,7 @@ impl ChannelStore {
|
|||||||
this.update_channels_tx
|
this.update_channels_tx
|
||||||
.unbounded_send(message.payload)
|
.unbounded_send(message.payload)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,7 +752,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
for chat in self.opened_chats.values() {
|
for chat in self.opened_chats.values() {
|
||||||
if let OpenedModelHandle::Open(chat) = chat {
|
if let OpenedModelHandle::Open(chat) = chat {
|
||||||
if let Some(chat) = chat.upgrade(cx) {
|
if let Some(chat) = chat.upgrade() {
|
||||||
chat.update(cx, |chat, cx| {
|
chat.update(cx, |chat, cx| {
|
||||||
chat.rejoin(cx);
|
chat.rejoin(cx);
|
||||||
});
|
});
|
||||||
@ -761,7 +763,7 @@ impl ChannelStore {
|
|||||||
let mut buffer_versions = Vec::new();
|
let mut buffer_versions = Vec::new();
|
||||||
for buffer in self.opened_buffers.values() {
|
for buffer in self.opened_buffers.values() {
|
||||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade() {
|
||||||
let channel_buffer = buffer.read(cx);
|
let channel_buffer = buffer.read(cx);
|
||||||
let buffer = channel_buffer.buffer().read(cx);
|
let buffer = channel_buffer.buffer().read(cx);
|
||||||
buffer_versions.push(proto::ChannelBufferVersion {
|
buffer_versions.push(proto::ChannelBufferVersion {
|
||||||
@ -787,7 +789,7 @@ impl ChannelStore {
|
|||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.opened_buffers.retain(|_, buffer| match buffer {
|
this.opened_buffers.retain(|_, buffer| match buffer {
|
||||||
OpenedModelHandle::Open(channel_buffer) => {
|
OpenedModelHandle::Open(channel_buffer) => {
|
||||||
let Some(channel_buffer) = channel_buffer.upgrade(cx) else {
|
let Some(channel_buffer) = channel_buffer.upgrade() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -824,7 +826,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
if let Some(operations) = operations {
|
if let Some(operations) = operations {
|
||||||
let client = this.client.clone();
|
let client = this.client.clone();
|
||||||
cx.background()
|
cx.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let operations = operations.await;
|
let operations = operations.await;
|
||||||
for chunk in
|
for chunk in
|
||||||
@ -849,7 +851,8 @@ impl ChannelStore {
|
|||||||
}
|
}
|
||||||
OpenedModelHandle::Loading(_) => true,
|
OpenedModelHandle::Loading(_) => true,
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -858,21 +861,22 @@ impl ChannelStore {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
self.disconnect_channel_buffers_task.get_or_insert_with(|| {
|
self.disconnect_channel_buffers_task.get_or_insert_with(|| {
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
if wait_for_reconnect {
|
if wait_for_reconnect {
|
||||||
cx.background().timer(RECONNECT_TIMEOUT).await;
|
cx.background_executor().timer(RECONNECT_TIMEOUT).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Some(this) = this.upgrade() {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
for (_, buffer) in this.opened_buffers.drain() {
|
for (_, buffer) in this.opened_buffers.drain() {
|
||||||
if let OpenedModelHandle::Open(buffer) = buffer {
|
if let OpenedModelHandle::Open(buffer) = buffer {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade() {
|
||||||
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
|
buffer.update(cx, |buffer, cx| buffer.disconnect(cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -892,14 +896,16 @@ impl ChannelStore {
|
|||||||
.channel_invitations
|
.channel_invitations
|
||||||
.binary_search_by_key(&channel.id, |c| c.id)
|
.binary_search_by_key(&channel.id, |c| c.id)
|
||||||
{
|
{
|
||||||
Ok(ix) => Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name,
|
Ok(ix) => {
|
||||||
|
Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into()
|
||||||
|
}
|
||||||
Err(ix) => self.channel_invitations.insert(
|
Err(ix) => self.channel_invitations.insert(
|
||||||
ix,
|
ix,
|
||||||
Arc::new(Channel {
|
Arc::new(Channel {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
visibility: channel.visibility(),
|
visibility: channel.visibility(),
|
||||||
role: channel.role(),
|
role: channel.role(),
|
||||||
name: channel.name,
|
name: channel.name.into(),
|
||||||
unseen_note_version: None,
|
unseen_note_version: None,
|
||||||
unseen_message_id: None,
|
unseen_message_id: None,
|
||||||
parent_path: channel.parent_path,
|
parent_path: channel.parent_path,
|
||||||
@ -931,7 +937,7 @@ impl ChannelStore {
|
|||||||
if let Some(OpenedModelHandle::Open(buffer)) =
|
if let Some(OpenedModelHandle::Open(buffer)) =
|
||||||
self.opened_buffers.remove(&channel_id)
|
self.opened_buffers.remove(&channel_id)
|
||||||
{
|
{
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade() {
|
||||||
buffer.update(cx, ChannelBuffer::disconnect);
|
buffer.update(cx, ChannelBuffer::disconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -945,7 +951,7 @@ impl ChannelStore {
|
|||||||
|
|
||||||
if channel_changed {
|
if channel_changed {
|
||||||
if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
|
if let Some(OpenedModelHandle::Open(buffer)) = self.opened_buffers.get(&id) {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade() {
|
||||||
buffer.update(cx, ChannelBuffer::channel_changed);
|
buffer.update(cx, ChannelBuffer::channel_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1010,8 +1016,7 @@ impl ChannelStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})
|
||||||
anyhow::Ok(())
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
|||||||
|
|
||||||
existing_channel.visibility = channel_proto.visibility();
|
existing_channel.visibility = channel_proto.visibility();
|
||||||
existing_channel.role = channel_proto.role();
|
existing_channel.role = channel_proto.role();
|
||||||
existing_channel.name = channel_proto.name;
|
existing_channel.name = channel_proto.name.into();
|
||||||
} else {
|
} else {
|
||||||
self.channels_by_id.insert(
|
self.channels_by_id.insert(
|
||||||
channel_proto.id,
|
channel_proto.id,
|
||||||
@ -112,7 +112,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
|
|||||||
id: channel_proto.id,
|
id: channel_proto.id,
|
||||||
visibility: channel_proto.visibility(),
|
visibility: channel_proto.visibility(),
|
||||||
role: channel_proto.role(),
|
role: channel_proto.role(),
|
||||||
name: channel_proto.name,
|
name: channel_proto.name.into(),
|
||||||
unseen_note_version: None,
|
unseen_note_version: None,
|
||||||
unseen_message_id: None,
|
unseen_message_id: None,
|
||||||
parent_path: channel_proto.parent_path,
|
parent_path: channel_proto.parent_path,
|
||||||
@ -146,11 +146,11 @@ fn channel_path_sorting_key<'a>(
|
|||||||
let (parent_path, name) = channels_by_id
|
let (parent_path, name) = channels_by_id
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.map_or((&[] as &[_], None), |channel| {
|
.map_or((&[] as &[_], None), |channel| {
|
||||||
(channel.parent_path.as_slice(), Some(channel.name.as_str()))
|
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
||||||
});
|
});
|
||||||
parent_path
|
parent_path
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_str()))
|
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
||||||
.chain(name)
|
.chain(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use crate::channel_chat::ChannelChatEvent;
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use client::{test::FakeServer, Client, UserStore};
|
use client::{test::FakeServer, Client, UserStore};
|
||||||
use gpui::{AppContext, ModelHandle, TestAppContext};
|
use gpui::{AppContext, Context, Model, TestAppContext};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use util::http::FakeHttpClient;
|
use util::http::FakeHttpClient;
|
||||||
@ -147,7 +147,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
let user_id = 5;
|
let user_id = 5;
|
||||||
let channel_id = 5;
|
let channel_id = 5;
|
||||||
let channel_store = cx.update(init_test);
|
let channel_store = cx.update(init_test);
|
||||||
let client = channel_store.read_with(cx, |s, _| s.client());
|
let client = channel_store.update(cx, |s, _| s.client());
|
||||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
let server = FakeServer::for_client(user_id, &client, cx).await;
|
||||||
|
|
||||||
// Get the available channels.
|
// Get the available channels.
|
||||||
@ -161,8 +161,8 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
cx.foreground().run_until_parked();
|
cx.executor().run_until_parked();
|
||||||
cx.read(|cx| {
|
cx.update(|cx| {
|
||||||
assert_channels(
|
assert_channels(
|
||||||
&channel_store,
|
&channel_store,
|
||||||
&[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
|
&[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
|
||||||
@ -214,7 +214,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.foreground().start_waiting();
|
cx.executor().start_waiting();
|
||||||
|
|
||||||
// Client requests all users for the received messages
|
// Client requests all users for the received messages
|
||||||
let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
||||||
@ -232,7 +232,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let channel = channel.await.unwrap();
|
let channel = channel.await.unwrap();
|
||||||
channel.read_with(cx, |channel, _| {
|
channel.update(cx, |channel, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel
|
channel
|
||||||
.messages_in_range(0..2)
|
.messages_in_range(0..2)
|
||||||
@ -273,13 +273,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel.next_event(cx).await,
|
channel.next_event(cx),
|
||||||
ChannelChatEvent::MessagesUpdated {
|
ChannelChatEvent::MessagesUpdated {
|
||||||
old_range: 2..2,
|
old_range: 2..2,
|
||||||
new_count: 1,
|
new_count: 1,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
channel.read_with(cx, |channel, _| {
|
channel.update(cx, |channel, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel
|
channel
|
||||||
.messages_in_range(2..3)
|
.messages_in_range(2..3)
|
||||||
@ -322,13 +322,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel.next_event(cx).await,
|
channel.next_event(cx),
|
||||||
ChannelChatEvent::MessagesUpdated {
|
ChannelChatEvent::MessagesUpdated {
|
||||||
old_range: 0..0,
|
old_range: 0..0,
|
||||||
new_count: 2,
|
new_count: 2,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
channel.read_with(cx, |channel, _| {
|
channel.update(cx, |channel, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
channel
|
channel
|
||||||
.messages_in_range(0..2)
|
.messages_in_range(0..2)
|
||||||
@ -342,13 +342,13 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
|
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
||||||
let http = FakeHttpClient::with_404_response();
|
let http = FakeHttpClient::with_404_response();
|
||||||
let client = Client::new(http.clone(), cx);
|
let client = Client::new(http.clone(), cx);
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
|
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||||
|
|
||||||
cx.foreground().forbid_parking();
|
let settings_store = SettingsStore::test(cx);
|
||||||
cx.set_global(SettingsStore::test(cx));
|
cx.set_global(settings_store);
|
||||||
client::init(&client, cx);
|
client::init(&client, cx);
|
||||||
crate::init(&client, user_store, cx);
|
crate::init(&client, user_store, cx);
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ fn init_test(cx: &mut AppContext) -> ModelHandle<ChannelStore> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_channels(
|
fn update_channels(
|
||||||
channel_store: &ModelHandle<ChannelStore>,
|
channel_store: &Model<ChannelStore>,
|
||||||
message: proto::UpdateChannels,
|
message: proto::UpdateChannels,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
@ -366,11 +366,11 @@ fn update_channels(
|
|||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_channels(
|
fn assert_channels(
|
||||||
channel_store: &ModelHandle<ChannelStore>,
|
channel_store: &Model<ChannelStore>,
|
||||||
expected_channels: &[(usize, String, proto::ChannelRole)],
|
expected_channels: &[(usize, String, proto::ChannelRole)],
|
||||||
cx: &AppContext,
|
cx: &mut AppContext,
|
||||||
) {
|
) {
|
||||||
let actual = channel_store.read_with(cx, |store, _| {
|
let actual = channel_store.update(cx, |store, _| {
|
||||||
store
|
store
|
||||||
.ordered_channels()
|
.ordered_channels()
|
||||||
.map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
|
.map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "channel2"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/channel2.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
client = { package = "client2", path = "../client2" }
|
|
||||||
collections = { path = "../collections" }
|
|
||||||
db = { package = "db2", path = "../db2" }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
rpc = { package = "rpc2", path = "../rpc2" }
|
|
||||||
text = { package = "text2", path = "../text2" }
|
|
||||||
language = { path = "../language" }
|
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
|
||||||
feature_flags = { path = "../feature_flags" }
|
|
||||||
sum_tree = { path = "../sum_tree" }
|
|
||||||
clock = { path = "../clock" }
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
|
||||||
futures.workspace = true
|
|
||||||
image = "0.23"
|
|
||||||
lazy_static.workspace = true
|
|
||||||
smallvec.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
parking_lot.workspace = true
|
|
||||||
postage.workspace = true
|
|
||||||
rand.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
smol.workspace = true
|
|
||||||
thiserror.workspace = true
|
|
||||||
time.workspace = true
|
|
||||||
tiny_http = "0.8"
|
|
||||||
uuid.workspace = true
|
|
||||||
url = "2.2"
|
|
||||||
serde.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
tempfile = "3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
|
||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
|
||||||
util = { path = "../util", features = ["test-support"] }
|
|
@ -1,23 +0,0 @@
|
|||||||
mod channel_buffer;
|
|
||||||
mod channel_chat;
|
|
||||||
mod channel_store;
|
|
||||||
|
|
||||||
use client::{Client, UserStore};
|
|
||||||
use gpui::{AppContext, Model};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub use channel_buffer::{ChannelBuffer, ChannelBufferEvent, ACKNOWLEDGE_DEBOUNCE_INTERVAL};
|
|
||||||
pub use channel_chat::{
|
|
||||||
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
|
|
||||||
MessageParams,
|
|
||||||
};
|
|
||||||
pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod channel_store_tests;
|
|
||||||
|
|
||||||
pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
|
|
||||||
channel_store::init(client, user_store, cx);
|
|
||||||
channel_buffer::init(client);
|
|
||||||
channel_chat::init(client);
|
|
||||||
}
|
|
@ -1,257 +0,0 @@
|
|||||||
use crate::{Channel, ChannelId, ChannelStore};
|
|
||||||
use anyhow::Result;
|
|
||||||
use client::{Client, Collaborator, UserStore};
|
|
||||||
use collections::HashMap;
|
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
|
||||||
use language::proto::serialize_version;
|
|
||||||
use rpc::{
|
|
||||||
proto::{self, PeerId},
|
|
||||||
TypedEnvelope,
|
|
||||||
};
|
|
||||||
use std::{sync::Arc, time::Duration};
|
|
||||||
use util::ResultExt;
|
|
||||||
|
|
||||||
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
|
|
||||||
|
|
||||||
pub(crate) fn init(client: &Arc<Client>) {
|
|
||||||
client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer);
|
|
||||||
client.add_model_message_handler(ChannelBuffer::handle_update_channel_buffer_collaborators);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ChannelBuffer {
|
|
||||||
pub channel_id: ChannelId,
|
|
||||||
connected: bool,
|
|
||||||
collaborators: HashMap<PeerId, Collaborator>,
|
|
||||||
user_store: Model<UserStore>,
|
|
||||||
channel_store: Model<ChannelStore>,
|
|
||||||
buffer: Model<language::Buffer>,
|
|
||||||
buffer_epoch: u64,
|
|
||||||
client: Arc<Client>,
|
|
||||||
subscription: Option<client::Subscription>,
|
|
||||||
acknowledge_task: Option<Task<Result<()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ChannelBufferEvent {
|
|
||||||
CollaboratorsChanged,
|
|
||||||
Disconnected,
|
|
||||||
BufferEdited,
|
|
||||||
ChannelChanged,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ChannelBufferEvent> for ChannelBuffer {}
|
|
||||||
|
|
||||||
impl ChannelBuffer {
|
|
||||||
pub(crate) async fn new(
|
|
||||||
channel: Arc<Channel>,
|
|
||||||
client: Arc<Client>,
|
|
||||||
user_store: Model<UserStore>,
|
|
||||||
channel_store: Model<ChannelStore>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<Model<Self>> {
|
|
||||||
let response = client
|
|
||||||
.request(proto::JoinChannelBuffer {
|
|
||||||
channel_id: channel.id,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let base_text = response.base_text;
|
|
||||||
let operations = response
|
|
||||||
.operations
|
|
||||||
.into_iter()
|
|
||||||
.map(language::proto::deserialize_operation)
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
let buffer = cx.new_model(|_| {
|
|
||||||
language::Buffer::remote(response.buffer_id, response.replica_id as u16, base_text)
|
|
||||||
})?;
|
|
||||||
buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??;
|
|
||||||
|
|
||||||
let subscription = client.subscribe_to_entity(channel.id)?;
|
|
||||||
|
|
||||||
anyhow::Ok(cx.new_model(|cx| {
|
|
||||||
cx.subscribe(&buffer, Self::on_buffer_update).detach();
|
|
||||||
cx.on_release(Self::release).detach();
|
|
||||||
let mut this = Self {
|
|
||||||
buffer,
|
|
||||||
buffer_epoch: response.epoch,
|
|
||||||
client,
|
|
||||||
connected: true,
|
|
||||||
collaborators: Default::default(),
|
|
||||||
acknowledge_task: None,
|
|
||||||
channel_id: channel.id,
|
|
||||||
subscription: Some(subscription.set_model(&cx.handle(), &mut cx.to_async())),
|
|
||||||
user_store,
|
|
||||||
channel_store,
|
|
||||||
};
|
|
||||||
this.replace_collaborators(response.collaborators, cx);
|
|
||||||
this
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release(&mut self, _: &mut AppContext) {
|
|
||||||
if self.connected {
|
|
||||||
if let Some(task) = self.acknowledge_task.take() {
|
|
||||||
task.detach();
|
|
||||||
}
|
|
||||||
self.client
|
|
||||||
.send(proto::LeaveChannelBuffer {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remote_id(&self, cx: &AppContext) -> u64 {
|
|
||||||
self.buffer.read(cx).remote_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn user_store(&self) -> &Model<UserStore> {
|
|
||||||
&self.user_store
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn replace_collaborators(
|
|
||||||
&mut self,
|
|
||||||
collaborators: Vec<proto::Collaborator>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
let mut new_collaborators = HashMap::default();
|
|
||||||
for collaborator in collaborators {
|
|
||||||
if let Ok(collaborator) = Collaborator::from_proto(collaborator) {
|
|
||||||
new_collaborators.insert(collaborator.peer_id, collaborator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, old_collaborator) in &self.collaborators {
|
|
||||||
if !new_collaborators.contains_key(&old_collaborator.peer_id) {
|
|
||||||
self.buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.remove_peer(old_collaborator.replica_id as u16, cx)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.collaborators = new_collaborators;
|
|
||||||
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_update_channel_buffer(
|
|
||||||
this: Model<Self>,
|
|
||||||
update_channel_buffer: TypedEnvelope<proto::UpdateChannelBuffer>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let ops = update_channel_buffer
|
|
||||||
.payload
|
|
||||||
.operations
|
|
||||||
.into_iter()
|
|
||||||
.map(language::proto::deserialize_operation)
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
cx.notify();
|
|
||||||
this.buffer
|
|
||||||
.update(cx, |buffer, cx| buffer.apply_ops(ops, cx))
|
|
||||||
})??;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_update_channel_buffer_collaborators(
|
|
||||||
this: Model<Self>,
|
|
||||||
message: TypedEnvelope<proto::UpdateChannelBufferCollaborators>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.replace_collaborators(message.payload.collaborators, cx);
|
|
||||||
cx.emit(ChannelBufferEvent::CollaboratorsChanged);
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_buffer_update(
|
|
||||||
&mut self,
|
|
||||||
_: Model<language::Buffer>,
|
|
||||||
event: &language::Event,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
language::Event::Operation(operation) => {
|
|
||||||
let operation = language::proto::serialize_operation(operation);
|
|
||||||
self.client
|
|
||||||
.send(proto::UpdateChannelBuffer {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
operations: vec![operation],
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
language::Event::Edited => {
|
|
||||||
cx.emit(ChannelBufferEvent::BufferEdited);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_buffer_version(&mut self, cx: &mut ModelContext<'_, ChannelBuffer>) {
|
|
||||||
let buffer = self.buffer.read(cx);
|
|
||||||
let version = buffer.version();
|
|
||||||
let buffer_id = buffer.remote_id();
|
|
||||||
let client = self.client.clone();
|
|
||||||
let epoch = self.epoch();
|
|
||||||
|
|
||||||
self.acknowledge_task = Some(cx.spawn(move |_, cx| async move {
|
|
||||||
cx.background_executor()
|
|
||||||
.timer(ACKNOWLEDGE_DEBOUNCE_INTERVAL)
|
|
||||||
.await;
|
|
||||||
client
|
|
||||||
.send(proto::AckBufferOperation {
|
|
||||||
buffer_id,
|
|
||||||
epoch,
|
|
||||||
version: serialize_version(&version),
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn epoch(&self) -> u64 {
|
|
||||||
self.buffer_epoch
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer(&self) -> Model<language::Buffer> {
|
|
||||||
self.buffer.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collaborators(&self) -> &HashMap<PeerId, Collaborator> {
|
|
||||||
&self.collaborators
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
|
||||||
self.channel_store
|
|
||||||
.read(cx)
|
|
||||||
.channel_for_id(self.channel_id)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn disconnect(&mut self, cx: &mut ModelContext<Self>) {
|
|
||||||
log::info!("channel buffer {} disconnected", self.channel_id);
|
|
||||||
if self.connected {
|
|
||||||
self.connected = false;
|
|
||||||
self.subscription.take();
|
|
||||||
cx.emit(ChannelBufferEvent::Disconnected);
|
|
||||||
cx.notify()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn channel_changed(&mut self, cx: &mut ModelContext<Self>) {
|
|
||||||
cx.emit(ChannelBufferEvent::ChannelChanged);
|
|
||||||
cx.notify()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_connected(&self) -> bool {
|
|
||||||
self.connected
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replica_id(&self, cx: &AppContext) -> u16 {
|
|
||||||
self.buffer.read(cx).replica_id()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,645 +0,0 @@
|
|||||||
use crate::{Channel, ChannelId, ChannelStore};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use client::{
|
|
||||||
proto,
|
|
||||||
user::{User, UserStore},
|
|
||||||
Client, Subscription, TypedEnvelope, UserId,
|
|
||||||
};
|
|
||||||
use futures::lock::Mutex;
|
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
|
|
||||||
use rand::prelude::*;
|
|
||||||
use std::{
|
|
||||||
collections::HashSet,
|
|
||||||
mem,
|
|
||||||
ops::{ControlFlow, Range},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use sum_tree::{Bias, SumTree};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
use util::{post_inc, ResultExt as _, TryFutureExt};
|
|
||||||
|
|
||||||
pub struct ChannelChat {
|
|
||||||
pub channel_id: ChannelId,
|
|
||||||
messages: SumTree<ChannelMessage>,
|
|
||||||
acknowledged_message_ids: HashSet<u64>,
|
|
||||||
channel_store: Model<ChannelStore>,
|
|
||||||
loaded_all_messages: bool,
|
|
||||||
last_acknowledged_id: Option<u64>,
|
|
||||||
next_pending_message_id: usize,
|
|
||||||
user_store: Model<UserStore>,
|
|
||||||
rpc: Arc<Client>,
|
|
||||||
outgoing_messages_lock: Arc<Mutex<()>>,
|
|
||||||
rng: StdRng,
|
|
||||||
_subscription: Subscription,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct MessageParams {
|
|
||||||
pub text: String,
|
|
||||||
pub mentions: Vec<(Range<usize>, UserId)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ChannelMessage {
|
|
||||||
pub id: ChannelMessageId,
|
|
||||||
pub body: String,
|
|
||||||
pub timestamp: OffsetDateTime,
|
|
||||||
pub sender: Arc<User>,
|
|
||||||
pub nonce: u128,
|
|
||||||
pub mentions: Vec<(Range<usize>, UserId)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum ChannelMessageId {
|
|
||||||
Saved(u64),
|
|
||||||
Pending(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct ChannelMessageSummary {
|
|
||||||
max_id: ChannelMessageId,
|
|
||||||
count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct Count(usize);
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ChannelChatEvent {
|
|
||||||
MessagesUpdated {
|
|
||||||
old_range: Range<usize>,
|
|
||||||
new_count: usize,
|
|
||||||
},
|
|
||||||
NewMessage {
|
|
||||||
channel_id: ChannelId,
|
|
||||||
message_id: u64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<ChannelChatEvent> for ChannelChat {}
|
|
||||||
pub fn init(client: &Arc<Client>) {
|
|
||||||
client.add_model_message_handler(ChannelChat::handle_message_sent);
|
|
||||||
client.add_model_message_handler(ChannelChat::handle_message_removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelChat {
|
|
||||||
pub async fn new(
|
|
||||||
channel: Arc<Channel>,
|
|
||||||
channel_store: Model<ChannelStore>,
|
|
||||||
user_store: Model<UserStore>,
|
|
||||||
client: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<Model<Self>> {
|
|
||||||
let channel_id = channel.id;
|
|
||||||
let subscription = client.subscribe_to_entity(channel_id).unwrap();
|
|
||||||
|
|
||||||
let response = client
|
|
||||||
.request(proto::JoinChannelChat { channel_id })
|
|
||||||
.await?;
|
|
||||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
|
||||||
let loaded_all_messages = response.done;
|
|
||||||
|
|
||||||
Ok(cx.new_model(|cx| {
|
|
||||||
cx.on_release(Self::release).detach();
|
|
||||||
let mut this = Self {
|
|
||||||
channel_id: channel.id,
|
|
||||||
user_store,
|
|
||||||
channel_store,
|
|
||||||
rpc: client,
|
|
||||||
outgoing_messages_lock: Default::default(),
|
|
||||||
messages: Default::default(),
|
|
||||||
acknowledged_message_ids: Default::default(),
|
|
||||||
loaded_all_messages,
|
|
||||||
next_pending_message_id: 0,
|
|
||||||
last_acknowledged_id: None,
|
|
||||||
rng: StdRng::from_entropy(),
|
|
||||||
_subscription: subscription.set_model(&cx.handle(), &mut cx.to_async()),
|
|
||||||
};
|
|
||||||
this.insert_messages(messages, cx);
|
|
||||||
this
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release(&mut self, _: &mut AppContext) {
|
|
||||||
self.rpc
|
|
||||||
.send(proto::LeaveChannelChat {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
|
||||||
self.channel_store
|
|
||||||
.read(cx)
|
|
||||||
.channel_for_id(self.channel_id)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client(&self) -> &Arc<Client> {
|
|
||||||
&self.rpc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_message(
|
|
||||||
&mut self,
|
|
||||||
message: MessageParams,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Result<Task<Result<u64>>> {
|
|
||||||
if message.text.is_empty() {
|
|
||||||
Err(anyhow!("message body can't be empty"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_user = self
|
|
||||||
.user_store
|
|
||||||
.read(cx)
|
|
||||||
.current_user()
|
|
||||||
.ok_or_else(|| anyhow!("current_user is not present"))?;
|
|
||||||
|
|
||||||
let channel_id = self.channel_id;
|
|
||||||
let pending_id = ChannelMessageId::Pending(post_inc(&mut self.next_pending_message_id));
|
|
||||||
let nonce = self.rng.gen();
|
|
||||||
self.insert_messages(
|
|
||||||
SumTree::from_item(
|
|
||||||
ChannelMessage {
|
|
||||||
id: pending_id,
|
|
||||||
body: message.text.clone(),
|
|
||||||
sender: current_user,
|
|
||||||
timestamp: OffsetDateTime::now_utc(),
|
|
||||||
mentions: message.mentions.clone(),
|
|
||||||
nonce,
|
|
||||||
},
|
|
||||||
&(),
|
|
||||||
),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let rpc = self.rpc.clone();
|
|
||||||
let outgoing_messages_lock = self.outgoing_messages_lock.clone();
|
|
||||||
Ok(cx.spawn(move |this, mut cx| async move {
|
|
||||||
let outgoing_message_guard = outgoing_messages_lock.lock().await;
|
|
||||||
let request = rpc.request(proto::SendChannelMessage {
|
|
||||||
channel_id,
|
|
||||||
body: message.text,
|
|
||||||
nonce: Some(nonce.into()),
|
|
||||||
mentions: mentions_to_proto(&message.mentions),
|
|
||||||
});
|
|
||||||
let response = request.await?;
|
|
||||||
drop(outgoing_message_guard);
|
|
||||||
let response = response.message.ok_or_else(|| anyhow!("invalid message"))?;
|
|
||||||
let id = response.id;
|
|
||||||
let message = ChannelMessage::from_proto(response, &user_store, &mut cx).await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
|
||||||
})?;
|
|
||||||
Ok(id)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
|
||||||
let response = self.rpc.request(proto::RemoveChannelMessage {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
message_id: id,
|
|
||||||
});
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
|
||||||
response.await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.message_removed(id, cx);
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_more_messages(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<Option<()>>> {
|
|
||||||
if self.loaded_all_messages {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rpc = self.rpc.clone();
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let channel_id = self.channel_id;
|
|
||||||
let before_message_id = self.first_loaded_message_id()?;
|
|
||||||
Some(cx.spawn(move |this, mut cx| {
|
|
||||||
async move {
|
|
||||||
let response = rpc
|
|
||||||
.request(proto::GetChannelMessages {
|
|
||||||
channel_id,
|
|
||||||
before_message_id,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let loaded_all_messages = response.done;
|
|
||||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.loaded_all_messages = loaded_all_messages;
|
|
||||||
this.insert_messages(messages, cx);
|
|
||||||
})?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
}
|
|
||||||
.log_err()
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first_loaded_message_id(&mut self) -> Option<u64> {
|
|
||||||
self.messages.first().and_then(|message| match message.id {
|
|
||||||
ChannelMessageId::Saved(id) => Some(id),
|
|
||||||
ChannelMessageId::Pending(_) => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load all of the chat messages since a certain message id.
|
|
||||||
///
|
|
||||||
/// For now, we always maintain a suffix of the channel's messages.
|
|
||||||
pub async fn load_history_since_message(
|
|
||||||
chat: Model<Self>,
|
|
||||||
message_id: u64,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Option<usize> {
|
|
||||||
loop {
|
|
||||||
let step = chat
|
|
||||||
.update(&mut cx, |chat, cx| {
|
|
||||||
if let Some(first_id) = chat.first_loaded_message_id() {
|
|
||||||
if first_id <= message_id {
|
|
||||||
let mut cursor = chat.messages.cursor::<(ChannelMessageId, Count)>();
|
|
||||||
let message_id = ChannelMessageId::Saved(message_id);
|
|
||||||
cursor.seek(&message_id, Bias::Left, &());
|
|
||||||
return ControlFlow::Break(
|
|
||||||
if cursor
|
|
||||||
.item()
|
|
||||||
.map_or(false, |message| message.id == message_id)
|
|
||||||
{
|
|
||||||
Some(cursor.start().1 .0)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::Continue(chat.load_more_messages(cx))
|
|
||||||
})
|
|
||||||
.log_err()?;
|
|
||||||
match step {
|
|
||||||
ControlFlow::Break(ix) => return ix,
|
|
||||||
ControlFlow::Continue(task) => task?.await?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_last_message(&mut self, cx: &mut ModelContext<Self>) {
|
|
||||||
if let ChannelMessageId::Saved(latest_message_id) = self.messages.summary().max_id {
|
|
||||||
if self
|
|
||||||
.last_acknowledged_id
|
|
||||||
.map_or(true, |acknowledged_id| acknowledged_id < latest_message_id)
|
|
||||||
{
|
|
||||||
self.rpc
|
|
||||||
.send(proto::AckChannelMessage {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
message_id: latest_message_id,
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
self.last_acknowledged_id = Some(latest_message_id);
|
|
||||||
self.channel_store.update(cx, |store, cx| {
|
|
||||||
store.acknowledge_message_id(self.channel_id, latest_message_id, cx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rejoin(&mut self, cx: &mut ModelContext<Self>) {
|
|
||||||
let user_store = self.user_store.clone();
|
|
||||||
let rpc = self.rpc.clone();
|
|
||||||
let channel_id = self.channel_id;
|
|
||||||
cx.spawn(move |this, mut cx| {
|
|
||||||
async move {
|
|
||||||
let response = rpc.request(proto::JoinChannelChat { channel_id }).await?;
|
|
||||||
let messages = messages_from_proto(response.messages, &user_store, &mut cx).await?;
|
|
||||||
let loaded_all_messages = response.done;
|
|
||||||
|
|
||||||
let pending_messages = this.update(&mut cx, |this, cx| {
|
|
||||||
if let Some((first_new_message, last_old_message)) =
|
|
||||||
messages.first().zip(this.messages.last())
|
|
||||||
{
|
|
||||||
if first_new_message.id > last_old_message.id {
|
|
||||||
let old_messages = mem::take(&mut this.messages);
|
|
||||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: 0..old_messages.summary().count,
|
|
||||||
new_count: 0,
|
|
||||||
});
|
|
||||||
this.loaded_all_messages = loaded_all_messages;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.insert_messages(messages, cx);
|
|
||||||
if loaded_all_messages {
|
|
||||||
this.loaded_all_messages = loaded_all_messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pending_messages().cloned().collect::<Vec<_>>()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for pending_message in pending_messages {
|
|
||||||
let request = rpc.request(proto::SendChannelMessage {
|
|
||||||
channel_id,
|
|
||||||
body: pending_message.body,
|
|
||||||
mentions: mentions_to_proto(&pending_message.mentions),
|
|
||||||
nonce: Some(pending_message.nonce.into()),
|
|
||||||
});
|
|
||||||
let response = request.await?;
|
|
||||||
let message = ChannelMessage::from_proto(
|
|
||||||
response.message.ok_or_else(|| anyhow!("invalid message"))?,
|
|
||||||
&user_store,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(())
|
|
||||||
}
|
|
||||||
.log_err()
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message_count(&self) -> usize {
|
|
||||||
self.messages.summary().count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn messages(&self) -> &SumTree<ChannelMessage> {
|
|
||||||
&self.messages
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message(&self, ix: usize) -> &ChannelMessage {
|
|
||||||
let mut cursor = self.messages.cursor::<Count>();
|
|
||||||
cursor.seek(&Count(ix), Bias::Right, &());
|
|
||||||
cursor.item().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_message(&mut self, id: u64) {
|
|
||||||
if self.acknowledged_message_ids.insert(id) {
|
|
||||||
self.rpc
|
|
||||||
.send(proto::AckChannelMessage {
|
|
||||||
channel_id: self.channel_id,
|
|
||||||
message_id: id,
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn messages_in_range(&self, range: Range<usize>) -> impl Iterator<Item = &ChannelMessage> {
|
|
||||||
let mut cursor = self.messages.cursor::<Count>();
|
|
||||||
cursor.seek(&Count(range.start), Bias::Right, &());
|
|
||||||
cursor.take(range.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pending_messages(&self) -> impl Iterator<Item = &ChannelMessage> {
|
|
||||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
|
||||||
cursor.seek(&ChannelMessageId::Pending(0), Bias::Left, &());
|
|
||||||
cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_message_sent(
|
|
||||||
this: Model<Self>,
|
|
||||||
message: TypedEnvelope<proto::ChannelMessageSent>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
let user_store = this.update(&mut cx, |this, _| this.user_store.clone())?;
|
|
||||||
let message = message
|
|
||||||
.payload
|
|
||||||
.message
|
|
||||||
.ok_or_else(|| anyhow!("empty message"))?;
|
|
||||||
let message_id = message.id;
|
|
||||||
|
|
||||||
let message = ChannelMessage::from_proto(message, &user_store, &mut cx).await?;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.insert_messages(SumTree::from_item(message, &()), cx);
|
|
||||||
cx.emit(ChannelChatEvent::NewMessage {
|
|
||||||
channel_id: this.channel_id,
|
|
||||||
message_id,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_message_removed(
|
|
||||||
this: Model<Self>,
|
|
||||||
message: TypedEnvelope<proto::RemoveChannelMessage>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.message_removed(message.payload.message_id, cx)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_messages(&mut self, messages: SumTree<ChannelMessage>, cx: &mut ModelContext<Self>) {
|
|
||||||
if let Some((first_message, last_message)) = messages.first().zip(messages.last()) {
|
|
||||||
let nonces = messages
|
|
||||||
.cursor::<()>()
|
|
||||||
.map(|m| m.nonce)
|
|
||||||
.collect::<HashSet<_>>();
|
|
||||||
|
|
||||||
let mut old_cursor = self.messages.cursor::<(ChannelMessageId, Count)>();
|
|
||||||
let mut new_messages = old_cursor.slice(&first_message.id, Bias::Left, &());
|
|
||||||
let start_ix = old_cursor.start().1 .0;
|
|
||||||
let removed_messages = old_cursor.slice(&last_message.id, Bias::Right, &());
|
|
||||||
let removed_count = removed_messages.summary().count;
|
|
||||||
let new_count = messages.summary().count;
|
|
||||||
let end_ix = start_ix + removed_count;
|
|
||||||
|
|
||||||
new_messages.append(messages, &());
|
|
||||||
|
|
||||||
let mut ranges = Vec::<Range<usize>>::new();
|
|
||||||
if new_messages.last().unwrap().is_pending() {
|
|
||||||
new_messages.append(old_cursor.suffix(&()), &());
|
|
||||||
} else {
|
|
||||||
new_messages.append(
|
|
||||||
old_cursor.slice(&ChannelMessageId::Pending(0), Bias::Left, &()),
|
|
||||||
&(),
|
|
||||||
);
|
|
||||||
|
|
||||||
while let Some(message) = old_cursor.item() {
|
|
||||||
let message_ix = old_cursor.start().1 .0;
|
|
||||||
if nonces.contains(&message.nonce) {
|
|
||||||
if ranges.last().map_or(false, |r| r.end == message_ix) {
|
|
||||||
ranges.last_mut().unwrap().end += 1;
|
|
||||||
} else {
|
|
||||||
ranges.push(message_ix..message_ix + 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_messages.push(message.clone(), &());
|
|
||||||
}
|
|
||||||
old_cursor.next(&());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(old_cursor);
|
|
||||||
self.messages = new_messages;
|
|
||||||
|
|
||||||
for range in ranges.into_iter().rev() {
|
|
||||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: range,
|
|
||||||
new_count: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: start_ix..end_ix,
|
|
||||||
new_count,
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message_removed(&mut self, id: u64, cx: &mut ModelContext<Self>) {
|
|
||||||
let mut cursor = self.messages.cursor::<ChannelMessageId>();
|
|
||||||
let mut messages = cursor.slice(&ChannelMessageId::Saved(id), Bias::Left, &());
|
|
||||||
if let Some(item) = cursor.item() {
|
|
||||||
if item.id == ChannelMessageId::Saved(id) {
|
|
||||||
let ix = messages.summary().count;
|
|
||||||
cursor.next(&());
|
|
||||||
messages.append(cursor.suffix(&()), &());
|
|
||||||
drop(cursor);
|
|
||||||
self.messages = messages;
|
|
||||||
cx.emit(ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: ix..ix + 1,
|
|
||||||
new_count: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn messages_from_proto(
|
|
||||||
proto_messages: Vec<proto::ChannelMessage>,
|
|
||||||
user_store: &Model<UserStore>,
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<SumTree<ChannelMessage>> {
|
|
||||||
let messages = ChannelMessage::from_proto_vec(proto_messages, user_store, cx).await?;
|
|
||||||
let mut result = SumTree::new();
|
|
||||||
result.extend(messages, &());
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelMessage {
|
|
||||||
pub async fn from_proto(
|
|
||||||
message: proto::ChannelMessage,
|
|
||||||
user_store: &Model<UserStore>,
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let sender = user_store
|
|
||||||
.update(cx, |user_store, cx| {
|
|
||||||
user_store.get_user(message.sender_id, cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
Ok(ChannelMessage {
|
|
||||||
id: ChannelMessageId::Saved(message.id),
|
|
||||||
body: message.body,
|
|
||||||
mentions: message
|
|
||||||
.mentions
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|mention| {
|
|
||||||
let range = mention.range?;
|
|
||||||
Some((range.start as usize..range.end as usize, mention.user_id))
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)?,
|
|
||||||
sender,
|
|
||||||
nonce: message
|
|
||||||
.nonce
|
|
||||||
.ok_or_else(|| anyhow!("nonce is required"))?
|
|
||||||
.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pending(&self) -> bool {
|
|
||||||
matches!(self.id, ChannelMessageId::Pending(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn from_proto_vec(
|
|
||||||
proto_messages: Vec<proto::ChannelMessage>,
|
|
||||||
user_store: &Model<UserStore>,
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<Vec<Self>> {
|
|
||||||
let unique_user_ids = proto_messages
|
|
||||||
.iter()
|
|
||||||
.map(|m| m.sender_id)
|
|
||||||
.collect::<HashSet<_>>()
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
user_store
|
|
||||||
.update(cx, |user_store, cx| {
|
|
||||||
user_store.get_users(unique_user_ids, cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut messages = Vec::with_capacity(proto_messages.len());
|
|
||||||
for message in proto_messages {
|
|
||||||
messages.push(ChannelMessage::from_proto(message, user_store, cx).await?);
|
|
||||||
}
|
|
||||||
Ok(messages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mentions_to_proto(mentions: &[(Range<usize>, UserId)]) -> Vec<proto::ChatMention> {
|
|
||||||
mentions
|
|
||||||
.iter()
|
|
||||||
.map(|(range, user_id)| proto::ChatMention {
|
|
||||||
range: Some(proto::Range {
|
|
||||||
start: range.start as u64,
|
|
||||||
end: range.end as u64,
|
|
||||||
}),
|
|
||||||
user_id: *user_id as u64,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl sum_tree::Item for ChannelMessage {
|
|
||||||
type Summary = ChannelMessageSummary;
|
|
||||||
|
|
||||||
fn summary(&self) -> Self::Summary {
|
|
||||||
ChannelMessageSummary {
|
|
||||||
max_id: self.id,
|
|
||||||
count: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChannelMessageId {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Saved(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl sum_tree::Summary for ChannelMessageSummary {
|
|
||||||
type Context = ();
|
|
||||||
|
|
||||||
fn add_summary(&mut self, summary: &Self, _: &()) {
|
|
||||||
self.max_id = summary.max_id;
|
|
||||||
self.count += summary.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for ChannelMessageId {
|
|
||||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
|
||||||
debug_assert!(summary.max_id > *self);
|
|
||||||
*self = summary.max_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ChannelMessageSummary> for Count {
|
|
||||||
fn add_summary(&mut self, summary: &'a ChannelMessageSummary, _: &()) {
|
|
||||||
self.0 += summary.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a str> for MessageParams {
|
|
||||||
fn from(value: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
text: value.into(),
|
|
||||||
mentions: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,184 +0,0 @@
|
|||||||
use crate::{Channel, ChannelId};
|
|
||||||
use collections::BTreeMap;
|
|
||||||
use rpc::proto;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct ChannelIndex {
|
|
||||||
channels_ordered: Vec<ChannelId>,
|
|
||||||
channels_by_id: BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelIndex {
|
|
||||||
pub fn by_id(&self) -> &BTreeMap<ChannelId, Arc<Channel>> {
|
|
||||||
&self.channels_by_id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ordered_channels(&self) -> &[ChannelId] {
|
|
||||||
&self.channels_ordered
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.channels_ordered.clear();
|
|
||||||
self.channels_by_id.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete the given channels from this index.
|
|
||||||
pub fn delete_channels(&mut self, channels: &[ChannelId]) {
|
|
||||||
self.channels_by_id
|
|
||||||
.retain(|channel_id, _| !channels.contains(channel_id));
|
|
||||||
self.channels_ordered
|
|
||||||
.retain(|channel_id| !channels.contains(channel_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bulk_insert(&mut self) -> ChannelPathsInsertGuard {
|
|
||||||
ChannelPathsInsertGuard {
|
|
||||||
channels_ordered: &mut self.channels_ordered,
|
|
||||||
channels_by_id: &mut self.channels_by_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_note_version(
|
|
||||||
&mut self,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
epoch: u64,
|
|
||||||
version: &clock::Global,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
|
|
||||||
let channel = Arc::make_mut(channel);
|
|
||||||
if let Some((unseen_epoch, unseen_version)) = &channel.unseen_note_version {
|
|
||||||
if epoch > *unseen_epoch
|
|
||||||
|| epoch == *unseen_epoch && version.observed_all(unseen_version)
|
|
||||||
{
|
|
||||||
channel.unseen_note_version = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn acknowledge_message_id(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
|
|
||||||
let channel = Arc::make_mut(channel);
|
|
||||||
if let Some(unseen_message_id) = channel.unseen_message_id {
|
|
||||||
if message_id >= unseen_message_id {
|
|
||||||
channel.unseen_message_id = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
|
||||||
insert_note_changed(&mut self.channels_by_id, channel_id, epoch, version);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_message(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
insert_new_message(&mut self.channels_by_id, channel_id, message_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A guard for ensuring that the paths index maintains its sort and uniqueness
|
|
||||||
/// invariants after a series of insertions
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ChannelPathsInsertGuard<'a> {
|
|
||||||
channels_ordered: &'a mut Vec<ChannelId>,
|
|
||||||
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
|
||||||
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
|
|
||||||
insert_note_changed(&mut self.channels_by_id, channel_id, epoch, &version);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
|
|
||||||
insert_new_message(&mut self.channels_by_id, channel_id, message_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
|
||||||
let mut ret = false;
|
|
||||||
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
|
|
||||||
let existing_channel = Arc::make_mut(existing_channel);
|
|
||||||
|
|
||||||
ret = existing_channel.visibility != channel_proto.visibility()
|
|
||||||
|| existing_channel.role != channel_proto.role()
|
|
||||||
|| existing_channel.name != channel_proto.name;
|
|
||||||
|
|
||||||
existing_channel.visibility = channel_proto.visibility();
|
|
||||||
existing_channel.role = channel_proto.role();
|
|
||||||
existing_channel.name = channel_proto.name.into();
|
|
||||||
} else {
|
|
||||||
self.channels_by_id.insert(
|
|
||||||
channel_proto.id,
|
|
||||||
Arc::new(Channel {
|
|
||||||
id: channel_proto.id,
|
|
||||||
visibility: channel_proto.visibility(),
|
|
||||||
role: channel_proto.role(),
|
|
||||||
name: channel_proto.name.into(),
|
|
||||||
unseen_note_version: None,
|
|
||||||
unseen_message_id: None,
|
|
||||||
parent_path: channel_proto.parent_path,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
self.insert_root(channel_proto.id);
|
|
||||||
}
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_root(&mut self, channel_id: ChannelId) {
|
|
||||||
self.channels_ordered.push(channel_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.channels_ordered.sort_by(|a, b| {
|
|
||||||
let a = channel_path_sorting_key(*a, &self.channels_by_id);
|
|
||||||
let b = channel_path_sorting_key(*b, &self.channels_by_id);
|
|
||||||
a.cmp(b)
|
|
||||||
});
|
|
||||||
self.channels_ordered.dedup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn channel_path_sorting_key<'a>(
|
|
||||||
id: ChannelId,
|
|
||||||
channels_by_id: &'a BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
) -> impl Iterator<Item = &str> {
|
|
||||||
let (parent_path, name) = channels_by_id
|
|
||||||
.get(&id)
|
|
||||||
.map_or((&[] as &[_], None), |channel| {
|
|
||||||
(channel.parent_path.as_slice(), Some(channel.name.as_ref()))
|
|
||||||
});
|
|
||||||
parent_path
|
|
||||||
.iter()
|
|
||||||
.filter_map(|id| Some(channels_by_id.get(id)?.name.as_ref()))
|
|
||||||
.chain(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_note_changed(
|
|
||||||
channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
channel_id: u64,
|
|
||||||
epoch: u64,
|
|
||||||
version: &clock::Global,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = channels_by_id.get_mut(&channel_id) {
|
|
||||||
let unseen_version = Arc::make_mut(channel)
|
|
||||||
.unseen_note_version
|
|
||||||
.get_or_insert((0, clock::Global::new()));
|
|
||||||
if epoch > unseen_version.0 {
|
|
||||||
*unseen_version = (epoch, version.clone());
|
|
||||||
} else {
|
|
||||||
unseen_version.1.join(&version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_new_message(
|
|
||||||
channels_by_id: &mut BTreeMap<ChannelId, Arc<Channel>>,
|
|
||||||
channel_id: u64,
|
|
||||||
message_id: u64,
|
|
||||||
) {
|
|
||||||
if let Some(channel) = channels_by_id.get_mut(&channel_id) {
|
|
||||||
let unseen_message_id = Arc::make_mut(channel).unseen_message_id.get_or_insert(0);
|
|
||||||
*unseen_message_id = message_id.max(*unseen_message_id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,380 +0,0 @@
|
|||||||
use crate::channel_chat::ChannelChatEvent;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use client::{test::FakeServer, Client, UserStore};
|
|
||||||
use gpui::{AppContext, Context, Model, TestAppContext};
|
|
||||||
use rpc::proto::{self};
|
|
||||||
use settings::SettingsStore;
|
|
||||||
use util::http::FakeHttpClient;
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_update_channels(cx: &mut AppContext) {
|
|
||||||
let channel_store = init_test(cx);
|
|
||||||
|
|
||||||
update_channels(
|
|
||||||
&channel_store,
|
|
||||||
proto::UpdateChannels {
|
|
||||||
channels: vec![
|
|
||||||
proto::Channel {
|
|
||||||
id: 1,
|
|
||||||
name: "b".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: Vec::new(),
|
|
||||||
},
|
|
||||||
proto::Channel {
|
|
||||||
id: 2,
|
|
||||||
name: "a".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: Vec::new(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
assert_channels(
|
|
||||||
&channel_store,
|
|
||||||
&[
|
|
||||||
//
|
|
||||||
(0, "a".to_string(), proto::ChannelRole::Member),
|
|
||||||
(0, "b".to_string(), proto::ChannelRole::Admin),
|
|
||||||
],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
update_channels(
|
|
||||||
&channel_store,
|
|
||||||
proto::UpdateChannels {
|
|
||||||
channels: vec![
|
|
||||||
proto::Channel {
|
|
||||||
id: 3,
|
|
||||||
name: "x".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![1],
|
|
||||||
},
|
|
||||||
proto::Channel {
|
|
||||||
id: 4,
|
|
||||||
name: "y".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: vec![2],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
assert_channels(
|
|
||||||
&channel_store,
|
|
||||||
&[
|
|
||||||
(0, "a".to_string(), proto::ChannelRole::Member),
|
|
||||||
(1, "y".to_string(), proto::ChannelRole::Member),
|
|
||||||
(0, "b".to_string(), proto::ChannelRole::Admin),
|
|
||||||
(1, "x".to_string(), proto::ChannelRole::Admin),
|
|
||||||
],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
fn test_dangling_channel_paths(cx: &mut AppContext) {
|
|
||||||
let channel_store = init_test(cx);
|
|
||||||
|
|
||||||
update_channels(
|
|
||||||
&channel_store,
|
|
||||||
proto::UpdateChannels {
|
|
||||||
channels: vec![
|
|
||||||
proto::Channel {
|
|
||||||
id: 0,
|
|
||||||
name: "a".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![],
|
|
||||||
},
|
|
||||||
proto::Channel {
|
|
||||||
id: 1,
|
|
||||||
name: "b".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![0],
|
|
||||||
},
|
|
||||||
proto::Channel {
|
|
||||||
id: 2,
|
|
||||||
name: "c".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Admin.into(),
|
|
||||||
parent_path: vec![0, 1],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
// Sanity check
|
|
||||||
assert_channels(
|
|
||||||
&channel_store,
|
|
||||||
&[
|
|
||||||
//
|
|
||||||
(0, "a".to_string(), proto::ChannelRole::Admin),
|
|
||||||
(1, "b".to_string(), proto::ChannelRole::Admin),
|
|
||||||
(2, "c".to_string(), proto::ChannelRole::Admin),
|
|
||||||
],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
update_channels(
|
|
||||||
&channel_store,
|
|
||||||
proto::UpdateChannels {
|
|
||||||
delete_channels: vec![1, 2],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make sure that the 1/2/3 path is gone
|
|
||||||
assert_channels(
|
|
||||||
&channel_store,
|
|
||||||
&[(0, "a".to_string(), proto::ChannelRole::Admin)],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
|
||||||
async fn test_channel_messages(cx: &mut TestAppContext) {
|
|
||||||
let user_id = 5;
|
|
||||||
let channel_id = 5;
|
|
||||||
let channel_store = cx.update(init_test);
|
|
||||||
let client = channel_store.update(cx, |s, _| s.client());
|
|
||||||
let server = FakeServer::for_client(user_id, &client, cx).await;
|
|
||||||
|
|
||||||
// Get the available channels.
|
|
||||||
server.send(proto::UpdateChannels {
|
|
||||||
channels: vec![proto::Channel {
|
|
||||||
id: channel_id,
|
|
||||||
name: "the-channel".to_string(),
|
|
||||||
visibility: proto::ChannelVisibility::Members as i32,
|
|
||||||
role: proto::ChannelRole::Member.into(),
|
|
||||||
parent_path: vec![],
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
cx.executor().run_until_parked();
|
|
||||||
cx.update(|cx| {
|
|
||||||
assert_channels(
|
|
||||||
&channel_store,
|
|
||||||
&[(0, "the-channel".to_string(), proto::ChannelRole::Member)],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
|
||||||
assert_eq!(get_users.payload.user_ids, vec![5]);
|
|
||||||
server.respond(
|
|
||||||
get_users.receipt(),
|
|
||||||
proto::UsersResponse {
|
|
||||||
users: vec![proto::User {
|
|
||||||
id: 5,
|
|
||||||
github_login: "nathansobo".into(),
|
|
||||||
avatar_url: "http://avatar.com/nathansobo".into(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Join a channel and populate its existing messages.
|
|
||||||
let channel = channel_store.update(cx, |store, cx| {
|
|
||||||
let channel_id = store.ordered_channels().next().unwrap().1.id;
|
|
||||||
store.open_channel_chat(channel_id, cx)
|
|
||||||
});
|
|
||||||
let join_channel = server.receive::<proto::JoinChannelChat>().await.unwrap();
|
|
||||||
server.respond(
|
|
||||||
join_channel.receipt(),
|
|
||||||
proto::JoinChannelChatResponse {
|
|
||||||
messages: vec![
|
|
||||||
proto::ChannelMessage {
|
|
||||||
id: 10,
|
|
||||||
body: "a".into(),
|
|
||||||
timestamp: 1000,
|
|
||||||
sender_id: 5,
|
|
||||||
mentions: vec![],
|
|
||||||
nonce: Some(1.into()),
|
|
||||||
},
|
|
||||||
proto::ChannelMessage {
|
|
||||||
id: 11,
|
|
||||||
body: "b".into(),
|
|
||||||
timestamp: 1001,
|
|
||||||
sender_id: 6,
|
|
||||||
mentions: vec![],
|
|
||||||
nonce: Some(2.into()),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
done: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
cx.executor().start_waiting();
|
|
||||||
|
|
||||||
// Client requests all users for the received messages
|
|
||||||
let mut get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
|
||||||
get_users.payload.user_ids.sort();
|
|
||||||
assert_eq!(get_users.payload.user_ids, vec![6]);
|
|
||||||
server.respond(
|
|
||||||
get_users.receipt(),
|
|
||||||
proto::UsersResponse {
|
|
||||||
users: vec![proto::User {
|
|
||||||
id: 6,
|
|
||||||
github_login: "maxbrunsfeld".into(),
|
|
||||||
avatar_url: "http://avatar.com/maxbrunsfeld".into(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let channel = channel.await.unwrap();
|
|
||||||
channel.update(cx, |channel, _| {
|
|
||||||
assert_eq!(
|
|
||||||
channel
|
|
||||||
.messages_in_range(0..2)
|
|
||||||
.map(|message| (message.sender.github_login.clone(), message.body.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&[
|
|
||||||
("nathansobo".into(), "a".into()),
|
|
||||||
("maxbrunsfeld".into(), "b".into())
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Receive a new message.
|
|
||||||
server.send(proto::ChannelMessageSent {
|
|
||||||
channel_id,
|
|
||||||
message: Some(proto::ChannelMessage {
|
|
||||||
id: 12,
|
|
||||||
body: "c".into(),
|
|
||||||
timestamp: 1002,
|
|
||||||
sender_id: 7,
|
|
||||||
mentions: vec![],
|
|
||||||
nonce: Some(3.into()),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Client requests user for message since they haven't seen them yet
|
|
||||||
let get_users = server.receive::<proto::GetUsers>().await.unwrap();
|
|
||||||
assert_eq!(get_users.payload.user_ids, vec![7]);
|
|
||||||
server.respond(
|
|
||||||
get_users.receipt(),
|
|
||||||
proto::UsersResponse {
|
|
||||||
users: vec![proto::User {
|
|
||||||
id: 7,
|
|
||||||
github_login: "as-cii".into(),
|
|
||||||
avatar_url: "http://avatar.com/as-cii".into(),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
channel.next_event(cx),
|
|
||||||
ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: 2..2,
|
|
||||||
new_count: 1,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
channel.update(cx, |channel, _| {
|
|
||||||
assert_eq!(
|
|
||||||
channel
|
|
||||||
.messages_in_range(2..3)
|
|
||||||
.map(|message| (message.sender.github_login.clone(), message.body.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&[("as-cii".into(), "c".into())]
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scroll up to view older messages.
|
|
||||||
channel.update(cx, |channel, cx| {
|
|
||||||
channel.load_more_messages(cx).unwrap().detach();
|
|
||||||
});
|
|
||||||
let get_messages = server.receive::<proto::GetChannelMessages>().await.unwrap();
|
|
||||||
assert_eq!(get_messages.payload.channel_id, 5);
|
|
||||||
assert_eq!(get_messages.payload.before_message_id, 10);
|
|
||||||
server.respond(
|
|
||||||
get_messages.receipt(),
|
|
||||||
proto::GetChannelMessagesResponse {
|
|
||||||
done: true,
|
|
||||||
messages: vec![
|
|
||||||
proto::ChannelMessage {
|
|
||||||
id: 8,
|
|
||||||
body: "y".into(),
|
|
||||||
timestamp: 998,
|
|
||||||
sender_id: 5,
|
|
||||||
nonce: Some(4.into()),
|
|
||||||
mentions: vec![],
|
|
||||||
},
|
|
||||||
proto::ChannelMessage {
|
|
||||||
id: 9,
|
|
||||||
body: "z".into(),
|
|
||||||
timestamp: 999,
|
|
||||||
sender_id: 6,
|
|
||||||
nonce: Some(5.into()),
|
|
||||||
mentions: vec![],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
channel.next_event(cx),
|
|
||||||
ChannelChatEvent::MessagesUpdated {
|
|
||||||
old_range: 0..0,
|
|
||||||
new_count: 2,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
channel.update(cx, |channel, _| {
|
|
||||||
assert_eq!(
|
|
||||||
channel
|
|
||||||
.messages_in_range(0..2)
|
|
||||||
.map(|message| (message.sender.github_login.clone(), message.body.clone()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
&[
|
|
||||||
("nathansobo".into(), "y".into()),
|
|
||||||
("maxbrunsfeld".into(), "z".into())
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
|
|
||||||
let http = FakeHttpClient::with_404_response();
|
|
||||||
let client = Client::new(http.clone(), cx);
|
|
||||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
|
||||||
|
|
||||||
let settings_store = SettingsStore::test(cx);
|
|
||||||
cx.set_global(settings_store);
|
|
||||||
client::init(&client, cx);
|
|
||||||
crate::init(&client, user_store, cx);
|
|
||||||
|
|
||||||
ChannelStore::global(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_channels(
|
|
||||||
channel_store: &Model<ChannelStore>,
|
|
||||||
message: proto::UpdateChannels,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) {
|
|
||||||
let task = channel_store.update(cx, |store, cx| store.update_channels(message, cx));
|
|
||||||
assert!(task.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn assert_channels(
|
|
||||||
channel_store: &Model<ChannelStore>,
|
|
||||||
expected_channels: &[(usize, String, proto::ChannelRole)],
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) {
|
|
||||||
let actual = channel_store.update(cx, |store, _| {
|
|
||||||
store
|
|
||||||
.ordered_channels()
|
|
||||||
.map(|(depth, channel)| (depth, channel.name.to_string(), channel.role))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
assert_eq!(actual, expected_channels);
|
|
||||||
}
|
|
@ -14,12 +14,12 @@ test-support = ["collections/test-support", "gpui/test-support", "rpc/test-suppo
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
db = { path = "../db" }
|
db = { package = "db2", path = "../db2" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { package = "rpc2", path = "../rpc2" }
|
||||||
text = { path = "../text" }
|
text = { package = "text2", path = "../text2" }
|
||||||
settings = { path = "../settings" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
feature_flags = { path = "../feature_flags" }
|
feature_flags = { path = "../feature_flags" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ url = "2.2"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
rpc = { path = "../rpc", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,11 @@
|
|||||||
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use gpui::{executor::Background, serde_json, AppContext, Task};
|
use futures::Future;
|
||||||
|
use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use settings::Settings;
|
||||||
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use sysinfo::{
|
use sysinfo::{
|
||||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
||||||
@ -14,19 +16,16 @@ use util::{channel::ReleaseChannel, TryFutureExt};
|
|||||||
|
|
||||||
pub struct Telemetry {
|
pub struct Telemetry {
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
executor: Arc<Background>,
|
executor: BackgroundExecutor,
|
||||||
state: Mutex<TelemetryState>,
|
state: Mutex<TelemetryState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TelemetryState {
|
struct TelemetryState {
|
||||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
metrics_id: Option<Arc<str>>, // Per logged-in user
|
||||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
||||||
session_id: Option<Arc<str>>, // Per app launch
|
session_id: Option<Arc<str>>, // Per app launch
|
||||||
app_version: Option<Arc<str>>,
|
|
||||||
release_channel: Option<&'static str>,
|
release_channel: Option<&'static str>,
|
||||||
os_name: &'static str,
|
app_metadata: AppMetadata,
|
||||||
os_version: Option<Arc<str>>,
|
|
||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
|
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
|
||||||
flush_clickhouse_events_task: Option<Task<()>>,
|
flush_clickhouse_events_task: Option<Task<()>>,
|
||||||
@ -48,9 +47,9 @@ struct ClickhouseEventRequestBody {
|
|||||||
installation_id: Option<Arc<str>>,
|
installation_id: Option<Arc<str>>,
|
||||||
session_id: Option<Arc<str>>,
|
session_id: Option<Arc<str>>,
|
||||||
is_staff: Option<bool>,
|
is_staff: Option<bool>,
|
||||||
app_version: Option<Arc<str>>,
|
app_version: Option<String>,
|
||||||
os_name: &'static str,
|
os_name: &'static str,
|
||||||
os_version: Option<Arc<str>>,
|
os_version: Option<String>,
|
||||||
architecture: &'static str,
|
architecture: &'static str,
|
||||||
release_channel: Option<&'static str>,
|
release_channel: Option<&'static str>,
|
||||||
events: Vec<ClickhouseEventWrapper>,
|
events: Vec<ClickhouseEventWrapper>,
|
||||||
@ -130,25 +129,23 @@ const MAX_QUEUE_LEN: usize = 50;
|
|||||||
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
|
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(120);
|
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
||||||
|
|
||||||
impl Telemetry {
|
impl Telemetry {
|
||||||
pub fn new(client: Arc<dyn HttpClient>, cx: &AppContext) -> Arc<Self> {
|
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
||||||
let platform = cx.platform();
|
|
||||||
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
||||||
Some(cx.global::<ReleaseChannel>().display_name())
|
Some(cx.global::<ReleaseChannel>().display_name())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
||||||
let this = Arc::new(Self {
|
let this = Arc::new(Self {
|
||||||
http_client: client,
|
http_client: client,
|
||||||
executor: cx.background().clone(),
|
executor: cx.background_executor().clone(),
|
||||||
state: Mutex::new(TelemetryState {
|
state: Mutex::new(TelemetryState {
|
||||||
os_name: platform.os_name().into(),
|
app_metadata: cx.app_metadata(),
|
||||||
os_version: platform.os_version().ok().map(|v| v.to_string().into()),
|
|
||||||
architecture: env::consts::ARCH,
|
architecture: env::consts::ARCH,
|
||||||
app_version: platform.app_version().ok().map(|v| v.to_string().into()),
|
|
||||||
release_channel,
|
release_channel,
|
||||||
installation_id: None,
|
installation_id: None,
|
||||||
metrics_id: None,
|
metrics_id: None,
|
||||||
@ -161,9 +158,30 @@ impl Telemetry {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive
|
||||||
|
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send
|
||||||
|
std::mem::forget(cx.on_app_quit({
|
||||||
|
let this = this.clone();
|
||||||
|
move |cx| this.shutdown_telemetry(cx)
|
||||||
|
}));
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
|
||||||
|
Task::ready(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip calling this function in tests.
|
||||||
|
// TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
|
||||||
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
|
fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
|
||||||
|
let telemetry_settings = TelemetrySettings::get_global(cx).clone();
|
||||||
|
self.report_app_event(telemetry_settings, "close", true);
|
||||||
|
Task::ready(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
pub fn log_file_path(&self) -> Option<PathBuf> {
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
||||||
}
|
}
|
||||||
@ -180,7 +198,7 @@ impl Telemetry {
|
|||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|cx| async move {
|
||||||
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
||||||
let refresh_kind = RefreshKind::new()
|
let refresh_kind = RefreshKind::new()
|
||||||
.with_memory() // For memory usage
|
.with_memory() // For memory usage
|
||||||
@ -209,7 +227,13 @@ impl Telemetry {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
|
let telemetry_settings = if let Ok(telemetry_settings) =
|
||||||
|
cx.update(|cx| *TelemetrySettings::get_global(cx))
|
||||||
|
{
|
||||||
|
telemetry_settings
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
this.report_memory_event(
|
this.report_memory_event(
|
||||||
telemetry_settings,
|
telemetry_settings,
|
||||||
@ -232,7 +256,7 @@ impl Telemetry {
|
|||||||
is_staff: bool,
|
is_staff: bool,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) {
|
) {
|
||||||
if !settings::get::<TelemetrySettings>(cx).metrics {
|
if !TelemetrySettings::get_global(cx).metrics {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,9 +485,15 @@ impl Telemetry {
|
|||||||
installation_id: state.installation_id.clone(),
|
installation_id: state.installation_id.clone(),
|
||||||
session_id: state.session_id.clone(),
|
session_id: state.session_id.clone(),
|
||||||
is_staff: state.is_staff.clone(),
|
is_staff: state.is_staff.clone(),
|
||||||
app_version: state.app_version.clone(),
|
app_version: state
|
||||||
os_name: state.os_name,
|
.app_metadata
|
||||||
os_version: state.os_version.clone(),
|
.app_version
|
||||||
|
.map(|version| version.to_string()),
|
||||||
|
os_name: state.app_metadata.os_name,
|
||||||
|
os_version: state
|
||||||
|
.app_metadata
|
||||||
|
.os_version
|
||||||
|
.map(|version| version.to_string()),
|
||||||
architecture: state.architecture,
|
architecture: state.architecture,
|
||||||
|
|
||||||
release_channel: state.release_channel,
|
release_channel: state.release_channel,
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
|
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use futures::{stream::BoxStream, StreamExt};
|
use futures::{stream::BoxStream, StreamExt};
|
||||||
use gpui::{executor, ModelHandle, TestAppContext};
|
use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rpc::{
|
use rpc::{
|
||||||
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
|
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
|
||||||
ConnectionId, Peer, Receipt, TypedEnvelope,
|
ConnectionId, Peer, Receipt, TypedEnvelope,
|
||||||
};
|
};
|
||||||
use std::{rc::Rc, sync::Arc};
|
use std::sync::Arc;
|
||||||
use util::http::FakeHttpClient;
|
|
||||||
|
|
||||||
pub struct FakeServer {
|
pub struct FakeServer {
|
||||||
peer: Arc<Peer>,
|
peer: Arc<Peer>,
|
||||||
state: Arc<Mutex<FakeServerState>>,
|
state: Arc<Mutex<FakeServerState>>,
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: BackgroundExecutor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -36,7 +35,7 @@ impl FakeServer {
|
|||||||
peer: Peer::new(0),
|
peer: Peer::new(0),
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
user_id: client_user_id,
|
user_id: client_user_id,
|
||||||
executor: cx.foreground(),
|
executor: cx.executor(),
|
||||||
};
|
};
|
||||||
|
|
||||||
client
|
client
|
||||||
@ -78,10 +77,11 @@ impl FakeServer {
|
|||||||
Err(EstablishConnectionError::Unauthorized)?
|
Err(EstablishConnectionError::Unauthorized)?
|
||||||
}
|
}
|
||||||
|
|
||||||
let (client_conn, server_conn, _) = Connection::in_memory(cx.background());
|
let (client_conn, server_conn, _) =
|
||||||
|
Connection::in_memory(cx.background_executor().clone());
|
||||||
let (connection_id, io, incoming) =
|
let (connection_id, io, incoming) =
|
||||||
peer.add_test_connection(server_conn, cx.background());
|
peer.add_test_connection(server_conn, cx.background_executor().clone());
|
||||||
cx.background().spawn(io).detach();
|
cx.background_executor().spawn(io).detach();
|
||||||
{
|
{
|
||||||
let mut state = state.lock();
|
let mut state = state.lock();
|
||||||
state.connection_id = Some(connection_id);
|
state.connection_id = Some(connection_id);
|
||||||
@ -193,9 +193,8 @@ impl FakeServer {
|
|||||||
&self,
|
&self,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> ModelHandle<UserStore> {
|
) -> Model<UserStore> {
|
||||||
let http_client = FakeHttpClient::with_404_response();
|
let user_store = cx.new_model(|cx| UserStore::new(client, cx));
|
||||||
let user_store = cx.add_model(|cx| UserStore::new(client, http_client, cx));
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.receive::<proto::GetUsers>()
|
self.receive::<proto::GetUsers>()
|
||||||
.await
|
.await
|
||||||
|
@ -2,13 +2,12 @@ use super::{proto, Client, Status, TypedEnvelope};
|
|||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
use collections::{hash_map::Entry, HashMap, HashSet};
|
||||||
use feature_flags::FeatureFlagAppExt;
|
use feature_flags::FeatureFlagAppExt;
|
||||||
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
|
use futures::{channel::mpsc, Future, StreamExt};
|
||||||
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
|
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
|
||||||
use postage::{sink::Sink, watch};
|
use postage::{sink::Sink, watch};
|
||||||
use rpc::proto::{RequestMessage, UsersResponse};
|
use rpc::proto::{RequestMessage, UsersResponse};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use text::ReplicaId;
|
use text::ReplicaId;
|
||||||
use util::http::HttpClient;
|
|
||||||
use util::TryFutureExt as _;
|
use util::TryFutureExt as _;
|
||||||
|
|
||||||
pub type UserId = u64;
|
pub type UserId = u64;
|
||||||
@ -20,7 +19,7 @@ pub struct ParticipantIndex(pub u32);
|
|||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
pub github_login: String,
|
pub github_login: String,
|
||||||
pub avatar: Option<Arc<ImageData>>,
|
pub avatar_uri: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -76,9 +75,8 @@ pub struct UserStore {
|
|||||||
pending_contact_requests: HashMap<u64, usize>,
|
pending_contact_requests: HashMap<u64, usize>,
|
||||||
invite_info: Option<InviteInfo>,
|
invite_info: Option<InviteInfo>,
|
||||||
client: Weak<Client>,
|
client: Weak<Client>,
|
||||||
http: Arc<dyn HttpClient>,
|
|
||||||
_maintain_contacts: Task<()>,
|
_maintain_contacts: Task<()>,
|
||||||
_maintain_current_user: Task<()>,
|
_maintain_current_user: Task<Result<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -103,9 +101,7 @@ pub enum ContactEventKind {
|
|||||||
Cancelled,
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for UserStore {
|
impl EventEmitter<Event> for UserStore {}
|
||||||
type Event = Event;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum UpdateContacts {
|
enum UpdateContacts {
|
||||||
Update(proto::UpdateContacts),
|
Update(proto::UpdateContacts),
|
||||||
@ -114,17 +110,13 @@ enum UpdateContacts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UserStore {
|
impl UserStore {
|
||||||
pub fn new(
|
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
client: Arc<Client>,
|
|
||||||
http: Arc<dyn HttpClient>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
let (mut current_user_tx, current_user_rx) = watch::channel();
|
||||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
||||||
let rpc_subscriptions = vec![
|
let rpc_subscriptions = vec![
|
||||||
client.add_message_handler(cx.handle(), Self::handle_update_contacts),
|
client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
|
||||||
client.add_message_handler(cx.handle(), Self::handle_update_invite_info),
|
client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
|
||||||
client.add_message_handler(cx.handle(), Self::handle_show_contacts),
|
client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
|
||||||
];
|
];
|
||||||
Self {
|
Self {
|
||||||
users: Default::default(),
|
users: Default::default(),
|
||||||
@ -136,76 +128,71 @@ impl UserStore {
|
|||||||
invite_info: None,
|
invite_info: None,
|
||||||
client: Arc::downgrade(&client),
|
client: Arc::downgrade(&client),
|
||||||
update_contacts_tx,
|
update_contacts_tx,
|
||||||
http,
|
_maintain_contacts: cx.spawn(|this, mut cx| async move {
|
||||||
_maintain_contacts: cx.spawn_weak(|this, mut cx| async move {
|
|
||||||
let _subscriptions = rpc_subscriptions;
|
let _subscriptions = rpc_subscriptions;
|
||||||
while let Some(message) = update_contacts_rx.next().await {
|
while let Some(message) = update_contacts_rx.next().await {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
if let Ok(task) =
|
||||||
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
|
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
|
||||||
.log_err()
|
{
|
||||||
.await;
|
task.log_err().await;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
_maintain_current_user: cx.spawn_weak(|this, mut cx| async move {
|
_maintain_current_user: cx.spawn(|this, mut cx| async move {
|
||||||
let mut status = client.status();
|
let mut status = client.status();
|
||||||
while let Some(status) = status.next().await {
|
while let Some(status) = status.next().await {
|
||||||
match status {
|
match status {
|
||||||
Status::Connected { .. } => {
|
Status::Connected { .. } => {
|
||||||
if let Some((this, user_id)) = this.upgrade(&cx).zip(client.user_id()) {
|
if let Some(user_id) = client.user_id() {
|
||||||
let fetch_user = this
|
let fetch_user = if let Ok(fetch_user) = this
|
||||||
.update(&mut cx, |this, cx| this.get_user(user_id, cx))
|
.update(&mut cx, |this, cx| {
|
||||||
.log_err();
|
this.get_user(user_id, cx).log_err()
|
||||||
|
}) {
|
||||||
|
fetch_user
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
let fetch_metrics_id =
|
let fetch_metrics_id =
|
||||||
client.request(proto::GetPrivateUserInfo {}).log_err();
|
client.request(proto::GetPrivateUserInfo {}).log_err();
|
||||||
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
|
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
|
||||||
|
|
||||||
if let Some(info) = info {
|
cx.update(|cx| {
|
||||||
cx.update(|cx| {
|
if let Some(info) = info {
|
||||||
cx.update_flags(info.staff, info.flags);
|
cx.update_flags(info.staff, info.flags);
|
||||||
client.telemetry.set_authenticated_user_info(
|
client.telemetry.set_authenticated_user_info(
|
||||||
Some(info.metrics_id.clone()),
|
Some(info.metrics_id.clone()),
|
||||||
info.staff,
|
info.staff,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
}
|
||||||
} else {
|
})?;
|
||||||
cx.read(|cx| {
|
|
||||||
client
|
|
||||||
.telemetry
|
|
||||||
.set_authenticated_user_info(None, false, cx)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
current_user_tx.send(user).await.ok();
|
current_user_tx.send(user).await.ok();
|
||||||
|
|
||||||
this.update(&mut cx, |_, cx| {
|
this.update(&mut cx, |_, cx| cx.notify())?;
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Status::SignedOut => {
|
Status::SignedOut => {
|
||||||
current_user_tx.send(None).await.ok();
|
current_user_tx.send(None).await.ok();
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.update(&mut cx, |this, cx| {
|
cx.notify();
|
||||||
cx.notify();
|
this.clear_contacts()
|
||||||
this.clear_contacts()
|
})?
|
||||||
})
|
.await;
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Status::ConnectionLost => {
|
Status::ConnectionLost => {
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.update(&mut cx, |this, cx| {
|
cx.notify();
|
||||||
cx.notify();
|
this.clear_contacts()
|
||||||
this.clear_contacts()
|
})?
|
||||||
})
|
.await;
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}),
|
}),
|
||||||
pending_contact_requests: Default::default(),
|
pending_contact_requests: Default::default(),
|
||||||
}
|
}
|
||||||
@ -217,7 +204,7 @@ impl UserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_invite_info(
|
async fn handle_update_invite_info(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -228,17 +215,17 @@ impl UserStore {
|
|||||||
count: message.payload.count,
|
count: message.payload.count,
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_show_contacts(
|
async fn handle_show_contacts(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
_: TypedEnvelope<proto::ShowContacts>,
|
_: TypedEnvelope<proto::ShowContacts>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts));
|
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +234,7 @@ impl UserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_update_contacts(
|
async fn handle_update_contacts(
|
||||||
this: ModelHandle<Self>,
|
this: Model<Self>,
|
||||||
message: TypedEnvelope<proto::UpdateContacts>,
|
message: TypedEnvelope<proto::UpdateContacts>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
@ -256,7 +243,7 @@ impl UserStore {
|
|||||||
this.update_contacts_tx
|
this.update_contacts_tx
|
||||||
.unbounded_send(UpdateContacts::Update(message.payload))
|
.unbounded_send(UpdateContacts::Update(message.payload))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,6 +279,9 @@ impl UserStore {
|
|||||||
// Users are fetched in parallel above and cached in call to get_users
|
// Users are fetched in parallel above and cached in call to get_users
|
||||||
// No need to paralellize here
|
// No need to paralellize here
|
||||||
let mut updated_contacts = Vec::new();
|
let mut updated_contacts = Vec::new();
|
||||||
|
let this = this
|
||||||
|
.upgrade()
|
||||||
|
.ok_or_else(|| anyhow!("can't upgrade user store handle"))?;
|
||||||
for contact in message.contacts {
|
for contact in message.contacts {
|
||||||
updated_contacts.push(Arc::new(
|
updated_contacts.push(Arc::new(
|
||||||
Contact::from_proto(contact, &this, &mut cx).await?,
|
Contact::from_proto(contact, &this, &mut cx).await?,
|
||||||
@ -300,18 +290,18 @@ impl UserStore {
|
|||||||
|
|
||||||
let mut incoming_requests = Vec::new();
|
let mut incoming_requests = Vec::new();
|
||||||
for request in message.incoming_requests {
|
for request in message.incoming_requests {
|
||||||
incoming_requests.push(
|
incoming_requests.push({
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.get_user(request.requester_id, cx)
|
this.get_user(request.requester_id, cx)
|
||||||
})
|
})?
|
||||||
.await?,
|
.await?
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut outgoing_requests = Vec::new();
|
let mut outgoing_requests = Vec::new();
|
||||||
for requested_user_id in message.outgoing_requests {
|
for requested_user_id in message.outgoing_requests {
|
||||||
outgoing_requests.push(
|
outgoing_requests.push(
|
||||||
this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))
|
this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))?
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -378,7 +368,7 @@ impl UserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -400,12 +390,6 @@ impl UserStore {
|
|||||||
&self.incoming_contact_requests
|
&self.incoming_contact_requests
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
|
|
||||||
self.incoming_contact_requests
|
|
||||||
.iter()
|
|
||||||
.any(|user| user.id == user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
|
pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
|
||||||
&self.outgoing_contact_requests
|
&self.outgoing_contact_requests
|
||||||
}
|
}
|
||||||
@ -454,6 +438,12 @@ impl UserStore {
|
|||||||
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
|
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
|
||||||
|
self.incoming_contact_requests
|
||||||
|
.iter()
|
||||||
|
.any(|user| user.id == user_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn respond_to_contact_request(
|
pub fn respond_to_contact_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
requester_id: u64,
|
requester_id: u64,
|
||||||
@ -480,7 +470,7 @@ impl UserStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let client = self.client.upgrade();
|
let client = self.client.upgrade();
|
||||||
cx.spawn_weak(|_, _| async move {
|
cx.spawn(move |_, _| async move {
|
||||||
client
|
client
|
||||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
||||||
.request(proto::RespondToContactRequest {
|
.request(proto::RespondToContactRequest {
|
||||||
@ -502,7 +492,7 @@ impl UserStore {
|
|||||||
*self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
|
*self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
let response = client
|
let response = client
|
||||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
||||||
.request(request)
|
.request(request)
|
||||||
@ -517,7 +507,7 @@ impl UserStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
})?;
|
||||||
response?;
|
response?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -560,11 +550,11 @@ impl UserStore {
|
|||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.read_with(&cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
user_ids
|
user_ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user_id| {
|
.map(|user_id| {
|
||||||
@ -574,7 +564,7 @@ impl UserStore {
|
|||||||
.ok_or_else(|| anyhow!("user {} not found", user_id))
|
.ok_or_else(|| anyhow!("user {} not found", user_id))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,18 +586,18 @@ impl UserStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Arc<User>>> {
|
) -> Task<Result<Arc<User>>> {
|
||||||
if let Some(user) = self.users.get(&user_id).cloned() {
|
if let Some(user) = self.users.get(&user_id).cloned() {
|
||||||
return cx.foreground().spawn(async move { Ok(user) });
|
return Task::ready(Ok(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
let load_users = self.get_users(vec![user_id], cx);
|
let load_users = self.get_users(vec![user_id], cx);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
load_users.await?;
|
load_users.await?;
|
||||||
this.update(&mut cx, |this, _| {
|
this.update(&mut cx, |this, _| {
|
||||||
this.users
|
this.users
|
||||||
.get(&user_id)
|
.get(&user_id)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| anyhow!("server responded with no users"))
|
.ok_or_else(|| anyhow!("server responded with no users"))
|
||||||
})
|
})?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,25 +615,22 @@ impl UserStore {
|
|||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<Arc<User>>>> {
|
) -> Task<Result<Vec<Arc<User>>>> {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let http = self.http.clone();
|
cx.spawn(|this, mut cx| async move {
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
|
||||||
if let Some(rpc) = client.upgrade() {
|
if let Some(rpc) = client.upgrade() {
|
||||||
let response = rpc.request(request).await.context("error loading users")?;
|
let response = rpc.request(request).await.context("error loading users")?;
|
||||||
let users = future::join_all(
|
let users = response
|
||||||
response
|
.users
|
||||||
.users
|
.into_iter()
|
||||||
.into_iter()
|
.map(|user| User::new(user))
|
||||||
.map(|user| User::new(user, http.as_ref())),
|
.collect::<Vec<_>>();
|
||||||
)
|
|
||||||
.await;
|
this.update(&mut cx, |this, _| {
|
||||||
|
for user in &users {
|
||||||
|
this.users.insert(user.id, user.clone());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
for user in &users {
|
|
||||||
this.users.insert(user.id, user.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(users)
|
Ok(users)
|
||||||
} else {
|
} else {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
@ -668,11 +655,11 @@ impl UserStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
async fn new(message: proto::User, http: &dyn HttpClient) -> Arc<Self> {
|
fn new(message: proto::User) -> Arc<Self> {
|
||||||
Arc::new(User {
|
Arc::new(User {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
github_login: message.github_login,
|
github_login: message.github_login,
|
||||||
avatar: fetch_avatar(http, &message.avatar_url).warn_on_err().await,
|
avatar_uri: message.avatar_url.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -680,13 +667,13 @@ impl User {
|
|||||||
impl Contact {
|
impl Contact {
|
||||||
async fn from_proto(
|
async fn from_proto(
|
||||||
contact: proto::Contact,
|
contact: proto::Contact,
|
||||||
user_store: &ModelHandle<UserStore>,
|
user_store: &Model<UserStore>,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let user = user_store
|
let user = user_store
|
||||||
.update(cx, |user_store, cx| {
|
.update(cx, |user_store, cx| {
|
||||||
user_store.get_user(contact.user_id, cx)
|
user_store.get_user(contact.user_id, cx)
|
||||||
})
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
user,
|
user,
|
||||||
@ -705,24 +692,3 @@ impl Collaborator {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
|
|
||||||
let mut response = http
|
|
||||||
.get(url, Default::default(), true)
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("failed to send user avatar request: {}", e))?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(anyhow!("avatar request failed {:?}", response.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut body = Vec::new();
|
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_end(&mut body)
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("failed to read user avatar response body: {}", e))?;
|
|
||||||
let format = image::guess_format(&body)?;
|
|
||||||
let image = image::load_from_memory_with_format(&body, format)?.into_bgra8();
|
|
||||||
Ok(ImageData::new(image))
|
|
||||||
}
|
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "client2"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/client2.rs"
|
|
||||||
doctest = false
|
|
||||||
|
|
||||||
[features]
|
|
||||||
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
|
||||||
collections = { path = "../collections" }
|
|
||||||
db = { package = "db2", path = "../db2" }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
|
||||||
util = { path = "../util" }
|
|
||||||
rpc = { package = "rpc2", path = "../rpc2" }
|
|
||||||
text = { package = "text2", path = "../text2" }
|
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
|
||||||
feature_flags = { path = "../feature_flags" }
|
|
||||||
sum_tree = { path = "../sum_tree" }
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
|
||||||
async-recursion = "0.3"
|
|
||||||
async-tungstenite = { version = "0.16", features = ["async-tls"] }
|
|
||||||
futures.workspace = true
|
|
||||||
image = "0.23"
|
|
||||||
lazy_static.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
parking_lot.workspace = true
|
|
||||||
postage.workspace = true
|
|
||||||
rand.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_derive.workspace = true
|
|
||||||
smol.workspace = true
|
|
||||||
sysinfo.workspace = true
|
|
||||||
tempfile = "3"
|
|
||||||
thiserror.workspace = true
|
|
||||||
time.workspace = true
|
|
||||||
tiny_http = "0.8"
|
|
||||||
uuid.workspace = true
|
|
||||||
url = "2.2"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
|
||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
|
||||||
util = { path = "../util", features = ["test-support"] }
|
|
File diff suppressed because it is too large
Load Diff
@ -1,515 +0,0 @@
|
|||||||
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use futures::Future;
|
|
||||||
use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use serde::Serialize;
|
|
||||||
use settings::Settings;
|
|
||||||
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
|
|
||||||
use sysinfo::{
|
|
||||||
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
|
|
||||||
};
|
|
||||||
use tempfile::NamedTempFile;
|
|
||||||
use util::http::HttpClient;
|
|
||||||
use util::{channel::ReleaseChannel, TryFutureExt};
|
|
||||||
|
|
||||||
pub struct Telemetry {
|
|
||||||
http_client: Arc<dyn HttpClient>,
|
|
||||||
executor: BackgroundExecutor,
|
|
||||||
state: Mutex<TelemetryState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TelemetryState {
|
|
||||||
metrics_id: Option<Arc<str>>, // Per logged-in user
|
|
||||||
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
|
|
||||||
session_id: Option<Arc<str>>, // Per app launch
|
|
||||||
release_channel: Option<&'static str>,
|
|
||||||
app_metadata: AppMetadata,
|
|
||||||
architecture: &'static str,
|
|
||||||
clickhouse_events_queue: Vec<ClickhouseEventWrapper>,
|
|
||||||
flush_clickhouse_events_task: Option<Task<()>>,
|
|
||||||
log_file: Option<NamedTempFile>,
|
|
||||||
is_staff: Option<bool>,
|
|
||||||
first_event_datetime: Option<DateTime<Utc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref CLICKHOUSE_EVENTS_URL: String =
|
|
||||||
format!("{}{}", *ZED_SERVER_URL, CLICKHOUSE_EVENTS_URL_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
struct ClickhouseEventRequestBody {
|
|
||||||
token: &'static str,
|
|
||||||
installation_id: Option<Arc<str>>,
|
|
||||||
session_id: Option<Arc<str>>,
|
|
||||||
is_staff: Option<bool>,
|
|
||||||
app_version: Option<String>,
|
|
||||||
os_name: &'static str,
|
|
||||||
os_version: Option<String>,
|
|
||||||
architecture: &'static str,
|
|
||||||
release_channel: Option<&'static str>,
|
|
||||||
events: Vec<ClickhouseEventWrapper>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
struct ClickhouseEventWrapper {
|
|
||||||
signed_in: bool,
|
|
||||||
#[serde(flatten)]
|
|
||||||
event: ClickhouseEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum AssistantKind {
|
|
||||||
Panel,
|
|
||||||
Inline,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum ClickhouseEvent {
|
|
||||||
Editor {
|
|
||||||
operation: &'static str,
|
|
||||||
file_extension: Option<String>,
|
|
||||||
vim_mode: bool,
|
|
||||||
copilot_enabled: bool,
|
|
||||||
copilot_enabled_for_language: bool,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Copilot {
|
|
||||||
suggestion_id: Option<String>,
|
|
||||||
suggestion_accepted: bool,
|
|
||||||
file_extension: Option<String>,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Call {
|
|
||||||
operation: &'static str,
|
|
||||||
room_id: Option<u64>,
|
|
||||||
channel_id: Option<u64>,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Assistant {
|
|
||||||
conversation_id: Option<String>,
|
|
||||||
kind: AssistantKind,
|
|
||||||
model: &'static str,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Cpu {
|
|
||||||
usage_as_percentage: f32,
|
|
||||||
core_count: u32,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Memory {
|
|
||||||
memory_in_bytes: u64,
|
|
||||||
virtual_memory_in_bytes: u64,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
App {
|
|
||||||
operation: &'static str,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
Setting {
|
|
||||||
setting: &'static str,
|
|
||||||
value: String,
|
|
||||||
milliseconds_since_first_event: i64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
const MAX_QUEUE_LEN: usize = 1;
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
const MAX_QUEUE_LEN: usize = 50;
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1);
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5);
|
|
||||||
|
|
||||||
impl Telemetry {
|
|
||||||
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
|
|
||||||
let release_channel = if cx.has_global::<ReleaseChannel>() {
|
|
||||||
Some(cx.global::<ReleaseChannel>().display_name())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Replace all hardware stuff with nested SystemSpecs json
|
|
||||||
let this = Arc::new(Self {
|
|
||||||
http_client: client,
|
|
||||||
executor: cx.background_executor().clone(),
|
|
||||||
state: Mutex::new(TelemetryState {
|
|
||||||
app_metadata: cx.app_metadata(),
|
|
||||||
architecture: env::consts::ARCH,
|
|
||||||
release_channel,
|
|
||||||
installation_id: None,
|
|
||||||
metrics_id: None,
|
|
||||||
session_id: None,
|
|
||||||
clickhouse_events_queue: Default::default(),
|
|
||||||
flush_clickhouse_events_task: Default::default(),
|
|
||||||
log_file: None,
|
|
||||||
is_staff: None,
|
|
||||||
first_event_datetime: None,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive
|
|
||||||
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send
|
|
||||||
std::mem::forget(cx.on_app_quit({
|
|
||||||
let this = this.clone();
|
|
||||||
move |cx| this.shutdown_telemetry(cx)
|
|
||||||
}));
|
|
||||||
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
|
||||||
fn shutdown_telemetry(self: &Arc<Self>, _: &mut AppContext) -> impl Future<Output = ()> {
|
|
||||||
Task::ready(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip calling this function in tests.
|
|
||||||
// TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings
|
|
||||||
#[cfg(not(any(test, feature = "test-support")))]
|
|
||||||
fn shutdown_telemetry(self: &Arc<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
|
|
||||||
let telemetry_settings = TelemetrySettings::get_global(cx).clone();
|
|
||||||
self.report_app_event(telemetry_settings, "close", true);
|
|
||||||
Task::ready(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_file_path(&self) -> Option<PathBuf> {
|
|
||||||
Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
installation_id: Option<String>,
|
|
||||||
session_id: String,
|
|
||||||
cx: &mut AppContext,
|
|
||||||
) {
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
state.installation_id = installation_id.map(|id| id.into());
|
|
||||||
state.session_id = Some(session_id.into());
|
|
||||||
drop(state);
|
|
||||||
|
|
||||||
let this = self.clone();
|
|
||||||
cx.spawn(|cx| async move {
|
|
||||||
// Avoiding calling `System::new_all()`, as there have been crashes related to it
|
|
||||||
let refresh_kind = RefreshKind::new()
|
|
||||||
.with_memory() // For memory usage
|
|
||||||
.with_processes(ProcessRefreshKind::everything()) // For process usage
|
|
||||||
.with_cpu(CpuRefreshKind::everything()); // For core count
|
|
||||||
|
|
||||||
let mut system = System::new_with_specifics(refresh_kind);
|
|
||||||
|
|
||||||
// Avoiding calling `refresh_all()`, just update what we need
|
|
||||||
system.refresh_specifics(refresh_kind);
|
|
||||||
|
|
||||||
// Waiting some amount of time before the first query is important to get a reasonable value
|
|
||||||
// https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
|
|
||||||
const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(4 * 60);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
|
|
||||||
|
|
||||||
system.refresh_specifics(refresh_kind);
|
|
||||||
|
|
||||||
let current_process = Pid::from_u32(std::process::id());
|
|
||||||
let Some(process) = system.processes().get(¤t_process) else {
|
|
||||||
let process = current_process;
|
|
||||||
log::error!("Failed to find own process {process:?} in system process table");
|
|
||||||
// TODO: Fire an error telemetry event
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let telemetry_settings = if let Ok(telemetry_settings) =
|
|
||||||
cx.update(|cx| *TelemetrySettings::get_global(cx))
|
|
||||||
{
|
|
||||||
telemetry_settings
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.report_memory_event(
|
|
||||||
telemetry_settings,
|
|
||||||
process.memory(),
|
|
||||||
process.virtual_memory(),
|
|
||||||
);
|
|
||||||
this.report_cpu_event(
|
|
||||||
telemetry_settings,
|
|
||||||
process.cpu_usage(),
|
|
||||||
system.cpus().len() as u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_authenticated_user_info(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
metrics_id: Option<String>,
|
|
||||||
is_staff: bool,
|
|
||||||
cx: &AppContext,
|
|
||||||
) {
|
|
||||||
if !TelemetrySettings::get_global(cx).metrics {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
let metrics_id: Option<Arc<str>> = metrics_id.map(|id| id.into());
|
|
||||||
state.metrics_id = metrics_id.clone();
|
|
||||||
state.is_staff = Some(is_staff);
|
|
||||||
drop(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_editor_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
file_extension: Option<String>,
|
|
||||||
vim_mode: bool,
|
|
||||||
operation: &'static str,
|
|
||||||
copilot_enabled: bool,
|
|
||||||
copilot_enabled_for_language: bool,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Editor {
|
|
||||||
file_extension,
|
|
||||||
vim_mode,
|
|
||||||
operation,
|
|
||||||
copilot_enabled,
|
|
||||||
copilot_enabled_for_language,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_copilot_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
suggestion_id: Option<String>,
|
|
||||||
suggestion_accepted: bool,
|
|
||||||
file_extension: Option<String>,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Copilot {
|
|
||||||
suggestion_id,
|
|
||||||
suggestion_accepted,
|
|
||||||
file_extension,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_assistant_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
conversation_id: Option<String>,
|
|
||||||
kind: AssistantKind,
|
|
||||||
model: &'static str,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Assistant {
|
|
||||||
conversation_id,
|
|
||||||
kind,
|
|
||||||
model,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_call_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
operation: &'static str,
|
|
||||||
room_id: Option<u64>,
|
|
||||||
channel_id: Option<u64>,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Call {
|
|
||||||
operation,
|
|
||||||
room_id,
|
|
||||||
channel_id,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_cpu_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
usage_as_percentage: f32,
|
|
||||||
core_count: u32,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Cpu {
|
|
||||||
usage_as_percentage,
|
|
||||||
core_count,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_memory_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
memory_in_bytes: u64,
|
|
||||||
virtual_memory_in_bytes: u64,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Memory {
|
|
||||||
memory_in_bytes,
|
|
||||||
virtual_memory_in_bytes,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_app_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
operation: &'static str,
|
|
||||||
immediate_flush: bool,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::App {
|
|
||||||
operation,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, immediate_flush)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn report_setting_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
setting: &'static str,
|
|
||||||
value: String,
|
|
||||||
) {
|
|
||||||
let event = ClickhouseEvent::Setting {
|
|
||||||
setting,
|
|
||||||
value,
|
|
||||||
milliseconds_since_first_event: self.milliseconds_since_first_event(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report_clickhouse_event(event, telemetry_settings, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn milliseconds_since_first_event(&self) -> i64 {
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
match state.first_event_datetime {
|
|
||||||
Some(first_event_datetime) => {
|
|
||||||
let now: DateTime<Utc> = Utc::now();
|
|
||||||
now.timestamp_millis() - first_event_datetime.timestamp_millis()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
state.first_event_datetime = Some(Utc::now());
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_clickhouse_event(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
event: ClickhouseEvent,
|
|
||||||
telemetry_settings: TelemetrySettings,
|
|
||||||
immediate_flush: bool,
|
|
||||||
) {
|
|
||||||
if !telemetry_settings.metrics {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
let signed_in = state.metrics_id.is_some();
|
|
||||||
state
|
|
||||||
.clickhouse_events_queue
|
|
||||||
.push(ClickhouseEventWrapper { signed_in, event });
|
|
||||||
|
|
||||||
if state.installation_id.is_some() {
|
|
||||||
if immediate_flush || state.clickhouse_events_queue.len() >= MAX_QUEUE_LEN {
|
|
||||||
drop(state);
|
|
||||||
self.flush_clickhouse_events();
|
|
||||||
} else {
|
|
||||||
let this = self.clone();
|
|
||||||
let executor = self.executor.clone();
|
|
||||||
state.flush_clickhouse_events_task = Some(self.executor.spawn(async move {
|
|
||||||
executor.timer(DEBOUNCE_INTERVAL).await;
|
|
||||||
this.flush_clickhouse_events();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn metrics_id(self: &Arc<Self>) -> Option<Arc<str>> {
|
|
||||||
self.state.lock().metrics_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn installation_id(self: &Arc<Self>) -> Option<Arc<str>> {
|
|
||||||
self.state.lock().installation_id.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_staff(self: &Arc<Self>) -> Option<bool> {
|
|
||||||
self.state.lock().is_staff
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush_clickhouse_events(self: &Arc<Self>) {
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
state.first_event_datetime = None;
|
|
||||||
let mut events = mem::take(&mut state.clickhouse_events_queue);
|
|
||||||
state.flush_clickhouse_events_task.take();
|
|
||||||
drop(state);
|
|
||||||
|
|
||||||
let this = self.clone();
|
|
||||||
self.executor
|
|
||||||
.spawn(
|
|
||||||
async move {
|
|
||||||
let mut json_bytes = Vec::new();
|
|
||||||
|
|
||||||
if let Some(file) = &mut this.state.lock().log_file {
|
|
||||||
let file = file.as_file_mut();
|
|
||||||
for event in &mut events {
|
|
||||||
json_bytes.clear();
|
|
||||||
serde_json::to_writer(&mut json_bytes, event)?;
|
|
||||||
file.write_all(&json_bytes)?;
|
|
||||||
file.write(b"\n")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let state = this.state.lock();
|
|
||||||
let request_body = ClickhouseEventRequestBody {
|
|
||||||
token: ZED_SECRET_CLIENT_TOKEN,
|
|
||||||
installation_id: state.installation_id.clone(),
|
|
||||||
session_id: state.session_id.clone(),
|
|
||||||
is_staff: state.is_staff.clone(),
|
|
||||||
app_version: state
|
|
||||||
.app_metadata
|
|
||||||
.app_version
|
|
||||||
.map(|version| version.to_string()),
|
|
||||||
os_name: state.app_metadata.os_name,
|
|
||||||
os_version: state
|
|
||||||
.app_metadata
|
|
||||||
.os_version
|
|
||||||
.map(|version| version.to_string()),
|
|
||||||
architecture: state.architecture,
|
|
||||||
|
|
||||||
release_channel: state.release_channel,
|
|
||||||
events,
|
|
||||||
};
|
|
||||||
json_bytes.clear();
|
|
||||||
serde_json::to_writer(&mut json_bytes, &request_body)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.http_client
|
|
||||||
.post_json(CLICKHOUSE_EVENTS_URL.as_str(), json_bytes.into())
|
|
||||||
.await?;
|
|
||||||
anyhow::Ok(())
|
|
||||||
}
|
|
||||||
.log_err(),
|
|
||||||
)
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use futures::{stream::BoxStream, StreamExt};
|
|
||||||
use gpui::{BackgroundExecutor, Context, Model, TestAppContext};
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use rpc::{
|
|
||||||
proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
|
|
||||||
ConnectionId, Peer, Receipt, TypedEnvelope,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct FakeServer {
|
|
||||||
peer: Arc<Peer>,
|
|
||||||
state: Arc<Mutex<FakeServerState>>,
|
|
||||||
user_id: u64,
|
|
||||||
executor: BackgroundExecutor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FakeServerState {
|
|
||||||
incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>,
|
|
||||||
connection_id: Option<ConnectionId>,
|
|
||||||
forbid_connections: bool,
|
|
||||||
auth_count: usize,
|
|
||||||
access_token: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FakeServer {
|
|
||||||
pub async fn for_client(
|
|
||||||
client_user_id: u64,
|
|
||||||
client: &Arc<Client>,
|
|
||||||
cx: &TestAppContext,
|
|
||||||
) -> Self {
|
|
||||||
let server = Self {
|
|
||||||
peer: Peer::new(0),
|
|
||||||
state: Default::default(),
|
|
||||||
user_id: client_user_id,
|
|
||||||
executor: cx.executor(),
|
|
||||||
};
|
|
||||||
|
|
||||||
client
|
|
||||||
.override_authenticate({
|
|
||||||
let state = Arc::downgrade(&server.state);
|
|
||||||
move |cx| {
|
|
||||||
let state = state.clone();
|
|
||||||
cx.spawn(move |_| async move {
|
|
||||||
let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
|
|
||||||
let mut state = state.lock();
|
|
||||||
state.auth_count += 1;
|
|
||||||
let access_token = state.access_token.to_string();
|
|
||||||
Ok(Credentials {
|
|
||||||
user_id: client_user_id,
|
|
||||||
access_token,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.override_establish_connection({
|
|
||||||
let peer = Arc::downgrade(&server.peer);
|
|
||||||
let state = Arc::downgrade(&server.state);
|
|
||||||
move |credentials, cx| {
|
|
||||||
let peer = peer.clone();
|
|
||||||
let state = state.clone();
|
|
||||||
let credentials = credentials.clone();
|
|
||||||
cx.spawn(move |cx| async move {
|
|
||||||
let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
|
|
||||||
let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
|
|
||||||
if state.lock().forbid_connections {
|
|
||||||
Err(EstablishConnectionError::Other(anyhow!(
|
|
||||||
"server is forbidding connections"
|
|
||||||
)))?
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(credentials.user_id, client_user_id);
|
|
||||||
|
|
||||||
if credentials.access_token != state.lock().access_token.to_string() {
|
|
||||||
Err(EstablishConnectionError::Unauthorized)?
|
|
||||||
}
|
|
||||||
|
|
||||||
let (client_conn, server_conn, _) =
|
|
||||||
Connection::in_memory(cx.background_executor().clone());
|
|
||||||
let (connection_id, io, incoming) =
|
|
||||||
peer.add_test_connection(server_conn, cx.background_executor().clone());
|
|
||||||
cx.background_executor().spawn(io).detach();
|
|
||||||
{
|
|
||||||
let mut state = state.lock();
|
|
||||||
state.connection_id = Some(connection_id);
|
|
||||||
state.incoming = Some(incoming);
|
|
||||||
}
|
|
||||||
peer.send(
|
|
||||||
connection_id,
|
|
||||||
proto::Hello {
|
|
||||||
peer_id: Some(connection_id.into()),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(client_conn)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client
|
|
||||||
.authenticate_and_connect(false, &cx.to_async())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
server
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disconnect(&self) {
|
|
||||||
if self.state.lock().connection_id.is_some() {
|
|
||||||
self.peer.disconnect(self.connection_id());
|
|
||||||
let mut state = self.state.lock();
|
|
||||||
state.connection_id.take();
|
|
||||||
state.incoming.take();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auth_count(&self) -> usize {
|
|
||||||
self.state.lock().auth_count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn roll_access_token(&self) {
|
|
||||||
self.state.lock().access_token += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forbid_connections(&self) {
|
|
||||||
self.state.lock().forbid_connections = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allow_connections(&self) {
|
|
||||||
self.state.lock().forbid_connections = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send<T: proto::EnvelopedMessage>(&self, message: T) {
|
|
||||||
self.peer.send(self.connection_id(), message).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::await_holding_lock)]
|
|
||||||
pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
|
|
||||||
self.executor.start_waiting();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let message = self
|
|
||||||
.state
|
|
||||||
.lock()
|
|
||||||
.incoming
|
|
||||||
.as_mut()
|
|
||||||
.expect("not connected")
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.ok_or_else(|| anyhow!("other half hung up"))?;
|
|
||||||
self.executor.finish_waiting();
|
|
||||||
let type_name = message.payload_type_name();
|
|
||||||
let message = message.into_any();
|
|
||||||
|
|
||||||
if message.is::<TypedEnvelope<M>>() {
|
|
||||||
return Ok(*message.downcast().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
|
|
||||||
self.respond(
|
|
||||||
message
|
|
||||||
.downcast::<TypedEnvelope<GetPrivateUserInfo>>()
|
|
||||||
.unwrap()
|
|
||||||
.receipt(),
|
|
||||||
GetPrivateUserInfoResponse {
|
|
||||||
metrics_id: "the-metrics-id".into(),
|
|
||||||
staff: false,
|
|
||||||
flags: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!(
|
|
||||||
"fake server received unexpected message type: {:?}",
|
|
||||||
type_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
|
|
||||||
self.peer.respond(receipt, response).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connection_id(&self) -> ConnectionId {
|
|
||||||
self.state.lock().connection_id.expect("not connected")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn build_user_store(
|
|
||||||
&self,
|
|
||||||
client: Arc<Client>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> Model<UserStore> {
|
|
||||||
let user_store = cx.new_model(|cx| UserStore::new(client, cx));
|
|
||||||
assert_eq!(
|
|
||||||
self.receive::<proto::GetUsers>()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.payload
|
|
||||||
.user_ids,
|
|
||||||
&[self.user_id]
|
|
||||||
);
|
|
||||||
user_store
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for FakeServer {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,694 +0,0 @@
|
|||||||
use super::{proto, Client, Status, TypedEnvelope};
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
|
||||||
use collections::{hash_map::Entry, HashMap, HashSet};
|
|
||||||
use feature_flags::FeatureFlagAppExt;
|
|
||||||
use futures::{channel::mpsc, Future, StreamExt};
|
|
||||||
use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task};
|
|
||||||
use postage::{sink::Sink, watch};
|
|
||||||
use rpc::proto::{RequestMessage, UsersResponse};
|
|
||||||
use std::sync::{Arc, Weak};
|
|
||||||
use text::ReplicaId;
|
|
||||||
use util::TryFutureExt as _;
|
|
||||||
|
|
||||||
pub type UserId = u64;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct ParticipantIndex(pub u32);
|
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct User {
|
|
||||||
pub id: UserId,
|
|
||||||
pub github_login: String,
|
|
||||||
pub avatar_uri: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Collaborator {
|
|
||||||
pub peer_id: proto::PeerId,
|
|
||||||
pub replica_id: ReplicaId,
|
|
||||||
pub user_id: UserId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for User {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for User {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.github_login.cmp(&other.github_login)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for User {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id == other.id && self.github_login == other.github_login
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for User {}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Contact {
|
|
||||||
pub user: Arc<User>,
|
|
||||||
pub online: bool,
|
|
||||||
pub busy: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ContactRequestStatus {
|
|
||||||
None,
|
|
||||||
RequestSent,
|
|
||||||
RequestReceived,
|
|
||||||
RequestAccepted,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UserStore {
|
|
||||||
users: HashMap<u64, Arc<User>>,
|
|
||||||
participant_indices: HashMap<u64, ParticipantIndex>,
|
|
||||||
update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
|
|
||||||
current_user: watch::Receiver<Option<Arc<User>>>,
|
|
||||||
contacts: Vec<Arc<Contact>>,
|
|
||||||
incoming_contact_requests: Vec<Arc<User>>,
|
|
||||||
outgoing_contact_requests: Vec<Arc<User>>,
|
|
||||||
pending_contact_requests: HashMap<u64, usize>,
|
|
||||||
invite_info: Option<InviteInfo>,
|
|
||||||
client: Weak<Client>,
|
|
||||||
_maintain_contacts: Task<()>,
|
|
||||||
_maintain_current_user: Task<Result<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InviteInfo {
|
|
||||||
pub count: u32,
|
|
||||||
pub url: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Event {
|
|
||||||
Contact {
|
|
||||||
user: Arc<User>,
|
|
||||||
kind: ContactEventKind,
|
|
||||||
},
|
|
||||||
ShowContacts,
|
|
||||||
ParticipantIndicesChanged,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum ContactEventKind {
|
|
||||||
Requested,
|
|
||||||
Accepted,
|
|
||||||
Cancelled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventEmitter<Event> for UserStore {}
|
|
||||||
|
|
||||||
enum UpdateContacts {
|
|
||||||
Update(proto::UpdateContacts),
|
|
||||||
Wait(postage::barrier::Sender),
|
|
||||||
Clear(postage::barrier::Sender),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserStore {
|
|
||||||
pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
|
|
||||||
let (mut current_user_tx, current_user_rx) = watch::channel();
|
|
||||||
let (update_contacts_tx, mut update_contacts_rx) = mpsc::unbounded();
|
|
||||||
let rpc_subscriptions = vec![
|
|
||||||
client.add_message_handler(cx.weak_model(), Self::handle_update_contacts),
|
|
||||||
client.add_message_handler(cx.weak_model(), Self::handle_update_invite_info),
|
|
||||||
client.add_message_handler(cx.weak_model(), Self::handle_show_contacts),
|
|
||||||
];
|
|
||||||
Self {
|
|
||||||
users: Default::default(),
|
|
||||||
current_user: current_user_rx,
|
|
||||||
contacts: Default::default(),
|
|
||||||
incoming_contact_requests: Default::default(),
|
|
||||||
participant_indices: Default::default(),
|
|
||||||
outgoing_contact_requests: Default::default(),
|
|
||||||
invite_info: None,
|
|
||||||
client: Arc::downgrade(&client),
|
|
||||||
update_contacts_tx,
|
|
||||||
_maintain_contacts: cx.spawn(|this, mut cx| async move {
|
|
||||||
let _subscriptions = rpc_subscriptions;
|
|
||||||
while let Some(message) = update_contacts_rx.next().await {
|
|
||||||
if let Ok(task) =
|
|
||||||
this.update(&mut cx, |this, cx| this.update_contacts(message, cx))
|
|
||||||
{
|
|
||||||
task.log_err().await;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
_maintain_current_user: cx.spawn(|this, mut cx| async move {
|
|
||||||
let mut status = client.status();
|
|
||||||
while let Some(status) = status.next().await {
|
|
||||||
match status {
|
|
||||||
Status::Connected { .. } => {
|
|
||||||
if let Some(user_id) = client.user_id() {
|
|
||||||
let fetch_user = if let Ok(fetch_user) = this
|
|
||||||
.update(&mut cx, |this, cx| {
|
|
||||||
this.get_user(user_id, cx).log_err()
|
|
||||||
}) {
|
|
||||||
fetch_user
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
let fetch_metrics_id =
|
|
||||||
client.request(proto::GetPrivateUserInfo {}).log_err();
|
|
||||||
let (user, info) = futures::join!(fetch_user, fetch_metrics_id);
|
|
||||||
|
|
||||||
cx.update(|cx| {
|
|
||||||
if let Some(info) = info {
|
|
||||||
cx.update_flags(info.staff, info.flags);
|
|
||||||
client.telemetry.set_authenticated_user_info(
|
|
||||||
Some(info.metrics_id.clone()),
|
|
||||||
info.staff,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
current_user_tx.send(user).await.ok();
|
|
||||||
|
|
||||||
this.update(&mut cx, |_, cx| cx.notify())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Status::SignedOut => {
|
|
||||||
current_user_tx.send(None).await.ok();
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
cx.notify();
|
|
||||||
this.clear_contacts()
|
|
||||||
})?
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
Status::ConnectionLost => {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
cx.notify();
|
|
||||||
this.clear_contacts()
|
|
||||||
})?
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}),
|
|
||||||
pending_contact_requests: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "test-support")]
|
|
||||||
pub fn clear_cache(&mut self) {
|
|
||||||
self.users.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_update_invite_info(
|
|
||||||
this: Model<Self>,
|
|
||||||
message: TypedEnvelope<proto::UpdateInviteInfo>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.invite_info = Some(InviteInfo {
|
|
||||||
url: Arc::from(message.payload.url),
|
|
||||||
count: message.payload.count,
|
|
||||||
});
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_show_contacts(
|
|
||||||
this: Model<Self>,
|
|
||||||
_: TypedEnvelope<proto::ShowContacts>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |_, cx| cx.emit(Event::ShowContacts))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invite_info(&self) -> Option<&InviteInfo> {
|
|
||||||
self.invite_info.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_update_contacts(
|
|
||||||
this: Model<Self>,
|
|
||||||
message: TypedEnvelope<proto::UpdateContacts>,
|
|
||||||
_: Arc<Client>,
|
|
||||||
mut cx: AsyncAppContext,
|
|
||||||
) -> Result<()> {
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
this.update_contacts_tx
|
|
||||||
.unbounded_send(UpdateContacts::Update(message.payload))
|
|
||||||
.unwrap();
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_contacts(
|
|
||||||
&mut self,
|
|
||||||
message: UpdateContacts,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
match message {
|
|
||||||
UpdateContacts::Wait(barrier) => {
|
|
||||||
drop(barrier);
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
UpdateContacts::Clear(barrier) => {
|
|
||||||
self.contacts.clear();
|
|
||||||
self.incoming_contact_requests.clear();
|
|
||||||
self.outgoing_contact_requests.clear();
|
|
||||||
drop(barrier);
|
|
||||||
Task::ready(Ok(()))
|
|
||||||
}
|
|
||||||
UpdateContacts::Update(message) => {
|
|
||||||
let mut user_ids = HashSet::default();
|
|
||||||
for contact in &message.contacts {
|
|
||||||
user_ids.insert(contact.user_id);
|
|
||||||
}
|
|
||||||
user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
|
|
||||||
user_ids.extend(message.outgoing_requests.iter());
|
|
||||||
|
|
||||||
let load_users = self.get_users(user_ids.into_iter().collect(), cx);
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
load_users.await?;
|
|
||||||
|
|
||||||
// Users are fetched in parallel above and cached in call to get_users
|
|
||||||
// No need to paralellize here
|
|
||||||
let mut updated_contacts = Vec::new();
|
|
||||||
let this = this
|
|
||||||
.upgrade()
|
|
||||||
.ok_or_else(|| anyhow!("can't upgrade user store handle"))?;
|
|
||||||
for contact in message.contacts {
|
|
||||||
updated_contacts.push(Arc::new(
|
|
||||||
Contact::from_proto(contact, &this, &mut cx).await?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut incoming_requests = Vec::new();
|
|
||||||
for request in message.incoming_requests {
|
|
||||||
incoming_requests.push({
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.get_user(request.requester_id, cx)
|
|
||||||
})?
|
|
||||||
.await?
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut outgoing_requests = Vec::new();
|
|
||||||
for requested_user_id in message.outgoing_requests {
|
|
||||||
outgoing_requests.push(
|
|
||||||
this.update(&mut cx, |this, cx| this.get_user(requested_user_id, cx))?
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let removed_contacts =
|
|
||||||
HashSet::<u64>::from_iter(message.remove_contacts.iter().copied());
|
|
||||||
let removed_incoming_requests =
|
|
||||||
HashSet::<u64>::from_iter(message.remove_incoming_requests.iter().copied());
|
|
||||||
let removed_outgoing_requests =
|
|
||||||
HashSet::<u64>::from_iter(message.remove_outgoing_requests.iter().copied());
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
// Remove contacts
|
|
||||||
this.contacts
|
|
||||||
.retain(|contact| !removed_contacts.contains(&contact.user.id));
|
|
||||||
// Update existing contacts and insert new ones
|
|
||||||
for updated_contact in updated_contacts {
|
|
||||||
match this.contacts.binary_search_by_key(
|
|
||||||
&&updated_contact.user.github_login,
|
|
||||||
|contact| &contact.user.github_login,
|
|
||||||
) {
|
|
||||||
Ok(ix) => this.contacts[ix] = updated_contact,
|
|
||||||
Err(ix) => this.contacts.insert(ix, updated_contact),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove incoming contact requests
|
|
||||||
this.incoming_contact_requests.retain(|user| {
|
|
||||||
if removed_incoming_requests.contains(&user.id) {
|
|
||||||
cx.emit(Event::Contact {
|
|
||||||
user: user.clone(),
|
|
||||||
kind: ContactEventKind::Cancelled,
|
|
||||||
});
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Update existing incoming requests and insert new ones
|
|
||||||
for user in incoming_requests {
|
|
||||||
match this
|
|
||||||
.incoming_contact_requests
|
|
||||||
.binary_search_by_key(&&user.github_login, |contact| {
|
|
||||||
&contact.github_login
|
|
||||||
}) {
|
|
||||||
Ok(ix) => this.incoming_contact_requests[ix] = user,
|
|
||||||
Err(ix) => this.incoming_contact_requests.insert(ix, user),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove outgoing contact requests
|
|
||||||
this.outgoing_contact_requests
|
|
||||||
.retain(|user| !removed_outgoing_requests.contains(&user.id));
|
|
||||||
// Update existing incoming requests and insert new ones
|
|
||||||
for request in outgoing_requests {
|
|
||||||
match this
|
|
||||||
.outgoing_contact_requests
|
|
||||||
.binary_search_by_key(&&request.github_login, |contact| {
|
|
||||||
&contact.github_login
|
|
||||||
}) {
|
|
||||||
Ok(ix) => this.outgoing_contact_requests[ix] = request,
|
|
||||||
Err(ix) => this.outgoing_contact_requests.insert(ix, request),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contacts(&self) -> &[Arc<Contact>] {
|
|
||||||
&self.contacts
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_contact(&self, user: &Arc<User>) -> bool {
|
|
||||||
self.contacts
|
|
||||||
.binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn incoming_contact_requests(&self) -> &[Arc<User>] {
|
|
||||||
&self.incoming_contact_requests
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outgoing_contact_requests(&self) -> &[Arc<User>] {
|
|
||||||
&self.outgoing_contact_requests
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_contact_request_pending(&self, user: &User) -> bool {
|
|
||||||
self.pending_contact_requests.contains_key(&user.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contact_request_status(&self, user: &User) -> ContactRequestStatus {
|
|
||||||
if self
|
|
||||||
.contacts
|
|
||||||
.binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
ContactRequestStatus::RequestAccepted
|
|
||||||
} else if self
|
|
||||||
.outgoing_contact_requests
|
|
||||||
.binary_search_by_key(&&user.github_login, |user| &user.github_login)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
ContactRequestStatus::RequestSent
|
|
||||||
} else if self
|
|
||||||
.incoming_contact_requests
|
|
||||||
.binary_search_by_key(&&user.github_login, |user| &user.github_login)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
ContactRequestStatus::RequestReceived
|
|
||||||
} else {
|
|
||||||
ContactRequestStatus::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_contact(
|
|
||||||
&mut self,
|
|
||||||
responder_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
self.perform_contact_request(responder_id, proto::RequestContact { responder_id }, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_contact(
|
|
||||||
&mut self,
|
|
||||||
user_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
self.perform_contact_request(user_id, proto::RemoveContact { user_id }, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_incoming_contact_request(&self, user_id: u64) -> bool {
|
|
||||||
self.incoming_contact_requests
|
|
||||||
.iter()
|
|
||||||
.any(|user| user.id == user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn respond_to_contact_request(
|
|
||||||
&mut self,
|
|
||||||
requester_id: u64,
|
|
||||||
accept: bool,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
self.perform_contact_request(
|
|
||||||
requester_id,
|
|
||||||
proto::RespondToContactRequest {
|
|
||||||
requester_id,
|
|
||||||
response: if accept {
|
|
||||||
proto::ContactRequestResponse::Accept
|
|
||||||
} else {
|
|
||||||
proto::ContactRequestResponse::Decline
|
|
||||||
} as i32,
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dismiss_contact_request(
|
|
||||||
&mut self,
|
|
||||||
requester_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
let client = self.client.upgrade();
|
|
||||||
cx.spawn(move |_, _| async move {
|
|
||||||
client
|
|
||||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
|
||||||
.request(proto::RespondToContactRequest {
|
|
||||||
requester_id,
|
|
||||||
response: proto::ContactRequestResponse::Dismiss as i32,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn perform_contact_request<T: RequestMessage>(
|
|
||||||
&mut self,
|
|
||||||
user_id: u64,
|
|
||||||
request: T,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<()>> {
|
|
||||||
let client = self.client.upgrade();
|
|
||||||
*self.pending_contact_requests.entry(user_id).or_insert(0) += 1;
|
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
|
||||||
let response = client
|
|
||||||
.ok_or_else(|| anyhow!("can't upgrade client reference"))?
|
|
||||||
.request(request)
|
|
||||||
.await;
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
if let Entry::Occupied(mut request_count) =
|
|
||||||
this.pending_contact_requests.entry(user_id)
|
|
||||||
{
|
|
||||||
*request_count.get_mut() -= 1;
|
|
||||||
if *request_count.get() == 0 {
|
|
||||||
request_count.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.notify();
|
|
||||||
})?;
|
|
||||||
response?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_contacts(&mut self) -> impl Future<Output = ()> {
|
|
||||||
let (tx, mut rx) = postage::barrier::channel();
|
|
||||||
self.update_contacts_tx
|
|
||||||
.unbounded_send(UpdateContacts::Clear(tx))
|
|
||||||
.unwrap();
|
|
||||||
async move {
|
|
||||||
rx.next().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contact_updates_done(&mut self) -> impl Future<Output = ()> {
|
|
||||||
let (tx, mut rx) = postage::barrier::channel();
|
|
||||||
self.update_contacts_tx
|
|
||||||
.unbounded_send(UpdateContacts::Wait(tx))
|
|
||||||
.unwrap();
|
|
||||||
async move {
|
|
||||||
rx.next().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_users(
|
|
||||||
&mut self,
|
|
||||||
user_ids: Vec<u64>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<Vec<Arc<User>>>> {
|
|
||||||
let mut user_ids_to_fetch = user_ids.clone();
|
|
||||||
user_ids_to_fetch.retain(|id| !self.users.contains_key(id));
|
|
||||||
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
if !user_ids_to_fetch.is_empty() {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.load_users(
|
|
||||||
proto::GetUsers {
|
|
||||||
user_ids: user_ids_to_fetch,
|
|
||||||
},
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
user_ids
|
|
||||||
.iter()
|
|
||||||
.map(|user_id| {
|
|
||||||
this.users
|
|
||||||
.get(user_id)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| anyhow!("user {} not found", user_id))
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fuzzy_search_users(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<Vec<Arc<User>>>> {
|
|
||||||
self.load_users(proto::FuzzySearchUsers { query }, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cached_user(&self, user_id: u64) -> Option<Arc<User>> {
|
|
||||||
self.users.get(&user_id).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_user(
|
|
||||||
&mut self,
|
|
||||||
user_id: u64,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<Arc<User>>> {
|
|
||||||
if let Some(user) = self.users.get(&user_id).cloned() {
|
|
||||||
return Task::ready(Ok(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
let load_users = self.get_users(vec![user_id], cx);
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
|
||||||
load_users.await?;
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
this.users
|
|
||||||
.get(&user_id)
|
|
||||||
.cloned()
|
|
||||||
.ok_or_else(|| anyhow!("server responded with no users"))
|
|
||||||
})?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_user(&self) -> Option<Arc<User>> {
|
|
||||||
self.current_user.borrow().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
|
|
||||||
self.current_user.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_users(
|
|
||||||
&mut self,
|
|
||||||
request: impl RequestMessage<Response = UsersResponse>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) -> Task<Result<Vec<Arc<User>>>> {
|
|
||||||
let client = self.client.clone();
|
|
||||||
cx.spawn(|this, mut cx| async move {
|
|
||||||
if let Some(rpc) = client.upgrade() {
|
|
||||||
let response = rpc.request(request).await.context("error loading users")?;
|
|
||||||
let users = response
|
|
||||||
.users
|
|
||||||
.into_iter()
|
|
||||||
.map(|user| User::new(user))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
this.update(&mut cx, |this, _| {
|
|
||||||
for user in &users {
|
|
||||||
this.users.insert(user.id, user.clone());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
Ok(users)
|
|
||||||
} else {
|
|
||||||
Ok(Vec::new())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_participant_indices(
|
|
||||||
&mut self,
|
|
||||||
participant_indices: HashMap<u64, ParticipantIndex>,
|
|
||||||
cx: &mut ModelContext<Self>,
|
|
||||||
) {
|
|
||||||
if participant_indices != self.participant_indices {
|
|
||||||
self.participant_indices = participant_indices;
|
|
||||||
cx.emit(Event::ParticipantIndicesChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
|
|
||||||
&self.participant_indices
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl User {
|
|
||||||
fn new(message: proto::User) -> Arc<Self> {
|
|
||||||
Arc::new(User {
|
|
||||||
id: message.id,
|
|
||||||
github_login: message.github_login,
|
|
||||||
avatar_uri: message.avatar_url.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Contact {
|
|
||||||
async fn from_proto(
|
|
||||||
contact: proto::Contact,
|
|
||||||
user_store: &Model<UserStore>,
|
|
||||||
cx: &mut AsyncAppContext,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let user = user_store
|
|
||||||
.update(cx, |user_store, cx| {
|
|
||||||
user_store.get_user(contact.user_id, cx)
|
|
||||||
})?
|
|
||||||
.await?;
|
|
||||||
Ok(Self {
|
|
||||||
user,
|
|
||||||
online: contact.online,
|
|
||||||
busy: contact.busy,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Collaborator {
|
|
||||||
pub fn from_proto(message: proto::Collaborator) -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
peer_id: message.peer_id.ok_or_else(|| anyhow!("invalid peer id"))?,
|
|
||||||
replica_id: message.replica_id as ReplicaId,
|
|
||||||
user_id: message.user_id as UserId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,9 +63,9 @@ uuid.workspace = true
|
|||||||
audio = { path = "../audio" }
|
audio = { path = "../audio" }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
call = { package = "call2", path = "../call2", features = ["test-support"] }
|
call = { path = "../call", features = ["test-support"] }
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
channel = { package = "channel2", path = "../channel2" }
|
channel = { path = "../channel" }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
||||||
|
@ -24,9 +24,9 @@ test-support = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
auto_update = { path = "../auto_update" }
|
auto_update = { path = "../auto_update" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
call = { package = "call2", path = "../call2" }
|
call = { path = "../call" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
channel = { package = "channel2", path = "../channel2" }
|
channel = { path = "../channel" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
# context_menu = { path = "../context_menu" }
|
# context_menu = { path = "../context_menu" }
|
||||||
@ -65,8 +65,8 @@ time.workspace = true
|
|||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
call = { package = "call2", path = "../call2", features = ["test-support"] }
|
call = { path = "../call", features = ["test-support"] }
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
|
@ -31,7 +31,7 @@ smallvec.workspace = true
|
|||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
@ -23,7 +23,7 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
copilot = { path = "../copilot" }
|
copilot = { path = "../copilot" }
|
||||||
db = { package="db2", path = "../db2" }
|
db = { package="db2", path = "../db2" }
|
||||||
|
@ -11,7 +11,7 @@ path = "src/feedback.rs"
|
|||||||
test-support = []
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
@ -61,7 +61,7 @@ tree-sitter-typescript = { workspace = true, optional = true }
|
|||||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
@ -26,7 +26,7 @@ anyhow.workspace = true
|
|||||||
tree-sitter.workspace = true
|
tree-sitter.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
|
@ -20,7 +20,7 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
git = { package = "git3", path = "../git3" }
|
git = { package = "git3", path = "../git3" }
|
||||||
|
@ -17,8 +17,8 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
channel = { package = "channel2", path = "../channel2" }
|
channel = { path = "../channel" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
@ -34,7 +34,7 @@ anyhow.workspace = true
|
|||||||
time.workspace = true
|
time.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||||
|
@ -12,7 +12,7 @@ doctest = false
|
|||||||
test-support = []
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections"}
|
collections = { path = "../collections"}
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
@ -22,7 +22,7 @@ test-support = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
text = { package = "text2", path = "../text2" }
|
text = { package = "text2", path = "../text2" }
|
||||||
copilot = { path = "../copilot" }
|
copilot = { path = "../copilot" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
db = { package = "db2", path = "../db2" }
|
db = { package = "db2", path = "../db2" }
|
||||||
@ -69,7 +69,7 @@ itertools = "0.10"
|
|||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
db = { package = "db2", path = "../db2", features = ["test-support"] }
|
db = { package = "db2", path = "../db2", features = ["test-support"] }
|
||||||
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
|
||||||
|
@ -33,7 +33,7 @@ pretty_assertions.workspace = true
|
|||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { path = "../client2", package = "client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
||||||
|
@ -32,7 +32,7 @@ smallvec.workspace = true
|
|||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
|||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
|
settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
|
||||||
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
rust-embed = { version = "8.0", features = ["include-exclude"] }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
node_runtime = { path = "../node_runtime"}
|
node_runtime = { path = "../node_runtime"}
|
||||||
|
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
@ -40,7 +40,7 @@ serde_derive.workspace = true
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||||
client = { package = "client2", path = "../client2", features = ["test-support"]}
|
client = { path = "../client", features = ["test-support"]}
|
||||||
project = { path = "../project", features = ["test-support"]}
|
project = { path = "../project", features = ["test-support"]}
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
|
@ -9,7 +9,7 @@ path = "src/theme_selector.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
feature_flags = { path = "../feature_flags" }
|
feature_flags = { path = "../feature_flags" }
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
|
@ -11,7 +11,7 @@ path = "src/welcome.rs"
|
|||||||
test-support = []
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
fs = { package = "fs2", path = "../fs2" }
|
fs = { package = "fs2", path = "../fs2" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
|
@ -20,8 +20,8 @@ test-support = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
db = { path = "../db2", package = "db2" }
|
db = { path = "../db2", package = "db2" }
|
||||||
call = { path = "../call2", package = "call2" }
|
call = { path = "../call" }
|
||||||
client = { path = "../client2", package = "client2" }
|
client = { path = "../client" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
# context_menu = { path = "../context_menu" }
|
# context_menu = { path = "../context_menu" }
|
||||||
fs = { path = "../fs2", package = "fs2" }
|
fs = { path = "../fs2", package = "fs2" }
|
||||||
@ -54,8 +54,8 @@ smallvec.workspace = true
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
call = { path = "../call2", package = "call2", features = ["test-support"] }
|
call = { path = "../call", features = ["test-support"] }
|
||||||
client = { path = "../client2", package = "client2", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
|
||||||
project = { path = "../project", features = ["test-support"] }
|
project = { path = "../project", features = ["test-support"] }
|
||||||
settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
|
settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
|
||||||
|
@ -20,14 +20,14 @@ audio = { package = "audio2", path = "../audio2" }
|
|||||||
activity_indicator = { path = "../activity_indicator"}
|
activity_indicator = { path = "../activity_indicator"}
|
||||||
auto_update = { path = "../auto_update" }
|
auto_update = { path = "../auto_update" }
|
||||||
breadcrumbs = { path = "../breadcrumbs" }
|
breadcrumbs = { path = "../breadcrumbs" }
|
||||||
call = { package = "call2", path = "../call2" }
|
call = { path = "../call" }
|
||||||
channel = { package = "channel2", path = "../channel2" }
|
channel = { path = "../channel" }
|
||||||
cli = { path = "../cli" }
|
cli = { path = "../cli" }
|
||||||
collab_ui = { path = "../collab_ui" }
|
collab_ui = { path = "../collab_ui" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
command_palette = { path = "../command_palette" }
|
command_palette = { path = "../command_palette" }
|
||||||
# component_test = { path = "../component_test" }
|
# component_test = { path = "../component_test" }
|
||||||
client = { package = "client2", path = "../client2" }
|
client = { path = "../client" }
|
||||||
# clock = { path = "../clock" }
|
# clock = { path = "../clock" }
|
||||||
copilot = { path = "../copilot" }
|
copilot = { path = "../copilot" }
|
||||||
copilot_button = { path = "../copilot_button" }
|
copilot_button = { path = "../copilot_button" }
|
||||||
@ -144,7 +144,7 @@ urlencoding = "2.1.2"
|
|||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
call = { package = "call2", path = "../call2", features = ["test-support"] }
|
call = { path = "../call", features = ["test-support"] }
|
||||||
# client = { path = "../client", features = ["test-support"] }
|
# client = { path = "../client", features = ["test-support"] }
|
||||||
# editor = { path = "../editor", features = ["test-support"] }
|
# editor = { path = "../editor", features = ["test-support"] }
|
||||||
# gpui = { path = "../gpui", features = ["test-support"] }
|
# gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
Loading…
Reference in New Issue
Block a user