示例#1
0
        public static CanvasDocument Load(Stream streamToMsapp, ErrorContainer errors)
        {
            if (streamToMsapp == null)
            {
                throw new ArgumentNullException(nameof(streamToMsapp));
            }

            // Read raw files.
            // Apply transforms.
            var app = new CanvasDocument();

            app._checksum         = new ChecksumJson(); // default empty. Will get overwritten if the file is present.
            app._templateStore    = new EditorState.TemplateStore();
            app._editorStateStore = new EditorState.EditorStateStore();

            ComponentsMetadataJson     componentsMetadata = null;
            DataComponentTemplatesJson dctemplate         = null;
            DataComponentSourcesJson   dcsources          = null;

            ChecksumMaker checksumMaker = new ChecksumMaker();
            // key = screen, value = index
            var screenOrder = new Dictionary <string, double>();

            ZipArchive zipOpen;

            try
            {
                zipOpen = new ZipArchive(streamToMsapp, ZipArchiveMode.Read);
            }
            catch (Exception e)
            {
                // Catch cases where stream is corrupted, can't be read, or unavailable.
                errors.MsAppFormatError(e.Message);
                return(null);
            }

            using (var z = zipOpen)
            {
                foreach (var entry in z.Entries)
                {
                    checksumMaker.AddFile(entry.FullName, entry.ToBytes());

                    var fullName = entry.FullName;
                    var kind     = FileEntry.TriageKind(FilePath.FromMsAppPath(fullName));

                    switch (kind)
                    {
                    default:
                        // Track any unrecognized files so we can save back.
                        app.AddFile(FileEntry.FromZip(entry));
                        break;

                    case FileKind.Resources:
                        app._resourcesJson = ToObject <ResourcesJson>(entry);
                        foreach (var resource in app._resourcesJson.Resources)
                        {
                            if (resource.ResourceKind == "LocalFile")
                            {
                                app._entropy.LocalResourceRootPaths.Add(resource.Name, resource.RootPath);
                                resource.RootPath = null;
                            }
                        }

                        break;

                    case FileKind.Asset:
                        app.AddAssetFile(FileEntry.FromZip(entry, name: fullName.Substring("Assets\\".Length)));
                        break;

                    case FileKind.Checksum:
                        app._checksum = ToObject <ChecksumJson>(entry);
                        break;

                    case FileKind.OldEntityJSon:
                        errors.FormatNotSupported($"This is using an older v1 msapp format that is not supported.");
                        throw new DocumentException();

                    case FileKind.DataComponentTemplates:
                        dctemplate = ToObject <DataComponentTemplatesJson>(entry);
                        break;

                    case FileKind.ComponentsMetadata:
                        componentsMetadata = ToObject <ComponentsMetadataJson>(entry);
                        break;

                    case FileKind.DataComponentSources:
                        dcsources = ToObject <DataComponentSourcesJson>(entry);
                        break;

                    case FileKind.Properties:
                        app._properties = ToObject <DocumentPropertiesJson>(entry);
                        break;

                    case FileKind.Themes:
                        app._themes = ToObject <ThemesJson>(entry);
                        break;

                    case FileKind.Header:
                        app._header = ToObject <HeaderJson>(entry);
                        app._entropy.SetHeaderLastSaved(app._header.LastSavedDateTimeUTC);
                        app._header.LastSavedDateTimeUTC = null;
                        break;

                    case FileKind.PublishInfo:
                        app._publishInfo = ToObject <PublishInfoJson>(entry);
                        break;

                    case FileKind.AppCheckerResult:
                        app._appCheckerResultJson = ToObject <AppCheckerResultJson>(entry);
                        break;

                    case FileKind.ComponentSrc:
                    case FileKind.ControlSrc:
                    case FileKind.TestSrc:
                    {
                        var control = ToObject <ControlInfoJson>(entry);
                        var sf      = SourceFile.New(control);
                        // Add to screen order, only screens have meaningful indices, components may have collisions
                        if (!ExcludeControlFromScreenOrdering(sf))
                        {
                            screenOrder.Add(control.TopParent.Name, control.TopParent.Index);
                        }
                        var flattenedControlTree = sf.Flatten();

                        foreach (var ctrl in flattenedControlTree)
                        {
                            // Add PublishOrderIndex to Entropy so it doesn't affect the editorstate diff.
                            app._entropy.PublishOrderIndices.Add(ctrl.Name, ctrl.PublishOrderIndex);

                            // For component instances, also track their index in Entropy
                            if (ctrl.Index == 0.0 || ctrl.Template.Id == "http://microsoft.com/appmagic/screen")
                            {
                                continue;
                            }
                            app._entropy.ComponentIndexes.Add(ctrl.Name, ctrl.Index);
                        }

                        IRStateHelpers.SplitIRAndState(sf, app._editorStateStore, app._templateStore, app._entropy, out var controlIR);
                        if (kind == FileKind.ComponentSrc)
                        {
                            app._components.Add(sf.ControlName, controlIR);
                        }
                        else
                        {
                            app._screens.Add(sf.ControlName, controlIR);
                        }
                    }
                    break;


                    case FileKind.DataSources:
                    {
                        var dataSources = ToObject <DataSourcesJson>(entry);
                        Utilities.EnsureNoExtraData(dataSources.ExtensionData);

                        int iOrder = 0;
                        foreach (var ds in dataSources.DataSources)
                        {
                            app.AddDataSourceForLoad(ds, iOrder);
                            iOrder++;
                        }
                    }
                    break;

                    case FileKind.Templates:
                    {
                        app._templates = ToObject <TemplatesJson>(entry);
                        int iOrder = 0;
                        foreach (var template in app._templates.UsedTemplates)
                        {
                            app._entropy.Add(template, iOrder);
                            iOrder++;
                        }
                        iOrder = 0;
                        foreach (var template in app._templates.ComponentTemplates ?? Enumerable.Empty <TemplateMetadataJson>())
                        {
                            app._entropy.AddComponent(template, iOrder);
                            iOrder++;
                        }
                    }
                    break;
                    }
                } // foreach zip entry

                foreach (var componentTemplate in app._templates.ComponentTemplates ?? Enumerable.Empty <TemplateMetadataJson>())
                {
                    if (!app._templateStore.TryGetTemplate(componentTemplate.Name, out var template))
                    {
                        continue;
                    }
                    template.TemplateOriginalName            = componentTemplate.OriginalName;
                    template.IsComponentLocked               = componentTemplate.IsComponentLocked;
                    template.ComponentChangedSinceFileImport = componentTemplate.ComponentChangedSinceFileImport;
                    template.ComponentAllowCustomization     = componentTemplate.ComponentAllowCustomization;
                    template.ComponentExtraMetadata          = componentTemplate.ExtensionData;

                    if (template.Version != componentTemplate.Version)
                    {
                        app._entropy.SetTemplateVersion(template.Name, componentTemplate.Version);
                    }
                }

                app._screenOrder = screenOrder.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key).ToList();

                // Checksums?
                var currentChecksum = checksumMaker.GetChecksum();

                // This is debug only. The server checksum is out of date with the client checksum
                // The main checksum validation that matters is the repack after unpack
#if DEBUG
                if (app._checksum.ServerStampedChecksum != null && app._checksum.ServerStampedChecksum != currentChecksum.wholeChecksum)
                {
                    // The server checksum doesn't match the actual contents.
                    // likely has been tampered.
                    errors.ChecksumMismatch("Checksum doesn't match on extract.");
                    if (app._checksum.ServerPerFileChecksums != null)
                    {
                        foreach (var file in app._checksum.ServerPerFileChecksums)
                        {
                            if (!currentChecksum.perFileChecksum.TryGetValue(file.Key, out var fileChecksum))
                            {
                                errors.ChecksumMismatch("Missing file " + file.Key);
                            }
                            if (fileChecksum != file.Value)
                            {
                                errors.ChecksumMismatch($"File {file.Key} checksum does not match on extract");
                            }
                        }
                        foreach (var file in currentChecksum.perFileChecksum)
                        {
                            if (!app._checksum.ServerPerFileChecksums.ContainsKey(file.Key))
                            {
                                errors.ChecksumMismatch("Extra file " + file.Key);
                            }
                        }
                    }
                }
#endif

                app._checksum.ClientStampedChecksum  = currentChecksum.wholeChecksum;
                app._checksum.ClientPerFileChecksums = currentChecksum.perFileChecksum;
                // Normalize logo filename.
                app.TranformLogoOnLoad();

                if (app._properties.LibraryDependencies != null)
                {
                    var refs = Utilities.JsonParse <ComponentDependencyInfo[]>(app._properties.LibraryDependencies);
                    app._libraryReferences = refs;
                    app._properties.LibraryDependencies = null;
                }

                if (app._properties.LocalConnectionReferences != null)
                {
                    var cxs = Utilities.JsonParse <IDictionary <String, ConnectionJson> >(app._properties.LocalConnectionReferences);
                    app._connections = cxs;
                    app._properties.LocalConnectionReferences = null;
                }

                if (!string.IsNullOrEmpty(app._properties.LocalDatabaseReferences))
                {
                    var dsrs = Utilities.JsonParse <IDictionary <String, LocalDatabaseReferenceJson> >(app._properties.LocalDatabaseReferences);
                    app._dataSourceReferences = dsrs;
                    app._properties.LocalDatabaseReferences     = null;
                    app._entropy.LocalDatabaseReferencesAsEmpty = false;
                }
                else
                {
                    app._entropy.LocalDatabaseReferencesAsEmpty = true;
                }


                if (componentsMetadata?.Components != null)
                {
                    int order = 0;
                    foreach (var x in componentsMetadata.Components)
                    {
                        var manifest = ComponentManifest.Create(x);
                        if (!app._templateStore.TryGetTemplate(x.TemplateName, out var templateState))
                        {
                            errors.FormatNotSupported("Component Metadata contains template not present in the app");
                            throw new DocumentException();
                        }

                        templateState.ComponentManifest = manifest;
                        app._entropy.Add(x, order);
                        order++;
                    }
                }

                // Only for data-compoents.
                if (dctemplate?.ComponentTemplates != null)
                {
                    int order = 0;
                    foreach (var x in dctemplate.ComponentTemplates)
                    {
                        if (x.ComponentType == null)
                        {
                            errors.FormatNotSupported($"Data component {x.Name} is using an outdated format");
                            throw new DocumentException();
                        }

                        if (!app._templateStore.TryGetTemplate(x.Name, out var templateState))
                        {
                            errors.FormatNotSupported("Component Metadata contains template not present in the app");
                            throw new DocumentException();
                        }

                        ComponentManifest manifest = templateState.ComponentManifest; // Should already exist
                        app._entropy.SetTemplateVersion(x.Name, x.Version);
                        app._entropy.Add(x, order);
                        manifest.Apply(x);
                        order++;
                    }
                }

                if (dcsources?.DataSources != null)
                {
                    // Component Data sources only appear if the data component is actually
                    // used as a data source in this app.
                    foreach (var x in dcsources.DataSources)
                    {
                        if (x.Type != DataComponentSourcesJson.NativeCDSDataSourceInfo)
                        {
                            throw new NotImplementedException(x.Type);
                        }

                        var ds = new DataSourceEntry
                        {
                            Name = x.Name,
                            DataComponentDetails = x, // pass in all details for full-fidelity
                            Type = DataSourceModel.DataComponentType
                        };

                        app.AddDataSourceForLoad(ds);
                    }
                }
            }

            app.ApplyAfterMsAppLoadTransforms(errors);
            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))
                {
                    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);
        }