Eric Zhang 4 роки тому
батько
коміт
53fad5cda7

+ 2 - 2
Cargo.lock

@@ -944,9 +944,9 @@ dependencies = [
 
 [[package]]
 name = "signal-hook-registry"
-version = "1.3.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
 dependencies = [
  "libc",
 ]

+ 58 - 0
rustpad-server/src/lib.rs

@@ -0,0 +1,58 @@
+//! Server backend for the Rustpad collaborative text editor
+
+#![forbid(unsafe_code)]
+#![warn(missing_docs)]
+
+use std::sync::Arc;
+
+use rustpad::Rustpad;
+use warp::{filters::BoxedFilter, ws::Ws, Filter, Reply};
+
+mod rustpad;
+
+/// A combined filter handling all server routes
+pub fn server() -> BoxedFilter<(impl Reply,)> {
+    warp::path("api").and(backend()).or(frontend()).boxed()
+}
+
+/// Construct routes for static files from React
+fn frontend() -> BoxedFilter<(impl Reply,)> {
+    warp::fs::dir("build")
+        .or(warp::get().and(warp::fs::file("build/index.html")))
+        .boxed()
+}
+
+/// Construct backend routes, including WebSocket handlers
+fn backend() -> BoxedFilter<(impl Reply,)> {
+    let rustpad = Arc::new(Rustpad::new());
+    let rustpad = warp::any().map(move || Arc::clone(&rustpad));
+
+    let socket = warp::path("socket")
+        .and(warp::path::end())
+        .and(warp::ws())
+        .and(rustpad)
+        .map(|ws: Ws, rustpad: Arc<Rustpad>| {
+            ws.on_upgrade(move |socket| async move { rustpad.on_connection(socket).await })
+        });
+
+    socket.boxed()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[tokio::test]
+    async fn test_single_message() {
+        let filter = backend();
+        let mut client = warp::test::ws()
+            .path("/socket")
+            .handshake(filter)
+            .await
+            .expect("handshake");
+        client.send_text("hello world").await;
+        let msg = client.recv().await.expect("recv");
+        let msg = msg.to_str().expect("string");
+        assert_eq!(msg, "[[0,\"hello world\"]]");
+    }
+}

+ 1 - 25
rustpad-server/src/main.rs

@@ -1,28 +1,4 @@
-//! Server backend for the Rustpad collaborative text editor
-
-#![forbid(unsafe_code)]
-#![warn(missing_docs)]
-
-use warp::{filters::BoxedFilter, Filter, Reply};
-
-mod server;
-
-/// Construct routes for static files from React
-fn frontend() -> BoxedFilter<(impl Reply,)> {
-    warp::fs::dir("build")
-        .or(warp::get().and(warp::fs::file("build/index.html")))
-        .boxed()
-}
-
-/// Construct backend routes, including WebSocket handlers
-fn backend() -> BoxedFilter<(impl Reply,)> {
-    server::routes()
-}
-
-/// A combined filter handling all server routes
-fn server() -> BoxedFilter<(impl Reply,)> {
-    warp::path("api").and(backend()).or(frontend()).boxed()
-}
+use rustpad_server::server;
 
 #[tokio::main]
 async fn main() {

+ 27 - 44
rustpad-server/src/server.rs → rustpad-server/src/rustpad.rs

@@ -1,38 +1,19 @@
-//! Server routes for Rustpad
+//! Asynchronous systems logic for Rustpad
 
 use std::sync::atomic::{AtomicU64, Ordering};
-use std::sync::Arc;
 use std::time::Duration;
 
 use futures::prelude::*;
 use log::{error, info};
+use operational_transform::OperationSeq;
 use parking_lot::RwLock;
+use serde::{Deserialize, Serialize};
 use tokio::{sync::Notify, time};
-use warp::{
-    filters::BoxedFilter,
-    ws::{Message, WebSocket, Ws},
-    Filter, Reply,
-};
-
-/// Construct a set of routes for the server
-pub fn routes() -> BoxedFilter<(impl Reply,)> {
-    let rustpad = Arc::new(Rustpad::default());
-    let rustpad = warp::any().map(move || Arc::clone(&rustpad));
-
-    let socket = warp::path("socket")
-        .and(warp::path::end())
-        .and(warp::ws())
-        .and(rustpad)
-        .map(|ws: Ws, rustpad: Arc<Rustpad>| {
-            ws.on_upgrade(move |socket| async move { rustpad.on_connection(socket).await })
-        });
-
-    socket.boxed()
-}
+use warp::ws::{Message, WebSocket};
 
 /// The main object for a collaborative session
 #[derive(Default)]
-struct Rustpad {
+pub struct Rustpad {
     state: RwLock<State>,
     count: AtomicU64,
     notify: Notify,
@@ -44,8 +25,29 @@ struct State {
     messages: Vec<(u64, String)>,
 }
 
+/// A message received from the client over WebSocket
+#[derive(Clone, Debug, Serialize, Deserialize)]
+enum ClientMsg {
+    Edit { revision: usize, op: OperationSeq },
+}
+
+/// A message sent to the client over WebSocket
+#[derive(Clone, Debug, Serialize, Deserialize)]
+enum ServerMsg {
+    History {
+        revision: usize,
+        ops: Vec<OperationSeq>,
+    },
+}
+
 impl Rustpad {
-    async fn on_connection(&self, mut socket: WebSocket) {
+    /// Construct a new, empty Rustpad object
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Handle a connection from a WebSocket
+    pub async fn on_connection(&self, mut socket: WebSocket) {
         let id = self.count.fetch_add(1, Ordering::Relaxed);
         info!("connection! id = {}", id);
 
@@ -123,22 +125,3 @@ impl Rustpad {
         self.notify.notify_waiters();
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[tokio::test]
-    async fn test_single_message() {
-        let filter = routes();
-        let mut client = warp::test::ws()
-            .path("/socket")
-            .handshake(filter)
-            .await
-            .expect("handshake");
-        client.send_text("hello world").await;
-        let msg = client.recv().await.expect("recv");
-        let msg = msg.to_str().expect("string");
-        assert_eq!(msg, "[[0,\"hello world\"]]");
-    }
-}

+ 10 - 0
rustpad-wasm/src/lib.rs

@@ -132,6 +132,16 @@ impl OpSeq {
     pub fn target_len(&self) -> usize {
         self.0.target_len()
     }
+
+    /// Attempts to deserialize an `OpSeq` from a JSON string.
+    pub fn from_str(s: &str) -> Option<OpSeq> {
+        serde_json::from_str(s).ok()
+    }
+
+    /// Converts this object to a JSON string.
+    pub fn to_string(&self) -> String {
+        serde_json::to_string(self).expect("json serialization failure")
+    }
 }
 
 #[wasm_bindgen]