Browse Source

New extension format .pixiext

flabbet 1 year ago
parent
commit
fd20e151d6

+ 2 - 2
samples/HelloWorld/HelloWorld.csproj

@@ -12,10 +12,10 @@
     <ItemGroup>
         <ProjectReference Include="..\..\src\PixiEditor.Extensions.Wasm\PixiEditor.Extensions.Wasm.csproj" />
     </ItemGroup>
-
+    
     <!--Below is not required if you use Nuget package, this sample references project directly, so it must be here-->
-    <Import Project="..\..\src\PixiEditor.Extensions.Wasm\build\PixiEditor.Extensions.Wasm.targets"/>
     <Import Project="..\..\src\PixiEditor.Extensions.Wasm\build\PixiEditor.Extensions.Wasm.props"/>
+    <Import Project="..\..\src\PixiEditor.Extensions.Wasm\build\PixiEditor.Extensions.Wasm.targets" />
 
     <ItemGroup>
         <None Remove="extension.json" />

+ 1 - 1
src/PixiEditor.AvaloniaUI/Initialization/ClassicDesktopEntry.cs

@@ -91,7 +91,7 @@ internal class ClassicDesktopEntry
 
         InitPlatform();
 
-        ExtensionLoader extensionLoader = new ExtensionLoader(Paths.ExtensionsFullPath);
+        ExtensionLoader extensionLoader = new ExtensionLoader(Paths.ExtensionPackagesPath, Paths.UserExtensionsPath);
         //TODO: fetch from extension store
         extensionLoader.AddOfficialExtension("pixieditor.supporterpack",
             new OfficialExtensionData("supporter-pack.snk", AdditionalContentProduct.SupporterPack));

+ 4 - 2
src/PixiEditor.AvaloniaUI/Models/IO/Paths.cs

@@ -7,10 +7,12 @@ public static class Paths
     public static string DataResourceUri { get; } = $"avares://{typeof(Paths).Assembly.GetName().Name}/Data/";
     public static string DataFullPath { get; } = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Data");
 
-    public static string ExtensionsFullPath { get; } = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Extensions");
+    public static string ExtensionPackagesPath { get; } = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "Extensions");
+    public static string UserExtensionsPath { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 
+        "PixiEditor", "Extensions");
 
     public static string PathToPalettesFolder { get; } = Path.Join(
-        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
         "PixiEditor", "Palettes");
 
     public static string InternalResourceDataPath { get; } =

+ 1 - 5
src/PixiEditor.Extensions.MSPackageBuilder/BuildPackageTask.cs

@@ -12,13 +12,9 @@ public class BuildPackageTask : Microsoft.Build.Utilities.Task
     
     public override bool Execute()
     {
-        string absoluteBuildResultDirectory = Path.GetFullPath(BuildResultDirectory);
-        string absoluteTargetDirectory = Path.GetFullPath(TargetDirectory);
-
         try
         {
-            PackageBuilder.Build(absoluteBuildResultDirectory, absoluteTargetDirectory,
-                (message) => Log.LogMessage(message, MessageImportance.Normal));
+            PackageBuilder.Build(BuildResultDirectory, TargetDirectory);
         }
         catch (Exception e)
         {

+ 30 - 12
src/PixiEditor.Extensions.MSPackageBuilder/PackageBuilder.cs

@@ -1,4 +1,5 @@
 using System.IO.Compression;
+using Newtonsoft.Json;
 
 namespace PixiEditor.Extensions.MSPackageBuilder;
 
@@ -14,8 +15,9 @@ public static class PackageBuilder
         new ElementToInclude("Localization/", false),
     };
     
-    public static void Build(string buildResultDirectory, string targetDirectory, Action<string> log)
+    public static void Build(string buildResultDirectory, string targetDirectory)
     {
+        string packageName = Path.GetFileName(buildResultDirectory);
         if (!Directory.Exists(buildResultDirectory))
         {
             throw new DirectoryNotFoundException($"Directory {buildResultDirectory} does not exist.");
@@ -25,33 +27,43 @@ public static class PackageBuilder
         {
             Directory.CreateDirectory(targetDirectory);
         }
-
+        
+        string targetTmpDirectory = Path.Combine(targetDirectory, "tmp");
+        if (Directory.Exists(targetTmpDirectory))
+        {
+            Directory.Delete(targetTmpDirectory, true);
+        }
+        
+        Directory.CreateDirectory(targetTmpDirectory);
+        
+        if(targetDirectory == buildResultDirectory)
+        {
+            throw new InvalidOperationException("Build result directory and target directory cannot be the same.");
+        }
+        
         foreach (ElementToInclude element in ElementsToInclude)
         {
-            log($"Copying {element.Path}...");
             if (element.Type == ElementToIncludeType.File)
             {
-                CopyFile(element.Path, buildResultDirectory, targetDirectory, element.IsRequired);
+                CopyFile(element.Path, buildResultDirectory, targetTmpDirectory, element.IsRequired);
             }
             else
             {
-                CopyDirectory(element.Path, buildResultDirectory, targetDirectory, element.IsRequired);
+                CopyDirectory(element.Path, buildResultDirectory, targetTmpDirectory, element.IsRequired);
             }
         } 
         
-        log("Copied all elements. Building package...");
+        SimplifiedExtensionMetadata metadata = JsonConvert.DeserializeObject<SimplifiedExtensionMetadata>(File.ReadAllText(Path.Combine(buildResultDirectory, "extension.json")));
         
-        string packagePath = Path.Combine(targetDirectory, "package.pixiext");
+        string packagePath = Path.Combine(targetDirectory, $"{metadata.UniqueName}.pixiext");
         if (File.Exists(packagePath))
         {
             File.Delete(packagePath);
         }
         
-        ZipFile.CreateFromDirectory(targetDirectory, packagePath);
-        
-        log($"Package created at {packagePath}.");
+        ZipFile.CreateFromDirectory(targetTmpDirectory, packagePath);
         
-        Directory.Delete(targetDirectory, true);
+        Directory.Delete(targetTmpDirectory, true);
     }
 
     private static void CopyFile(string elementPath, string buildResultDirectory, string targetDirectory, bool elementIsRequired)
@@ -75,7 +87,8 @@ public static class PackageBuilder
     
     private static void CopyDirectory(string elementPath, string buildResultDirectory, string targetDirectory, bool elementIsRequired)
     {
-        string[] directories = Directory.GetDirectories(buildResultDirectory, elementPath, SearchOption.AllDirectories);
+        string pattern = elementPath.EndsWith("/") ? elementPath.Substring(0, elementPath.Length - 1) : elementPath;
+        string[] directories = Directory.GetDirectories(buildResultDirectory, pattern);
         if (directories.Length == 0)
         {
             if (elementIsRequired)
@@ -117,3 +130,8 @@ enum ElementToIncludeType
     File,
     Directory
 }
+
+class SimplifiedExtensionMetadata
+{
+    public string UniqueName { get; set; }
+}

+ 14 - 13
src/PixiEditor.Extensions.MSPackageBuilder/PixiEditor.Extensions.MSPackageBuilder.csproj

@@ -1,17 +1,18 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-    <PropertyGroup>
-        <TargetFramework>netstandard2.0</TargetFramework>
-        <ImplicitUsings>enable</ImplicitUsings>
-      <OutputType>Library</OutputType>
-      <LangVersion>default</LangVersion>
-      <OutputPath>../PixiEditor.Extensions.Wasm/build/</OutputPath>
-      <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
-      <DebugType>None</DebugType>
-      <DebugType>None</DebugType>
-    </PropertyGroup>
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <OutputType>Library</OutputType>
+    <LangVersion>default</LangVersion>
+    <OutputPath>../PixiEditor.Extensions.Wasm/build/</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
+    <DebugType>None</DebugType>
+  </PropertyGroup>
 
-    <ItemGroup>
-      <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.9.5" ExcludeAssets="Runtime" />
-    </ItemGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.9.5" ExcludeAssets="Runtime"/>
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
+  </ItemGroup>
 </Project>

+ 105 - 14
src/PixiEditor.Extensions.Runtime/ExtensionLoader.cs

@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.IO.Compression;
+using System.Reflection;
 using System.Runtime.InteropServices;
 using Newtonsoft.Json;
 using PixiEditor.Extensions.Metadata;
@@ -12,13 +13,15 @@ public class ExtensionLoader
     private readonly Dictionary<string, OfficialExtensionData> _officialExtensionsKeys = new Dictionary<string, OfficialExtensionData>();
     public List<Extension> LoadedExtensions { get; } = new();
     
-    public string ExtensionsFullPath { get; }
+    public string PackagesPath { get; }
+    public string UnpackedExtensionsPath { get; }
 
     private WasmRuntime.WasmRuntime _wasmRuntime = new WasmRuntime.WasmRuntime();
 
-    public ExtensionLoader(string extensionsFullPath)
+    public ExtensionLoader(string packagesPath, string unpackedExtensionsPath)
     {
-        ExtensionsFullPath = extensionsFullPath;
+        PackagesPath = packagesPath;
+        UnpackedExtensionsPath = unpackedExtensionsPath;
         ValidateExtensionFolder();
     }
     
@@ -29,14 +32,15 @@ public class ExtensionLoader
 
     public void LoadExtensions()
     {
-        var directories = Directory.GetDirectories(ExtensionsFullPath);
+        var directories = Directory.GetDirectories(PackagesPath);
         foreach (var directory in directories)
         {
-            string packageJsonPath = Path.Combine(directory, "extension.json");
-            bool isExtension = File.Exists(packageJsonPath);
-            if (isExtension)
+            foreach (var file in Directory.GetFiles(directory))
             {
-                LoadExtension(packageJsonPath);
+                if (file.EndsWith(".pixiext"))
+                {
+                    LoadExtension(file);
+                }
             }
         }
     }
@@ -81,13 +85,95 @@ public class ExtensionLoader
         }
     }
 
-    public Extension? LoadExtension(string packageJsonPath)
+    public Extension? LoadExtension(string extension)
+    {
+        var extZip = ZipFile.OpenRead(extension);
+        ExtensionMetadata metadata = ExtractMetadata(extZip);
+        if(IsDifferentThanCached(metadata, extension))
+        {
+            UnpackExtension(extZip, metadata);
+        }
+        
+        string extensionJson = Path.Combine(UnpackedExtensionsPath, metadata.UniqueName, "extension.json");
+        if (!File.Exists(extensionJson))
+        {
+            return null;
+        }
+            
+        return LoadExtensionFromCache(extensionJson);
+    }
+
+    public void UnpackExtension(ZipArchive extZip, ExtensionMetadata metadata)
+    {
+        string extensionPath = Path.Combine(UnpackedExtensionsPath, metadata.UniqueName);
+        if (Directory.Exists(extensionPath))
+        {
+            Directory.Delete(extensionPath, true);
+        }
+
+        extZip.ExtractToDirectory(extensionPath);
+    }
+
+    private ExtensionMetadata ExtractMetadata(ZipArchive extZip)
+    {
+        var metadataEntry = extZip.GetEntry("extension.json");
+        if (metadataEntry == null)
+        {
+            throw new FileNotFoundException("Extension metadata not found");
+        }
+
+        using var stream = metadataEntry.Open();
+        using var sr = new StreamReader(stream);
+        using var jsonTextReader = new JsonTextReader(sr);
+        var serializer = new JsonSerializer();
+        return serializer.Deserialize<ExtensionMetadata>(jsonTextReader);
+    }
+    
+    private bool IsDifferentThanCached(ExtensionMetadata metadata, string extension)
+    {
+        string extensionJson = Path.Combine(UnpackedExtensionsPath, metadata.UniqueName, "extension.json");
+        if (!File.Exists(extensionJson))
+        {
+            return true;
+        }
+
+        string json = File.ReadAllText(extensionJson);
+        ExtensionMetadata? cachedMetadata = JsonConvert.DeserializeObject<ExtensionMetadata>(json);
+        
+        if(cachedMetadata is null)
+        {
+            return true;
+        }
+        
+        if (metadata.UniqueName != cachedMetadata.UniqueName)
+        {
+            return true;
+        }
+        
+        bool isDifferent = metadata.Version != cachedMetadata.Version;
+        
+        if (isDifferent)
+        {
+            return true;
+        }
+        
+        return PackageWriteTimeIsBigger(Path.Combine(UnpackedExtensionsPath, metadata.UniqueName), extension);
+    }
+
+    private bool PackageWriteTimeIsBigger(string unpackedDirectory, string extension)
     {
-        string json = File.ReadAllText(packageJsonPath);
+        DateTime extensionWriteTime = File.GetLastWriteTime(extension);
+        DateTime unpackedWriteTime = Directory.GetLastWriteTime(unpackedDirectory);
+        return extensionWriteTime > unpackedWriteTime;
+    }
+
+    private Extension LoadExtensionFromCache(string extension)
+    {
+        string json = File.ReadAllText(extension);
         try
         {
             var metadata = JsonConvert.DeserializeObject<ExtensionMetadata>(json);
-            string directory = Path.GetDirectoryName(packageJsonPath);
+            string directory = Path.GetDirectoryName(extension);
             ExtensionEntry? entry = GetEntry(directory);
             if (entry is null)
             {
@@ -303,9 +389,14 @@ public class ExtensionLoader
 
     private void ValidateExtensionFolder()
     {
-        if (!Directory.Exists(ExtensionsFullPath))
+        if (!Directory.Exists(PackagesPath))
+        {
+            Directory.CreateDirectory(PackagesPath);
+        }
+        
+        if (!Directory.Exists(UnpackedExtensionsPath))
         {
-            Directory.CreateDirectory(ExtensionsFullPath);
+            Directory.CreateDirectory(UnpackedExtensionsPath);
         }
     }
 

+ 0 - 3
src/PixiEditor.Extensions.Wasm/PixiEditor.Extensions.Wasm.csproj

@@ -25,9 +25,6 @@
     <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.8.3"/>
   </ItemGroup>
 
-  <Import Project="build\PixiEditor.Extensions.Wasm.targets"/>
-  <Import Project="build\PixiEditor.Extensions.Wasm.props"/>
-
   <Target Name="PackTaskDependencies" BeforeTargets="GenerateNuspec">
     <ItemGroup>
       <_PackageFiles Include="build\**" BuildAction="Content" PackagePath="build"/>

BIN
src/PixiEditor.Extensions.Wasm/build/Newtonsoft.Json.dll


+ 16 - 0
src/PixiEditor.Extensions.Wasm/build/PixiEditor.Extensions.MSPackageBuilder.deps.json

@@ -11,6 +11,7 @@
         "dependencies": {
           "Microsoft.Build.Utilities.Core": "17.9.5",
           "NETStandard.Library": "2.0.3",
+          "Newtonsoft.Json": "13.0.1",
           "StyleCop.Analyzers": "1.1.118"
         },
         "runtime": {
@@ -58,6 +59,14 @@
           "Microsoft.NETCore.Platforms": "1.1.0"
         }
       },
+      "Newtonsoft.Json/13.0.1": {
+        "runtime": {
+          "lib/netstandard2.0/Newtonsoft.Json.dll": {
+            "assemblyVersion": "13.0.0.0",
+            "fileVersion": "13.0.1.25517"
+          }
+        }
+      },
       "StyleCop.Analyzers/1.1.118": {},
       "System.Buffers/4.5.1": {},
       "System.Collections.Immutable/8.0.0": {
@@ -147,6 +156,13 @@
       "path": "netstandard.library/2.0.3",
       "hashPath": "netstandard.library.2.0.3.nupkg.sha512"
     },
+    "Newtonsoft.Json/13.0.1": {
+      "type": "package",
+      "serviceable": true,
+      "sha512": "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==",
+      "path": "newtonsoft.json/13.0.1",
+      "hashPath": "newtonsoft.json.13.0.1.nupkg.sha512"
+    },
     "StyleCop.Analyzers/1.1.118": {
       "type": "package",
       "serviceable": true,

BIN
src/PixiEditor.Extensions.Wasm/build/PixiEditor.Extensions.MSPackageBuilder.dll


+ 6 - 3
src/PixiEditor.Extensions.Wasm/build/PixiEditor.Extensions.Wasm.targets

@@ -14,8 +14,11 @@
     </ItemGroup>
   </Target>
   <UsingTask TaskName="BuildPackageTask"
-             AssemblyFile="$(MSBuildThisFileDirectory)PixiEditor.Extensions.MSPackageBuilder.dll" Condition="'$(RuntimeIdentifier)' == 'wasi-wasm'"/>
-  <Target Name="BuildPackageTask" AfterTargets="Build" Condition="'$(RuntimeIdentifier)' == 'wasi-wasm'">
-    <BuildPackageTask BuildResultDirectory="$(OutputPath)" TargetDirectory="$(OutputPath)" />
+             AssemblyFile="$(MSBuildThisFileDirectory)PixiEditor.Extensions.MSPackageBuilder.dll"
+             Condition="'$(RuntimeIdentifier)' == 'wasi-wasm'"/>
+
+  <Target Name="BuildPackageTask" AfterTargets="_WasiGenerateAppBundle" Condition="'$(RuntimeIdentifier)' == 'wasi-wasm'">
+    <Message Text="Building extension package"/>
+    <BuildPackageTask BuildResultDirectory="$(OutputPath)" TargetDirectory="$(OutputPath).." />
   </Target>
 </Project>