1use anyhow::{anyhow, Result};
15use rayon::prelude::*;
16
17use serde_json::{Number, Value};
18
19use std::collections::BTreeMap;
20
21use std::borrow::Cow;
22use std::collections::{HashMap, HashSet};
23use std::path::Path;
24
25use rpfm_extensions::dependencies::{Dependencies, KEY_DELETES_TABLE_NAME, TableReferences};
26use rpfm_extensions::merge::{db_baseline, delta_merge_db, delta_merge_loc, loc_baseline, MergeConflict, MergeOptions, MergeResolution};
27
28use rpfm_ipc::api::ApiError;
29use rpfm_ipc::api::files::{ASSEMBLY_KIT_TABLE_FILE_NAME, FileRef, FileSource};
30use rpfm_ipc::api::session::{DependencyTableData, DependencyTables};
31use rpfm_ipc::api::references::{DEFAULT_REFERENCE_VALUES_LIMIT, DEFAULT_USAGES_LIMIT, FindUsages, FoundRow, GetReferenceValues, RowLoc, RowLocs, GetTableReferenceData, TableReferenceData, LocSource, ReferenceValue, ReferenceValues, RowLocation, Usage, Usages};
32use rpfm_ipc::api::tables::{AddKeyDeletes, ExportTsv, FilesEdited, ImportTsv, MergeTables, RenameKey, TableUpgraded, TablesMerged, UpgradeTable};
33use rpfm_ipc::api::tables::{ColumnValues, DEFAULT_VALUES_LIMIT, GetColumnValues, GetUnusedNumbers, MAX_UNUSED_NUMBERS, UnusedNumbers, GetTableDefinition, DEFAULT_ROWS_LIMIT, EditTable, FilterOp, GetTableRows, RowEdit, RowFilter, RowRef, TableEdited, TableInfo, TableRow, TableRows};
34use rpfm_ipc::helpers::{DataSource, RFileInfo};
35
36use rpfm_lib::files::{Container, ContainerPath, db::DB, DecodeableExtraData, FileType, pack::Pack, RFile, RFileDecoded, table::{DecodedData, local::TableInMemory, Table}};
37use rpfm_lib::schema::{Definition, Field, FieldType};
38use rpfm_lib::utils::current_time;
39
40use super::{SessionState, loaded_schema, pack, pack_mut};
41
42enum PreparedEdit {
44 Insert(usize, Vec<DecodedData>),
45 Update(usize, Vec<(usize, DecodedData)>),
46
47 Delete(Vec<usize>),
49}
50
51struct PreparedFilter {
53 column_index: usize,
54 op: FilterOp,
55 value: String,
56 ignore_case: bool,
57}
58
59#[derive(Debug)]
61pub enum MergeOutcome {
62
63 Merged(String),
65
66 Conflicts(Vec<MergeConflict>),
68}
69
70enum DeltaMergeOutcome {
72 Merged(RFile),
73 Conflicts(Vec<MergeConflict>),
74}
75
76impl SessionState {
77
78 pub fn merge_files(&mut self, pack_key: &str, paths: &[ContainerPath], merged_path: &str, delete_source_files: bool, options: &MergeOptions) -> Result<MergeOutcome> {
92 let pack = pack_mut(&mut self.packs, pack_key)?;
93 let files_to_merge = pack.files_by_paths(paths, false);
94
95 let merge_result = if *options.delta_merge() {
96 delta_merge_files(&files_to_merge, merged_path, &self.dependencies, options.resolutions())?
97 } else {
98 DeltaMergeOutcome::Merged(RFile::merge(&files_to_merge, merged_path)?)
99 };
100
101 match merge_result {
102 DeltaMergeOutcome::Merged(file) => {
103 let _ = pack.insert(file);
104
105 if delete_source_files {
107 paths.iter()
108 .filter(|path| merged_path != path.path_raw())
109 .for_each(|path| { pack.remove(path); });
110 }
111
112 Ok(MergeOutcome::Merged(merged_path.to_owned()))
113 },
114 DeltaMergeOutcome::Conflicts(conflicts) => Ok(MergeOutcome::Conflicts(conflicts)),
115 }
116 }
117
118 pub fn update_table(&mut self, pack_key: &str, path: &ContainerPath) -> Result<(i32, i32, Vec<String>, Vec<String>)> {
124 let path = path.path_raw();
125 let file = pack_mut(&mut self.packs, pack_key)?.file_mut(path, false)
126 .ok_or_else(|| anyhow!("File not found in the open Pack: {}", path))?;
127
128 let decoded = file.decoded_mut().map_err(|_| anyhow!("File with the following path undecoded: {}", path))?;
129 Ok(self.dependencies.update_db(decoded)?)
130 }
131
132 pub fn export_tsv(&mut self, pack_key: &str, internal_path: &str, external_path: &Path, data_source: DataSource, tsv_keys_first: bool) -> Result<()> {
142 let schema = loaded_schema(&self.schema)?;
143 let file = match data_source {
144 DataSource::PackFile => self.packs.get_mut(pack_key).and_then(|pack| pack.file_mut(internal_path, false)),
145 DataSource::ParentFiles => self.dependencies.file_mut(internal_path, false, true).ok(),
146 DataSource::GameFiles => self.dependencies.file_mut(internal_path, true, false).ok(),
147 DataSource::AssKitFiles => return Err(anyhow!("Exporting a TSV from the Assembly Kit is not yet supported.")),
148 DataSource::ExternalFile => return Err(anyhow!("Exporting a TSV from a external file is not yet supported.")),
149 }.ok_or_else(|| anyhow!("File with the following path not found in the Pack: {}", internal_path))?;
150
151 file.tsv_export_to_path(external_path, schema, tsv_keys_first)?;
152 Ok(())
153 }
154
155 pub fn import_tsv(&mut self, pack_key: &str, internal_path: &str, external_path: &Path) -> Result<RFileDecoded> {
161 let file = pack_mut(&mut self.packs, pack_key)?.file_mut(internal_path, false)
162 .ok_or_else(|| anyhow!("File with the following path not found in the Pack: {}", internal_path))?;
163
164 let original_guid = match file.decoded() {
167 Ok(RFileDecoded::DB(table)) => Some(table.guid().to_owned()),
168 _ => None,
169 };
170
171 let imported = RFile::tsv_import_from_path(external_path, &self.schema)?;
172 let mut decoded = imported.decoded()?.clone();
173 if let (RFileDecoded::DB(table), Some(guid)) = (&mut decoded, original_guid) {
174 table.set_guid(guid);
175 }
176
177 file.set_decoded(decoded.clone())?;
178 Ok(decoded)
179 }
180
181 pub fn cascade_edition(&mut self, pack_key: &str, table_name: &str, definition: &Definition, changes: &[(Field, String, String)]) -> Result<(Vec<ContainerPath>, Vec<RFileInfo>)> {
194 let pack = pack_mut(&mut self.packs, pack_key)?;
195 let edited_paths = match self.schema {
196 Some(ref schema) => changes.iter()
197 .flat_map(|(field, value_before, value_after)| DB::cascade_edition(pack, schema, table_name, field, definition, value_before, value_after))
198 .collect::<Vec<_>>(),
199 None => vec![],
200 };
201
202 let files_info = pack.files_by_paths(&edited_paths, false).into_par_iter().map(From::from).collect();
203 Ok((edited_paths, files_info))
204 }
205
206 pub fn add_keys_to_key_deletes(&mut self, pack_key: &str, table_file_name: &str, key_table_name: &str, keys: &HashSet<String>) -> Result<Option<ContainerPath>> {
219 let path = ContainerPath::File(format!("db/{KEY_DELETES_TABLE_NAME}/{table_file_name}"));
220 let mut files = pack_mut(&mut self.packs, pack_key)?.files_by_type_and_paths_mut(&[FileType::DB], &[path], true);
221
222 let Some(file) = files.first_mut() else { return Ok(None) };
223 let Ok(RFileDecoded::DB(db)) = file.decoded_mut() else { return Ok(None) };
224
225 for key in keys {
226 db.data_mut().push(vec![
227 DecodedData::StringU8(key.to_owned()),
228 DecodedData::StringU8(key_table_name.to_owned()),
229 ]);
230 }
231
232 Ok(Some(file.path_in_container()))
233 }
234
235 pub fn reference_data(&mut self, table_name: &str, definition: &Definition, force_local_generation: bool) -> HashMap<i32, TableReferences> {
247 let Some(ref schema) = self.schema else { return HashMap::new() };
248
249 if force_local_generation || !self.dependencies.local_tables_references().contains_key(table_name) {
250 self.dependencies.generate_local_definition_references(schema, table_name, definition);
251 }
252
253 self.dependencies.db_reference_data(schema, &self.packs, table_name, definition, &None)
254 }
255
256 pub fn table_reference_data(&mut self, request: &GetTableReferenceData) -> Result<TableReferenceData> {
262 let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, request.version)
263 .cloned()
264 .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?;
265
266 Ok(TableReferenceData { columns: self.reference_data(&request.table_name, &definition, request.regenerate) })
267 }
268
269 pub fn find_definition(&self, pack_key: Option<&str>, table_name: &str, column: &str, value: &str) -> Result<FoundRow> {
284 if loaded_schema(&self.schema)?.definitions_by_table_name(table_name).is_none_or(|definitions| definitions.is_empty()) {
285 return Err(ApiError::DefinitionNotFound(table_name.to_owned()).into());
286 }
287
288 let table_folders = ContainerPath::db_table_folders(table_name);
289 for (key, pack) in self.packs_to_search(pack_key)? {
290 if let Some(found) = find_in_db_files(&pack.files_by_paths(&table_folders, true), column, value, &FileSource::Pack(key.to_owned())) {
291 return Ok(found);
292 }
293 }
294
295 if pack_key.is_none() {
296 for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
297 if let Ok(files) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
298 if let Some(found) = find_in_db_files(&files, column, value, &source) {
299 return Ok(found);
300 }
301 }
302 }
303
304 if let Some(table) = self.dependencies.asskit_only_db_tables().get(table_name) {
305 if let Some((column_index, row_index)) = find_in_db(table, column, value) {
306 let path = format!("db/{table_name}/{ASSEMBLY_KIT_TABLE_FILE_NAME}");
307 let location = RowLocation { source: FileSource::AssemblyKit, path, column_index, row_index };
308 return Ok(FoundRow { location, values: row_values_by_column(table.table(), row_index) });
309 }
310 }
311 }
312
313 Err(ApiError::NotFound(format!("The value {value} of the column {column} of {table_name}")).into())
314 }
315
316 pub fn find_loc(&self, pack_key: Option<&str>, loc_key: &str) -> Result<FoundRow> {
329 for (key, pack) in self.packs_to_search(pack_key)? {
330 if let Some(found) = find_in_loc_files(&pack.files_by_type(&[FileType::Loc]), loc_key, &FileSource::Pack(key.to_owned())) {
331 return Ok(found);
332 }
333 }
334
335 if pack_key.is_none() {
336 for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
337 if let Ok(files) = self.dependencies.loc_data(include_vanilla, include_parent) {
338 if let Some(found) = find_in_loc_files(&files, loc_key, &source) {
339 return Ok(found);
340 }
341 }
342 }
343 }
344
345 Err(ApiError::NotFound(format!("The loc key {loc_key}")).into())
346 }
347
348 pub fn row_locs(&mut self, row_ref: &RowRef) -> Result<RowLocs> {
354 let table_name = row_ref.file.path.strip_prefix("db/")
355 .and_then(|path| path.split('/').next())
356 .ok_or_else(|| ApiError::NotATable(row_ref.file.path.clone()))?
357 .to_owned();
358
359 let table = self.table(&row_ref.file)?;
360 let definition = table.definition();
361 let data = table.data();
362 let row = data.get(row_ref.index)
363 .ok_or_else(|| ApiError::InvalidParams(format!("Row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
364
365 if definition.localised_fields().is_empty() {
366 return Ok(RowLocs::default());
367 }
368
369 if definition.localised_key_order().is_empty() {
370 return Err(ApiError::InvalidParams(format!("The schema doesn't say which columns make the loc keys of {table_name}.")).into());
371 }
372
373 let key = definition.localised_key_order().iter().map(|index| row[*index as usize].data_to_string()).collect::<String>();
374 let table_name = table_name.strip_suffix("_tables").unwrap_or(&table_name);
375 let keys = definition.localised_fields().iter()
376 .map(|field| (field.name().to_owned(), format!("{table_name}_{}_{key}", field.name())))
377 .collect::<Vec<_>>();
378
379 let locs = keys.into_iter()
380 .map(|(column, key)| {
381 let text = self.find_loc(None, &key).ok()
382 .and_then(|found| found.values.get("text").and_then(Value::as_str).map(str::to_owned));
383 RowLoc { column, key, text }
384 })
385 .collect();
386
387 Ok(RowLocs { locs })
388 }
389
390 fn packs_to_search<'a>(&'a self, pack_key: Option<&'a str>) -> Result<Vec<(&'a str, &'a Pack)>> {
397 Ok(match pack_key {
398 Some(pack_key) => vec![(pack_key, pack(&self.packs, pack_key)?)],
399 None => self.packs.iter().map(|(key, pack)| (key.as_str(), pack)).collect(),
400 })
401 }
402
403 pub fn search_references(&self, pack_key: Option<&str>, reference_map: &HashMap<String, Vec<String>>, value: &str) -> Result<Vec<Usage>> {
415 let paths = reference_map.keys().flat_map(|table_name| ContainerPath::db_table_folders(table_name)).collect::<Vec<ContainerPath>>();
416 let packs = self.packs_to_search(pack_key)?;
417
418 let mut references = vec![];
419 for (key, pack) in packs {
420 let files = pack.files_by_paths(&paths, true);
421 let source = FileSource::Pack(key.to_owned());
422 for (table_name, columns) in reference_map {
423 for file in &files {
424 if file.db_table_name_from_path() == Some(table_name.as_str()) {
425 references.extend(references_in_file(file, columns, value, &source));
426 }
427 }
428 }
429 }
430
431 if pack_key.is_some() {
432 return Ok(references);
433 }
434
435 for (source, include_vanilla, include_parent) in [(FileSource::ParentFiles, false, true), (FileSource::GameFiles, true, false)] {
436 for (table_name, columns) in reference_map {
437 if let Ok(tables) = self.dependencies.db_data(table_name, include_vanilla, include_parent) {
438 references.par_extend(tables.par_iter().flat_map(|table| references_in_file(table, columns, value, &source)));
439 }
440 }
441 }
442
443 Ok(references)
444 }
445
446 pub fn find_usages(&self, request: &FindUsages) -> Result<Usages> {
448 let schema = loaded_schema(&self.schema)?;
449 let version = match request.version {
450 Some(version) => version,
451 None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
452 };
453
454 let definition = schema.definition_by_name_and_version(&request.table_name, version)
455 .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?;
456
457 let reference_map = schema.referencing_columns_for_table(&request.table_name, definition)
458 .remove(&request.column)
459 .unwrap_or_default();
460
461 let usages = self.search_references(request.pack.as_deref(), &reference_map, &request.value)?;
462 let total = usages.len();
463 let usages = usages.into_iter()
464 .skip(request.offset)
465 .take(request.limit.unwrap_or(DEFAULT_USAGES_LIMIT))
466 .collect();
467
468 Ok(Usages { usages, total })
469 }
470
471 pub fn loc_key_source(&self, loc_key: &str) -> Option<(String, String, Vec<String>)> {
473 self.dependencies.loc_key_source(loc_key)
474 }
475
476 pub fn column_values(&self, table_name: &str, column_name: &str, include_packs: bool, include_dependencies: bool) -> HashSet<String> {
485 let packs = if include_packs { Some(&self.packs) } else { None };
486 self.dependencies.db_values_from_table_name_and_column_name(packs, table_name, column_name, include_dependencies, include_dependencies)
487 }
488
489 pub fn column_values_page(&self, request: &GetColumnValues) -> Result<ColumnValues> {
495 let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
496 if !definition.columns.iter().any(|column| column.name == request.column) {
497 return Err(ApiError::InvalidParams(format!("The table has no column named {}.", request.column)).into());
498 }
499
500 let mut values = self.column_values(&request.table_name, &request.column, request.include_packs, request.include_dependencies)
501 .into_iter()
502 .filter(|value| value.starts_with(&request.prefix))
503 .collect::<Vec<_>>();
504 values.sort_unstable();
505
506 let total = values.len();
507 let values = values.into_iter()
508 .skip(request.offset)
509 .take(request.limit.unwrap_or(DEFAULT_VALUES_LIMIT))
510 .collect();
511
512 Ok(ColumnValues { values, total })
513 }
514
515 pub fn unused_numbers(&self, request: &GetUnusedNumbers) -> Result<UnusedNumbers> {
522 if request.count > MAX_UNUSED_NUMBERS {
523 return Err(ApiError::InvalidParams(format!("Up to {MAX_UNUSED_NUMBERS} numbers can be requested at once.")).into());
524 }
525
526 let definition = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?;
527 let column = definition.columns.iter()
528 .find(|column| column.name == request.column)
529 .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
530
531 let highest_allowed = match column.field_type {
532 FieldType::I16 | FieldType::OptionalI16 => i64::from(i16::MAX),
533 FieldType::I32 | FieldType::OptionalI32 => i64::from(i32::MAX),
534 FieldType::I64 | FieldType::OptionalI64 => i64::MAX,
535 FieldType::Boolean |
536 FieldType::F32 |
537 FieldType::F64 |
538 FieldType::ColourRGB |
539 FieldType::StringU8 |
540 FieldType::StringU16 |
541 FieldType::OptionalStringU8 |
542 FieldType::OptionalStringU16 |
543 FieldType::SequenceU16(_) |
544 FieldType::SequenceU32(_) => return Err(ApiError::InvalidParams(format!("The column {} is not an integer column.", request.column)).into()),
545 };
546
547 let highest_used = self.column_values(&request.table_name, &request.column, true, true).iter()
548 .filter_map(|value| value.parse::<i64>().ok())
549 .max()
550 .unwrap_or(0);
551
552 let numbers = (1..=request.count as i64)
553 .map(|offset| highest_used.checked_add(offset).filter(|number| *number <= highest_allowed))
554 .collect::<Option<Vec<_>>>()
555 .ok_or_else(|| ApiError::InvalidParams(format!("The column {} has less than {} numbers left above {highest_used}.", request.column, request.count)))?;
556
557 Ok(UnusedNumbers { numbers })
558 }
559
560 pub fn reference_values(&mut self, request: &GetReferenceValues) -> Result<ReferenceValues> {
566 let version = self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version;
567 let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, version)
568 .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?
569 .clone();
570
571 let column_index = definition.fields_processed().iter()
572 .position(|field| field.name() == request.column)
573 .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
574
575 let references = self.reference_data(&request.table_name, &definition, false).remove(&(column_index as i32))
576 .ok_or_else(|| ApiError::InvalidParams(format!("The column {} is not a reference column.", request.column)))?;
577
578 let mut values = references.data().iter()
579 .filter(|(value, _)| value.starts_with(&request.prefix))
580 .map(|(value, lookup)| ReferenceValue { value: value.clone(), lookup: lookup.clone() })
581 .collect::<Vec<_>>();
582 values.sort_unstable_by(|a, b| a.value.cmp(&b.value));
583
584 let total = values.len();
585 let values = values.into_iter()
586 .skip(request.offset)
587 .take(request.limit.unwrap_or(DEFAULT_REFERENCE_VALUES_LIMIT))
588 .collect();
589
590 Ok(ReferenceValues { values, total })
591 }
592
593 pub fn loc_source(&self, loc_key: &str) -> Option<LocSource> {
595 self.loc_key_source(loc_key).map(|(table, column, key_values)| LocSource { table_name: format!("{table}_tables"), column, key_values })
596 }
597
598 pub fn dependency_table_names(&self) -> Vec<String> {
600 self.dependencies.vanilla_loose_tables().keys()
601 .chain(self.dependencies.vanilla_tables().keys())
602 .cloned()
603 .collect()
604 }
605
606 pub fn dependency_table_version(&self, table_name: &str) -> Result<i32> {
610 if !self.dependencies.is_vanilla_data_loaded(false) {
611 return Err(ApiError::DependenciesNotLoaded.into());
612 }
613
614 if let Some(version) = self.dependencies.db_version(table_name) {
615 return Ok(version);
616 }
617
618 if !(table_name.starts_with("start_pos_") || table_name.starts_with("twad_") || table_name.starts_with("ceo")) {
619 return Err(anyhow!("Table not found in the game files."));
620 }
621
622 loaded_schema(&self.schema)?.definitions_by_table_name(table_name)
623 .and_then(|definitions| definitions.first())
624 .map(|definition| *definition.version())
625 .ok_or_else(|| anyhow!("There are no definitions for this specific table."))
626 }
627
628 pub fn dependency_tables(&self, table_name: &str) -> Result<Vec<RFile>> {
630 Ok(self.dependencies.db_data(table_name, true, true)?.into_iter().cloned().collect())
631 }
632
633 pub fn dependency_table_versions(&self) -> DependencyTables {
635 let names = self.dependency_table_names().into_iter().chain(self.custom_table_names().unwrap_or_default());
636 let tables = names.filter_map(|name| self.dependency_table_version(&name).ok().map(|version| (name, version))).collect();
637 DependencyTables { tables }
638 }
639
640 pub fn dependency_table_data(&self, table_name: &str) -> Result<DependencyTableData> {
642 let tables = self.dependency_tables(table_name)?.iter()
643 .filter_map(|file| match file.decoded() {
644 Ok(RFileDecoded::DB(table)) => Some(table.clone()),
645 _ => None,
646 })
647 .collect();
648
649 Ok(DependencyTableData { tables })
650 }
651}
652
653impl SessionState {
654
655 pub fn table_info(&mut self, file: &FileRef) -> Result<TableInfo> {
657 let table = self.table(file)?;
658 Ok(TableInfo {
659 table_name: table.name().to_owned(),
660 version: *table.definition().version(),
661 columns: table.definition().fields_processed().to_vec(),
662 row_count: table.len(),
663 })
664 }
665
666 pub fn table_rows(&mut self, request: &GetTableRows) -> Result<TableRows> {
672 let table = self.table(&request.file)?;
673 let fields = table.definition().fields_processed();
674 let column_index = |name: &str| fields.iter()
675 .position(|field| field.name() == name)
676 .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {name}.")));
677
678 let columns = match request.columns {
679 Some(ref names) => names.iter()
680 .map(|name| column_index(name).map(|index| (index, name.to_owned())))
681 .collect::<Result<Vec<_>, _>>()?,
682 None => fields.iter().enumerate().map(|(index, field)| (index, field.name().to_owned())).collect(),
683 };
684
685 let filters = request.filters.iter()
686 .map(|filter| column_index(&filter.column).map(|column_index| PreparedFilter::new(filter, column_index)))
687 .collect::<Result<Vec<_>, _>>()?;
688
689 let limit = request.limit.unwrap_or(DEFAULT_ROWS_LIMIT);
690 let mut rows = vec![];
691 let mut total = 0;
692
693 for (index, row) in table.data().iter().enumerate() {
694 if !filters.iter().all(|filter| filter.matches(row)) {
695 continue;
696 }
697
698 if total >= request.offset && rows.len() < limit {
699 let values = columns.iter()
700 .map(|(column_index, _)| row.get(*column_index).map(decoded_to_json).unwrap_or(Value::Null))
701 .collect();
702
703 rows.push(TableRow { index, values });
704 }
705
706 total += 1;
707 }
708
709 Ok(TableRows {
710 columns: columns.into_iter().map(|(_, name)| name).collect(),
711 rows,
712 total,
713 })
714 }
715
716 pub fn edit_table(&mut self, request: &EditTable) -> Result<TableEdited> {
723 let file = FileRef { source: FileSource::Pack(request.pack.clone()), path: request.path.clone() };
724 let fields = self.table(&file)?.definition().fields_processed().to_vec();
725 let copied = self.copied_row_values(&request.edits, &fields)?;
726
727 let row_count = match self.decoded_table_file(&file)?.decoded_mut()? {
728 RFileDecoded::DB(table) => {
729 let edits = prepare_row_edits(&request.edits, &fields, table.new_row(), table.data().len(), &copied)?;
730 apply_row_edits(table.data_mut(), edits)
731 }
732 RFileDecoded::Loc(table) => {
733 let edits = prepare_row_edits(&request.edits, &fields, table.new_row(), table.data().len(), &copied)?;
734 apply_row_edits(table.data_mut(), edits)
735 }
736 _ => return Err(ApiError::NotATable(request.path.clone()).into()),
737 };
738
739 Ok(TableEdited { row_count })
740 }
741
742 fn copied_row_values(&mut self, edits: &[RowEdit], fields: &[Field]) -> Result<BTreeMap<usize, Vec<(usize, DecodedData)>>> {
753 let mut copied = BTreeMap::new();
754 for (edit_index, edit) in edits.iter().enumerate() {
755 let RowEdit::Insert { copy_from: Some(row_ref), .. } = edit else {
756 continue;
757 };
758
759 let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
760 let table = self.table(&row_ref.file)?;
761 let source_fields = table.definition().fields_processed();
762 let data = table.data();
763 let row = data.get(row_ref.index)
764 .ok_or_else(|| invalid(format!("row {} of {} doesn't exist.", row_ref.index, row_ref.file.path)))?;
765
766 let values = fields.iter().enumerate()
767 .filter_map(|(column_index, field)| {
768 let source_index = source_fields.iter().position(|source_field| source_field.name() == field.name())?;
769 Some(copied_value(field.field_type(), &row[source_index])
770 .map(|value| (column_index, value))
771 .map_err(|_| invalid(format!("the value of the column {} can't be copied, as its type is different.", field.name()))))
772 })
773 .collect::<Result<Vec<_>, _>>()?;
774
775 copied.insert(edit_index, values);
776 }
777
778 Ok(copied)
779 }
780
781 pub fn merge_tables(&mut self, request: &MergeTables) -> Result<TablesMerged> {
787 let paths = self.pack_container_paths(&request.pack, &request.paths)?;
788 let mut options = MergeOptions::default();
789 options.set_delta_merge(request.delta);
790 options.set_resolutions(request.resolutions.clone());
791
792 Ok(match self.merge_files(&request.pack, &paths, &request.merged_path, request.delete_sources, &options)? {
793 MergeOutcome::Merged(path) => TablesMerged { merged: Some(path), conflicts: vec![] },
794 MergeOutcome::Conflicts(conflicts) => TablesMerged { merged: None, conflicts },
795 })
796 }
797
798 pub fn upgrade_table(&mut self, request: &UpgradeTable) -> Result<TableUpgraded> {
800 let (old_version, new_version, deleted_columns, added_columns) = self.update_table(&request.pack, &ContainerPath::File(request.path.clone()))?;
801 Ok(TableUpgraded { old_version, new_version, deleted_columns, added_columns })
802 }
803
804 pub fn rename_key(&mut self, request: &RenameKey) -> Result<FilesEdited> {
810 let version = match request.version {
811 Some(version) => version,
812 None => self.table_definition(&GetTableDefinition { table_name: request.table_name.clone(), version: None })?.version,
813 };
814
815 let definition = loaded_schema(&self.schema)?.definition_by_name_and_version(&request.table_name, version)
816 .ok_or_else(|| ApiError::DefinitionNotFound(request.table_name.clone()))?
817 .clone();
818
819 let field = definition.fields_processed().iter()
820 .find(|field| field.name() == request.column)
821 .cloned()
822 .ok_or_else(|| ApiError::InvalidParams(format!("The table has no column named {}.", request.column)))?;
823
824 let changes = [(field, request.old_value.clone(), request.new_value.clone())];
825 let (edited, _) = self.cascade_edition(&request.pack, &request.table_name, &definition, &changes)?;
826 Ok(FilesEdited { edited: edited.iter().map(|path| path.path_raw().to_owned()).collect() })
827 }
828
829 pub fn add_key_deletes(&mut self, request: &AddKeyDeletes) -> Result<FilesEdited> {
835 let keys = request.keys.iter().cloned().collect::<HashSet<_>>();
836 let path = self.add_keys_to_key_deletes(&request.pack, &request.file_name, &request.table_name, &keys)?
837 .ok_or_else(|| ApiError::FileNotFound(format!("db/{KEY_DELETES_TABLE_NAME}/{}", request.file_name)))?;
838
839 Ok(FilesEdited { edited: vec![path.path_raw().to_owned()] })
840 }
841
842 pub fn export_table_tsv(&mut self, request: &ExportTsv, keys_first: bool) -> Result<()> {
849 let (pack_key, data_source) = match request.file.source {
850 FileSource::Pack(ref pack_key) => (pack_key.as_str(), DataSource::PackFile),
851 FileSource::GameFiles => ("", DataSource::GameFiles),
852 FileSource::ParentFiles => ("", DataSource::ParentFiles),
853 FileSource::AssemblyKit => return Err(ApiError::InvalidParams("Assembly Kit tables can't be exported to TSV.".to_owned()).into()),
854 };
855
856 self.export_tsv(pack_key, &request.file.path, &request.destination, data_source, keys_first)
857 }
858
859 pub fn import_table_tsv(&mut self, request: &ImportTsv) -> Result<TableEdited> {
861 let row_count = match self.import_tsv(&request.pack, &request.path, &request.tsv_path)? {
862 RFileDecoded::DB(table) => table.len(),
863 RFileDecoded::Loc(table) => table.len(),
864 _ => return Err(ApiError::NotATable(request.path.clone()).into()),
865 };
866
867 Ok(TableEdited { row_count })
868 }
869
870 fn table(&mut self, file: &FileRef) -> Result<&TableInMemory> {
872 self.check_file_exists(file)?;
873
874 if file.source == FileSource::AssemblyKit {
875 let not_found = || ApiError::FileNotFound(file.path.clone());
876 let table_name = file.path.split('/').nth(1).ok_or_else(not_found)?;
877 let table = self.dependencies.asskit_only_db_tables().get(table_name).ok_or_else(not_found)?;
878 return Ok(table.table());
879 }
880
881 match self.decoded_table_file(file)?.decoded()? {
882 RFileDecoded::DB(table) => Ok(table.table()),
883 RFileDecoded::Loc(table) => Ok(table.table()),
884 _ => Err(ApiError::NotATable(file.path.clone()).into()),
885 }
886 }
887
888 fn decoded_table_file(&mut self, file: &FileRef) -> Result<&mut RFile> {
890 let rfile = match file.source {
891 FileSource::Pack(ref pack_key) => pack_mut(&mut self.packs, pack_key)?.files_mut().get_mut(&file.path),
892 FileSource::GameFiles => self.dependencies.file_mut(&file.path, true, false).ok(),
893 FileSource::ParentFiles => self.dependencies.file_mut(&file.path, false, true).ok(),
894
895 FileSource::AssemblyKit => return Err(ApiError::ReadOnly(file.path.clone()).into()),
897 }.ok_or_else(|| ApiError::FileNotFound(file.path.clone()))?;
898
899 let file_type = rfile.file_type();
900 if file_type != FileType::DB && file_type != FileType::Loc {
901 return Err(ApiError::NotATable(file.path.clone()).into());
902 }
903
904 if file_type == FileType::DB && self.schema.is_none() {
905 return Err(ApiError::SchemaNotLoaded.into());
906 }
907
908 let mut extra_data = DecodeableExtraData::default();
909 extra_data.set_schema(self.schema.as_ref());
910 rfile.decode(&Some(extra_data), true, false)?;
911 Ok(rfile)
912 }
913}
914
915impl PreparedFilter {
916
917 fn new(filter: &RowFilter, column_index: usize) -> Self {
919 Self {
920 column_index,
921 op: filter.op,
922 value: if filter.ignore_case { filter.value.to_lowercase() } else { filter.value.clone() },
923 ignore_case: filter.ignore_case,
924 }
925 }
926
927 fn matches(&self, row: &[DecodedData]) -> bool {
929 let Some(data) = row.get(self.column_index) else { return false };
930 let value = data.data_to_string();
931 let value = if self.ignore_case { Cow::Owned(value.to_lowercase()) } else { value };
932
933 match self.op {
934 FilterOp::Equals => *value == self.value,
935 FilterOp::NotEquals => *value != self.value,
936 FilterOp::Contains => value.contains(&self.value),
937 FilterOp::StartsWith => value.starts_with(&self.value),
938 FilterOp::EndsWith => value.ends_with(&self.value),
939 }
940 }
941}
942
943fn prepare_row_edits(edits: &[RowEdit], fields: &[Field], new_row: Vec<DecodedData>, mut row_count: usize, copied: &BTreeMap<usize, Vec<(usize, DecodedData)>>) -> Result<Vec<PreparedEdit>, ApiError> {
953 let mut prepared = Vec::with_capacity(edits.len());
954 for (edit_index, edit) in edits.iter().enumerate() {
955 let invalid = |message: String| ApiError::InvalidParams(format!("Edit {edit_index}: {message}"));
956 let missing_row = |index: usize, row_count: usize| invalid(format!("row {index} doesn't exist, the table has {row_count} rows at this point."));
957
958 match edit {
959 RowEdit::Insert { index, values, .. } => {
960 let index = index.unwrap_or(row_count);
961 if index > row_count {
962 return Err(missing_row(index, row_count));
963 }
964
965 let mut row = new_row.clone();
966 let copied_values = copied.get(&edit_index).into_iter().flatten().cloned();
967 for (column_index, value) in copied_values.chain(row_values(fields, values).map_err(invalid)?) {
968 row[column_index] = value;
969 }
970
971 prepared.push(PreparedEdit::Insert(index, row));
972 row_count += 1;
973 }
974 RowEdit::Update { index, values } => {
975 if *index >= row_count {
976 return Err(missing_row(*index, row_count));
977 }
978
979 prepared.push(PreparedEdit::Update(*index, row_values(fields, values).map_err(invalid)?));
980 }
981 RowEdit::Delete { indexes } => {
982 let mut indexes = indexes.clone();
983 indexes.sort_unstable_by(|a, b| b.cmp(a));
984 indexes.dedup();
985
986 if let Some(index) = indexes.first().filter(|index| **index >= row_count) {
987 return Err(missing_row(*index, row_count));
988 }
989
990 row_count -= indexes.len();
991 prepared.push(PreparedEdit::Delete(indexes));
992 }
993 }
994 }
995
996 Ok(prepared)
997}
998
999fn apply_row_edits(rows: &mut Vec<Vec<DecodedData>>, edits: Vec<PreparedEdit>) -> usize {
1005 for edit in edits {
1006 match edit {
1007 PreparedEdit::Insert(index, row) => rows.insert(index, row),
1008 PreparedEdit::Update(index, values) => {
1009 for (column_index, value) in values {
1010 rows[index][column_index] = value;
1011 }
1012 }
1013 PreparedEdit::Delete(indexes) => {
1014 for index in indexes {
1015 rows.remove(index);
1016 }
1017 }
1018 }
1019 }
1020
1021 rows.len()
1022}
1023
1024fn row_values(fields: &[Field], values: &BTreeMap<String, Value>) -> Result<Vec<(usize, DecodedData)>, String> {
1030 values.iter()
1031 .map(|(column, value)| {
1032 let column_index = fields.iter()
1033 .position(|field| field.name() == column)
1034 .ok_or_else(|| format!("the table has no column named {column}."))?;
1035
1036 let value = json_to_decoded(fields[column_index].field_type(), value)
1037 .map_err(|error| format!("invalid value for column {column}: {error}"))?;
1038
1039 Ok((column_index, value))
1040 })
1041 .collect()
1042}
1043
1044fn copied_value(field_type: &FieldType, value: &DecodedData) -> Result<DecodedData> {
1046 if value.is_field_type_correct(field_type) {
1047 Ok(value.clone())
1048 } else {
1049 Ok(DecodedData::new_from_type_and_string(field_type, &value.data_to_string())?)
1050 }
1051}
1052
1053fn json_to_decoded(field_type: &FieldType, value: &Value) -> Result<DecodedData> {
1057 let text = match value {
1058 Value::String(text) => Cow::Borrowed(text.as_str()),
1059 Value::Bool(_) | Value::Number(_) => Cow::Owned(value.to_string()),
1060 Value::Null | Value::Array(_) | Value::Object(_) => return Err(anyhow!("expected a boolean, number or string, found {value}.")),
1061 };
1062
1063 DecodedData::new_from_type_and_string(field_type, &text)
1064 .map_err(|_| anyhow!("expected {}, found {value}.", expected_value(field_type)))
1065}
1066
1067fn expected_value(field_type: &FieldType) -> &'static str {
1069 match field_type {
1070 FieldType::Boolean => "a boolean",
1071 FieldType::F32 | FieldType::F64 => "a number",
1072 FieldType::I16 | FieldType::OptionalI16 => "a 16-bit integer",
1073 FieldType::I32 | FieldType::OptionalI32 => "a 32-bit integer",
1074 FieldType::I64 | FieldType::OptionalI64 => "a 64-bit integer",
1075 FieldType::ColourRGB => "a hexadecimal colour, like FF0000",
1076 FieldType::StringU8 |
1077 FieldType::StringU16 |
1078 FieldType::OptionalStringU8 |
1079 FieldType::OptionalStringU16 |
1080 FieldType::SequenceU16(_) |
1081 FieldType::SequenceU32(_) => "a string",
1082 }
1083}
1084
1085fn decoded_to_json(data: &DecodedData) -> Value {
1089 match data {
1090 DecodedData::Boolean(value) => Value::Bool(*value),
1091
1092 DecodedData::F32(value) => value.to_string().parse::<f64>().ok().and_then(Number::from_f64).map_or(Value::Null, Value::Number),
1094 DecodedData::F64(value) => Number::from_f64(*value).map_or(Value::Null, Value::Number),
1095 DecodedData::I16(value) | DecodedData::OptionalI16(value) => Value::from(*value),
1096 DecodedData::I32(value) | DecodedData::OptionalI32(value) => Value::from(*value),
1097 DecodedData::I64(value) | DecodedData::OptionalI64(value) => Value::from(*value),
1098 DecodedData::ColourRGB(value) |
1099 DecodedData::StringU8(value) |
1100 DecodedData::StringU16(value) |
1101 DecodedData::OptionalStringU8(value) |
1102 DecodedData::OptionalStringU16(value) => Value::String(value.clone()),
1103 DecodedData::SequenceU16(_) |
1104 DecodedData::SequenceU32(_) => Value::String(data.data_to_string().into_owned()),
1105 }
1106}
1107
1108fn find_in_db(table: &DB, column_name: &str, value: &str) -> Option<(usize, usize)> {
1116 let definition = table.definition();
1117 let key_column_name = if definition.localised_fields().iter().any(|field| field.name() == column_name) {
1118 definition.localised_key_order().first()
1119 .and_then(|index| definition.fields_processed().get(*index as usize).map(|field| field.name().to_owned()))
1120 } else {
1121 None
1122 };
1123
1124 let column_name = key_column_name.as_deref().unwrap_or(column_name);
1125 let (column_index, row_indexes) = table.table().rows_containing_data(column_name, value)?;
1126 row_indexes.first().map(|row_index| (column_index, *row_index))
1127}
1128
1129fn find_in_db_files(files: &[&RFile], column_name: &str, value: &str, source: &FileSource) -> Option<FoundRow> {
1131 files.iter().find_map(|file| match file.decoded() {
1132 Ok(RFileDecoded::DB(table)) => find_in_db(table, column_name, value)
1133 .map(|(column_index, row_index)| FoundRow {
1134 location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1135 values: row_values_by_column(table.table(), row_index),
1136 }),
1137 _ => None,
1138 })
1139}
1140
1141fn find_in_loc_files(files: &[&RFile], loc_key: &str, source: &FileSource) -> Option<FoundRow> {
1143 files.iter().find_map(|file| match file.decoded() {
1144 Ok(RFileDecoded::Loc(table)) => {
1145 let (column_index, row_indexes) = table.table().rows_containing_data("key", loc_key)?;
1146 row_indexes.first().map(|row_index| FoundRow {
1147 location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index: *row_index },
1148 values: row_values_by_column(table.table(), *row_index),
1149 })
1150 }
1151 _ => None,
1152 })
1153}
1154
1155fn row_values_by_column(table: &TableInMemory, row_index: usize) -> BTreeMap<String, Value> {
1157 let data = table.data();
1158 let Some(row) = data.get(row_index) else {
1159 return BTreeMap::new();
1160 };
1161
1162 table.definition().fields_processed().iter()
1163 .zip(row)
1164 .map(|(field, value)| (field.name().to_owned(), decoded_to_json(value)))
1165 .collect()
1166}
1167
1168fn references_in_file(file: &RFile, columns: &[String], value: &str, source: &FileSource) -> Vec<Usage> {
1170 let Ok(RFileDecoded::DB(table)) = file.decoded() else { return vec![] };
1171
1172 columns.iter()
1173 .filter_map(|column_name| table.table().rows_containing_data(column_name, value).map(|found| (column_name, found)))
1174 .flat_map(|(column_name, (column_index, row_indexes))| row_indexes.into_iter()
1175 .map(|row_index| Usage {
1176 location: RowLocation { source: source.clone(), path: file.path_in_container_raw().to_owned(), column_index, row_index },
1177 column: column_name.to_owned(),
1178 values: row_values_by_column(table.table(), row_index),
1179 })
1180 .collect::<Vec<_>>())
1181 .collect()
1182}
1183
1184fn delta_merge_files(sources: &[&RFile], merged_path: &str, dependencies: &Dependencies, resolutions: &[MergeResolution]) -> Result<DeltaMergeOutcome> {
1187 if sources.len() < 2 {
1188 return Err(anyhow!("Not enough tables provided to merge."));
1189 }
1190
1191 let (merged, conflicts) = match sources[0].decoded()? {
1192 RFileDecoded::DB(_) => {
1193 let tables = sources.iter()
1194 .filter_map(|file| if let Ok(RFileDecoded::DB(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1195 .collect::<Vec<_>>();
1196
1197 let baseline = db_baseline(dependencies, tables[0].1.table_name());
1198 let (merged, conflicts) = delta_merge_db(&tables, baseline.as_ref(), resolutions)?;
1199 (RFileDecoded::DB(merged), conflicts)
1200 },
1201 RFileDecoded::Loc(_) => {
1202 let tables = sources.iter()
1203 .filter_map(|file| if let Ok(RFileDecoded::Loc(table)) = file.decoded() { Some((file.path_in_container_raw(), table)) } else { None })
1204 .collect::<Vec<_>>();
1205
1206 let baseline = loc_baseline(dependencies);
1207 let (merged, conflicts) = delta_merge_loc(&tables, baseline.as_ref(), resolutions)?;
1208 (RFileDecoded::Loc(merged), conflicts)
1209 },
1210 _ => return Err(anyhow!("Delta merge is only supported for DB and Loc tables.")),
1211 };
1212
1213 if conflicts.is_empty() {
1214 Ok(DeltaMergeOutcome::Merged(RFile::new_from_decoded(&merged, current_time()?, merged_path)))
1215 } else {
1216 Ok(DeltaMergeOutcome::Conflicts(conflicts))
1217 }
1218}
1219
1220#[cfg(test)]
1225#[path = "tables_test.rs"]
1226mod tables_test;