// Full fidelity read-write public static CanvasDocument LoadFromSource(string directory2, ErrorContainer errors) { if (File.Exists(directory2)) { if (directory2.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException($"Must point to a source directory, not an msapp file ({directory2}"); } } if (!Directory.Exists(directory2)) { throw new InvalidOperationException($"No directory {directory2}"); } var dir = new DirectoryReader(directory2); var app = new CanvasDocument(); // Do the manifest check (and version check) first. // MAnifest lives in top-level directory. foreach (var file in dir.EnumerateFiles("", "*.json")) { switch (file.Kind) { case FileKind.CanvasManifest: var manifest = file.ToObject <CanvasManifestJson>(); if (manifest.FormatVersion != CurrentSourceVersion) { errors.FormatNotSupported($"This tool only supports {CurrentSourceVersion}, the manifest version is {manifest.FormatVersion}"); throw new DocumentException(); } app._properties = manifest.Properties; app._header = manifest.Header; app._publishInfo = manifest.PublishInfo; app._screenOrder = manifest.ScreenOrder; break; case FileKind.Templates: foreach (var kvp in file.ToObject <Dictionary <string, CombinedTemplateState> >()) { app._templateStore.AddTemplate(kvp.Key, kvp.Value); } break; case FileKind.ComponentReferences: var refs = file.ToObject <ComponentDependencyInfo[]>(); app._libraryReferences = refs; break; } } if (app._header == null) { // Manifest not found. errors.FormatNotSupported($"Can't find CanvasManifest.json file - is sources an old version?"); throw new DocumentException(); } // Load template files, recreate References/templates.json LoadTemplateFiles(errors, app, Path.Combine(directory2, PackagesDir), out var templateDefaults); foreach (var file in dir.EnumerateFiles(AssetsDir)) { if (file._relativeName == "Resources.json") { app._resourcesJson = file.ToObject <ResourcesJson>(); continue; } app.AddAssetFile(file.ToFileEntry()); } foreach (var file in dir.EnumerateFiles(EntropyDir)) { switch (file.Kind) { case FileKind.Entropy: app._entropy = file.ToObject <Entropy>(); break; case FileKind.AppCheckerResult: app._appCheckerResultJson = file.ToObject <AppCheckerResultJson>(); break; case FileKind.Checksum: app._checksum = file.ToObject <ChecksumJson>(); app._checksum.ClientBuildDetails = _buildVerJson; break; default: errors.GenericWarning($"Unexpected file in Entropy, discarding"); break; } } foreach (var file in dir.EnumerateFiles(OtherDir)) { // Special files like Header / Properties switch (file.Kind) { case FileKind.Unknown: // Track any unrecognized files so we can save back. app.AddFile(file.ToFileEntry()); break; default: // Shouldn't find anything else not unknown in here, but just ignore them for now errors.GenericWarning($"Unexpected file in Other, discarding"); break; } } // each loose file in '\other' app.GetLogoFile(); LoadDataSources(app, dir, errors); LoadSourceFiles(app, dir, templateDefaults, errors); foreach (var file in dir.EnumerateFiles(ConnectionDir)) { // Special files like Header / Properties switch (file.Kind) { case FileKind.Connections: app._connections = file.ToObject <IDictionary <string, ConnectionJson> >(); break; } } // Defaults. // - DynamicTypes.Json, Resources.Json , Templates.Json - could all be empty // - Themes.json- default to app.OnLoadComplete(errors); return(app); }
// Full fidelity read-write public static CanvasDocument LoadFromSource(string directory2, ErrorContainer errors) { if (File.Exists(directory2)) { if (directory2.EndsWith(".msapp", StringComparison.OrdinalIgnoreCase)) { errors.BadParameter($"Must point to a source directory, not an msapp file ({directory2}"); } } Utilities.VerifyDirectoryExists(errors, directory2); if (errors.HasErrors) { return(null); } var dir = new DirectoryReader(directory2); var app = new CanvasDocument(); string appInsightsInstumentationKey = null; // Do the manifest check (and version check) first. // MAnifest lives in top-level directory. foreach (var file in dir.EnumerateFiles("", "*.json")) { switch (file.Kind) { case FileKind.CanvasManifest: var manifest = file.ToObject <CanvasManifestJson>(); if (manifest.FormatVersion != CurrentSourceVersion) { errors.FormatNotSupported($"This tool only supports {CurrentSourceVersion}, the manifest version is {manifest.FormatVersion}"); throw new DocumentException(); } app._properties = manifest.Properties; app._header = manifest.Header; app._publishInfo = manifest.PublishInfo; app._screenOrder = manifest.ScreenOrder; break; case FileKind.Templates: foreach (var kvp in file.ToObject <Dictionary <string, CombinedTemplateState> >()) { app._templateStore.AddTemplate(kvp.Key, kvp.Value); } break; case FileKind.ComponentReferences: var refs = file.ToObject <ComponentDependencyInfo[]>(); app._libraryReferences = refs; break; case FileKind.AppInsightsKey: var appInsights = file.ToObject <AppInsightsKeyJson>(); appInsightsInstumentationKey = appInsights.InstrumentationKey; break; } } if (appInsightsInstumentationKey != null) { app._properties.InstrumentationKey = appInsightsInstumentationKey; } if (app._header == null) { // Manifest not found. errors.FormatNotSupported($"Can't find CanvasManifest.json file - is sources an old version?"); throw new DocumentException(); } // Load template files, recreate References/templates.json LoadTemplateFiles(errors, app, Path.Combine(directory2, PackagesDir), out var templateDefaults); foreach (var file in dir.EnumerateFiles(EntropyDir)) { switch (file.Kind) { case FileKind.Entropy: app._entropy = file.ToObject <Entropy>(); break; case FileKind.AppCheckerResult: app._appCheckerResultJson = file.ToObject <AppCheckerResultJson>(); break; case FileKind.Checksum: app._checksum = file.ToObject <ChecksumJson>(); app._checksum.ClientBuildDetails = _buildVerJson; break; default: errors.GenericWarning($"Unexpected file in Entropy, discarding"); break; } } // The resource entries for sample data is sharded into individual json files. // Add each of these entries back into Resrouces.json var resources = new List <ResourceJson>(); app._resourcesJson = new ResourcesJson() { Resources = new ResourceJson[0] }; foreach (var file in dir.EnumerateFiles(AssetsDir, "*", false)) { var fileEntry = file.ToFileEntry(); if (fileEntry.Name.GetExtension() == ".json") { // If its a json file then this must be one of the sharded files from Resources.json resources.Add(file.ToObject <ResourceJson>()); } } // Add the resources from sharded files to _resourcesJson.Resources if (resources.Count > 0) { app._resourcesJson.Resources = resources.ToArray(); } // We have processed all the json files in Assets directory, now interate through all tge files to add the asset files. foreach (var file in dir.EnumerateFiles(AssetsDir)) { // Skip adding the json files which were created to contain the information for duplicate asset files. // The name of the such json files is of the format - <assetFileName>.<assetFileExtension>.json (eg. close_1.jpg.json) var fileName = file._relativeName; var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName); // Check if the original extension was .json and the remaining file name has still got an extension, // Then this is an additional file that was created to contain information for duplicate assets. if (Path.HasExtension(fileNameWithoutExtension) && Path.GetExtension(fileName) == ".json") { var localAssetInfoJson = file.ToObject <LocalAssetInfoJson>(); app._localAssetInfoJson.Add(localAssetInfoJson.NewFileName, localAssetInfoJson); } // Add non json files to _assetFiles else if (Path.GetExtension(fileName) != ".json") { app.AddAssetFile(file.ToFileEntry()); } } app.GetLogoFile(); // Add the entries for local assets back to resrouces.json TranformResourceJson.AddLocalAssetEntriesToResourceJson(app); foreach (var file in dir.EnumerateFiles(OtherDir)) { // Special files like Header / Properties switch (file.Kind) { case FileKind.Unknown: // Track any unrecognized files so we can save back. app.AddFile(file.ToFileEntry()); break; default: // Shouldn't find anything else not unknown in here, but just ignore them for now errors.GenericWarning($"Unexpected file in Other, discarding"); break; } } // each loose file in '\other' LoadDataSources(app, dir, errors); LoadSourceFiles(app, dir, templateDefaults, errors); foreach (var file in dir.EnumerateFiles(ConnectionDir)) { // Special files like Header / Properties switch (file.Kind) { case FileKind.Connections: app._connections = file.ToObject <IDictionary <string, ConnectionJson> >(); break; } } // Defaults. // - DynamicTypes.Json, Resources.Json , Templates.Json - could all be empty // - Themes.json- default to app.OnLoadComplete(errors); return(app); }
private static void WriteDataSources(DirectoryWriter dir, CanvasDocument app, ErrorContainer errors) { // Data Sources - write out each individual source. HashSet <string> filenames = new HashSet <string>(); foreach (var kvp in app.GetDataSources()) { // Filename doesn't actually matter, but careful to avoid collisions and overwriting. // Also be determinstic. string filename = kvp.Key + ".json"; if (!filenames.Add(filename.ToLower())) { int index = 1; var altFileName = kvp.Key + "_" + index + ".json"; while (!filenames.Add(altFileName.ToLower())) { ++index; } errors.GenericWarning("Data source name collision: " + filename + ", writing as " + altFileName + " to avoid."); filename = altFileName; } var dataSourceStateToWrite = kvp.Value.JsonClone().OrderBy(ds => ds.Name, StringComparer.Ordinal); DataSourceDefinition dataSourceDef = null; // Split out the changeable parts of the data source. foreach (var ds in dataSourceStateToWrite.Where(ds => ds.Type != "ViewInfo")) { // CDS DataSource if (ds.TableDefinition != null) { dataSourceDef = new DataSourceDefinition(); dataSourceDef.TableDefinition = Utility.JsonParse <DataSourceTableDefinition>(ds.TableDefinition); dataSourceDef.DatasetName = ds.DatasetName; dataSourceDef.EntityName = ds.RelatedEntityName ?? ds.Name; ds.DatasetName = null; ds.TableDefinition = null; } // CDP DataSource else if (ds.DataEntityMetadataJson != null) { if (ds.ApiId == "/providers/microsoft.powerapps/apis/shared_commondataservice") { // This is the old CDS connector, we can't support it since it's optionset format is incompatable with the newer one errors.ValidationError($"Connection {ds.Name} is using the old CDS connector which is incompatable with this tool"); throw new DocumentException(); } dataSourceDef = new DataSourceDefinition(); dataSourceDef.DataEntityMetadataJson = ds.DataEntityMetadataJson; dataSourceDef.EntityName = ds.Name; dataSourceDef.TableName = ds.TableName; ds.TableName = null; ds.DataEntityMetadataJson = null; } else if (ds.Type == "OptionSetInfo") { // This looks like a left over from previous versions of studio, account for it by // tracking optionsets with empty dataset names ds.DatasetName = ds.DatasetName == null ? string.Empty : null; } else if (ds.WadlMetadata != null) { // For some reason some connectors have both, investigate if one could be discarded by the server? if (ds.WadlMetadata.WadlXml != null) { dir.WriteAllXML(WadlPackageDir, filename.Replace(".json", ".xml"), ds.WadlMetadata.WadlXml); } if (ds.WadlMetadata.SwaggerJson != null) { dir.WriteAllJson(SwaggerPackageDir, filename, JsonSerializer.Deserialize <SwaggerDefinition>(ds.WadlMetadata.SwaggerJson, Utility._jsonOpts)); } ds.WadlMetadata = null; } } if (dataSourceDef != null) { TrimViewNames(dataSourceStateToWrite, dataSourceDef.DatasetName); } if (dataSourceDef?.DatasetName != null && app._dataSourceReferences.TryGetValue(dataSourceDef.DatasetName, out var referenceJson)) { // copy over the localconnectionreference if (referenceJson.dataSources.TryGetValue(dataSourceDef.EntityName, out var dsRef)) { dataSourceDef.LocalReferenceDSJson = dsRef; } dataSourceDef.InstanceUrl = referenceJson.instanceUrl; dataSourceDef.ExtensionData = referenceJson.ExtensionData; } if (dataSourceDef != null) { dir.WriteAllJson(DataSourcePackageDir, filename, dataSourceDef); } dir.WriteAllJson(DataSourcesDir, filename, dataSourceStateToWrite); } }
internal void StabilizeAssetFilePaths(ErrorContainer errors) { _entropy.LocalResourceFileNames.Clear(); // If a name matches caseinsensitive but not casesensitive, it is a candidate for rename var caseInsensitiveNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var caseSensitiveNames = new HashSet<string>(StringComparer.Ordinal); foreach (var resource in _resourcesJson.Resources.OrderBy(resource => resource.Name, StringComparer.Ordinal)) { if (resource.ResourceKind != ResourceKind.LocalFile) continue; if (caseInsensitiveNames.Add(resource.Name)) { caseSensitiveNames.Add(resource.Name); } } // Update AssetFile paths foreach (var resource in _resourcesJson.Resources.OrderBy(resource => resource.Name, StringComparer.Ordinal)) { if (resource.ResourceKind != ResourceKind.LocalFile) continue; resource.OriginalName = resource.Name; var assetFilePath = GetAssetFilePathWithoutPrefix(resource.Path); if (!_assetFiles.TryGetValue(assetFilePath, out var fileEntry)) continue; if (!caseSensitiveNames.Contains(resource.Name) && caseInsensitiveNames.Contains(resource.Name)) { int i = 1; var newResourceName = resource.Name + '_' + i; while (caseInsensitiveNames.Contains(newResourceName)) { ++i; newResourceName = resource.Name + '_' + i; } resource.OriginalName = resource.Name; resource.Name = newResourceName; caseInsensitiveNames.Add(resource.Name); caseSensitiveNames.Add(resource.Name); var colliding = _entropy.LocalResourceFileNames.Keys.First(key => string.Equals(key, resource.OriginalName, StringComparison.OrdinalIgnoreCase)); errors.GenericWarning($"Asset named {resource.OriginalName} collides with {colliding}, unpacking as {resource.Name}"); } var extension = assetFilePath.GetExtension(); var newFileName = resource.Name + extension; _entropy.LocalResourceFileNames.Add(resource.Name, resource.FileName); var updatedPath = FilePath.FromMsAppPath(Utilities.GetResourceRelativePath(resource.Content)).Append(newFileName); resource.Path = updatedPath.ToMsAppPath(); resource.FileName = newFileName; var withoutPrefix = GetAssetFilePathWithoutPrefix(resource.Path); fileEntry.Name = withoutPrefix; _assetFiles.Remove(assetFilePath); _assetFiles.Add(withoutPrefix, fileEntry); } }