Preallocations

This commit is contained in:
Adrian Woźniak 2021-08-12 16:10:30 +02:00
parent 146be0184b
commit c8730485d0
No known key found for this signature in database
GPG Key ID: DE43476F72AD3F6C
14 changed files with 129 additions and 48 deletions

4
Cargo.lock generated
View File

@ -2042,9 +2042,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.94" version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
[[package]] [[package]]
name = "line-wrap" name = "line-wrap"

View File

@ -16,6 +16,7 @@ use jirs_data::{User, UserId, WsMsg};
use websocket_actor::server::InnerMsg::BroadcastToChannel; use websocket_actor::server::InnerMsg::BroadcastToChannel;
use websocket_actor::server::WsServer; use websocket_actor::server::WsServer;
#[cfg(feature = "aws-s3")]
#[post("/")] #[post("/")]
pub async fn upload( pub async fn upload(
mut payload: Multipart, mut payload: Multipart,
@ -80,11 +81,74 @@ pub async fn upload(
} }
} }
#[cfg(not(feature = "aws-s3"))]
#[post("/")]
pub async fn upload(
mut payload: Multipart,
db: Data<Addr<DbExecutor>>,
ws: Data<Addr<WsServer>>,
fs: Data<Addr<filesystem_actor::FileSystemExecutor>>,
) -> Result<HttpResponse, Error> {
let mut user_id: Option<UserId> = None;
let mut avatar_url: Option<String> = None;
while let Ok(Some(field)) = payload.try_next().await {
let disposition: ContentDisposition = match field.content_disposition() {
Some(d) => d,
_ => continue,
};
if !disposition.is_form_data() {
return Ok(HttpResponse::BadRequest().finish());
}
match disposition.get_name() {
Some("token") => {
user_id = Some(handle_token(field, db.clone()).await?);
}
Some("avatar") => {
let id = user_id.ok_or_else(|| HttpResponse::Unauthorized().finish())?;
avatar_url = Some(
crate::handlers::upload_avatar_image::handle_image(
id,
field,
disposition,
fs.clone(),
)
.await?,
);
}
_ => continue,
};
}
let user_id = match user_id {
Some(id) => id,
_ => return Ok(HttpResponse::Unauthorized().finish()),
};
let project_id = match block_on(db.send(CurrentUserProject { user_id })) {
Ok(Ok(user_project)) => user_project.project_id,
_ => return Ok(HttpResponse::UnprocessableEntity().finish()),
};
match (user_id, avatar_url) {
(user_id, Some(avatar_url)) => {
let user = update_user_avatar(user_id, avatar_url.clone(), db).await?;
ws.send(BroadcastToChannel(
project_id,
WsMsg::AvatarUrlChanged(user.id, avatar_url),
))
.await
.map_err(|_| HttpResponse::UnprocessableEntity().finish())?;
Ok(HttpResponse::NoContent().finish())
}
_ => Ok(HttpResponse::UnprocessableEntity().finish()),
}
}
async fn update_user_avatar( async fn update_user_avatar(
user_id: UserId, user_id: UserId,
new_url: String, new_url: String,
db: Data<Addr<DbExecutor>>, db: Data<Addr<DbExecutor>>,
) -> Result<User, Error> { ) -> Result<User, actix_web::Error> {
match db match db
.send(UpdateAvatarUrl { .send(UpdateAvatarUrl {
user_id, user_id,
@ -96,16 +160,23 @@ async fn update_user_avatar(
Ok(Err(e)) => { Ok(Err(e)) => {
error!("{:?}", e); error!("{:?}", e);
Err(HttpResponse::Unauthorized().finish().into()) Err(actix_web::Error::from(
HttpResponse::Unauthorized().finish(),
))
} }
Err(e) => { Err(e) => {
error!("{:?}", e); error!("{:?}", e);
Err(HttpResponse::Unauthorized().finish().into()) Err(actix_web::Error::from(
HttpResponse::Unauthorized().finish(),
))
} }
} }
} }
async fn handle_token(mut field: Field, db: Data<Addr<DbExecutor>>) -> Result<UserId, Error> { async fn handle_token(
mut field: Field,
db: Data<Addr<DbExecutor>>,
) -> Result<UserId, actix_web::Error> {
let mut f: Vec<u8> = vec![]; let mut f: Vec<u8> = vec![];
while let Some(chunk) = field.next().await { while let Some(chunk) = field.next().await {
let data = chunk.unwrap(); let data = chunk.unwrap();

View File

@ -29,9 +29,9 @@ flate2 = { version = "*" }
syntect = { version = "*" } syntect = { version = "*" }
lazy_static = { version = "*" } lazy_static = { version = "*" }
log = "0.4" log = { version = "0.4" }
pretty_env_logger = "0.4" pretty_env_logger = { version = "0.4" }
env_logger = "0.7" env_logger = { version = "0.7" }
uuid = { version = "0.8.1", features = ["serde", "v4", "v5"] } uuid = { version = "0.8.1", features = ["serde", "v4", "v5"] }

View File

@ -7,10 +7,8 @@ use jirs_data::msg::WsError;
use jirs_data::{Token, WsMsg}; use jirs_data::{Token, WsMsg};
use mail_actor::welcome::Welcome; use mail_actor::welcome::Welcome;
use crate::{ use crate::server::InnerMsg;
db_or_debug_and_return, db_or_debug_or_fallback, mail_or_debug_and_return, WebSocketActor, use crate::{db_or_debug_and_return, db_or_debug_or_fallback, mail_or_debug_and_return, *};
WsHandler, WsResult,
};
pub struct Authenticate { pub struct Authenticate {
pub name: String, pub name: String,
@ -69,7 +67,7 @@ impl WsHandler<CheckAuthToken> for WebSocketActor {
self.current_user_project = self.load_user_project().ok(); self.current_user_project = self.load_user_project().ok();
self.current_project = self.load_project().ok(); self.current_project = self.load_project().ok();
block_on(self.join_channel(ctx.address().recipient())); block_on(self.join_channel(ctx.address().recipient::<InnerMsg>()));
Ok(Some(WsMsg::AuthorizeLoaded(Ok((user, setting))))) Ok(Some(WsMsg::AuthorizeLoaded(Ok((user, setting)))))
} }
} }

View File

@ -3,7 +3,7 @@ use jirs_data::{
DescriptionString, EndsAt, EpicId, IssueType, NameString, StartsAt, UserProject, WsMsg, DescriptionString, EndsAt, EpicId, IssueType, NameString, StartsAt, UserProject, WsMsg,
}; };
use crate::{db_or_debug_and_return, WebSocketActor, WsHandler, WsResult}; use crate::{db_or_debug_and_return, *};
pub struct LoadEpics; pub struct LoadEpics;

View File

@ -26,7 +26,7 @@ trait WsMessageSender {
fn send_msg(&mut self, msg: &jirs_data::WsMsg); fn send_msg(&mut self, msg: &jirs_data::WsMsg);
} }
struct WebSocketActor { pub struct WebSocketActor {
db: Data<Addr<DbExecutor>>, db: Data<Addr<DbExecutor>>,
mail: Data<Addr<MailExecutor>>, mail: Data<Addr<MailExecutor>>,
addr: Addr<WsServer>, addr: Addr<WsServer>,
@ -36,13 +36,20 @@ struct WebSocketActor {
current_project: Option<jirs_data::Project>, current_project: Option<jirs_data::Project>,
} }
impl Actor for WebSocketActor { pub type WsCtx = ws::WebsocketContext<WebSocketActor>;
type Context = ws::WebsocketContext<WebSocketActor>;
impl actix::Actor for WebSocketActor {
type Context = WsCtx;
} }
impl WsMessageSender for ws::WebsocketContext<WebSocketActor> { impl WsMessageSender for ws::WebsocketContext<WebSocketActor> {
fn send_msg(&mut self, msg: &WsMsg) { fn send_msg(&mut self, msg: &WsMsg) {
self.binary(bincode::serialize(msg).unwrap()) match bincode::serialize(msg) {
Err(err) => {
log::error!("{}", err);
}
Ok(v) => self.binary(v),
}
} }
} }
@ -328,7 +335,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketActor {
self.addr.do_send(InnerMsg::Leave( self.addr.do_send(InnerMsg::Leave(
up.project_id, up.project_id,
user.id, user.id,
ctx.address().recipient(), ctx.address().recipient::<InnerMsg>(),
)); ));
} }
ctx.stop() ctx.stop()
@ -337,7 +344,7 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketActor {
pub trait WsHandler<Message> pub trait WsHandler<Message>
where where
Self: Actor, Self: actix::Actor<Context = WsCtx>,
{ {
fn handle_msg(&mut self, msg: Message, _ctx: &mut <Self as Actor>::Context) -> WsResult; fn handle_msg(&mut self, msg: Message, _ctx: &mut <Self as Actor>::Context) -> WsResult;
} }

View File

@ -280,7 +280,7 @@ impl Model {
issue_form: None, issue_form: None,
project_form: None, project_form: None,
comment_form: None, comment_form: None,
comments_by_project_id: Default::default(), comments_by_project_id: HashMap::with_capacity(1_000),
page_content: page.build_content(), page_content: page.build_content(),
page, page,
host_url, host_url,
@ -291,22 +291,22 @@ impl Model {
messages_tooltip_visible: false, messages_tooltip_visible: false,
issues: vec![], issues: vec![],
users: vec![], users: vec![],
users_by_id: Default::default(), users_by_id: HashMap::with_capacity(1_000),
user_settings: None, user_settings: None,
comments: vec![], comments: vec![],
comments_by_id: Default::default(), comments_by_id: HashMap::with_capacity(1_000),
issue_statuses: vec![], issue_statuses: vec![],
issue_statuses_by_id: Default::default(), issue_statuses_by_id: HashMap::with_capacity(1_000),
issue_statuses_by_name: Default::default(), issue_statuses_by_name: HashMap::with_capacity(1_000),
messages: vec![], messages: vec![],
user_projects: vec![], user_projects: vec![],
projects: vec![], projects: vec![],
epics: vec![], epics: vec![],
issues_by_id: Default::default(), issues_by_id: HashMap::with_capacity(1_000),
show_extras: false, show_extras: false,
epics_by_id: Default::default(), epics_by_id: HashMap::with_capacity(1_000),
modals_stack: vec![], modals_stack: vec![],
modals: Default::default(), modals: Modals::default(),
key_triggers: std::rc::Rc::new(std::cell::RefCell::new(HashMap::with_capacity(20))), key_triggers: std::rc::Rc::new(std::cell::RefCell::new(HashMap::with_capacity(20))),
distinct_key_up: crate::shared::on_event::distinct(), distinct_key_up: crate::shared::on_event::distinct(),
} }

View File

@ -16,12 +16,15 @@ impl EpicsPage {
} }
pub fn build_issues_per_epic(model: &Model) -> HashMap<EpicId, Vec<IssueId>> { pub fn build_issues_per_epic(model: &Model) -> HashMap<EpicId, Vec<IssueId>> {
model.issues().iter().fold(HashMap::new(), |mut h, issue| { model.issues().iter().fold(
HashMap::with_capacity(model.issues().len()),
|mut h, issue| {
if let Some(epic_id) = issue.epic_id.as_ref() { if let Some(epic_id) = issue.epic_id.as_ref() {
h.entry(*epic_id).or_default().push(issue.id); h.entry(*epic_id).or_default().push(issue.id);
} }
h h
}) },
)
} }
pub fn issues(&self, epic_id: EpicId) -> Option<&Vec<IssueId>> { pub fn issues(&self, epic_id: EpicId) -> Option<&Vec<IssueId>> {

View File

@ -130,7 +130,7 @@ async fn update_avatar(data: FormData, host_url: String) -> Option<Msg> {
let path = format!("{}/avatar/", host_url); let path = format!("{}/avatar/", host_url);
let result = Request::new(path) let result = Request::new(path)
.method(Method::Post) .method(Method::Post)
.body(data.into()) .body(&data)
.fetch() .fetch()
.await; .await;
let response = match result { let response = match result {

View File

@ -68,10 +68,15 @@ impl ProjectPage {
issues.collect() issues.collect()
}; };
let issues_per_epic_id = issues.into_iter().fold(HashMap::new(), |mut m, issue| { let issues_per_epic_id = {
let issues_len = issues.len();
issues
.into_iter()
.fold(HashMap::with_capacity(issues_len), |mut m, issue| {
m.entry(issue.epic_id).or_insert_with(Vec::new).push(issue); m.entry(issue.epic_id).or_insert_with(Vec::new).push(issue);
m m
}); })
};
epics epics
.map(|epic| { .map(|epic| {

View File

@ -1,7 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use jirs_data::{IssueStatus, IssueStatusId, ProjectFieldId, UpdateProjectPayload, WsMsg}; use jirs_data::{IssueStatus, IssueStatusId, ProjectFieldId, UpdateProjectPayload, WsMsg};
use seed::error;
use seed::prelude::Orders; use seed::prelude::Orders;
use crate::components::styled_select::StyledSelectChanged; use crate::components::styled_select::StyledSelectChanged;

View File

@ -15,10 +15,7 @@ path = "./src/main.rs"
[features] [features]
aws-s3 = ["amazon-actor"] aws-s3 = ["amazon-actor"]
local-storage = ["filesystem-actor"] local-storage = ["filesystem-actor"]
default = [ default = ["local-storage"]
"aws-s3",
"local-storage",
]
[dependencies] [dependencies]
actix = { version = "0.10.0" } actix = { version = "0.10.0" }

View File

@ -20,9 +20,10 @@ hi = []
mail = [] mail = []
web = ["aws-s3", "local-storage"] web = ["aws-s3", "local-storage"]
websocket = [] websocket = []
default = ["local-storage", "database", "hi", "mail", "web", "websocket"]
[dependencies] [dependencies]
serde = "*" serde = { version = "*" }
toml = { version = "*" } toml = { version = "*" }
# Amazon S3 # Amazon S3

View File

@ -17,8 +17,8 @@ backend = ["diesel", "actix", "derive_enum_sql"]
frontend = [] frontend = []
[dependencies] [dependencies]
serde = "*" serde = { version = "*" }
serde_json = "*" serde_json = { version = "*" }
chrono = { version = "*", features = ["serde"] } chrono = { version = "*", features = ["serde"] }
uuid = { version = ">=0.7.0, <0.9.0", features = ["serde"] } uuid = { version = ">=0.7.0, <0.9.0", features = ["serde"] }