diff --git a/rc_services_room/src/data/crf.rs b/rc_core/src/data/crf.rs similarity index 67% rename from rc_services_room/src/data/crf.rs rename to rc_core/src/data/crf.rs index 1ed6e01..30cb523 100644 --- a/rc_services_room/src/data/crf.rs +++ b/rc_core/src/data/crf.rs @@ -28,15 +28,15 @@ impl ShopItemListFilters { page_size: read_u32(r)?, weapon_filter: read_i32(r)?, movement_filter: read_i32(r)?, - weapon_groups: rc_core::data::read_str_for_binwriter(r)?, - movement_groups: rc_core::data::read_str_for_binwriter(r)?, + weapon_groups: super::read_str_for_binwriter(r)?, + movement_groups: super::read_str_for_binwriter(r)?, player: read_bool(r)?, sort_mode: read_i32(r)?, min_cpu: read_i32(r)?, max_cpu: read_i32(r)?, min_robot_ranking: read_i32(r)?, max_robot_ranking: read_i32(r)?, - text: rc_core::data::read_str_for_binwriter(r)?, + text: super::read_str_for_binwriter(r)?, text_search_field: read_i32(r)?, show_featured: read_bool(r)?, show_hidden: read_bool(r)?, @@ -94,22 +94,22 @@ pub struct ItemResult { impl ItemResult { pub fn dump(&self, w: &mut dyn std::io::Write) -> std::io::Result { let mut total_len = write_i32(w, self.id)?; - total_len += rc_core::data::write_str_for_binreader(&self.name, w)?; - total_len += rc_core::data::write_str_for_binreader(&self.description, w)?; - total_len += rc_core::data::write_str_for_binreader(&self.thumbnail, w)?; + total_len += super::write_str_for_binreader(&self.name, w)?; + total_len += super::write_str_for_binreader(&self.description, w)?; + total_len += super::write_str_for_binreader(&self.thumbnail, w)?; total_len += write_f64(w, self.style_rating)?; total_len += write_f64(w, self.combat_rating)?; total_len += write_i32(w, self.cpu)?; total_len += write_i32(w, self.total_robot_ranking)?; total_len += write_i64(w, self.expiry_date)?; total_len += write_bool(w, self.buyable)?; - total_len += rc_core::data::write_str_for_binreader(&self.added_by, w)?; - total_len += rc_core::data::write_str_for_binreader(&self.added_by_display_name, w)?; + total_len += super::write_str_for_binreader(&self.added_by, w)?; + total_len += super::write_str_for_binreader(&self.added_by_display_name, w)?; total_len += write_i64(w, self.added_date)?; total_len += write_i32(w, self.rent_count)?; total_len += write_i32(w, self.buy_count)?; total_len += write_bool(w, self.featured)?; - total_len += rc_core::data::write_str_for_binreader(&self.banner_message, w)?; + total_len += super::write_str_for_binreader(&self.banner_message, w)?; total_len += write_i32(w, self.cube_counts.len() as i32)?; for (key, val) in self.cube_counts.iter() { total_len += write_u32(w, *key)?; @@ -192,6 +192,69 @@ impl std::convert::From for ItemData { } } +pub struct UploadData { + pub version: String, + pub slot: i32, + pub name: String, + pub description: String, + pub thumbnail: Vec, +} + +impl UploadData { + pub fn from_transmissibles(build_version: String, mut data: polariton::operation::Dict) -> Result { + if let Some(slot_i) = data.items.iter().position(|(key, _)| typed_is_str(key, "SlotId")) { + if let (_, polariton::operation::Typed::Int(slot)) = data.items.swap_remove(slot_i) { + if let Some(name_i) = data.items.iter().position(|(key, _)| typed_is_str(key, "Name")) { + if let (_, polariton::operation::Typed::Str(name)) = data.items.swap_remove(name_i) { + if let Some(description_i) = data.items.iter().position(|(key, _)| typed_is_str(key, "Description")) { + if let (_, polariton::operation::Typed::Str(description)) = data.items.swap_remove(description_i) { + if let Some(thumb_i) = data.items.iter().position(|(key, _)| typed_is_str(key, "Thumbnail")) { + if let (_, polariton::operation::Typed::Bytes(thumb)) = data.items.swap_remove(thumb_i) { + return Ok(Self { + version: build_version, + slot, + name: name.string, + description: description.string, + thumbnail: thumb.vec, + }); + } else { + log::warn!("Factory upload data Thumbnail is not Bytes"); + } + } else { + log::warn!("Factory upload data is missing Thumbnail"); + } + } else { + log::warn!("Factory upload data Description is not Str"); + } + } else { + log::warn!("Factory upload data is missing Description"); + } + } else { + log::warn!("Factory upload data Name is not Str"); + } + } else { + log::warn!("Factory upload data is missing Name"); + } + } else { + log::warn!("Factory upload data SlotId is not Int") + } + } else { + log::warn!("Factory upload data is missing SlotId") + } + Err(crate::data::error_codes::WebServicesError::UnexpectedError as i16) + } + + pub fn into_core(self) -> crate::persist::user::VehicleUploadData { + crate::persist::user::VehicleUploadData { + version: self.version, + slot: self.slot, + name: self.name, + description: self.description, + thumbnail: self.thumbnail, + } + } +} + #[inline] fn read_i32(r: &mut dyn std::io::Read) -> std::io::Result { let mut buf = [0u8; 4]; @@ -247,3 +310,11 @@ fn write_bool(w: &mut dyn std::io::Write, b: bool) -> std::io::Result { fn split_u32(s: &str) -> Vec { s.split(',').filter_map(|x| x.parse().ok()).collect() } + +fn typed_is_str(ty: &polariton::operation::Typed, s: &str) -> bool { + if let polariton::operation::Typed::Str(ty_s) = ty { + ty_s.string == s + } else { + false + } +} diff --git a/rc_core/src/data/mod.rs b/rc_core/src/data/mod.rs index 66b7d66..1af29fe 100644 --- a/rc_core/src/data/mod.rs +++ b/rc_core/src/data/mod.rs @@ -10,6 +10,7 @@ pub mod tech_tree; pub mod voting; pub mod weapon_list; pub mod weapon_upgrade; +pub mod crf; pub mod error_codes; diff --git a/rc_core/src/factory/adapter_enum.rs b/rc_core/src/factory/adapter_enum.rs index 357942b..69dde44 100644 --- a/rc_core/src/factory/adapter_enum.rs +++ b/rc_core/src/factory/adapter_enum.rs @@ -21,6 +21,14 @@ impl rc_factory::VehicleFactoryAdapter for Factory { Self::None => Ok(Vec::default()), } } + + async fn upload(&self, vehicle: rc_factory::VehicleUploadInfo) -> Result> { + match self { + Self::Arc(x) => x.upload(vehicle).await, + Self::Custom(x) => x.upload(vehicle).await, + Self::None => Ok(false), + } + } } impl Factory { diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index f318426..47d0a2e 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -470,4 +470,26 @@ impl super::User for UserData { ], }.as_transmissible()) } + + async fn prepare_factory_upload(&self, vehicle: super::VehicleUploadData) -> Result { + let slot = self.load_garage_by_slot(vehicle.slot as u32).await.map_err(|e| { + log::error!("Failed to retrieve vehicle slot {} for user_id {} (prepare_factory_upload): {}", vehicle.slot, self.account.id, e); + DATABASE_ERR + })?.ok_or_else(|| { + log::error!("Failed to find vehicle slot {} for user_id {} (prepare_factory_upload)", vehicle.slot, self.account.id); + INVALID_ROBOT_ERR + })?; + Ok(rc_factory::VehicleUploadInfo { + name: vehicle.name, + description: vehicle.description, + thumbnail: vehicle.thumbnail, + added_by: self.account.public_id.clone(), + added_by_display_name: self.account.display_name.clone(), + cpu: slot.total_robot_cpu, + total_robot_ranking: slot.total_robot_ranking, + build_version: vehicle.version, + cube_data: slot.robot_data, + colour_data: slot.colour_data, + }) + } } diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index f489f55..c3449e0 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -11,7 +11,7 @@ mod inventory; pub use inventory::UnlockedParts; mod traits; -pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo}; +pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData}; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 5f22c8d..6841fd8 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -71,6 +71,7 @@ pub trait User { async fn upgrade_slot(&self, increments: i32) -> Result, i16>; fn signup_date(&self) -> i64; async fn singleplayer_robots(&self) -> Result, i16>; + async fn prepare_factory_upload(&self, vehicle: VehicleUploadData) -> Result; } pub struct UserSlots { @@ -111,3 +112,11 @@ pub struct VehicleData { pub weapon_order: Vec, pub crf_id: Option, } + +pub struct VehicleUploadData { + pub version: String, + pub slot: i32, + pub name: String, + pub description: String, + pub thumbnail: Vec, +} diff --git a/rc_factory/src/arc/adapter.rs b/rc_factory/src/arc/adapter.rs index be57376..76ef109 100644 --- a/rc_factory/src/arc/adapter.rs +++ b/rc_factory/src/arc/adapter.rs @@ -146,4 +146,9 @@ impl crate::VehicleFactoryAdapter for ArcAdapter { log::debug!("Search vehicles returned {} results", infos.len()); Ok(infos) } + + async fn upload(&self, _vehicle: crate::VehicleUploadInfo) -> Result> { + log::info!("Arc adapter does not support uploading factory vehicles"); + Ok(false) + } } diff --git a/rc_factory/src/lib.rs b/rc_factory/src/lib.rs index 0f3746a..6030f05 100644 --- a/rc_factory/src/lib.rs +++ b/rc_factory/src/lib.rs @@ -1,4 +1,4 @@ pub mod arc; mod traits; -pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo}; +pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleUploadInfo}; diff --git a/rc_factory/src/traits.rs b/rc_factory/src/traits.rs index bc6818a..93418ef 100644 --- a/rc_factory/src/traits.rs +++ b/rc_factory/src/traits.rs @@ -2,6 +2,7 @@ pub trait VehicleFactoryAdapter: Send + Sync + 'static { async fn vehicle(&self, id: u32) -> Result, Box>; async fn list(&self, query: libfj::robocraft::ListQuery) -> Result, Box>; + async fn upload(&self, vehicle: VehicleUploadInfo) -> Result>; } #[derive(Debug, Clone)] @@ -50,3 +51,17 @@ pub struct VehicleQueryInfo { pub cosmetic_rating: f64, pub cube_amounts: std::collections::HashMap, } + +#[derive(Debug)] +pub struct VehicleUploadInfo { + pub name: String, + pub description: String, + pub thumbnail: Vec, + pub added_by: String, + pub added_by_display_name: String, + pub cpu: u32, + pub total_robot_ranking: u32, + pub cube_data: Vec, + pub colour_data: Vec, + pub build_version: String, +} diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs index 81cdb36..43af8bb 100644 --- a/rc_services_room/src/data/mod.rs +++ b/rc_services_room/src/data/mod.rs @@ -28,4 +28,4 @@ pub use rc_core::data::error_codes; //pub use rc_core::data::game_mode; pub mod score_multipliers; //pub use rc_core::data::campaign; -pub mod crf; +pub use rc_core::data::crf; diff --git a/rc_services_room/src/operations/crf_upload.rs b/rc_services_room/src/operations/crf_upload.rs new file mode 100644 index 0000000..c716260 --- /dev/null +++ b/rc_services_room/src/operations/crf_upload.rs @@ -0,0 +1,51 @@ +use polariton_server::operations::{Operation, OperationCode}; +use polariton::operation::{Typed, ParameterTable}; +use rc_factory::VehicleFactoryAdapter; + +const CODE: u8 = 96; + +const DATA_PARAM_KEY: u8 = 101; // in; dict +const VERSION_PARAM_KEY: u8 = 99; // in; str +const SUCCESS_PARAM_KEY: u8 = 103; // out; bool + +async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy, factory: &std::sync::Arc) -> Result { + let mut params = params.to_dict(); + if let Some(Typed::Str(version)) = params.remove(&VERSION_PARAM_KEY) { + if let Some(Typed::Dict(data)) = params.remove(&DATA_PARAM_KEY) { + let upload_info = crate::data::crf::UploadData::from_transmissibles(version.string, data)?; + let user_info = user.user()?; + let prepared = user_info.prepare_factory_upload(upload_info.into_core()).await?; + let success = factory.upload(prepared).await.map_err(|e| { + log::error!("Failed to upload to factory: {}", e); + rc_core::data::error_codes::WebServicesError::UnexpectedError as i16 + })?; + params.insert(SUCCESS_PARAM_KEY, Typed::Bool(success)); + } + } + Ok(params.into()) +} + +pub struct CrfUploadProvider { + factory: std::sync::Arc, +} + +#[async_trait::async_trait] +impl Operation<()> for CrfUploadProvider { + type User = crate::UserTy; + + async fn handle_async(&self, params: polariton::operation::ParameterTable<()>, user: &Self::User) -> polariton::operation::OperationResponse<()> { + polariton_server::operations::result_to_op_resp::(do_handling(params, user, &self.factory).await) + } +} + +impl OperationCode for CrfUploadProvider { + fn op_code() -> u8 { + CODE + } +} + +pub(super) fn crf_upload_provider(factory: &std::sync::Arc) -> CrfUploadProvider { + CrfUploadProvider { + factory: factory.to_owned(), + } +} diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index a21723b..3eba0b8 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -90,6 +90,7 @@ mod crf_earnings; mod crf_list_query; mod crf_vehicle_data; mod crf_purchase; +mod crf_upload; use polariton_server::operations::OperationsHandler; @@ -202,4 +203,5 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(crf_list_query::crf_item_list_query_provider(&init_ctx.factory)) .add(crf_vehicle_data::crf_item_data_provider(&init_ctx.factory)) .add(crf_purchase::crf_copy_to_bay_provider(&init_ctx.factory, init_ctx.parsers.weapon_order())) + .add(crf_upload::crf_upload_provider(&init_ctx.factory)) }