From 56d6c5b4e3f8b4b599e7c340cb8ed4e05aa45278 Mon Sep 17 00:00:00 2001 From: akfakmot Date: Thu, 5 Jun 2025 08:07:00 +0200 Subject: [PATCH 1/4] Add skipping of classes excluded by configuration Add: Add skipping of classes excluded by configuration --- .../Handlers/MigrateCustomModulesCommandHandler.cs | 7 +++++++ .../Handlers/MigrateCustomTablesHandler.cs | 11 ++++++++++- .../Handlers/MigratePageTypesCommandHandler.cs | 10 +++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs index 29042a29..9a76d00b 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs @@ -60,6 +60,7 @@ public async Task Handle(MigrateCustomModulesCommand request, Can private async Task MigrateClasses(EntityConfiguration entityConfiguration, CancellationToken cancellationToken) { + var dataClassEntityConfiguration = toolConfiguration.EntityConfigurations.GetEntityConfiguration(); var manualMappings = classMappingProvider.ExecuteMappings(); using var cmsClasses = EnumerableHelper.CreateDeferrableItemWrapper( @@ -96,6 +97,12 @@ private async Task MigrateClasses(EntityConfiguration entityConfiguration, Cance { continue; } + + if (dataClassEntityConfiguration.ExcludeCodeNames.Contains(cmsClass.ClassName, + StringComparer.InvariantCultureIgnoreCase)) + { + continue; + } if (!remapped()) { diff --git a/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs index e3d5e612..0085f227 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs @@ -35,7 +35,8 @@ public class MigrateCustomTablesHandler( IUmtMapper mapper, IEntityMapper dataClassMapper, PrimaryKeyMappingContext primaryKeyMappingContext, - ClassMappingProvider classMappingProvider + ClassMappingProvider classMappingProvider, + ToolConfiguration toolConfiguration ) : IRequestHandler { @@ -83,6 +84,8 @@ private async Task EnsureCustomTablesResource() private async Task MigrateCustomTables() { + var dataClassEntityConfiguration = toolConfiguration.EntityConfigurations.GetEntityConfiguration(); + using var srcClassesDe = EnumerableHelper.CreateDeferrableItemWrapper( modelFacade.Select("ClassIsCustomTable=1", "ClassID ASC") ); @@ -104,6 +107,12 @@ private async Task MigrateCustomTables() { continue; } + + if (dataClassEntityConfiguration.ExcludeCodeNames.Contains(ksClass.ClassName, + StringComparer.InvariantCultureIgnoreCase)) + { + continue; + } if (ksClass.ClassInheritsFromClassID is { } classInheritsFromClassId && !primaryKeyMappingContext.HasMapping(c => c.ClassID, classInheritsFromClassId)) { diff --git a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs index 52cb49ca..83fc0aa3 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs @@ -9,6 +9,7 @@ using Migration.Tool.Common; using Migration.Tool.Common.Abstractions; +using Migration.Tool.Common.Builders; using Migration.Tool.Common.Helpers; using Migration.Tool.Common.MigrationProtocol; using Migration.Tool.KXP.Api; @@ -31,7 +32,8 @@ public class MigratePageTypesCommandHandler( PageTemplateMigrator pageTemplateMigrator, ReusableSchemaService reusableSchemaService, ClassMappingProvider classMappingProvider, - IImporter importer + IImporter importer, + IEnumerable reusableSchemaBuilders ) : IRequestHandler { @@ -64,6 +66,12 @@ public async Task Handle(MigratePageTypesCommand request, Cancell continue; } + if (entityConfiguration.ExcludeCodeNames.Contains(ksClass.ClassName, + StringComparer.InvariantCultureIgnoreCase)) + { + continue; + } + if (ksClass.ClassInheritsFromClassID is { } classInheritsFromClassId && !(primaryKeyMappingContext.HasMapping(c => c.ClassID, classInheritsFromClassId) || manuallyMappedSourceClassIDs.Contains(classInheritsFromClassId))) { // defer migration to later stage From 8b43afe0e3eaa5e392b39b18653a0baf164e7c89 Mon Sep 17 00:00:00 2001 From: akfakmot Date: Thu, 5 Jun 2025 08:09:39 +0200 Subject: [PATCH 2/4] Forbid both custom reusable schema builders & config based reusable schema conversion Modify: Forbid both custom reusable schema builders & config based reusable schema conversion --- .../MigratePageTypesCommandHandler.cs | 8 ++++ Migration.Tool.CLI/README.md | 44 +++++++++---------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs index 83fc0aa3..66d9d102 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs @@ -52,6 +52,14 @@ public async Task Handle(MigratePageTypesCommand request, Cancell var manuallyMappedSourceClassIDs = new HashSet(); var manuallyMappedSourceClassNames = manualMappings.Values.SelectMany(x => x.mappping.SourceClassNames).ToHashSet(); + if (reusableSchemaBuilders.Any() && !string.IsNullOrWhiteSpace(toolConfiguration.CreateReusableFieldSchemaForClasses)) + { + logger.LogError("Conversion to reusable field schema using appsettings configuration " + + "is not allowed when custom class mapping reusable schema builders are " + + "used. Use one option or the other. Terminating migration of page types."); + return new CommandFailureResult(); + } + while (ksClasses.GetNext(out var di)) { var (_, ksClass) = di; diff --git a/Migration.Tool.CLI/README.md b/Migration.Tool.CLI/README.md index 8ffe5b27..d2f81123 100644 --- a/Migration.Tool.CLI/README.md +++ b/Migration.Tool.CLI/README.md @@ -410,29 +410,29 @@ Before you run the migration, configure options in the `Migration.Tool.CLI/appse Add the options under the `Settings` section in the configuration file. -| Configuration | Description | -| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| KxConnectionString | The connection string to the source Kentico Xperience 13, Kentico 12, or Kentico 11 database. | -| KxCmsDirPath | The absolute file system path of the **CMS** folder in the source Kentico Xperience 13, Kentico 12, or Kentico 11 administration project. Required to migrate media library files. | -| XbyKDirPath | The absolute file system path of the root of the target Xperience by Kentico project. Required to migrate media library and page attachment files. | -| XbyKApiSettings | Configuration options set for the API when creating migrated objects in the target application.

The `ConnectionStrings.CMSConnectionString`option is required - set the connection string to the target Xperience by Kentico database (the same value as obsolete `XbKConnectionString`). | -| MigrationProtocolPath | The absolute file system path of the location where the [migration protocol file](./MIGRATION_PROTOCOL_REFERENCE.md) is generated.

For example: `"C:\\Logs\\Migration.Tool.Protocol.log"` | -| ConvertClassesToContentHub | Specifies which page types, custom tables or custom module classes are migrated to [reusable content items](https://docs.kentico.com/x/content_items_xp) (instead of website channel pages or custom module classes for custom tables and classes). Enter page type code names, separated with either `;` or `,`. See [Convert pages or custom tables to Content hub](#convert-pages-or-custom-tables-to-content-hub) or [Convert module classes to Content hub](#convert-module-classes-to-content-hub) for detailed information. | -| CustomModuleClassDisplayNamePatterns | Specifies the format of content item names for items migrated from custom module classes. Add a dictionary with the class name as the key and the name pattern as the value. The name pattern can use placeholders that are replaced by values from a specific column in the source class.

Example: `CustomModuleItem-{CustomClassGuid}` | -| MigrateOnlyMediaFileInfo | If set to `true`, only the database representations of media files are migrated, without the files in the media folder in the project's file system. For example, enable this option if your media library files are mapped to a shared directory or Cloud storage.

If `false`, media files are migrated based on the `KxCmsDirPath` location. | +| Configuration | Description | +| ----------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| KxConnectionString | The connection string to the source Kentico Xperience 13, Kentico 12, or Kentico 11 database. | +| KxCmsDirPath | The absolute file system path of the **CMS** folder in the source Kentico Xperience 13, Kentico 12, or Kentico 11 administration project. Required to migrate media library files. | +| XbyKDirPath | The absolute file system path of the root of the target Xperience by Kentico project. Required to migrate media library and page attachment files. | +| XbyKApiSettings | Configuration options set for the API when creating migrated objects in the target application.

The `ConnectionStrings.CMSConnectionString`option is required - set the connection string to the target Xperience by Kentico database (the same value as obsolete `XbKConnectionString`). | +| MigrationProtocolPath | The absolute file system path of the location where the [migration protocol file](./MIGRATION_PROTOCOL_REFERENCE.md) is generated.

For example: `"C:\\Logs\\Migration.Tool.Protocol.log"` | +| ConvertClassesToContentHub | Specifies which page types, custom tables or custom module classes are migrated to [reusable content items](https://docs.kentico.com/x/content_items_xp) (instead of website channel pages or custom module classes for custom tables and classes). Enter page type code names, separated with either `;` or `,`. See [Convert pages or custom tables to Content hub](#convert-pages-or-custom-tables-to-content-hub) or [Convert module classes to Content hub](#convert-module-classes-to-content-hub) for detailed information. | +| CustomModuleClassDisplayNamePatterns | Specifies the format of content item names for items migrated from custom module classes. Add a dictionary with the class name as the key and the name pattern as the value. The name pattern can use placeholders that are replaced by values from a specific column in the source class.

Example: `CustomModuleItem-{CustomClassGuid}` | +| MigrateOnlyMediaFileInfo | If set to `true`, only the database representations of media files are migrated, without the files in the media folder in the project's file system. For example, enable this option if your media library files are mapped to a shared directory or Cloud storage.

If `false`, media files are migrated based on the `KxCmsDirPath` location. | | MigrateMediaToMediaLibrary | Determines whether media library files and attachments from the source instance are migrated to the target instance as media libraries or as [content item assets](https://docs.kentico.com/x/content_item_assets_xp) in the content hub. The default value is `false` – media files and attachments are migrated as content item assets.

See [Convert attachments and media library files to media libraries instead of content item assets](#convert-attachments-and-media-library-files-to-media-libraries-instead-of-content-item-assets) | -| LegacyFlatAssetTree | Use legacy behavior of versions up to 2.3.0. Content folders for asset content items will be created in a flat structure (all under root folder) | -| AssetRootFolders | Dictionary defining the root folder for Asset content items per site: Key is site name (CMS_Site.SiteName). Value is in format _/FolderDisplayName1/FolderDisplayName2/..._ | -| MemberIncludeUserSystemFields | Determines which system fields from the _CMS_User_ and _CMS_UserSettings_ tables are migrated to _CMS_Member_ in Xperience by Kentico. Fields that do not exist in _CMS_Member_ are automatically created.

The sample `appsettings.json` file included with the tool by default includes all user fields that can be migrated from Kentico Xperience 13. Exclude specific fields from the migration by removing them from this configuration option. | -| IncludeExtendedMetadata | Migrates DocumentPageTitle, DocumentPageDescription and DocumentPageKeywords if they are available in the source instance | -| UseOmActivityNodeRelationAutofix | Determines how the migration handles references from Contact management activities to non-existing pages.

Possible options:
`DiscardData` - faulty references are removed,
`AttemptFix` - references are updated to the IDs of corresponding pages created by the migration,
`Error` - an error is reported and the reference can be translated or otherwise handled manually | -| UseOmActivitySiteRelationAutofix | Determines how the migration handles site references from Contact management activities.

Possible options: `DiscardData`,`AttemptFix`,`Error` | -| EntityConfigurations | Contains options that allow you to fine-tune the migration of specific object types. | -| EntityConfigurations._<object table name>_.ExcludeCodeNames | Excludes objects with the specified code names from the migration. | -| CreateReusableFieldSchemaForClasses | Specifies which page types are also converted to [reusable field schemas](#convert-page-types-to-reusable-field-schemas). | -| OptInFeatures.QuerySourceInstanceApi.Enabled | If `true`, [source instance API discovery](#source-instance-api-discovery) is enabled to allow advanced migration of page builder content for pages and page templates. | -| OptInFeatures.QuerySourceInstanceApi.Connections | To use [source instance API discovery](#source-instance-api-discovery), you need to add a connection JSON object containing the following values:
`SourceInstanceUri` - the base URI where the source instance's live site application is running.
`Secret` - the secret that you set in the _ToolkitApiController.cs_ file on the source instance. | -| OptInFeatures.CustomMigration.FieldMigrations | Enables conversion of media selection text fields to content item assets or media library files. See [Convert text fields with media links](#convert-text-fields-with-media-links) for more information. | +| LegacyFlatAssetTree | Use legacy behavior of versions up to 2.3.0. Content folders for asset content items will be created in a flat structure (all under root folder) | +| AssetRootFolders | Dictionary defining the root folder for Asset content items per site: Key is site name (CMS_Site.SiteName). Value is in format _/FolderDisplayName1/FolderDisplayName2/..._ | +| MemberIncludeUserSystemFields | Determines which system fields from the _CMS_User_ and _CMS_UserSettings_ tables are migrated to _CMS_Member_ in Xperience by Kentico. Fields that do not exist in _CMS_Member_ are automatically created.

The sample `appsettings.json` file included with the tool by default includes all user fields that can be migrated from Kentico Xperience 13. Exclude specific fields from the migration by removing them from this configuration option. | +| IncludeExtendedMetadata | Migrates DocumentPageTitle, DocumentPageDescription and DocumentPageKeywords if they are available in the source instance | +| UseOmActivityNodeRelationAutofix | Determines how the migration handles references from Contact management activities to non-existing pages.

Possible options:
`DiscardData` - faulty references are removed,
`AttemptFix` - references are updated to the IDs of corresponding pages created by the migration,
`Error` - an error is reported and the reference can be translated or otherwise handled manually | +| UseOmActivitySiteRelationAutofix | Determines how the migration handles site references from Contact management activities.

Possible options: `DiscardData`,`AttemptFix`,`Error` | +| EntityConfigurations | Contains options that allow you to fine-tune the migration of specific object types. | +| EntityConfigurations._<object table name>_.ExcludeCodeNames | Excludes objects with the specified code names from the migration. | +| CreateReusableFieldSchemaForClasses | Specifies which page types are also converted to [reusable field schemas](#convert-page-types-to-reusable-field-schemas). Only available when reusable field schema builders are not used in custom class mapping extension. | +| OptInFeatures.QuerySourceInstanceApi.Enabled | If `true`, [source instance API discovery](#source-instance-api-discovery) is enabled to allow advanced migration of page builder content for pages and page templates. | +| OptInFeatures.QuerySourceInstanceApi.Connections | To use [source instance API discovery](#source-instance-api-discovery), you need to add a connection JSON object containing the following values:
`SourceInstanceUri` - the base URI where the source instance's live site application is running.
`Secret` - the secret that you set in the _ToolkitApiController.cs_ file on the source instance. | +| OptInFeatures.CustomMigration.FieldMigrations | Enables conversion of media selection text fields to content item assets or media library files. See [Convert text fields with media links](#convert-text-fields-with-media-links) for more information. | ### Example From 6b8b88ef5b8b367c7716aaf0148242eeca612184 Mon Sep 17 00:00:00 2001 From: akfakmot Date: Thu, 5 Jun 2025 11:25:14 +0200 Subject: [PATCH 3/4] Fix formatting Fix: Fix formatting --- .../Handlers/MigrateCustomModulesCommandHandler.cs | 2 +- .../Handlers/MigrateCustomTablesHandler.cs | 4 ++-- .../Handlers/MigratePageTypesCommandHandler.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs index 9a76d00b..4f168e23 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigrateCustomModulesCommandHandler.cs @@ -97,7 +97,7 @@ private async Task MigrateClasses(EntityConfiguration entityConfiguration, Cance { continue; } - + if (dataClassEntityConfiguration.ExcludeCodeNames.Contains(cmsClass.ClassName, StringComparer.InvariantCultureIgnoreCase)) { diff --git a/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs index 0085f227..d55123de 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigrateCustomTablesHandler.cs @@ -85,7 +85,7 @@ private async Task EnsureCustomTablesResource() private async Task MigrateCustomTables() { var dataClassEntityConfiguration = toolConfiguration.EntityConfigurations.GetEntityConfiguration(); - + using var srcClassesDe = EnumerableHelper.CreateDeferrableItemWrapper( modelFacade.Select("ClassIsCustomTable=1", "ClassID ASC") ); @@ -107,7 +107,7 @@ private async Task MigrateCustomTables() { continue; } - + if (dataClassEntityConfiguration.ExcludeCodeNames.Contains(ksClass.ClassName, StringComparer.InvariantCultureIgnoreCase)) { diff --git a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs index 66d9d102..f846a97e 100644 --- a/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs +++ b/KVA/Migration.Tool.Source/Handlers/MigratePageTypesCommandHandler.cs @@ -59,7 +59,7 @@ public async Task Handle(MigratePageTypesCommand request, Cancell "used. Use one option or the other. Terminating migration of page types."); return new CommandFailureResult(); } - + while (ksClasses.GetNext(out var di)) { var (_, ksClass) = di; From 9b6899e969eefe98d447202765cfd118c42edf6d Mon Sep 17 00:00:00 2001 From: David Benovsky Date: Thu, 5 Jun 2025 13:37:12 +0200 Subject: [PATCH 4/4] Reusable field schema improvements TW review --- Migration.Tool.CLI/README.md | 7 +++++-- Migration.Tool.Extensions/README.md | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Migration.Tool.CLI/README.md b/Migration.Tool.CLI/README.md index d2f81123..fb705ccf 100644 --- a/Migration.Tool.CLI/README.md +++ b/Migration.Tool.CLI/README.md @@ -140,7 +140,7 @@ You can create [reusable field schemas](https://docs.kentico.com/x/D4_OD) from p inherit, by setting the `Settings.CreateReusableFieldSchemaForClasses` configuration option. See [Convert page types to reusable field schemas](#convert-page-types-to-reusable-field-schemas) for detailed information. -Additionally, you can use the extensibility feature to implement [customizations](../Migration.Tool.Extensions/README.md) that allow you to inject reusable field schemas into content types or [extract fields from multiple page types into a shared schema](https://docs.kentico.com/x/remodel_page_types_as_reusable_field_schemas_guides). +Additionally, you can use the extensibility feature to implement [customizations](../Migration.Tool.Extensions/README.md) that allow you to inject reusable field schemas into content types or [extract fields from multiple page types into a shared schema](https://docs.kentico.com/x/remodel_page_types_as_reusable_field_schemas_guides). Note that usage of `ReusableSchemaBuilder` in custom class mappings cannot be combined together with the `Settings.CreateReusableFieldSchemaForClasses` configuration option. #### Content items @@ -429,7 +429,8 @@ Add the options under the `Settings` section in the configuration file. | UseOmActivitySiteRelationAutofix | Determines how the migration handles site references from Contact management activities.

Possible options: `DiscardData`,`AttemptFix`,`Error` | | EntityConfigurations | Contains options that allow you to fine-tune the migration of specific object types. | | EntityConfigurations._<object table name>_.ExcludeCodeNames | Excludes objects with the specified code names from the migration. | -| CreateReusableFieldSchemaForClasses | Specifies which page types are also converted to [reusable field schemas](#convert-page-types-to-reusable-field-schemas). Only available when reusable field schema builders are not used in custom class mapping extension. | +| CreateReusableFieldSchemaForClasses | Specifies which page types are also converted to [reusable field schemas](#convert-page-types-to-reusable-field-schemas). This option cannot be combined with usage of `ReusableSchemaBuilder` in [custom class mappings](../Migration.Tool.Extensions/README.md#custom-class-mappings). + | | OptInFeatures.QuerySourceInstanceApi.Enabled | If `true`, [source instance API discovery](#source-instance-api-discovery) is enabled to allow advanced migration of page builder content for pages and page templates. | | OptInFeatures.QuerySourceInstanceApi.Connections | To use [source instance API discovery](#source-instance-api-discovery), you need to add a connection JSON object containing the following values:
`SourceInstanceUri` - the base URI where the source instance's live site application is running.
`Secret` - the secret that you set in the _ToolkitApiController.cs_ file on the source instance. | | OptInFeatures.CustomMigration.FieldMigrations | Enables conversion of media selection text fields to content item assets or media library files. See [Convert text fields with media links](#convert-text-fields-with-media-links) for more information. | @@ -705,6 +706,8 @@ For advanced scenarios, you can use the extensibility feature to implement [cust - Page types specified by the `CreateReusableFieldSchemaForClasses` configuration option are also migrated as content types into to the target instance. +- Any usage of `ReusableSchemaBuilder` in [custom class mappings](../Migration.Tool.Extensions/README.md) cannot be combined together with usage of the `Settings.CreateReusableFieldSchemaForClasses` [configuration option](#configuration). + ## Convert text fields with media links By default, page type and module class fields with the _Text_ data type and the _Media diff --git a/Migration.Tool.Extensions/README.md b/Migration.Tool.Extensions/README.md index a287162d..ccbc1955 100644 --- a/Migration.Tool.Extensions/README.md +++ b/Migration.Tool.Extensions/README.md @@ -235,6 +235,8 @@ You can customize class mappings to adjust the content model between the source For an end-to-end example of how to extract common fields from two page types from Kentico Xperience 13 and move them to a [reusable field schema](https://docs.kentico.com/x/D4_OD) shared by both web page content types in Xperience by Kentico follow this [migration guide](https://docs.kentico.com/x/remodel_page_types_as_reusable_field_schemas_guides) in the documentation. +Note that any usage of `ReusableSchemaBuilder` in custom class mappings cannot be combined together with the `Settings.CreateReusableFieldSchemaForClasses` configuration option. + ### Sample class mappings You can find sample class mappings in the [ClassMappingSample.cs](/Migration.Tool.Extensions/ClassMappings/ClassMappingSample.cs) file.