use std::sync::Arc; use super::{ CloudObjectMetadata, CloudObjectPermissions, CloudObjectStatuses, CloudObjectSyncStatus, ConflictStatus, GenericServerObject, NumInFlightRequests, ObjectType, Owner, }; use crate::ids::{ClientId, SyncId}; /// A portable payload for persisting or otherwise upserting a cloud object without app-local event types. #[derive(Clone, Debug)] pub struct CloudObjectUpsertParams { pub id: SyncId, pub object_type: ObjectType, pub metadata: CloudObjectMetadata, pub permissions: CloudObjectPermissions, pub model: M, } /// A generic implementation of cloud objects that can be used for any model and id types. /// /// For instance, rather than directly implementing the CloudObject trait, CloudObjects can /// implement GenericCloudObject where K is their id type and M is their model type. /// /// For example, CloudNotebook becomes: /// /// pub type CloudNotebook = GenericCloudObject /// /// The advantage of using the generic model is you get common implementations /// of CloudObject methods like ```versions``` for free. /// /// See the comments for CloudObject to understand the relationship between /// this trait, CloudObject and CloudModelType. They are tightly coupled. #[derive(Clone, Debug)] pub struct GenericCloudObject { pub id: SyncId, pub metadata: CloudObjectMetadata, pub permissions: CloudObjectPermissions, /// Tracks whether this object has a conflict with the server version. /// This is runtime state (not persisted) - conflicts are always NoConflicts when loaded from SQLite. pub conflict_status: ConflictStatus>, // Intentionally not public to prevent users of this class from holding // onto references to the model outside of this struct. // // This is an Arc in order to support clone-on-write semantics for the model. // By wrapping the model in an Arc, clones become cheap, and we can avoid // doing deep clones of the model whenever the containing object is cloned. // // Callers who want to update the model need to call set_model to update the // entire model atomically. model: Arc, } impl PartialEq for GenericCloudObject where M: PartialEq, { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.model() == other.model() } } impl GenericCloudObject { /// Gets a reference to the model held by the object. pub fn model(&self) -> &M { &self.model } /// Returns a shared handle to the model. pub fn shared_model(&self) -> Arc { self.model.clone() } /// Sets a new version of the model on the object, replacing the old version. pub fn set_model(&mut self, model: M) { self.model = model.into(); } /// Constructs a new instance of this model with the given id, model, metadata and permissions. pub fn new( id: SyncId, model: M, metadata: CloudObjectMetadata, permissions: CloudObjectPermissions, ) -> Self { Self { id, model: model.into(), metadata, permissions, conflict_status: ConflictStatus::NoConflicts, } } /// Creates a new GenericCloudObject with the given model, owner, and initial folder id. /// This is for the local creation flow, as opposed to creating from a server update. pub fn new_local( model: M, owner: Owner, initial_folder_id: Option, client_id: ClientId, ) -> Self { Self { id: SyncId::ClientId(client_id), model: model.into(), metadata: CloudObjectMetadata { pending_changes_statuses: CloudObjectStatuses { content_sync_status: CloudObjectSyncStatus::InFlight(NumInFlightRequests(1)), has_pending_metadata_change: false, has_pending_permissions_change: false, pending_untrash: false, pending_delete: false, }, folder_id: initial_folder_id, revision: Default::default(), metadata_last_updated_ts: Default::default(), current_editor_uid: Default::default(), trashed_ts: Default::default(), // Objects created from the client are never welcome objects. is_welcome_object: false, creator_uid: None, last_editor_uid: None, last_task_run_ts: None, }, permissions: CloudObjectPermissions { owner, anyone_with_link: None, guests: Default::default(), permissions_last_updated_ts: None, }, conflict_status: ConflictStatus::NoConflicts, } } /// Creates a new [`GenericCloudObject`] from a [`GenericServerObject`]. pub fn new_from_server(server_object: GenericServerObject) -> Self { Self { id: server_object.id, model: server_object.model.into(), metadata: CloudObjectMetadata::new_from_server(server_object.metadata), permissions: CloudObjectPermissions::new_from_server(server_object.permissions), conflict_status: ConflictStatus::NoConflicts, } } /// Marks this object as being in conflict with the provided object. pub fn set_conflicting_object(&mut self, object: Arc>) { self.conflict_status = ConflictStatus::ConflictingChanges { object }; } pub fn update_from_server_object(&mut self, server_object: GenericServerObject) { // Check if we should create a conflict or apply the update. if self.metadata.has_pending_content_changes() || self.conflict_status.has_conflicts() { // There are pending changes, so this creates a conflict. self.conflict_status = ConflictStatus::ConflictingChanges { object: Arc::new(server_object), }; } else { // No pending changes, apply the server update. self.metadata .update_revision_from_server(&server_object.metadata); self.model = server_object.model.into(); self.conflict_status = ConflictStatus::NoConflicts; } } /// Returns portable upsert parameters for this object. pub fn upsert_params(&self, object_type: ObjectType) -> CloudObjectUpsertParams where M: Clone, { CloudObjectUpsertParams { id: self.id, object_type, metadata: self.metadata.clone(), permissions: self.permissions.clone(), model: self.model().clone(), } } /// Converts this object into portable upsert parameters. pub fn into_upsert_params(self, object_type: ObjectType) -> CloudObjectUpsertParams where M: Clone, { let Self { id, metadata, permissions, model, conflict_status: _, } = self; let model = Arc::try_unwrap(model).unwrap_or_else(|model| (*model).clone()); CloudObjectUpsertParams { id, object_type, metadata, permissions, model, } } } impl From> for GenericCloudObject { fn from(params: CloudObjectUpsertParams) -> Self { Self::new(params.id, params.model, params.metadata, params.permissions) } }