Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added global capture and capture enum #45

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# the CodeChat Editor. If not, see
# [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
#
# # `Cargo.toml` -- Rust build/package management config for the server
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

# # `Cargo.toml` -- Rust build/package management config
#
# ## General package configurations
[package]
Expand All @@ -29,59 +29,70 @@ license = "GPL-3.0-only"
name = "codechat-editor-server"
readme = "../README.md"
repository = "https://github.com/bjones1/CodeChat_Editor"
version = "0.1.5"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert this change.

version = "0.1.3"

# This library allows other packages to use core CodeChat Editor features.
[lib]
name = "code_chat_editor"

# ## Dependencies
[dependencies]
actix = "0.13.1"
async-once-cell = "0.5.0"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you install cargo sort then run it? I think most of these changes are due to sorted vs. non-sorted dependencies.

actix-files = "0.6"
actix-rt = "2.9.0"
actix-web = "4"
actix-ws = "0.3.0"
bytes = { version = "1", features = ["serde"] }
chrono = "0.4"
clap = { version = "4.5.19", features = ["derive"] }
dunce = "1.0.5"
futures-util = "0.3.29"
indoc = "2.0.5"
lazy_static = "1"
log = "0.4"
log4rs = "1.3"
mime = "0.3.17"
mime_guess = "2.0.5"
minreq = "2.12.0"
normalize-line-endings = "0.3.0"
notify-debouncer-full = "0.4"
open = "5.3.0"
path-slash = "0.2.1"
pest = "2.7.14"
pest_derive = "2.7.14"
# Per the [docs](https://docs.rs/crate/pulldown-cmark/latest), skip building the
# binary.
pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] }
regex = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"] }
url = "2.5.2"
urlencoding = "2"
# Per the [docs](https://docs.rs/crate/pulldown-cmark/latest), skip building the
# binary.
pulldown-cmark = { version = "0.12", default-features = false, features = ["html"] }
futures-util = "0.3.29"
async-trait = "0.1.81"
open = "5.3.0"
dunce = "1.0.5"
minreq = "2.12.0"
mime = "0.3.17"
mime_guess = "2.0.5"
url = "2.5.2"
clap = { version = "4.5.19", features = ["derive"] }
indoc = "2.0.5"
cmd_lib = "1.9.5"
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4"] }
simplelog = "0.12.0"
config = "0.13"
pest = "2.7.14"
pest_derive = "2.7.14"
normalize-line-endings = "0.3.0"
uuid = { version = "1", features = ["v4"] }
dirs = "5.0.1"

# [Windows-only dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies).
[target.'cfg(windows)'.dependencies]
win_partitions = "0.3.0"

# ### Development-only dependencies
[dev-dependencies]
assertables = "9"
assert_fs = "1"
tokio-tungstenite = "0.24"
actix-http = "3.9.0"
assert_cmd = "2.0.16"
assert_fs = "1"
assertables = "9"
predicates = "3.1.2"
tokio-tungstenite = "0.26"
# See the [docs](https://github.com/rust-lang/rust-clippy#usage) to install
# clippy; it can't be installed as a dev-dependency. See the
# [fmt docs](https://github.com/rust-lang/rustfmt#quick-start) to install fmt.

# #### Use local packages for development
[patch.crates-io]
Expand Down
120 changes: 109 additions & 11 deletions server/src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,103 @@
// Standard library
use indoc::indoc;
use std::fs;
use std::io;
use std::io::{self, Write};
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

// Third-party
use async_once_cell::OnceCell;
use chrono::Local;
use dirs::config_dir;
use log::{error, info};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use tokio_postgres::{Client, NoTls};
use uuid::Uuid;

pub static GLOBAL_EVENT_CAPTURE: OnceCell<Arc<EventCapture>> = OnceCell::new();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove pub -- I assume this is only used by the function below.


pub async fn get_event_capture() -> Arc<EventCapture> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find! I like this!

GLOBAL_EVENT_CAPTURE
.get_or_init(async {
let capture = EventCapture::new("config.json")
.await
.expect("Failed to initialize EventCapture");
Arc::new(capture)
})
.await
.clone()
}

// Local

/// Returns the path where the identifier file is stored.
fn get_uuid_file_path() -> Result<PathBuf, io::Error> {
let mut config_path = config_dir().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"Could not find configuration directory",
)
})?;
config_path.push("vsc_extension");
config_path.push("uuid.txt");
Ok(config_path)
}

/// Creates a new UUID and writes it to the file if it does not already exist.
fn create_uuid() -> Result<(), io::Error> {
let path = self::get_uuid_file_path()?;

// Check if the file already exists
if path.exists() {
return Ok(()); // UUID already exists, no need to create
}

// Generate a new UUID
let new_uuid = Uuid::new_v4().to_string();

// Ensure the parent directory exists
if let Some(parent) = path.parent() {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong logic -- only runs if path.parent() is Some, not if it exists. The parent should always exist. I'm guessing you want

    if !path.parent().expect("Config directory has no parent").exists()`.

fs::create_dir_all(parent)?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some context into errors returned. For example,

        fs::create_dir_all(parent).map_err(|err| {
            io::Error::new(
                err.kind(),
                format!("Unable to create config directory {path:?}: {err}"),
            )
        })?;

}

// Write the new UUID to the file
let mut file = fs::File::create(&path)?;
file.write_all(new_uuid.as_bytes())?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above -- add context to the error.

Ok(())
}

/// Retrieves the UUID from the file. If the file does not exist, it creates one.
pub fn get_uuid() -> Result<String, io::Error> {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be non-pub?

let path = get_uuid_file_path()?;

// If the file does not exist, create it
if !path.exists() {
self::create_uuid()?;
}

// Read and return the UUID
let uuid = fs::read_to_string(&path)?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add context to error.

Ok(uuid.trim().to_string())
}

/* ## The Event Structure:

The `Event` struct represents an event to be stored in the database.

Fields: - `user_id`: The ID of the user associated with the event. -
Fields: -
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check formatting in CodeChat -- it's messed up here and elsewhere.

`event_type`: The type of event (e.g., "keystroke", "file_open"). - `data`:
Optional additional data associated with the event.

### Example

let event = Event { user_id: "user123".to_string(), event_type:
"keystroke".to_string(), data: Some("Pressed key A".to_string()), };
let event = Event {event_type:"keystroke".to_string(), data: Some("Pressed key A".to_string()), };
*/

#[derive(Deserialize, Debug)]
pub struct Event {
pub user_id: String,
pub event_type: String,
pub event_type: EventType, // Use the EventType enum
pub data: Option<String>,
}

Expand Down Expand Up @@ -95,7 +161,6 @@ holds a `tokio_postgres::Client` for database operations.

// Create an event
let event = Event {
user_id: "user123".to_string(),
event_type: "keystroke".to_string(),
data: Some("Pressed key A".to_string()),
};
Expand All @@ -111,6 +176,31 @@ pub struct EventCapture {
db_client: Arc<Mutex<Client>>,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps make this a Arc<Mutex<Option<Client>>>? If the config file is found and the db connections, the logging happens. Otherwise, no logging happens but the code still runs. (Currently, it errors if without a connection/config file/etc.)

}

// Define a globally available EventType enum
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EventType {
KeyStroke,
FileOpen,
FileSave,
ApplicationStart,
ApplicationExit,
System,
}

impl EventType {
/// Convert the EventType enum to a string representation
pub fn as_str(&self) -> &'static str {
match self {
EventType::KeyStroke => "KeyStroke",
EventType::FileOpen => "FileOpen",
EventType::FileSave => "FileSave",
EventType::ApplicationStart => "ApplicationStart",
EventType::ApplicationExit => "ApplicationExit",
EventType::System => "System Event",
}
}
}

/*
## The EventCapture Implementation
*/
Expand Down Expand Up @@ -181,7 +271,6 @@ impl EventCapture {
let event_capture = EventCapture::new("config.json").await?;

let event = Event {
user_id: "user123".to_string(),
event_type: "keystroke".to_string(),
data: Some("Pressed key A".to_string()),
};
Expand All @@ -201,6 +290,15 @@ impl EventCapture {
VALUES ($1, $2, $3, $4)
"};

// Retrieve the UUID, creating it if necessary
let uuid = match get_uuid() {
Ok(uuid) => uuid,
Err(err) => {
error!("Error obtaining UUID: {:?}", err);
String::new() // Provide a fallback value, e.g., an empty string
}
};

// Acquire a lock on the database client for thread-safe access
let client = self.db_client.lock().await;

Expand All @@ -209,8 +307,8 @@ impl EventCapture {
.execute(
stmt,
&[
&event.user_id,
&event.event_type,
&uuid,
&event.event_type.as_str(),
&formatted_time,
&event.data,
],
Expand All @@ -232,7 +330,7 @@ CREATE TABLE events ( id SERIAL PRIMARY KEY, user_id TEXT NOT NULL,
event_type TEXT NOT NULL, timestamp TEXT NOT NULL, data TEXT );

- **`id SERIAL PRIMARY KEY`**: Auto-incrementing primary key.
- **`user_id TEXT NOT NULL`**: The ID of the user associated with the event.
- **`user_id TEXT NOT NULL`**: The ID of the user associated with the event (UUID).
- **`event_type TEXT NOT NULL`**: The type of event.
- **`timestamp TEXT NOT NULL`**: The timestamp of the event.
- **`data TEXT`**: Optional additional data associated with the event.
Expand Down
31 changes: 19 additions & 12 deletions server/src/webserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ use vscode::{
};

// ### Local
//use crate::capture::EventCapture;
use crate::capture::get_event_capture;
use crate::capture::Event;
use crate::capture::EventCapture;
use crate::capture::EventType;
use crate::processing::{
source_to_codechat_for_web_string, CodeChatForWeb, TranslationResultsString,
};
Expand All @@ -80,10 +83,10 @@ use filewatcher::{
filewatcher_websocket,
};

// ## Data structures
//
// ### Data structures supporting a websocket connection between the IDE, this server, and the CodeChat Editor Client
//
/// ## Data structures
///
/// ### Data structures supporting a websocket connection between the IDE, this server, and the CodeChat Editor Client
///
/// Provide queues which send data to the IDE and the CodeChat Editor Client.
#[derive(Debug)]
struct WebsocketQueues {
Expand Down Expand Up @@ -318,9 +321,6 @@ lazy_static! {
static ref ROOT_PATH: PathBuf = {
let exe_path = env::current_exe().unwrap();
let exe_dir = exe_path.parent().unwrap();
#[cfg(not(any(test, debug_assertions)))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep these changes.

let root_path = PathBuf::from(exe_dir);
#[cfg(any(test, debug_assertions))]
let mut root_path = PathBuf::from(exe_dir);
// When in debug or running tests, use the layout of the Git repo to
// find client files. In release mode, we assume the static folder is a
Expand Down Expand Up @@ -1077,12 +1077,22 @@ async fn client_websocket(
// ## Webserver core
#[actix_web::main]
pub async fn main(port: u16) -> std::io::Result<()> {
// Initialize EventCapture
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in run_server, since the old init code is there.

let event_capture = get_event_capture().await;

// Example usage
let event = Event {
event_type: EventType::System,
data: Some("Server Started".to_string()),
};

event_capture.insert_event(event).await?;
run_server(port).await
}

pub async fn run_server(port: u16) -> std::io::Result<()> {
// Connect to the Capture Database
//let _event_capture = EventCapture::new("config.json").await?;
let _event_capture = EventCapture::new("config.json").await?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this, I think -- I assume the code above will replace this?


// Pre-load the bundled files before starting the webserver.
let _ = &*BUNDLED_FILES_MAP;
Expand All @@ -1104,9 +1114,6 @@ pub async fn run_server(port: u16) -> std::io::Result<()> {
}

pub fn configure_logger(level: LevelFilter) {
#[cfg(not(debug_assertions))]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep these changes -- you removed them.

let l4rs = ROOT_PATH.clone();
#[cfg(debug_assertions)]
let mut l4rs = ROOT_PATH.clone();
#[cfg(debug_assertions)]
l4rs.push("server");
Expand Down
Loading