mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor(cli): remove Clone
on ProcState
(#18874)
Slowly phasing this out.
This commit is contained in:
parent
96e214d9d0
commit
9a9473533e
6 changed files with 84 additions and 110 deletions
|
@ -225,7 +225,9 @@ impl TestRun {
|
|||
let permissions =
|
||||
Permissions::from_options(&ps.options.permissions_options())?;
|
||||
test::check_specifiers(
|
||||
&ps,
|
||||
&ps.options,
|
||||
&ps.file_fetcher,
|
||||
&ps.module_load_preparer,
|
||||
self
|
||||
.queue
|
||||
.iter()
|
||||
|
@ -257,7 +259,7 @@ impl TestRun {
|
|||
let tests: Arc<RwLock<IndexMap<usize, test::TestDescription>>> =
|
||||
Arc::new(RwLock::new(IndexMap::new()));
|
||||
let mut test_steps = IndexMap::new();
|
||||
let worker_factory = Arc::new(ps.into_cli_main_worker_factory());
|
||||
let worker_factory = Arc::new(ps.create_cli_main_worker_factory());
|
||||
|
||||
let join_handles = queue.into_iter().map(move |specifier| {
|
||||
let specifier = specifier.clone();
|
||||
|
|
|
@ -49,17 +49,13 @@ use deno_semver::npm::NpmPackageReqReference;
|
|||
use import_map::ImportMap;
|
||||
use log::warn;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// This structure represents state of single "deno" program.
|
||||
///
|
||||
/// It is shared by all created workers (thus V8 isolates).
|
||||
#[derive(Clone)]
|
||||
pub struct ProcState(Arc<Inner>);
|
||||
|
||||
pub struct Inner {
|
||||
/// This structure used to represent state of single "deno" program
|
||||
/// that was shared by all created workers. It morphed into being the
|
||||
/// "factory" for all objects, but is being slowly phased out.
|
||||
pub struct ProcState {
|
||||
pub dir: DenoDir,
|
||||
pub caches: Arc<Caches>,
|
||||
pub file_fetcher: Arc<FileFetcher>,
|
||||
|
@ -87,14 +83,6 @@ pub struct Inner {
|
|||
pub npm_resolution: Arc<NpmResolution>,
|
||||
pub package_json_deps_installer: Arc<PackageJsonDepsInstaller>,
|
||||
pub cjs_resolutions: Arc<CjsResolutionStore>,
|
||||
progress_bar: ProgressBar,
|
||||
}
|
||||
|
||||
impl Deref for ProcState {
|
||||
type Target = Arc<Inner>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcState {
|
||||
|
@ -123,48 +111,18 @@ impl ProcState {
|
|||
|
||||
/// Reset all runtime state to its default. This should be used on file
|
||||
/// watcher restarts.
|
||||
pub fn reset_for_file_watcher(&mut self) {
|
||||
pub fn reset_for_file_watcher(&self) {
|
||||
self.cjs_resolutions.clear();
|
||||
self.parsed_source_cache.clear();
|
||||
self.graph_container.clear();
|
||||
|
||||
self.0 = Arc::new(Inner {
|
||||
dir: self.dir.clone(),
|
||||
caches: self.caches.clone(),
|
||||
options: self.options.clone(),
|
||||
emit_cache: self.emit_cache.clone(),
|
||||
emitter: self.emitter.clone(),
|
||||
file_fetcher: self.file_fetcher.clone(),
|
||||
http_client: self.http_client.clone(),
|
||||
graph_container: self.graph_container.clone(),
|
||||
lockfile: self.lockfile.clone(),
|
||||
maybe_import_map: self.maybe_import_map.clone(),
|
||||
maybe_inspector_server: self.maybe_inspector_server.clone(),
|
||||
root_cert_store: self.root_cert_store.clone(),
|
||||
blob_store: self.blob_store.clone(),
|
||||
parsed_source_cache: self.parsed_source_cache.clone(),
|
||||
resolver: self.resolver.clone(),
|
||||
maybe_file_watcher_reporter: self.maybe_file_watcher_reporter.clone(),
|
||||
module_graph_builder: self.module_graph_builder.clone(),
|
||||
module_load_preparer: self.module_load_preparer.clone(),
|
||||
node_code_translator: self.node_code_translator.clone(),
|
||||
node_fs: self.node_fs.clone(),
|
||||
node_resolver: self.node_resolver.clone(),
|
||||
npm_api: self.npm_api.clone(),
|
||||
npm_cache: self.npm_cache.clone(),
|
||||
npm_resolver: self.npm_resolver.clone(),
|
||||
npm_resolution: self.npm_resolution.clone(),
|
||||
package_json_deps_installer: self.package_json_deps_installer.clone(),
|
||||
cjs_resolutions: self.cjs_resolutions.clone(),
|
||||
progress_bar: self.progress_bar.clone(),
|
||||
});
|
||||
self.init_watcher();
|
||||
}
|
||||
|
||||
// Add invariant files like the import map and explicit watch flag list to
|
||||
// the watcher. Dedup for build_for_file_watcher and reset_for_file_watcher.
|
||||
fn init_watcher(&self) {
|
||||
let files_to_watch_sender = match &self.0.maybe_file_watcher_reporter {
|
||||
let files_to_watch_sender = match &self.maybe_file_watcher_reporter {
|
||||
Some(reporter) => &reporter.sender,
|
||||
None => return,
|
||||
};
|
||||
|
@ -338,7 +296,7 @@ impl ProcState {
|
|||
type_checker,
|
||||
));
|
||||
|
||||
Ok(ProcState(Arc::new(Inner {
|
||||
Ok(ProcState {
|
||||
dir,
|
||||
caches,
|
||||
options: cli_options,
|
||||
|
@ -366,13 +324,12 @@ impl ProcState {
|
|||
package_json_deps_installer,
|
||||
cjs_resolutions: Default::default(),
|
||||
module_load_preparer,
|
||||
progress_bar,
|
||||
})))
|
||||
})
|
||||
}
|
||||
|
||||
// todo(dsherret): this is a transitory method as we separate out
|
||||
// ProcState from more code
|
||||
pub fn into_cli_main_worker_factory(self) -> CliMainWorkerFactory {
|
||||
pub fn create_cli_main_worker_factory(&self) -> CliMainWorkerFactory {
|
||||
CliMainWorkerFactory::new(
|
||||
StorageKeyResolver::from_options(&self.options),
|
||||
self.npm_resolver.clone(),
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::args::TypeCheckMode;
|
|||
use crate::colors;
|
||||
use crate::display::write_json_to_stdout;
|
||||
use crate::graph_util::graph_valid_with_cli_options;
|
||||
use crate::module_loader::ModuleLoadPreparer;
|
||||
use crate::ops;
|
||||
use crate::proc_state::ProcState;
|
||||
use crate::tools::test::format_test_error;
|
||||
|
@ -36,7 +37,6 @@ use indexmap::IndexSet;
|
|||
use log::Level;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
@ -418,11 +418,12 @@ impl BenchReporter for ConsoleReporter {
|
|||
|
||||
/// Type check a collection of module and document specifiers.
|
||||
async fn check_specifiers(
|
||||
ps: &ProcState,
|
||||
cli_options: &CliOptions,
|
||||
module_load_preparer: &ModuleLoadPreparer,
|
||||
specifiers: Vec<ModuleSpecifier>,
|
||||
) -> Result<(), AnyError> {
|
||||
let lib = ps.options.ts_type_lib_window();
|
||||
ps.module_load_preparer
|
||||
let lib = cli_options.ts_type_lib_window();
|
||||
module_load_preparer
|
||||
.prepare_module_load(
|
||||
specifiers,
|
||||
false,
|
||||
|
@ -648,14 +649,15 @@ pub async fn run_benchmarks(
|
|||
return Err(generic_error("No bench modules found"));
|
||||
}
|
||||
|
||||
check_specifiers(&ps, specifiers.clone()).await?;
|
||||
check_specifiers(&ps.options, &ps.module_load_preparer, specifiers.clone())
|
||||
.await?;
|
||||
|
||||
if bench_options.no_run {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let log_level = ps.options.log_level();
|
||||
let worker_factory = Arc::new(ps.into_cli_main_worker_factory());
|
||||
let worker_factory = Arc::new(ps.create_cli_main_worker_factory());
|
||||
bench_specifiers(
|
||||
worker_factory,
|
||||
&permissions,
|
||||
|
@ -684,14 +686,13 @@ pub async fn run_benchmarks_with_watch(
|
|||
Permissions::from_options(&ps.options.permissions_options())?;
|
||||
let no_check = ps.options.type_check_mode() == TypeCheckMode::None;
|
||||
|
||||
let ps = RefCell::new(ps);
|
||||
|
||||
let resolver = |changed: Option<Vec<PathBuf>>| {
|
||||
let paths_to_watch = bench_options.files.include.clone();
|
||||
let paths_to_watch_clone = paths_to_watch.clone();
|
||||
let files_changed = changed.is_some();
|
||||
let bench_options = &bench_options;
|
||||
let ps = ps.borrow().clone();
|
||||
let module_graph_builder = ps.module_graph_builder.clone();
|
||||
let cli_options = ps.options.clone();
|
||||
|
||||
async move {
|
||||
let bench_modules =
|
||||
|
@ -703,11 +704,10 @@ pub async fn run_benchmarks_with_watch(
|
|||
} else {
|
||||
bench_modules.clone()
|
||||
};
|
||||
let graph = ps
|
||||
.module_graph_builder
|
||||
let graph = module_graph_builder
|
||||
.create_graph(bench_modules.clone())
|
||||
.await?;
|
||||
graph_valid_with_cli_options(&graph, &bench_modules, &ps.options)?;
|
||||
graph_valid_with_cli_options(&graph, &bench_modules, &cli_options)?;
|
||||
|
||||
// TODO(@kitsonk) - This should be totally derivable from the graph.
|
||||
for specifier in bench_modules {
|
||||
|
@ -800,8 +800,10 @@ pub async fn run_benchmarks_with_watch(
|
|||
let operation = |modules_to_reload: Vec<ModuleSpecifier>| {
|
||||
let permissions = &permissions;
|
||||
let bench_options = &bench_options;
|
||||
ps.borrow_mut().reset_for_file_watcher();
|
||||
let ps = ps.borrow().clone();
|
||||
ps.reset_for_file_watcher();
|
||||
let module_load_preparer = ps.module_load_preparer.clone();
|
||||
let cli_options = ps.options.clone();
|
||||
let worker_factory = Arc::new(ps.create_cli_main_worker_factory());
|
||||
|
||||
async move {
|
||||
let specifiers =
|
||||
|
@ -810,14 +812,14 @@ pub async fn run_benchmarks_with_watch(
|
|||
.filter(|specifier| modules_to_reload.contains(specifier))
|
||||
.collect::<Vec<ModuleSpecifier>>();
|
||||
|
||||
check_specifiers(&ps, specifiers.clone()).await?;
|
||||
check_specifiers(&cli_options, &module_load_preparer, specifiers.clone())
|
||||
.await?;
|
||||
|
||||
if bench_options.no_run {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let log_level = ps.options.log_level();
|
||||
let worker_factory = Arc::new(ps.into_cli_main_worker_factory());
|
||||
let log_level = cli_options.log_level();
|
||||
bench_specifiers(
|
||||
worker_factory,
|
||||
permissions,
|
||||
|
@ -834,7 +836,7 @@ pub async fn run_benchmarks_with_watch(
|
|||
}
|
||||
};
|
||||
|
||||
let clear_screen = !ps.borrow().options.no_clear_screen();
|
||||
let clear_screen = !ps.options.no_clear_screen();
|
||||
file_watcher::watch_func(
|
||||
resolver,
|
||||
operation,
|
||||
|
|
|
@ -108,7 +108,7 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
|
|||
let resolver = ps.resolver.clone();
|
||||
let dir = ps.dir.clone();
|
||||
let file_fetcher = ps.file_fetcher.clone();
|
||||
let worker_factory = ps.into_cli_main_worker_factory();
|
||||
let worker_factory = ps.create_cli_main_worker_factory();
|
||||
|
||||
let mut worker = worker_factory
|
||||
.create_main_worker(main_module, permissions)
|
||||
|
|
|
@ -47,7 +47,7 @@ To grant permissions, set them before the script argument. For example:
|
|||
let permissions = PermissionsContainer::new(Permissions::from_options(
|
||||
&ps.options.permissions_options(),
|
||||
)?);
|
||||
let worker_factory = ps.into_cli_main_worker_factory();
|
||||
let worker_factory = ps.create_cli_main_worker_factory();
|
||||
let mut worker = worker_factory
|
||||
.create_main_worker(main_module, permissions)
|
||||
.await?;
|
||||
|
@ -78,7 +78,7 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
|
|||
// to allow module access by TS compiler
|
||||
ps.file_fetcher.insert_cached(source_file);
|
||||
|
||||
let worker_factory = ps.into_cli_main_worker_factory();
|
||||
let worker_factory = ps.create_cli_main_worker_factory();
|
||||
let mut worker = worker_factory
|
||||
.create_main_worker(main_module, permissions)
|
||||
.await?;
|
||||
|
@ -90,19 +90,19 @@ pub async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
|
|||
// code properly.
|
||||
async fn run_with_watch(flags: Flags) -> Result<i32, AnyError> {
|
||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
|
||||
let mut ps =
|
||||
let ps =
|
||||
ProcState::from_flags_for_file_watcher(flags, sender.clone()).await?;
|
||||
let clear_screen = !ps.options.no_clear_screen();
|
||||
let main_module = ps.options.resolve_main_module()?;
|
||||
|
||||
let operation = |main_module: ModuleSpecifier| {
|
||||
ps.reset_for_file_watcher();
|
||||
let ps = ps.clone();
|
||||
Ok(async move {
|
||||
let permissions = PermissionsContainer::new(Permissions::from_options(
|
||||
&ps.options.permissions_options(),
|
||||
)?);
|
||||
let worker_factory = ps.into_cli_main_worker_factory();
|
||||
let worker_factory = ps.create_cli_main_worker_factory();
|
||||
|
||||
Ok(async move {
|
||||
let worker = worker_factory
|
||||
.create_main_worker(main_module, permissions)
|
||||
.await?;
|
||||
|
@ -157,7 +157,7 @@ pub async fn eval_command(
|
|||
ps.file_fetcher.insert_cached(file);
|
||||
|
||||
let mut worker = ps
|
||||
.into_cli_main_worker_factory()
|
||||
.create_cli_main_worker_factory()
|
||||
.create_main_worker(main_module, permissions)
|
||||
.await?;
|
||||
let exit_code = worker.run().await?;
|
||||
|
|
|
@ -7,7 +7,9 @@ use crate::args::TypeCheckMode;
|
|||
use crate::colors;
|
||||
use crate::display;
|
||||
use crate::file_fetcher::File;
|
||||
use crate::file_fetcher::FileFetcher;
|
||||
use crate::graph_util::graph_valid_with_cli_options;
|
||||
use crate::module_loader::ModuleLoadPreparer;
|
||||
use crate::ops;
|
||||
use crate::proc_state::ProcState;
|
||||
use crate::util::checksum;
|
||||
|
@ -49,7 +51,6 @@ use rand::seq::SliceRandom;
|
|||
use rand::SeedableRng;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
|
@ -1200,13 +1201,13 @@ fn extract_files_from_fenced_blocks(
|
|||
}
|
||||
|
||||
async fn fetch_inline_files(
|
||||
ps: &ProcState,
|
||||
file_fetcher: &FileFetcher,
|
||||
specifiers: Vec<ModuleSpecifier>,
|
||||
) -> Result<Vec<File>, AnyError> {
|
||||
let mut files = Vec::new();
|
||||
for specifier in specifiers {
|
||||
let fetch_permissions = PermissionsContainer::allow_all();
|
||||
let file = ps.file_fetcher.fetch(&specifier, fetch_permissions).await?;
|
||||
let file = file_fetcher.fetch(&specifier, fetch_permissions).await?;
|
||||
|
||||
let inline_files = if file.media_type == MediaType::Unknown {
|
||||
extract_files_from_fenced_blocks(
|
||||
|
@ -1230,12 +1231,14 @@ async fn fetch_inline_files(
|
|||
|
||||
/// Type check a collection of module and document specifiers.
|
||||
pub async fn check_specifiers(
|
||||
ps: &ProcState,
|
||||
cli_options: &CliOptions,
|
||||
file_fetcher: &FileFetcher,
|
||||
module_load_preparer: &ModuleLoadPreparer,
|
||||
specifiers: Vec<(ModuleSpecifier, TestMode)>,
|
||||
) -> Result<(), AnyError> {
|
||||
let lib = ps.options.ts_type_lib_window();
|
||||
let lib = cli_options.ts_type_lib_window();
|
||||
let inline_files = fetch_inline_files(
|
||||
ps,
|
||||
file_fetcher,
|
||||
specifiers
|
||||
.iter()
|
||||
.filter_map(|(specifier, mode)| {
|
||||
|
@ -1256,10 +1259,10 @@ pub async fn check_specifiers(
|
|||
.collect();
|
||||
|
||||
for file in inline_files {
|
||||
ps.file_fetcher.insert_cached(file);
|
||||
file_fetcher.insert_cached(file);
|
||||
}
|
||||
|
||||
ps.module_load_preparer
|
||||
module_load_preparer
|
||||
.prepare_module_load(
|
||||
specifiers,
|
||||
false,
|
||||
|
@ -1280,7 +1283,7 @@ pub async fn check_specifiers(
|
|||
})
|
||||
.collect();
|
||||
|
||||
ps.module_load_preparer
|
||||
module_load_preparer
|
||||
.prepare_module_load(
|
||||
module_specifiers,
|
||||
false,
|
||||
|
@ -1601,15 +1604,14 @@ fn collect_specifiers_with_test_mode(
|
|||
/// cannot be run, and therefore need to be marked as `TestMode::Documentation`
|
||||
/// as well.
|
||||
async fn fetch_specifiers_with_test_mode(
|
||||
ps: &ProcState,
|
||||
file_fetcher: &FileFetcher,
|
||||
files: &FilesConfig,
|
||||
doc: &bool,
|
||||
) -> Result<Vec<(ModuleSpecifier, TestMode)>, AnyError> {
|
||||
let mut specifiers_with_mode = collect_specifiers_with_test_mode(files, doc)?;
|
||||
|
||||
for (specifier, mode) in &mut specifiers_with_mode {
|
||||
let file = ps
|
||||
.file_fetcher
|
||||
let file = file_fetcher
|
||||
.fetch(specifier, PermissionsContainer::allow_all())
|
||||
.await?;
|
||||
|
||||
|
@ -1636,7 +1638,7 @@ pub async fn run_tests(
|
|||
let log_level = ps.options.log_level();
|
||||
|
||||
let specifiers_with_mode = fetch_specifiers_with_test_mode(
|
||||
&ps,
|
||||
&ps.file_fetcher,
|
||||
&test_options.files,
|
||||
&test_options.doc,
|
||||
)
|
||||
|
@ -1646,13 +1648,19 @@ pub async fn run_tests(
|
|||
return Err(generic_error("No test modules found"));
|
||||
}
|
||||
|
||||
check_specifiers(&ps, specifiers_with_mode.clone()).await?;
|
||||
check_specifiers(
|
||||
&ps.options,
|
||||
&ps.file_fetcher,
|
||||
&ps.module_load_preparer,
|
||||
specifiers_with_mode.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if test_options.no_run {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let worker_factory = Arc::new(ps.into_cli_main_worker_factory());
|
||||
let worker_factory = Arc::new(ps.create_cli_main_worker_factory());
|
||||
|
||||
test_specifiers(
|
||||
worker_factory,
|
||||
|
@ -1693,14 +1701,13 @@ pub async fn run_tests_with_watch(
|
|||
let no_check = ps.options.type_check_mode() == TypeCheckMode::None;
|
||||
let log_level = ps.options.log_level();
|
||||
|
||||
let ps = RefCell::new(ps);
|
||||
|
||||
let resolver = |changed: Option<Vec<PathBuf>>| {
|
||||
let paths_to_watch = test_options.files.include.clone();
|
||||
let paths_to_watch_clone = paths_to_watch.clone();
|
||||
let files_changed = changed.is_some();
|
||||
let test_options = &test_options;
|
||||
let ps = ps.borrow().clone();
|
||||
let cli_options = ps.options.clone();
|
||||
let module_graph_builder = ps.module_graph_builder.clone();
|
||||
|
||||
async move {
|
||||
let test_modules = if test_options.doc {
|
||||
|
@ -1715,11 +1722,10 @@ pub async fn run_tests_with_watch(
|
|||
} else {
|
||||
test_modules.clone()
|
||||
};
|
||||
let graph = ps
|
||||
.module_graph_builder
|
||||
let graph = module_graph_builder
|
||||
.create_graph(test_modules.clone())
|
||||
.await?;
|
||||
graph_valid_with_cli_options(&graph, &test_modules, &ps.options)?;
|
||||
graph_valid_with_cli_options(&graph, &test_modules, &cli_options)?;
|
||||
|
||||
// TODO(@kitsonk) - This should be totally derivable from the graph.
|
||||
for specifier in test_modules {
|
||||
|
@ -1812,12 +1818,15 @@ pub async fn run_tests_with_watch(
|
|||
let operation = |modules_to_reload: Vec<ModuleSpecifier>| {
|
||||
let permissions = &permissions;
|
||||
let test_options = &test_options;
|
||||
ps.borrow_mut().reset_for_file_watcher();
|
||||
let ps = ps.borrow().clone();
|
||||
ps.reset_for_file_watcher();
|
||||
let cli_options = ps.options.clone();
|
||||
let file_fetcher = ps.file_fetcher.clone();
|
||||
let module_load_preparer = ps.module_load_preparer.clone();
|
||||
let worker_factory = Arc::new(ps.create_cli_main_worker_factory());
|
||||
|
||||
async move {
|
||||
let specifiers_with_mode = fetch_specifiers_with_test_mode(
|
||||
&ps,
|
||||
&file_fetcher,
|
||||
&test_options.files,
|
||||
&test_options.doc,
|
||||
)
|
||||
|
@ -1826,14 +1835,18 @@ pub async fn run_tests_with_watch(
|
|||
.filter(|(specifier, _)| modules_to_reload.contains(specifier))
|
||||
.collect::<Vec<(ModuleSpecifier, TestMode)>>();
|
||||
|
||||
check_specifiers(&ps, specifiers_with_mode.clone()).await?;
|
||||
check_specifiers(
|
||||
&cli_options,
|
||||
&file_fetcher,
|
||||
&module_load_preparer,
|
||||
specifiers_with_mode.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if test_options.no_run {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let worker_factory = Arc::new(ps.into_cli_main_worker_factory());
|
||||
|
||||
test_specifiers(
|
||||
worker_factory,
|
||||
permissions,
|
||||
|
@ -1874,7 +1887,7 @@ pub async fn run_tests_with_watch(
|
|||
}
|
||||
});
|
||||
|
||||
let clear_screen = !ps.borrow().options.no_clear_screen();
|
||||
let clear_screen = !ps.options.no_clear_screen();
|
||||
file_watcher::watch_func(
|
||||
resolver,
|
||||
operation,
|
||||
|
|
Loading…
Reference in a new issue