2018-06-29 19:52:40 +02:00
using System ;
using System.Collections.Generic ;
using System.Data ;
using System.IO ;
using System.Linq ;
using System.Xml.Linq ;
using System.Xml.XPath ;
using Umbraco.Core.Configuration ;
using Umbraco.Core.IO ;
using Umbraco.Core.Models ;
using Umbraco.Core.Models.Packaging ;
using Umbraco.Core.Services ;
using File = System . IO . File ;
namespace Umbraco.Core.Packaging
{
internal class PackageInstallation : IPackageInstallation
{
private readonly IFileService _fileService ;
private readonly IMacroService _macroService ;
private readonly IPackagingService _packagingService ;
private IConflictingPackageData _conflictingPackageData ;
private readonly IPackageExtraction _packageExtraction ;
private string _fullPathToRoot ;
public PackageInstallation ( IPackagingService packagingService , IMacroService macroService ,
IFileService fileService , IPackageExtraction packageExtraction )
: this ( packagingService , macroService , fileService , packageExtraction , IOHelper . GetRootDirectorySafe ( ) )
{ }
public PackageInstallation ( IPackagingService packagingService , IMacroService macroService ,
IFileService fileService , IPackageExtraction packageExtraction , string fullPathToRoot )
{
if ( packageExtraction ! = null ) _packageExtraction = packageExtraction ;
else throw new ArgumentNullException ( "packageExtraction" ) ;
if ( macroService ! = null ) _macroService = macroService ;
else throw new ArgumentNullException ( "macroService" ) ;
if ( fileService ! = null ) _fileService = fileService ;
else throw new ArgumentNullException ( "fileService" ) ;
if ( packagingService ! = null ) _packagingService = packagingService ;
else throw new ArgumentNullException ( "packagingService" ) ;
_fullPathToRoot = fullPathToRoot ;
}
public IConflictingPackageData ConflictingPackageData
{
private get
{
return _conflictingPackageData ? ?
( _conflictingPackageData = new ConflictingPackageData ( _macroService , _fileService ) ) ;
}
set
{
if ( _conflictingPackageData ! = null )
{
throw new PropertyConstraintException ( "This property already have a value" ) ;
}
_conflictingPackageData = value ;
}
}
public string FullPathToRoot
{
private get { return _fullPathToRoot ; }
set
{
if ( _fullPathToRoot ! = null )
{
throw new PropertyConstraintException ( "This property already have a value" ) ;
}
_fullPathToRoot = value ;
}
}
public MetaData GetMetaData ( string packageFilePath )
{
try
{
XElement rootElement = GetConfigXmlElement ( packageFilePath ) ;
return GetMetaData ( rootElement ) ;
}
catch ( Exception e )
{
throw new Exception ( "Error reading " + packageFilePath , e ) ;
}
}
public PreInstallWarnings GetPreInstallWarnings ( string packageFilePath )
{
try
{
XElement rootElement = GetConfigXmlElement ( packageFilePath ) ;
return GetPreInstallWarnings ( rootElement ) ;
}
catch ( Exception e )
{
throw new Exception ( "Error reading " + packageFilePath , e ) ;
}
}
public InstallationSummary InstallPackage ( string packageFile , int userId )
{
XElement dataTypes ;
XElement languages ;
XElement dictionaryItems ;
XElement macroes ;
XElement files ;
XElement templates ;
XElement documentTypes ;
XElement styleSheets ;
XElement documentSet ;
XElement documents ;
XElement actions ;
MetaData metaData ;
InstallationSummary installationSummary ;
try
{
XElement rootElement = GetConfigXmlElement ( packageFile ) ;
PackageSupportedCheck ( rootElement ) ;
PackageStructureSanetyCheck ( packageFile , rootElement ) ;
dataTypes = rootElement . Element ( Constants . Packaging . DataTypesNodeName ) ;
languages = rootElement . Element ( Constants . Packaging . LanguagesNodeName ) ;
dictionaryItems = rootElement . Element ( Constants . Packaging . DictionaryItemsNodeName ) ;
macroes = rootElement . Element ( Constants . Packaging . MacrosNodeName ) ;
files = rootElement . Element ( Constants . Packaging . FilesNodeName ) ;
templates = rootElement . Element ( Constants . Packaging . TemplatesNodeName ) ;
documentTypes = rootElement . Element ( Constants . Packaging . DocumentTypesNodeName ) ;
styleSheets = rootElement . Element ( Constants . Packaging . StylesheetsNodeName ) ;
documentSet = rootElement . Element ( Constants . Packaging . DocumentSetNodeName ) ;
documents = rootElement . Element ( Constants . Packaging . DocumentsNodeName ) ;
actions = rootElement . Element ( Constants . Packaging . ActionsNodeName ) ;
metaData = GetMetaData ( rootElement ) ;
installationSummary = new InstallationSummary { MetaData = metaData } ;
}
catch ( Exception e )
{
throw new Exception ( "Error reading " + packageFile , e ) ;
}
try
{
var dataTypeDefinitions = EmptyEnumerableIfNull < IDataType > ( dataTypes ) ? ? InstallDataTypes ( dataTypes , userId ) ;
installationSummary . DataTypesInstalled = dataTypeDefinitions ;
var languagesInstalled = EmptyEnumerableIfNull < ILanguage > ( languages ) ? ? InstallLanguages ( languages , userId ) ;
installationSummary . LanguagesInstalled = languagesInstalled ;
var dictionaryInstalled = EmptyEnumerableIfNull < IDictionaryItem > ( dictionaryItems ) ? ? InstallDictionaryItems ( dictionaryItems ) ;
installationSummary . DictionaryItemsInstalled = dictionaryInstalled ;
var macros = EmptyEnumerableIfNull < IMacro > ( macroes ) ? ? InstallMacros ( macroes , userId ) ;
installationSummary . MacrosInstalled = macros ;
var keyValuePairs = EmptyEnumerableIfNull < string > ( packageFile ) ? ? InstallFiles ( packageFile , files ) ;
installationSummary . FilesInstalled = keyValuePairs ;
var templatesInstalled = EmptyEnumerableIfNull < ITemplate > ( templates ) ? ? InstallTemplats ( templates , userId ) ;
installationSummary . TemplatesInstalled = templatesInstalled ;
var documentTypesInstalled = EmptyEnumerableIfNull < IContentType > ( documentTypes ) ? ? InstallDocumentTypes ( documentTypes , userId ) ;
installationSummary . ContentTypesInstalled = documentTypesInstalled ;
var stylesheetsInstalled = EmptyEnumerableIfNull < IFile > ( styleSheets ) ? ? InstallStylesheets ( styleSheets ) ;
installationSummary . StylesheetsInstalled = stylesheetsInstalled ;
var documentsInstalled = documents ! = null ? InstallDocuments ( documents , userId )
: EmptyEnumerableIfNull < IContent > ( documentSet )
? ? InstallDocuments ( documentSet , userId ) ;
installationSummary . ContentInstalled = documentsInstalled ;
var packageActions = EmptyEnumerableIfNull < PackageAction > ( actions ) ? ? GetPackageActions ( actions , metaData . Name ) ;
installationSummary . Actions = packageActions ;
installationSummary . PackageInstalled = true ;
return installationSummary ;
}
catch ( Exception e )
{
throw new Exception ( "Error installing package " + packageFile , e ) ;
}
}
/// <summary>
/// Temperary check to test that we support stylesheets
/// </summary>
/// <param name="rootElement"></param>
private void PackageSupportedCheck ( XElement rootElement )
{
XElement styleSheets = rootElement . Element ( Constants . Packaging . StylesheetsNodeName ) ;
if ( styleSheets ! = null & & styleSheets . Elements ( ) . Any ( ) )
throw new NotSupportedException ( "Stylesheets is not suported in this version of umbraco" ) ;
}
private static T [ ] EmptyArrayIfNull < T > ( object obj )
{
return obj = = null ? new T [ 0 ] : null ;
}
private static IEnumerable < T > EmptyEnumerableIfNull < T > ( object obj )
{
return obj = = null ? Enumerable . Empty < T > ( ) : null ;
}
private XDocument GetConfigXmlDoc ( string packageFilePath )
{
var configXmlContent = _packageExtraction . ReadTextFileFromArchive ( packageFilePath ,
Constants . Packaging . PackageXmlFileName , out _ ) ;
return XDocument . Parse ( configXmlContent ) ;
}
public XElement GetConfigXmlElement ( string packageFilePath )
{
XDocument document = GetConfigXmlDoc ( packageFilePath ) ;
if ( document . Root = = null | |
document . Root . Name . LocalName . Equals ( Constants . Packaging . UmbPackageNodeName ) = = false )
{
throw new ArgumentException ( "xml does not have a root node called \"umbPackage\"" , packageFilePath ) ;
}
return document . Root ;
}
internal void PackageStructureSanetyCheck ( string packageFilePath )
{
XElement rootElement = GetConfigXmlElement ( packageFilePath ) ;
PackageStructureSanetyCheck ( packageFilePath , rootElement ) ;
}
private void PackageStructureSanetyCheck ( string packageFilePath , XElement rootElement )
{
XElement filesElement = rootElement . Element ( Constants . Packaging . FilesNodeName ) ;
if ( filesElement ! = null )
{
var sourceDestination = ExtractSourceDestinationFileInformation ( filesElement ) . ToArray ( ) ;
var missingFiles = _packageExtraction . FindMissingFiles ( packageFilePath , sourceDestination . Select ( i = > i . Key ) ) . ToArray ( ) ;
if ( missingFiles . Any ( ) )
{
throw new Exception ( "The following file(s) are missing in the package: " +
string . Join ( ", " , missingFiles . Select (
mf = >
{
var sd = sourceDestination . Single ( fi = > fi . Key = = mf ) ;
return string . Format ( "source: \"{0}\" destination: \"{1}\"" ,
sd . Key , sd . Value ) ;
} ) ) ) ;
}
IEnumerable < string > dubletFileNames = _packageExtraction . FindDubletFileNames ( packageFilePath ) . ToArray ( ) ;
if ( dubletFileNames . Any ( ) )
{
throw new Exception ( "The following filename(s) are found more than one time in the package, since the filename is used ad primary key, this is not allowed: " +
string . Join ( ", " , dubletFileNames ) ) ;
}
}
}
private static IEnumerable < PackageAction > GetPackageActions ( XElement actionsElement , string packageName )
{
if ( actionsElement = = null ) { return new PackageAction [ 0 ] ; }
if ( string . Equals ( Constants . Packaging . ActionsNodeName , actionsElement . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . ActionsNodeName + "\" as root" ,
"actionsElement" ) ;
}
return actionsElement . Elements ( Constants . Packaging . ActionNodeName )
. Select ( elemet = >
{
XAttribute aliasAttr = elemet . Attribute ( Constants . Packaging . AliasNodeNameCapital ) ;
if ( aliasAttr = = null )
throw new ArgumentException (
"missing \"" + Constants . Packaging . AliasNodeNameCapital + "\" atribute in alias element" ,
"actionsElement" ) ;
var packageAction = new PackageAction
{
XmlData = elemet ,
Alias = aliasAttr . Value ,
PackageName = packageName ,
} ;
var attr = elemet . Attribute ( Constants . Packaging . RunatNodeAttribute ) ;
if ( attr ! = null & & Enum . TryParse ( attr . Value , true , out ActionRunAt runAt ) ) { packageAction . RunAt = runAt ; }
attr = elemet . Attribute ( Constants . Packaging . UndoNodeAttribute ) ;
if ( attr ! = null & & bool . TryParse ( attr . Value , out var undo ) ) { packageAction . Undo = undo ; }
return packageAction ;
} ) . ToArray ( ) ;
}
private IEnumerable < IContent > InstallDocuments ( XElement documentsElement , int userId = 0 )
{
if ( ( string . Equals ( Constants . Packaging . DocumentSetNodeName , documentsElement . Name . LocalName ) = = false )
& & ( string . Equals ( Constants . Packaging . DocumentsNodeName , documentsElement . Name . LocalName ) = = false ) )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . DocumentsNodeName + "\" as root" ,
"documentsElement" ) ;
}
if ( string . Equals ( Constants . Packaging . DocumentSetNodeName , documentsElement . Name . LocalName ) )
return _packagingService . ImportContent ( documentsElement , - 1 , userId ) ;
return
documentsElement . Elements ( Constants . Packaging . DocumentSetNodeName )
. SelectMany ( documentSetElement = > _packagingService . ImportContent ( documentSetElement , - 1 , userId ) )
. ToArray ( ) ;
}
private IEnumerable < IFile > InstallStylesheets ( XElement styleSheetsElement )
{
if ( string . Equals ( Constants . Packaging . StylesheetsNodeName , styleSheetsElement . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . StylesheetsNodeName + "\" as root" ,
"styleSheetsElement" ) ;
}
// TODO: Call _packagingService when import stylesheets import has been implimentet
if ( styleSheetsElement . HasElements = = false ) { return new List < IFile > ( ) ; }
throw new NotImplementedException ( "The packaging service do not yes have a method for importing stylesheets" ) ;
}
private IEnumerable < IContentType > InstallDocumentTypes ( XElement documentTypes , int userId = 0 )
{
if ( string . Equals ( Constants . Packaging . DocumentTypesNodeName , documentTypes . Name . LocalName ) = = false )
{
if ( string . Equals ( Constants . Packaging . DocumentTypeNodeName , documentTypes . Name . LocalName ) = = false )
throw new ArgumentException (
"Must be \"" + Constants . Packaging . DocumentTypesNodeName + "\" as root" , "documentTypes" ) ;
documentTypes = new XElement ( Constants . Packaging . DocumentTypesNodeName , documentTypes ) ;
}
return _packagingService . ImportContentTypes ( documentTypes , userId ) ;
}
private IEnumerable < ITemplate > InstallTemplats ( XElement templateElement , int userId = 0 )
{
if ( string . Equals ( Constants . Packaging . TemplatesNodeName , templateElement . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . TemplatesNodeName + "\" as root" ,
"templateElement" ) ;
}
return _packagingService . ImportTemplates ( templateElement , userId ) ;
}
private IEnumerable < string > InstallFiles ( string packageFilePath , XElement filesElement )
{
var sourceDestination = ExtractSourceDestinationFileInformation ( filesElement ) ;
sourceDestination = AppendRootToDestination ( FullPathToRoot , sourceDestination ) ;
_packageExtraction . CopyFilesFromArchive ( packageFilePath , sourceDestination ) ;
return sourceDestination . Select ( sd = > sd . Value ) . ToArray ( ) ;
}
private KeyValuePair < string , string > [ ] AppendRootToDestination ( string fullpathToRoot , IEnumerable < KeyValuePair < string , string > > sourceDestination )
{
return
sourceDestination . Select (
sd = > new KeyValuePair < string , string > ( sd . Key , Path . Combine ( fullpathToRoot , sd . Value ) ) ) . ToArray ( ) ;
}
private IEnumerable < IMacro > InstallMacros ( XElement macroElements , int userId = 0 )
{
if ( string . Equals ( Constants . Packaging . MacrosNodeName , macroElements . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . MacrosNodeName + "\" as root" ,
"macroElements" ) ;
}
return _packagingService . ImportMacros ( macroElements , userId ) ;
}
private IEnumerable < IDictionaryItem > InstallDictionaryItems ( XElement dictionaryItemsElement )
{
if ( string . Equals ( Constants . Packaging . DictionaryItemsNodeName , dictionaryItemsElement . Name . LocalName ) = =
false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . DictionaryItemsNodeName + "\" as root" ,
"dictionaryItemsElement" ) ;
}
return _packagingService . ImportDictionaryItems ( dictionaryItemsElement ) ;
}
private IEnumerable < ILanguage > InstallLanguages ( XElement languageElement , int userId = 0 )
{
if ( string . Equals ( Constants . Packaging . LanguagesNodeName , languageElement . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . LanguagesNodeName + "\" as root" , "languageElement" ) ;
}
return _packagingService . ImportLanguages ( languageElement , userId ) ;
}
private IEnumerable < IDataType > InstallDataTypes ( XElement dataTypeElements , int userId = 0 )
{
if ( string . Equals ( Constants . Packaging . DataTypesNodeName , dataTypeElements . Name . LocalName ) = = false )
{
if ( string . Equals ( Constants . Packaging . DataTypeNodeName , dataTypeElements . Name . LocalName ) = = false )
{
throw new ArgumentException ( "Must be \"" + Constants . Packaging . DataTypeNodeName + "\" as root" , "dataTypeElements" ) ;
}
}
return _packagingService . ImportDataTypeDefinitions ( dataTypeElements , userId ) ;
}
private PreInstallWarnings GetPreInstallWarnings ( XElement rootElement )
{
XElement files = rootElement . Element ( Constants . Packaging . FilesNodeName ) ;
XElement styleSheets = rootElement . Element ( Constants . Packaging . StylesheetsNodeName ) ;
XElement templates = rootElement . Element ( Constants . Packaging . TemplatesNodeName ) ;
XElement alias = rootElement . Element ( Constants . Packaging . MacrosNodeName ) ;
var sourceDestination = EmptyArrayIfNull < KeyValuePair < string , string > > ( files ) ? ? ExtractSourceDestinationFileInformation ( files ) ;
var installWarnings = new PreInstallWarnings ( ) ;
var macroAliases = EmptyEnumerableIfNull < IMacro > ( alias ) ? ? ConflictingPackageData . FindConflictingMacros ( alias ) ;
installWarnings . ConflictingMacroAliases = macroAliases ;
var templateAliases = EmptyEnumerableIfNull < ITemplate > ( templates ) ? ? ConflictingPackageData . FindConflictingTemplates ( templates ) ;
installWarnings . ConflictingTemplateAliases = templateAliases ;
var stylesheetNames = EmptyEnumerableIfNull < IFile > ( styleSheets ) ? ? ConflictingPackageData . FindConflictingStylesheets ( styleSheets ) ;
installWarnings . ConflictingStylesheetNames = stylesheetNames ;
installWarnings . UnsecureFiles = FindUnsecureFiles ( sourceDestination ) ;
installWarnings . FilesReplaced = FindFilesToBeReplaced ( sourceDestination ) ;
return installWarnings ;
}
private KeyValuePair < string , string > [ ] FindFilesToBeReplaced ( IEnumerable < KeyValuePair < string , string > > sourceDestination )
{
return sourceDestination . Where ( sd = > File . Exists ( Path . Combine ( FullPathToRoot , sd . Value ) ) ) . ToArray ( ) ;
}
private KeyValuePair < string , string > [ ] FindUnsecureFiles ( IEnumerable < KeyValuePair < string , string > > sourceDestinationPair )
{
return sourceDestinationPair . Where ( sd = > IsFileDestinationUnsecure ( sd . Value ) ) . ToArray ( ) ;
}
private bool IsFileDestinationUnsecure ( string destination )
{
var unsecureDirNames = new [ ] { "bin" , "app_code" } ;
if ( unsecureDirNames . Any ( ud = > destination . StartsWith ( ud , StringComparison . InvariantCultureIgnoreCase ) ) )
return true ;
string extension = Path . GetExtension ( destination ) ;
return extension ! = null & & extension . Equals ( ".dll" , StringComparison . InvariantCultureIgnoreCase ) ;
}
private KeyValuePair < string , string > [ ] ExtractSourceDestinationFileInformation ( XElement filesElement )
{
if ( string . Equals ( Constants . Packaging . FilesNodeName , filesElement . Name . LocalName ) = = false )
{
throw new ArgumentException ( "the root element must be \"Files\"" , "filesElement" ) ;
}
return filesElement . Elements ( Constants . Packaging . FileNodeName )
. Select ( e = >
{
XElement guidElement = e . Element ( Constants . Packaging . GuidNodeName ) ;
if ( guidElement = = null )
{
throw new ArgumentException ( "Missing element \"" + Constants . Packaging . GuidNodeName + "\"" ,
"filesElement" ) ;
}
XElement orgPathElement = e . Element ( Constants . Packaging . OrgPathNodeName ) ;
if ( orgPathElement = = null )
{
throw new ArgumentException ( "Missing element \"" + Constants . Packaging . OrgPathNodeName + "\"" ,
"filesElement" ) ;
}
XElement orgNameElement = e . Element ( Constants . Packaging . OrgNameNodeName ) ;
if ( orgNameElement = = null )
{
throw new ArgumentException ( "Missing element \"" + Constants . Packaging . OrgNameNodeName + "\"" ,
"filesElement" ) ;
}
var fileName = PrepareAsFilePathElement ( orgNameElement . Value ) ;
var relativeDir = UpdatePathPlaceholders ( PrepareAsFilePathElement ( orgPathElement . Value ) ) ;
var relativePath = Path . Combine ( relativeDir , fileName ) ;
return new KeyValuePair < string , string > ( guidElement . Value , relativePath ) ;
} ) . ToArray ( ) ;
}
private static string PrepareAsFilePathElement ( string pathElement )
{
return pathElement . TrimStart ( new [ ] { '\\' , '/' , '~' } ) . Replace ( "/" , "\\" ) ;
}
private MetaData GetMetaData ( XElement xRootElement )
{
XElement infoElement = xRootElement . Element ( Constants . Packaging . InfoNodeName ) ;
if ( infoElement = = null )
{
throw new ArgumentException ( "Did not hold a \"" + Constants . Packaging . InfoNodeName + "\" element" ,
"xRootElement" ) ;
}
var majorElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageRequirementsMajorXpath ) ;
var minorElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageRequirementsMinorXpath ) ;
var patchElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageRequirementsPatchXpath ) ;
var nameElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageNameXpath ) ;
var versionElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageVersionXpath ) ;
var urlElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageUrlXpath ) ;
var licenseElement = infoElement . XPathSelectElement ( Constants . Packaging . PackageLicenseXpath ) ;
var authorNameElement = infoElement . XPathSelectElement ( Constants . Packaging . AuthorNameXpath ) ;
var authorUrlElement = infoElement . XPathSelectElement ( Constants . Packaging . AuthorWebsiteXpath ) ;
var readmeElement = infoElement . XPathSelectElement ( Constants . Packaging . ReadmeXpath ) ;
XElement controlElement = xRootElement . Element ( Constants . Packaging . ControlNodeName ) ;
return new MetaData
{
Name = StringValue ( nameElement ) ,
Version = StringValue ( versionElement ) ,
Url = StringValue ( urlElement ) ,
License = StringValue ( licenseElement ) ,
LicenseUrl = StringAttribute ( licenseElement , Constants . Packaging . PackageLicenseXpathUrlAttribute ) ,
AuthorName = StringValue ( authorNameElement ) ,
AuthorUrl = StringValue ( authorUrlElement ) ,
Readme = StringValue ( readmeElement ) ,
Control = StringValue ( controlElement ) ,
ReqMajor = IntValue ( majorElement ) ,
ReqMinor = IntValue ( minorElement ) ,
ReqPatch = IntValue ( patchElement )
} ;
}
private static string StringValue ( XElement xElement , string defaultValue = "" )
{
return xElement = = null ? defaultValue : xElement . Value ;
}
private static string StringAttribute ( XElement xElement , string attribute , string defaultValue = "" )
{
return xElement = = null
? defaultValue
: xElement . HasAttributes ? xElement . AttributeValue < string > ( attribute ) : defaultValue ;
}
private static int IntValue ( XElement xElement , int defaultValue = 0 )
{
return xElement = = null ? defaultValue : int . TryParse ( xElement . Value , out var val ) ? val : defaultValue ;
}
private static string UpdatePathPlaceholders ( string path )
{
if ( path . Contains ( "[$" ) )
{
//this is experimental and undocumented...
path = path . Replace ( "[$UMBRACO]" , SystemDirectories . Umbraco ) ;
path = path . Replace ( "[$UMBRACOCLIENT]" , SystemDirectories . UmbracoClient ) ;
path = path . Replace ( "[$CONFIG]" , SystemDirectories . Config ) ;
path = path . Replace ( "[$DATA]" , SystemDirectories . Data ) ;
}
return path ;
}
}
}