diff --git a/backend/src/api/api_utils.rs b/backend/src/api/api_utils.rs index 81b866256..0dedb88bb 100644 --- a/backend/src/api/api_utils.rs +++ b/backend/src/api/api_utils.rs @@ -32,10 +32,10 @@ use shared::utils::{ }; use std::borrow::Cow; use std::collections::HashMap; -use tokio::io::BufWriter; use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tokio::io::BufWriter; use tokio::sync::Mutex; use url::Url; @@ -809,7 +809,7 @@ pub async fn stream_response( let share_stream = is_stream_share_enabled(item_type, target); if share_stream { if let Some(value) = - try_shared_stream_response_if_any(app_state, stream_url, fingerprint, user, connection_permission, stream_channel.clone(), req_headers).await + try_shared_stream_response_if_any(app_state, stream_url, fingerprint, user, connection_permission, stream_channel.clone(), session_token, req_headers).await { return value.into_response(); } @@ -845,7 +845,7 @@ pub async fn stream_response( }; stream_channel.shared = share_stream; let stream = - ActiveClientStream::new(stream_details, app_state, user, connection_permission, fingerprint, stream_channel, None, req_headers) + ActiveClientStream::new(stream_details, app_state, user, connection_permission, fingerprint, stream_channel, Some(session_token), req_headers) .await; let stream_resp = if share_stream { debug_if_enabled!( @@ -953,6 +953,7 @@ fn get_stream_throttle(app_state: &AppState) -> u64 { .unwrap_or_default() } +#[allow(clippy::too_many_arguments)] async fn try_shared_stream_response_if_any( app_state: &AppState, stream_url: &str, @@ -960,6 +961,7 @@ async fn try_shared_stream_response_if_any( user: &ProxyUserCredentials, connect_permission: UserConnectionPermission, mut stream_channel: StreamChannel, + session_token: &str, req_headers: &HeaderMap, ) -> Option { if let Some((stream, provider)) = @@ -982,7 +984,7 @@ async fn try_shared_stream_response_if_any( stream_details.provider_name = provider; stream_channel.shared = true; let stream = - ActiveClientStream::new(stream_details, app_state, user, connect_permission, fingerprint, stream_channel, None, req_headers) + ActiveClientStream::new(stream_details, app_state, user, connect_permission, fingerprint, stream_channel, Some(session_token), req_headers) .await .boxed(); let mut response = axum::response::Response::builder().status(status_code); @@ -1258,7 +1260,7 @@ pub fn separate_number_and_remainder(input: &str) -> (String, Option) { } /// # Panics -pub fn empty_json_list_response() -> impl IntoResponse + Send { +pub fn empty_json_list_response() -> axum::response::Response { try_unwrap_body!(axum::response::Response::builder() .status(axum::http::StatusCode::OK) .header(axum::http::header::CONTENT_TYPE, mime::APPLICATION_JSON.to_string()) diff --git a/backend/src/api/endpoints/xmltv_api.rs b/backend/src/api/endpoints/xmltv_api.rs index 22044c071..6cd1f284c 100644 --- a/backend/src/api/endpoints/xmltv_api.rs +++ b/backend/src/api/endpoints/xmltv_api.rs @@ -18,7 +18,7 @@ use crate::repository::storage::get_target_storage_path; use crate::repository::xtream_repository::{xtream_get_epg_file_path, xtream_get_storage_path}; use crate::utils; -pub fn get_empty_epg_response() -> impl axum::response::IntoResponse + Send { +pub fn get_empty_epg_response() -> axum::response::Response { try_unwrap_body!(axum::response::Response::builder() .status(axum::http::StatusCode::OK) .header(axum::http::header::CONTENT_TYPE, axum::http::HeaderValue::from_static("text/xml")) @@ -145,13 +145,13 @@ fn parse_timeshift(time_shift: Option<&String>) -> Option { async fn serve_epg( epg_path: &Path, user: &ProxyUserCredentials, -) -> impl axum::response::IntoResponse + Send { +) -> axum::response::Response { match tokio::fs::File::open(epg_path).await { Ok(epg_file) => match parse_timeshift(user.epg_timeshift.as_ref()) { None => serve_file(epg_path, mime::TEXT_XML).await.into_response(), Some(duration) => serve_epg_with_timeshift(epg_file, duration), }, - Err(_) => get_empty_epg_response().into_response(), + Err(_) => get_empty_epg_response(), } } @@ -235,7 +235,6 @@ fn serve_epg_with_timeshift( ) .header(axum::http::header::CONTENT_ENCODING, "gzip") // Set Content-Encoding header .body(axum::body::Body::from_stream(body_stream))) - .into_response() } /// Handles XMLTV EPG API requests, serving the appropriate EPG file with optional time-shifting based on user configuration. diff --git a/backend/src/api/model/active_user_manager.rs b/backend/src/api/model/active_user_manager.rs index f71ac8768..d21900cca 100644 --- a/backend/src/api/model/active_user_manager.rs +++ b/backend/src/api/model/active_user_manager.rs @@ -152,7 +152,9 @@ impl ActiveUserManager { }; if let Some(username) = disconnected_user { - debug!("Released connection for user {username} at {addr}"); + if !username.is_empty() { + debug!("Released connection for user {username} at {addr}"); + } } if log_active_user { diff --git a/backend/src/main.rs b/backend/src/main.rs index d8cea7370..706f29763 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -175,15 +175,30 @@ async fn start_in_server_mode(cfg: Arc, targets: Arc) async fn healthcheck(config_file: &str) -> bool { let path = std::path::PathBuf::from(config_file); - let file = File::open(path).expect("Failed to open config file"); - let config: HealthcheckConfig = serde_yaml::from_reader(config_file_reader(file, true)).expect("Failed to parse config file"); + match File::open(path) { + Ok(file) => { + match serde_yaml::from_reader::<_, HealthcheckConfig>(config_file_reader(file, true)) { + Ok(config) => { + match reqwest::Client::new() + .get(format!("http://localhost:{}/healthcheck", config.api.port)) + .send() + .await + { + Ok(response) => matches!(response.json::().await, Ok(check) if check.status == "ok"), + Err(_) => false, + } + + } + Err(err) => { + error!("Failed to parse config file for healthcheck {err:?}"); + false + } + } + }, + Err(err) => { + error!("Failed to open config file for healthcheck {err:?}"); + false + } + } - match reqwest::Client::new() - .get(format!("http://localhost:{}/healthcheck", config.api.port)) - .send() - .await - { - Ok(response) => matches!(response.json::().await, Ok(check) if check.status == "ok"), - Err(_) => false, - } } diff --git a/backend/src/model/config/api_proxy.rs b/backend/src/model/config/api_proxy.rs index 47c130cab..672b499dc 100644 --- a/backend/src/model/config/api_proxy.rs +++ b/backend/src/model/config/api_proxy.rs @@ -1,6 +1,5 @@ use crate::model::{macros, AppConfig, Config, ProxyUserCredentials, TargetUser}; use crate::repository::user_repository::{backup_api_user_db_file, get_api_user_db_path, load_api_user, merge_api_user}; -use crate::utils::{save_api_proxy}; use log::debug; use std::cmp::PartialEq; use std::fs; @@ -148,7 +147,7 @@ impl ApiProxyConfig { let config = > as Access>::load(&cfg.config); let backup_dir = config.get_backup_dir(); - if let Err(err) = save_api_proxy(api_proxy_file, backup_dir.as_ref(), &ApiProxyConfigDto::from(&*self)).await { + if let Err(err) = utils::save_api_proxy(api_proxy_file, backup_dir.as_ref(), &ApiProxyConfigDto::from(&*self)).await { errors.push(format!("Error saving api proxy file: {err}")); } else { backup_api_user_db_file(cfg, &user_db_path).await; diff --git a/backend/src/model/config/base.rs b/backend/src/model/config/base.rs index aad2bfae4..5aeef6f8a 100644 --- a/backend/src/model/config/base.rs +++ b/backend/src/model/config/base.rs @@ -43,7 +43,7 @@ fn create_directories(cfg: &Config, temp_path: &Path) { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, Default)] pub struct Config { - pub threads: u8, + pub process_parallel: bool, pub api: ConfigApi, pub working_dir: String, pub backup_dir: Option, @@ -128,7 +128,7 @@ macros::from_impl!(Config); impl From<&ConfigDto> for Config { fn from(dto: &ConfigDto) -> Self { Config { - threads: dto.threads, + process_parallel: dto.process_parallel, api: ConfigApi::from(&dto.api), working_dir: dto.working_dir.clone(), backup_dir: dto.backup_dir.clone(), diff --git a/backend/src/processing/processor/playlist.rs b/backend/src/processing/processor/playlist.rs index 03429e5ec..5d6c86aae 100644 --- a/backend/src/processing/processor/playlist.rs +++ b/backend/src/processing/processor/playlist.rs @@ -386,11 +386,10 @@ async fn process_sources(client: Arc, config: &Arc, event_manager: Option>, playlist_state: Option<&Arc>, ) -> (Vec, Vec) { let mut async_tasks = JoinSet::new(); - let thread_num = config.config.load().threads; let sources = config.sources.load(); - let process_parallel = thread_num > 1 && sources.sources.len() > 1; + let process_parallel = config.config.load().process_parallel && sources.sources.len() > 1; if process_parallel && log_enabled!(Level::Debug) { - debug!("Using {thread_num} threads"); + debug!("Parallel processing enabled"); } let errors = Arc::new(Mutex::>::new(vec![])); let stats = Arc::new(Mutex::>::new(vec![])); diff --git a/backend/src/repository/epg_repository.rs b/backend/src/repository/epg_repository.rs index cffed4ccd..d94da7518 100644 --- a/backend/src/repository/epg_repository.rs +++ b/backend/src/repository/epg_repository.rs @@ -1,39 +1,57 @@ +use std::io::Write; use shared::error::{notify_err, TuliproxError}; use crate::model::{Config, ConfigTarget, TargetOutput}; use crate::model::Epg; use crate::repository::m3u_repository::m3u_get_epg_file_path; use crate::repository::xtream_repository::{xtream_get_epg_file_path, xtream_get_storage_path}; use crate::utils::debug_if_enabled; -use quick_xml::Writer; -use std::io::{Cursor}; use std::path::Path; -use tokio::fs::File; -use tokio::io::AsyncWriteExt; async fn epg_write_file(target: &ConfigTarget, epg: &Epg, path: &Path) -> Result<(), TuliproxError> { - let mut writer = Writer::new(Cursor::new(vec![])); - match epg.write_to(&mut writer) { - Ok(()) => { - let result = writer.into_inner().into_inner(); - match File::create(path).await { - Ok(mut epg_file) => { - match epg_file.write_all("".as_bytes()).await { - Ok(()) => {} - Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), - } - match epg_file.write_all(&result).await { - Ok(()) => { - debug_if_enabled!("Epg for target {} written to {}", target.name, path.to_str().unwrap_or("?")); - } - Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), - } - } - Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), - } - } - Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), - } + let file = tokio::fs::File::create(path).await.map_err(|e| notify_err!(format!("failed to create epg file: {}", e)))?; + // problem quickxml is not async + let sync_writer = tokio_util::io::SyncIoBridge::new(file); + let mut writer = quick_xml::Writer::new(std::io::BufWriter::new(sync_writer)); + + writer.write_event(quick_xml::events::Event::Decl( + quick_xml::events::BytesDecl::new("1.0", Some("utf-8"), None) + )).map_err(|e| notify_err!(format!("failed to write XML header: {}", e)))?; + + writer.write_event(quick_xml::events::Event::DocType(quick_xml::events::BytesText::new("tv SYSTEM \"xmltv.dtd\""))) + .map_err(|e| notify_err!(format!("failed to write doctype: {}", e)))?; + + + epg.write_to(&mut writer).map_err(|e| notify_err!(format!("failed to write epg: {}", e)))?; + + writer.into_inner().flush().map_err(|e| notify_err!(format!("failed to flush epg: {}", e)))?; + + debug_if_enabled!("Epg for target {} written to {}", target.name, path.to_str().unwrap_or("?")); Ok(()) + // + // + // let mut writer = Writer::new(Cursor::new(vec![])); + // match epg.write_to(&mut writer) { + // Ok(()) => { + // let result = writer.into_inner().into_inner(); + // match File::create(path).await { + // Ok(mut epg_file) => { + // match epg_file.write_all("".as_bytes()).await { + // Ok(()) => {} + // Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), + // } + // match epg_file.write_all(&result).await { + // Ok(()) => { + // debug_if_enabled!("Epg for target {} written to {}", target.name, path.to_str().unwrap_or("?")); + // } + // Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), + // } + // } + // Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), + // } + // } + // Err(err) => return Err(notify_err!(format!("failed to write epg: {} - {}", path.to_str().unwrap_or("?"), err))), + // } + // Ok(()) } pub async fn epg_write(cfg: &Config, target: &ConfigTarget, target_path: &Path, epg: Option<&Epg>, output: &TargetOutput) -> Result<(), TuliproxError> { diff --git a/backend/src/repository/user_repository.rs b/backend/src/repository/user_repository.rs index 555c7b0de..31a7b8f9b 100644 --- a/backend/src/repository/user_repository.rs +++ b/backend/src/repository/user_repository.rs @@ -123,16 +123,14 @@ fn add_target_user_to_user_tree(target_users: &[TargetUser], user_tree: &mut BPl pub async fn merge_api_user(cfg: &AppConfig, target_users: &[TargetUser]) -> Result { let path = get_api_user_db_path(cfg); - let read_lock = cfg.file_locks.read_lock(&path).await; + let write_lock = cfg.file_locks.write_lock(&path).await; let mut user_tree: BPlusTree = task::spawn_blocking({ let path = path.clone(); move || BPlusTree::load(&path).unwrap_or_else(|_| BPlusTree::new()) }) .await .map_err(|err| Error::other(format!("Failed to load user db: {err}")))?; - drop(read_lock); add_target_user_to_user_tree(target_users, &mut user_tree); - let write_lock = cfg.file_locks.write_lock(&path).await; let result = task::spawn_blocking({ let path = path.clone(); move || user_tree.store(&path) diff --git a/backend/src/utils/network/request.rs b/backend/src/utils/network/request.rs index 1c011f3fa..8deb52d3a 100644 --- a/backend/src/utils/network/request.rs +++ b/backend/src/utils/network/request.rs @@ -204,29 +204,34 @@ pub fn get_request_headers(request_header headers } -async fn decode_local_file_bytes(content: Vec) -> Result { - if content.len() >= 2 && is_gzip(&content[0..2]) { - let mut decoder = async_compression::tokio::bufread::GzipDecoder::new(&content[..]); - let mut decode_buffer = String::new(); - decoder - .read_to_string(&mut decode_buffer).await - .map_err(|err| str_to_io_error(&format!("failed to decode gzip content {err}")))?; - Ok(decode_buffer) +pub async fn get_local_file_content(file_path: &Path) -> Result { + // Datei öffnen + let file = File::open(file_path).await.map_err(|_| { + std::io::Error::new(ErrorKind::NotFound, format!("File not found: {}", file_path.display())) + })?; + + let mut buf_reader = BufReader::new(file); + + // Peek die ersten 2 Bytes, um gzip zu erkennen + let buffer = buf_reader.fill_buf().await?; + let is_gzipped = buffer.len() >= 2 && is_gzip(&buffer[0..2]); + + let mut decoded = String::new(); + + if is_gzipped { + // Async Gzip Decoder verwenden + let mut gzip_decoder = async_compression::tokio::bufread::GzipDecoder::new(buf_reader); + gzip_decoder.read_to_string(&mut decoded).await.map_err(|e| { + std::io::Error::other(format!("Failed to decode gzip content: {e}")) + })?; } else { - Ok(String::from_utf8_lossy(&content).parse().unwrap_or_default()) + // Plaintext lesen + buf_reader.read_to_string(&mut decoded).await.map_err(|e| { + std::io::Error::other(format!("Failed to read file: {e}")) + })?; } -} -fn local_file_not_found(path: &Path) -> Error { - let file_str = path.to_str().unwrap_or("?"); - Error::new(ErrorKind::InvalidData, format!("Cant find file {file_str}")) -} - -pub async fn get_local_file_content(file_path: &Path) -> Result { - match tokio::fs::read(file_path).await { - Ok(content) => decode_local_file_bytes(content).await, - Err(_) => Err(local_file_not_found(file_path)), - } + Ok(decoded) } // pub fn get_local_file_content_blocking(file_path: &PathBuf) -> Result { diff --git a/frontend/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json index fdde2dd9b..5caeaa687 100644 --- a/frontend/public/assets/i18n/en.json +++ b/frontend/public/assets/i18n/en.json @@ -27,7 +27,7 @@ "HOST": "Host", "PORT": "Port", "WEB_ROOT": "Web-Root", - "THREADS": "Threads", + "PROCESS_PARALLEL": "Process Parallel", "WORKING_DIR": "Working dir", "BACKUP_DIR": "Backup dir", "UPDATE_ON_BOOT": "Update on boot", diff --git a/frontend/src/app/components/config/main_config_view.rs b/frontend/src/app/components/config/main_config_view.rs index 75481ae1e..6c633cd43 100644 --- a/frontend/src/app/components/config/main_config_view.rs +++ b/frontend/src/app/components/config/main_config_view.rs @@ -5,12 +5,12 @@ use crate::app::context::ConfigContext; use crate::app::components::config::config_view_context::ConfigViewContext; use crate::app::components::config::config_page::{ConfigForm, LABEL_MAIN_CONFIG}; use crate::{config_field_optional, config_field_bool, config_field, edit_field_text_option, edit_field_bool, - generate_form_reducer, edit_field_number, edit_field_number_option, edit_field_text, edit_field_number_u8}; + generate_form_reducer, edit_field_number, edit_field_number_option, edit_field_text}; const LABEL_UPDATE_ON_BOOT: &str = "LABEL.UPDATE_ON_BOOT"; const LABEL_CONFIG_HOT_RELOAD: &str = "LABEL.CONFIG_HOT_RELOAD"; const LABEL_USER_ACCESS_CONTROL: &str = "LABEL.USER_ACCESS_CONTROL"; -const LABEL_THREADS: &str = "LABEL.THREADS"; +const LABEL_PROCESS_PARALLEL: &str = "LABEL.PROCESS_PARALLEL"; const LABEL_WORKING_DIR: &str = "LABEL.WORKING_DIR"; const LABEL_MAPPING_PATH: &str = "LABEL.MAPPING_PATH"; const LABEL_BACKUP_DIR: &str = "LABEL.BACKUP_DIR"; @@ -28,7 +28,7 @@ generate_form_reducer!( ConfigHotReload => config_hot_reload: bool, UserAccessControl => user_access_control: bool, AcceptInsecureSslCertificates => accept_insecure_ssl_certificates: bool, - Threads => threads: u8, + ProcessParallel => process_parallel: bool, WorkingDir => working_dir: String, MappingPath => mapping_path: Option, BackupDir => backup_dir: Option, @@ -78,7 +78,7 @@ pub fn MainConfigView() -> Html { { config_field_bool!(form_state.form, translate.t(LABEL_CONFIG_HOT_RELOAD), config_hot_reload) } { config_field_bool!(form_state.form, translate.t(LABEL_USER_ACCESS_CONTROL), user_access_control) } { config_field_bool!(form_state.form, translate.t(LABEL_ACCEPT_INSECURE_SSL_CERTIFICATES), accept_insecure_ssl_certificates) } - { config_field!(form_state.form, translate.t(LABEL_THREADS), threads) } + { config_field_bool!(form_state.form, translate.t(LABEL_PROCESS_PARALLEL), process_parallel) } { config_field!(form_state.form, translate.t(LABEL_WORKING_DIR), working_dir) } { config_field_optional!(form_state.form, translate.t(LABEL_MAPPING_PATH), mapping_path) } { config_field_optional!(form_state.form, translate.t(LABEL_BACKUP_DIR), backup_dir) } @@ -96,7 +96,7 @@ pub fn MainConfigView() -> Html { { edit_field_bool!(form_state, translate.t(LABEL_CONFIG_HOT_RELOAD), config_hot_reload, MainConfigFormAction::ConfigHotReload) } { edit_field_bool!(form_state, translate.t(LABEL_USER_ACCESS_CONTROL), user_access_control, MainConfigFormAction::UserAccessControl) } { edit_field_bool!(form_state, translate.t(LABEL_ACCEPT_INSECURE_SSL_CERTIFICATES), accept_insecure_ssl_certificates, MainConfigFormAction::AcceptInsecureSslCertificates) } - { edit_field_number_u8!(form_state, translate.t(LABEL_THREADS), threads, MainConfigFormAction::Threads) } + { edit_field_bool!(form_state, translate.t(LABEL_PROCESS_PARALLEL), process_parallel, MainConfigFormAction::ProcessParallel) } { edit_field_text!(form_state, translate.t(LABEL_WORKING_DIR), working_dir, MainConfigFormAction::WorkingDir) } { edit_field_text_option!(form_state, translate.t(LABEL_MAPPING_PATH), mapping_path, MainConfigFormAction::MappingPath) } { edit_field_text_option!(form_state, translate.t(LABEL_BACKUP_DIR), backup_dir, MainConfigFormAction::BackupDir) } diff --git a/frontend/src/app/components/table.rs b/frontend/src/app/components/table.rs index 771ee0e35..f06704eaa 100644 --- a/frontend/src/app/components/table.rs +++ b/frontend/src/app/components/table.rs @@ -94,6 +94,7 @@ pub fn Table(props: &TableProps) -> Html { Some(match order { SortOrder::Asc => "ascending", SortOrder::Desc => "descending", + SortOrder::None => "none", }.to_string()) } else { Some("none".to_string()) } } else { Some("none".to_string()) } diff --git a/frontend/src/app/components/userlist/user_table.rs b/frontend/src/app/components/userlist/user_table.rs index 3fc4aef18..ea821d92f 100644 --- a/frontend/src/app/components/userlist/user_table.rs +++ b/frontend/src/app/components/userlist/user_table.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use crate::app::components::menu_item::MenuItem; use crate::app::components::popup_menu::PopupMenu; use crate::app::components::{convert_bool_to_chip_style, AppIcon, CellValue, Chip, HideContent, MaxConnections, ProxyTypeView, RevealContent, Table, TableDefinition, UserStatus, UserlistContext, UserlistPage}; @@ -213,6 +214,7 @@ pub fn UserTable(props: &UserTableProps) -> Html { match order { SortOrder::Asc => a_value.cmp(&b_value), SortOrder::Desc => b_value.cmp(&a_value), + SortOrder::None => Ordering::Equal, } }); user_list.set(Some(Rc::new(new_user_list))); diff --git a/shared/src/model/config/base.rs b/shared/src/model/config/base.rs index 621b52523..8c46d16fa 100644 --- a/shared/src/model/config/base.rs +++ b/shared/src/model/config/base.rs @@ -9,7 +9,7 @@ pub const DEFAULT_USER_AGENT: &str = "VLC/3.0.16 LibVLC/3.0.16"; #[serde(deny_unknown_fields)] pub struct ConfigDto { #[serde(default)] - pub threads: u8, + pub process_parallel: bool, pub api: ConfigApiDto, pub working_dir: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -57,7 +57,7 @@ pub struct ConfigDto { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] pub struct MainConfigDto { #[serde(default)] - pub threads: u8, + pub process_parallel: bool, pub working_dir: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub backup_dir: Option, @@ -84,7 +84,7 @@ pub struct MainConfigDto { impl Default for MainConfigDto { fn default() -> Self { MainConfigDto { - threads: 0, + process_parallel: false, working_dir: String::new(), backup_dir: None, user_config_dir: None, @@ -103,7 +103,7 @@ impl Default for MainConfigDto { impl From<&ConfigDto> for MainConfigDto { fn from(config: &ConfigDto) -> Self { Self { - threads: config.threads, + process_parallel: config.process_parallel, working_dir: config.working_dir.clone(), backup_dir: config.backup_dir.clone(), user_config_dir: config.user_config_dir.clone(), @@ -237,7 +237,7 @@ impl ConfigDto { } pub fn update_from_main_config(&mut self, main_config: &MainConfigDto) { - self.threads = main_config.threads; + self.process_parallel = main_config.process_parallel; self.working_dir = main_config.working_dir.clone(); self.backup_dir = main_config.backup_dir.clone(); self.user_config_dir = main_config.user_config_dir.clone();