use std::borrow::Cow;use std::fmt;use std::str::FromStr; use anyhow::{Result, anyhow};use chrono::{DateTime, Utc};use derivative::Derivative;use pathfinder_geometry::vector::vec2f;use serde::{Deserialize, Serialize};use warp_core::features::FeatureFlag;use warp_core::ui::Icon;use warp_core::ui::appearance::Appearance;use warp_core::ui::theme::Fill;use warp_graphql::object_permissions::AccessLevel;use warp_graphql::scalars::time::ServerTimestamp;use warpui_core::Element;use warpui_core::elements::{ Align, ChildAnchor, ConstrainedBox, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Stack,};use warpui_core::ui_components::components::UiComponent; use crate::auth::UserUid;use crate::drive::sharing::{SharingAccessLevel, Subject, TeamKind, UserKind};use crate::ids::{FolderId, ServerId, SyncId}; mod creation;mod generic_cloud_object;mod generic_string_model;pub mod models;mod server_object;mod update; pub use creation::*;pub use generic_cloud_object::*;pub use generic_string_model::*;pub use server_object::*;pub use update::*;/// The type of object id each ObjectType corresponds to.#[derive(Copy, Clone, Debug, Eq, PartialEq)]pub enum ObjectIdType { Notebook, Workflow, Folder, GenericStringObject,} impl ObjectIdType { /// Returns the prefix for server IDs as we store them in sqlite. The prefix for these /// objects is in title case unlike how we store the object types, which is why two different /// APIs are needed. pub fn sqlite_prefix(&self) -> &'static str { match self { ObjectIdType::Notebook => "Notebook", ObjectIdType::Workflow => "Workflow", ObjectIdType::Folder => "Folder", ObjectIdType::GenericStringObject => "GenericStringObject", } }} /// A type for communicating the type of cloud object to/from the server, absent of the object itself.#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]pub enum ObjectType { Notebook, Workflow, Folder, GenericStringObject(GenericStringObjectFormat),} impl ObjectType { /// Returns the serialized string for the object type, to be used for storing object_type in sqlite. pub fn sqlite_object_type_as_str(&self) -> Cow<'_, str> { match self { ObjectType::Notebook => "NOTEBOOK".into(), ObjectType::Workflow => "WORKFLOW".into(), ObjectType::Folder => "FOLDER".into(), ObjectType::GenericStringObject(format) => format.to_string().into(), } }} const NOTEBOOK_OBJECT_STRING: &str = "notebook";const WORKFLOW_OBJECT_STRING: &str = "workflow";const PROMPT_OBJECT_STRING: &str = "prompt";const FOLDER_OBJECT_STRING: &str = "folder";const ENV_VAR_COLLECTION_STRING: &str = "env-vars"; impl FromStr for ObjectType { type Err = anyhow::Error; fn from_str(s: &str) -> Result<Self> { match s { NOTEBOOK_OBJECT_STRING => Ok(Self::Notebook), WORKFLOW_OBJECT_STRING => Ok(Self::Workflow), PROMPT_OBJECT_STRING => Ok(Self::Workflow), FOLDER_OBJECT_STRING => Ok(Self::Folder), ENV_VAR_COLLECTION_STRING => Ok(Self::GenericStringObject( GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection), )), _ => Err(anyhow!("Unexpected object type")), } }} impl fmt::Display for ObjectType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ObjectType::Notebook => write!(f, "{NOTEBOOK_OBJECT_STRING}"), ObjectType::Workflow => write!(f, "{WORKFLOW_OBJECT_STRING}"), ObjectType::Folder => write!(f, "{FOLDER_OBJECT_STRING}"), ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::EnvVarCollection, )) => write!(f, "{ENV_VAR_COLLECTION_STRING}"), ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::AIFact, )) => write!(f, "rule"), ObjectType::GenericStringObject(_) => write!(f, "string_object_placeholder"), // placeholder value } }} impl From<ObjectType> for ObjectIdType { fn from(value: ObjectType) -> Self { match value { ObjectType::Notebook => ObjectIdType::Notebook, ObjectType::Workflow => ObjectIdType::Workflow, ObjectType::Folder => ObjectIdType::Folder, ObjectType::GenericStringObject(_) => ObjectIdType::GenericStringObject, } }} /// The object type prefix for generic string objects.pub const GENERIC_STRING_OBJECT_PREFIX: &str = "GENERIC_STRING_"; /// The object type prefix for json objects.pub const JSON_OBJECT_PREFIX: &str = "JSON_"; /// The data format for the generic string object type./// Right now we only support json, but this is left/// open to support markdown, yaml and other text based types.#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]pub enum GenericStringObjectFormat { Json(JsonObjectType),} /// Represents a unique key for a generic string object. The server enforces that/// no two generic string objects have the same key.#[derive(PartialEq, Eq, Debug, Clone)]pub struct GenericStringObjectUniqueKey { /// The unique key. E.g. for cloud prefs this is the storage key of the pref. pub key: String, /// Whether this key is unique for all generic string objects, or unique per user. pub unique_per: UniquePer,} #[derive(PartialEq, Eq, Debug, Clone)]pub enum UniquePer { User,} // Temporarily suppress clippy warnings about the `ToString` impl until we// move `ObjectType` away from using `std::fmt::Display` for serialization.#[allow(clippy::to_string_trait_impl)]impl ToString for GenericStringObjectFormat { fn to_string(&self) -> String { match self { GenericStringObjectFormat::Json(json_object_type) => format!( "{}{}{}", GENERIC_STRING_OBJECT_PREFIX, JSON_OBJECT_PREFIX, json_object_type.as_str() ), } }} /// An object sub-type for objects that implement the JsonModel trait.#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]pub enum JsonObjectType { Preference, EnvVarCollection, WorkflowEnum, AIFact, MCPServer, AIExecutionProfile, TemplatableMCPServer, CloudEnvironment, ScheduledAmbientAgent, CloudAgentConfig,} impl JsonObjectType { pub fn as_str(&self) -> &'static str { match self { JsonObjectType::Preference => "PREFERENCE", JsonObjectType::EnvVarCollection => "ENVVARCOLLECTION", JsonObjectType::WorkflowEnum => "WORKFLOWENUM", JsonObjectType::AIFact => "AIFACT", JsonObjectType::MCPServer => "MCPSERVER", JsonObjectType::AIExecutionProfile => "AIEXECUTIONPROFILE", JsonObjectType::TemplatableMCPServer => "TEMPLATABLEMCPSERVER", JsonObjectType::CloudEnvironment => "CLOUDENVIRONMENT", JsonObjectType::ScheduledAmbientAgent => "SCHEDULEDAMBIENTAGENT", JsonObjectType::CloudAgentConfig => "CLOUDAGENTCONFIG", } }} impl TryFrom<&str> for JsonObjectType { type Error = anyhow::Error; fn try_from(value: &str) -> std::result::Result<Self, Self::Error> { match value { "PREFERENCE" => Ok(JsonObjectType::Preference), "ENVVARCOLLECTION" => Ok(JsonObjectType::EnvVarCollection), "WORKFLOWENUM" => Ok(JsonObjectType::WorkflowEnum), "AIFACT" => Ok(JsonObjectType::AIFact), "MCPSERVER" => Ok(JsonObjectType::MCPServer), "AIEXECUTIONPROFILE" => Ok(JsonObjectType::AIExecutionProfile), "TEMPLATABLEMCPSERVER" => Ok(JsonObjectType::TemplatableMCPServer), "CLOUDENVIRONMENT" => Ok(JsonObjectType::CloudEnvironment), "SCHEDULEDAMBIENTAGENT" => Ok(JsonObjectType::ScheduledAmbientAgent), "CLOUDAGENTCONFIG" => Ok(JsonObjectType::CloudAgentConfig), _ => Err(anyhow!("could not convert unknown json object type")), } }} impl TryFrom<warp_graphql::object::ObjectType> for ObjectIdType { type Error = anyhow::Error; fn try_from(object_type: warp_graphql::object::ObjectType) -> Result<Self, Self::Error> { match object_type { warp_graphql::object::ObjectType::AIConversation => Err(anyhow!( "AIConversation is not a supported object type for this operation" )), warp_graphql::object::ObjectType::Notebook => Ok(ObjectIdType::Notebook), warp_graphql::object::ObjectType::Workflow => Ok(ObjectIdType::Workflow), warp_graphql::object::ObjectType::Folder => Ok(ObjectIdType::Folder), warp_graphql::object::ObjectType::GenericStringObject => { Ok(ObjectIdType::GenericStringObject) } warp_graphql::object::ObjectType::Unknown => { Err(anyhow!("could not convert unknown cloud object type")) } } }} impl From<ObjectType> for warp_graphql::object::ObjectType { fn from(value: ObjectType) -> Self { match value { ObjectType::Notebook => warp_graphql::object::ObjectType::Notebook, ObjectType::Workflow => warp_graphql::object::ObjectType::Workflow, ObjectType::Folder => warp_graphql::object::ObjectType::Folder, ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::EnvVarCollection, )) => warp_graphql::object::ObjectType::GenericStringObject, ObjectType::GenericStringObject(gso) => { todo!("Moving is not implemented for {:?}", gso); } } }} /// The revision timestamp at which an object was edited. This is used by the server/// to determine if an edit to an object was at the latest revision. Edits at older/// revisions are rejected by the server.#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)]pub struct Revision(ServerTimestamp); impl Revision { pub fn from_unix_timestamp_micros(ms_since_epoch: i64) -> Result<Self> { let ts = ServerTimestamp::from_unix_timestamp_micros(ms_since_epoch)?; Ok(Self(ts)) } pub fn timestamp_micros(&self) -> i64 { self.0.timestamp_micros() } pub fn utc(&self) -> DateTime<Utc> { self.0.utc() } /// Returns the inner `ServerTimestamp`. pub fn timestamp(&self) -> ServerTimestamp { self.0 } #[cfg(any(test, feature = "test-util"))] pub fn now() -> Self { Self(ServerTimestamp::new(Utc::now())) }} impl From<Revision> for ServerTimestamp { fn from(revision: Revision) -> Self { revision.0 }} impl From<ServerTimestamp> for Revision { fn from(time: ServerTimestamp) -> Self { Revision(time) }} #[cfg(any(test, feature = "test-util"))]impl From<DateTime<Utc>> for Revision { fn from(time: DateTime<Utc>) -> Self { Self(ServerTimestamp::new(time)) }} /// The owner for a given object.#[derive(Copy, Clone, Debug, Eq, Serialize, Deserialize, Derivative)]#[derivative(PartialEq)]pub enum Owner { /// The owner of the object is a user (the object is in their personal drive). User { user_uid: UserUid }, /// The owner of the object is a team (the object is in a team drive). Team { team_uid: ServerId },} impl Owner { /// A mock [`Owner`] ID for testing. #[cfg(any(test, feature = "test-util"))] pub fn mock_current_user() -> Owner { use crate::auth::TEST_USER_UID; Owner::User { user_uid: UserUid::new(TEST_USER_UID), } }} impl From<Owner> for Option<ServerId> { fn from(owner: Owner) -> Option<ServerId> { match owner { Owner::User { .. } => None, Owner::Team { team_uid, .. } => Some(team_uid), } }} /// Server representation of an object's container. This corresponds to the `Container` GraphQL/// type.////// Containers are similar to, but not quite the same as, the [`CloudObjectLocation`] type./// Locations depend on object and user state - an object might currently be in the trash, or/// it could be in one user's [shared space](Space::Shared) but another's/// [team space](Space::Team). Containers, on the other hand, represent an object's canonical/// parent - its one parent folder or drive that permissions are inherited from.#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]pub enum ServerObjectContainer { Folder { folder_uid: ServerId }, Drive { owner: Owner },} /// Server representation of a user object guest, as part of [`ServerObjectGuest`].#[derive(Clone, Debug, PartialEq)]pub enum ServerGuestSubject { User { firebase_uid: String }, PendingUser { email: Option<String> }, Team { team_uid: ServerId },} /// Server representation of a link-sharing setting.#[derive(Clone, Debug, PartialEq)]pub struct ServerLinkSharing { pub access_level: AccessLevel, pub source: Option<ServerObjectContainer>,} /// Server representation of an object guest. This corresponds to the `ObjectGuest` GraphQL type.#[derive(Clone, Debug, PartialEq)]pub struct ServerObjectGuest { pub subject: ServerGuestSubject, pub access_level: AccessLevel, /// If this guest is inherited, this is the ancestor that it's inherited from. pub source: Option<ServerObjectContainer>,} /// Metadata for a cloud object that was fetched from the server.#[derive(Clone, Debug)]pub struct ServerMetadata { pub uid: ServerId, pub revision: Revision, pub metadata_last_updated_ts: ServerTimestamp, pub trashed_ts: Option<ServerTimestamp>, pub folder_id: Option<FolderId>, pub is_welcome_object: bool, pub creator_uid: Option<String>, pub last_editor_uid: Option<String>, pub current_editor_uid: Option<String>,} /// Permissions for a cloud object that was fetched from the server.#[derive(Clone, Debug, PartialEq)]pub struct ServerPermissions { /// The GraphQL definition of a `Space` is closer to the client's definition of an `Owner` (due /// to sharing). This is also going to migrate back to [ServerMetadata] as part of the /// `Container` migration. pub space: Owner, pub guests: Vec<ServerObjectGuest>, pub anyone_link_sharing: Option<ServerLinkSharing>, pub permissions_last_updated_ts: ServerTimestamp,} impl ServerPermissions { #[cfg(any(test, feature = "test-util"))] pub fn mock_personal() -> Self { Self { space: Owner::mock_current_user(), guests: Vec::new(), anyone_link_sharing: None, permissions_last_updated_ts: DateTime::<Utc>::default().into(), } }} #[derive(Clone, Debug)]pub struct NumInFlightRequests(pub usize); #[derive(Clone, Debug)]/// An enum representing what state a local cloud object's content changes can be in,/// in relation to the server.pub enum CloudObjectSyncStatus { /// The object's content hasn't changed from what we believe the server's representation /// to be. NoLocalChanges, /// The object's content has been modified locally, and is currently in the sync queue /// attempting to sync up with the server. InFlight(NumInFlightRequests), /// The object's content has been modified locally but has unresolved conflict with the server /// revision. InConflict, /// The object's content has been modified locally, but persisting the change on the server /// could not complete for some reason. Errored,} const SYNC_ICON_DIMENSIONS: f32 = 16.; const SYNC_STATUS_TOOLTIP_LOCAL_ONLY: &str = "Saved locally";const SYNC_STATUS_TOOLTIP_INFLIGHT: &str = "Saving";const SYNC_STATUS_TOOLTIP_ERROR: &str = "Failed to save"; #[derive(Debug, Clone, PartialEq)]pub struct CloudObjectPermissions { pub owner: Owner, pub permissions_last_updated_ts: Option<ServerTimestamp>, pub anyone_with_link: Option<CloudLinkSharing>, pub guests: Vec<CloudObjectGuest>,} impl CloudObjectPermissions { pub fn new_from_server(server_permissions: ServerPermissions) -> Self { let guests = if FeatureFlag::SharedWithMe.is_enabled() { server_permissions .guests .into_iter() .map(CloudObjectGuest::from_server) .collect() } else { Vec::new() }; let anyone_with_link = if FeatureFlag::SharedWithMe.is_enabled() { server_permissions .anyone_link_sharing .map(CloudLinkSharing::from_server) } else { None }; Self { owner: server_permissions.space, permissions_last_updated_ts: Some(server_permissions.permissions_last_updated_ts), guests, anyone_with_link, } } /// Mock permissions for a personal object. #[cfg(any(test, feature = "test-util"))] pub fn mock_personal() -> Self { Self { owner: Owner::mock_current_user(), permissions_last_updated_ts: Some(Utc::now().into()), guests: Vec::new(), anyone_with_link: None, } } /// Returns `true` if the given user has direct personal access to this object — /// either via an explicit user guest ACL entry or via link sharing.Lines 1–500 of 1075. Load more
use std::borrow::Cow; use std::fmt; use std::str::FromStr; use anyhow::{Result, anyhow}; use chrono::{DateTime, Utc}; use derivative::Derivative; use pathfinder_geometry::vector::vec2f; use serde::{Deserialize, Serialize}; use warp_core::features::FeatureFlag; use warp_core::ui::Icon; use warp_core::ui::appearance::Appearance; use warp_core::ui::theme::Fill; use warp_graphql::object_permissions::AccessLevel; use warp_graphql::scalars::time::ServerTimestamp; use warpui_core::Element; use warpui_core::elements::{ Align, ChildAnchor, ConstrainedBox, Hoverable, MouseStateHandle, OffsetPositioning, ParentAnchor, ParentElement, ParentOffsetBounds, Stack, }; use warpui_core::ui_components::components::UiComponent; use crate::auth::UserUid; use crate::drive::sharing::{SharingAccessLevel, Subject, TeamKind, UserKind}; use crate::ids::{FolderId, ServerId, SyncId}; mod creation; mod generic_cloud_object; mod generic_string_model; pub mod models; mod server_object; mod update; pub use creation::*; pub use generic_cloud_object::*; pub use generic_string_model::*; pub use server_object::*; pub use update::*; /// The type of object id each ObjectType corresponds to. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ObjectIdType { Notebook, Workflow, Folder, GenericStringObject, } impl ObjectIdType { /// Returns the prefix for server IDs as we store them in sqlite. The prefix for these /// objects is in title case unlike how we store the object types, which is why two different /// APIs are needed. pub fn sqlite_prefix(&self) -> &'static str { match self { ObjectIdType::Notebook => "Notebook", ObjectIdType::Workflow => "Workflow", ObjectIdType::Folder => "Folder", ObjectIdType::GenericStringObject => "GenericStringObject", } } } /// A type for communicating the type of cloud object to/from the server, absent of the object itself. #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] pub enum ObjectType { Notebook, Workflow, Folder, GenericStringObject(GenericStringObjectFormat), } impl ObjectType { /// Returns the serialized string for the object type, to be used for storing object_type in sqlite. pub fn sqlite_object_type_as_str(&self) -> Cow<'_, str> { match self { ObjectType::Notebook => "NOTEBOOK".into(), ObjectType::Workflow => "WORKFLOW".into(), ObjectType::Folder => "FOLDER".into(), ObjectType::GenericStringObject(format) => format.to_string().into(), } } } const NOTEBOOK_OBJECT_STRING: &str = "notebook"; const WORKFLOW_OBJECT_STRING: &str = "workflow"; const PROMPT_OBJECT_STRING: &str = "prompt"; const FOLDER_OBJECT_STRING: &str = "folder"; const ENV_VAR_COLLECTION_STRING: &str = "env-vars"; impl FromStr for ObjectType { type Err = anyhow::Error; fn from_str(s: &str) -> Result<Self> { match s { NOTEBOOK_OBJECT_STRING => Ok(Self::Notebook), WORKFLOW_OBJECT_STRING => Ok(Self::Workflow), PROMPT_OBJECT_STRING => Ok(Self::Workflow), FOLDER_OBJECT_STRING => Ok(Self::Folder), ENV_VAR_COLLECTION_STRING => Ok(Self::GenericStringObject( GenericStringObjectFormat::Json(JsonObjectType::EnvVarCollection), )), _ => Err(anyhow!("Unexpected object type")), } } } impl fmt::Display for ObjectType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ObjectType::Notebook => write!(f, "{NOTEBOOK_OBJECT_STRING}"), ObjectType::Workflow => write!(f, "{WORKFLOW_OBJECT_STRING}"), ObjectType::Folder => write!(f, "{FOLDER_OBJECT_STRING}"), ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::EnvVarCollection, )) => write!(f, "{ENV_VAR_COLLECTION_STRING}"), ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::AIFact, )) => write!(f, "rule"), ObjectType::GenericStringObject(_) => write!(f, "string_object_placeholder"), // placeholder value } } } impl From<ObjectType> for ObjectIdType { fn from(value: ObjectType) -> Self { match value { ObjectType::Notebook => ObjectIdType::Notebook, ObjectType::Workflow => ObjectIdType::Workflow, ObjectType::Folder => ObjectIdType::Folder, ObjectType::GenericStringObject(_) => ObjectIdType::GenericStringObject, } } } /// The object type prefix for generic string objects. pub const GENERIC_STRING_OBJECT_PREFIX: &str = "GENERIC_STRING_"; /// The object type prefix for json objects. pub const JSON_OBJECT_PREFIX: &str = "JSON_"; /// The data format for the generic string object type. /// Right now we only support json, but this is left /// open to support markdown, yaml and other text based types. #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] pub enum GenericStringObjectFormat { Json(JsonObjectType), } /// Represents a unique key for a generic string object. The server enforces that /// no two generic string objects have the same key. #[derive(PartialEq, Eq, Debug, Clone)] pub struct GenericStringObjectUniqueKey { /// The unique key. E.g. for cloud prefs this is the storage key of the pref. pub key: String, /// Whether this key is unique for all generic string objects, or unique per user. pub unique_per: UniquePer, } #[derive(PartialEq, Eq, Debug, Clone)] pub enum UniquePer { User, } // Temporarily suppress clippy warnings about the `ToString` impl until we // move `ObjectType` away from using `std::fmt::Display` for serialization. #[allow(clippy::to_string_trait_impl)] impl ToString for GenericStringObjectFormat { fn to_string(&self) -> String { match self { GenericStringObjectFormat::Json(json_object_type) => format!( "{}{}{}", GENERIC_STRING_OBJECT_PREFIX, JSON_OBJECT_PREFIX, json_object_type.as_str() ), } } } /// An object sub-type for objects that implement the JsonModel trait. #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] pub enum JsonObjectType { Preference, EnvVarCollection, WorkflowEnum, AIFact, MCPServer, AIExecutionProfile, TemplatableMCPServer, CloudEnvironment, ScheduledAmbientAgent, CloudAgentConfig, } impl JsonObjectType { pub fn as_str(&self) -> &'static str { match self { JsonObjectType::Preference => "PREFERENCE", JsonObjectType::EnvVarCollection => "ENVVARCOLLECTION", JsonObjectType::WorkflowEnum => "WORKFLOWENUM", JsonObjectType::AIFact => "AIFACT", JsonObjectType::MCPServer => "MCPSERVER", JsonObjectType::AIExecutionProfile => "AIEXECUTIONPROFILE", JsonObjectType::TemplatableMCPServer => "TEMPLATABLEMCPSERVER", JsonObjectType::CloudEnvironment => "CLOUDENVIRONMENT", JsonObjectType::ScheduledAmbientAgent => "SCHEDULEDAMBIENTAGENT", JsonObjectType::CloudAgentConfig => "CLOUDAGENTCONFIG", } } } impl TryFrom<&str> for JsonObjectType { type Error = anyhow::Error; fn try_from(value: &str) -> std::result::Result<Self, Self::Error> { match value { "PREFERENCE" => Ok(JsonObjectType::Preference), "ENVVARCOLLECTION" => Ok(JsonObjectType::EnvVarCollection), "WORKFLOWENUM" => Ok(JsonObjectType::WorkflowEnum), "AIFACT" => Ok(JsonObjectType::AIFact), "MCPSERVER" => Ok(JsonObjectType::MCPServer), "AIEXECUTIONPROFILE" => Ok(JsonObjectType::AIExecutionProfile), "TEMPLATABLEMCPSERVER" => Ok(JsonObjectType::TemplatableMCPServer), "CLOUDENVIRONMENT" => Ok(JsonObjectType::CloudEnvironment), "SCHEDULEDAMBIENTAGENT" => Ok(JsonObjectType::ScheduledAmbientAgent), "CLOUDAGENTCONFIG" => Ok(JsonObjectType::CloudAgentConfig), _ => Err(anyhow!("could not convert unknown json object type")), } } } impl TryFrom<warp_graphql::object::ObjectType> for ObjectIdType { type Error = anyhow::Error; fn try_from(object_type: warp_graphql::object::ObjectType) -> Result<Self, Self::Error> { match object_type { warp_graphql::object::ObjectType::AIConversation => Err(anyhow!( "AIConversation is not a supported object type for this operation" )), warp_graphql::object::ObjectType::Notebook => Ok(ObjectIdType::Notebook), warp_graphql::object::ObjectType::Workflow => Ok(ObjectIdType::Workflow), warp_graphql::object::ObjectType::Folder => Ok(ObjectIdType::Folder), warp_graphql::object::ObjectType::GenericStringObject => { Ok(ObjectIdType::GenericStringObject) } warp_graphql::object::ObjectType::Unknown => { Err(anyhow!("could not convert unknown cloud object type")) } } } } impl From<ObjectType> for warp_graphql::object::ObjectType { fn from(value: ObjectType) -> Self { match value { ObjectType::Notebook => warp_graphql::object::ObjectType::Notebook, ObjectType::Workflow => warp_graphql::object::ObjectType::Workflow, ObjectType::Folder => warp_graphql::object::ObjectType::Folder, ObjectType::GenericStringObject(GenericStringObjectFormat::Json( JsonObjectType::EnvVarCollection, )) => warp_graphql::object::ObjectType::GenericStringObject, ObjectType::GenericStringObject(gso) => { todo!("Moving is not implemented for {:?}", gso); } } } } /// The revision timestamp at which an object was edited. This is used by the server /// to determine if an edit to an object was at the latest revision. Edits at older /// revisions are rejected by the server. #[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, PartialOrd, Ord)] pub struct Revision(ServerTimestamp); impl Revision { pub fn from_unix_timestamp_micros(ms_since_epoch: i64) -> Result<Self> { let ts = ServerTimestamp::from_unix_timestamp_micros(ms_since_epoch)?; Ok(Self(ts)) } pub fn timestamp_micros(&self) -> i64 { self.0.timestamp_micros() } pub fn utc(&self) -> DateTime<Utc> { self.0.utc() } /// Returns the inner `ServerTimestamp`. pub fn timestamp(&self) -> ServerTimestamp { self.0 } #[cfg(any(test, feature = "test-util"))] pub fn now() -> Self { Self(ServerTimestamp::new(Utc::now())) } } impl From<Revision> for ServerTimestamp { fn from(revision: Revision) -> Self { revision.0 } } impl From<ServerTimestamp> for Revision { fn from(time: ServerTimestamp) -> Self { Revision(time) } } #[cfg(any(test, feature = "test-util"))] impl From<DateTime<Utc>> for Revision { fn from(time: DateTime<Utc>) -> Self { Self(ServerTimestamp::new(time)) } } /// The owner for a given object. #[derive(Copy, Clone, Debug, Eq, Serialize, Deserialize, Derivative)] #[derivative(PartialEq)] pub enum Owner { /// The owner of the object is a user (the object is in their personal drive). User { user_uid: UserUid }, /// The owner of the object is a team (the object is in a team drive). Team { team_uid: ServerId }, } impl Owner { /// A mock [`Owner`] ID for testing. #[cfg(any(test, feature = "test-util"))] pub fn mock_current_user() -> Owner { use crate::auth::TEST_USER_UID; Owner::User { user_uid: UserUid::new(TEST_USER_UID), } } } impl From<Owner> for Option<ServerId> { fn from(owner: Owner) -> Option<ServerId> { match owner { Owner::User { .. } => None, Owner::Team { team_uid, .. } => Some(team_uid), } } } /// Server representation of an object's container. This corresponds to the `Container` GraphQL /// type. /// /// Containers are similar to, but not quite the same as, the [`CloudObjectLocation`] type. /// Locations depend on object and user state - an object might currently be in the trash, or /// it could be in one user's [shared space](Space::Shared) but another's /// [team space](Space::Team). Containers, on the other hand, represent an object's canonical /// parent - its one parent folder or drive that permissions are inherited from. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ServerObjectContainer { Folder { folder_uid: ServerId }, Drive { owner: Owner }, } /// Server representation of a user object guest, as part of [`ServerObjectGuest`]. #[derive(Clone, Debug, PartialEq)] pub enum ServerGuestSubject { User { firebase_uid: String }, PendingUser { email: Option<String> }, Team { team_uid: ServerId }, } /// Server representation of a link-sharing setting. #[derive(Clone, Debug, PartialEq)] pub struct ServerLinkSharing { pub access_level: AccessLevel, pub source: Option<ServerObjectContainer>, } /// Server representation of an object guest. This corresponds to the `ObjectGuest` GraphQL type. #[derive(Clone, Debug, PartialEq)] pub struct ServerObjectGuest { pub subject: ServerGuestSubject, pub access_level: AccessLevel, /// If this guest is inherited, this is the ancestor that it's inherited from. pub source: Option<ServerObjectContainer>, } /// Metadata for a cloud object that was fetched from the server. #[derive(Clone, Debug)] pub struct ServerMetadata { pub uid: ServerId, pub revision: Revision, pub metadata_last_updated_ts: ServerTimestamp, pub trashed_ts: Option<ServerTimestamp>, pub folder_id: Option<FolderId>, pub is_welcome_object: bool, pub creator_uid: Option<String>, pub last_editor_uid: Option<String>, pub current_editor_uid: Option<String>, } /// Permissions for a cloud object that was fetched from the server. #[derive(Clone, Debug, PartialEq)] pub struct ServerPermissions { /// The GraphQL definition of a `Space` is closer to the client's definition of an `Owner` (due /// to sharing). This is also going to migrate back to [ServerMetadata] as part of the /// `Container` migration. pub space: Owner, pub guests: Vec<ServerObjectGuest>, pub anyone_link_sharing: Option<ServerLinkSharing>, pub permissions_last_updated_ts: ServerTimestamp, } impl ServerPermissions { #[cfg(any(test, feature = "test-util"))] pub fn mock_personal() -> Self { Self { space: Owner::mock_current_user(), guests: Vec::new(), anyone_link_sharing: None, permissions_last_updated_ts: DateTime::<Utc>::default().into(), } } } #[derive(Clone, Debug)] pub struct NumInFlightRequests(pub usize); #[derive(Clone, Debug)] /// An enum representing what state a local cloud object's content changes can be in, /// in relation to the server. pub enum CloudObjectSyncStatus { /// The object's content hasn't changed from what we believe the server's representation /// to be. NoLocalChanges, /// The object's content has been modified locally, and is currently in the sync queue /// attempting to sync up with the server. InFlight(NumInFlightRequests), /// The object's content has been modified locally but has unresolved conflict with the server /// revision. InConflict, /// The object's content has been modified locally, but persisting the change on the server /// could not complete for some reason. Errored, } const SYNC_ICON_DIMENSIONS: f32 = 16.; const SYNC_STATUS_TOOLTIP_LOCAL_ONLY: &str = "Saved locally"; const SYNC_STATUS_TOOLTIP_INFLIGHT: &str = "Saving"; const SYNC_STATUS_TOOLTIP_ERROR: &str = "Failed to save"; #[derive(Debug, Clone, PartialEq)] pub struct CloudObjectPermissions { pub owner: Owner, pub permissions_last_updated_ts: Option<ServerTimestamp>, pub anyone_with_link: Option<CloudLinkSharing>, pub guests: Vec<CloudObjectGuest>, } impl CloudObjectPermissions { pub fn new_from_server(server_permissions: ServerPermissions) -> Self { let guests = if FeatureFlag::SharedWithMe.is_enabled() { server_permissions .guests .into_iter() .map(CloudObjectGuest::from_server) .collect() } else { Vec::new() }; let anyone_with_link = if FeatureFlag::SharedWithMe.is_enabled() { server_permissions .anyone_link_sharing .map(CloudLinkSharing::from_server) } else { None }; Self { owner: server_permissions.space, permissions_last_updated_ts: Some(server_permissions.permissions_last_updated_ts), guests, anyone_with_link, } } /// Mock permissions for a personal object. #[cfg(any(test, feature = "test-util"))] pub fn mock_personal() -> Self { Self { owner: Owner::mock_current_user(), permissions_last_updated_ts: Some(Utc::now().into()), guests: Vec::new(), anyone_with_link: None, } } /// Returns `true` if the given user has direct personal access to this object — /// either via an explicit user guest ACL entry or via link sharing.