Browse Source

Linux autoupdate wip

flabbet 2 months ago
parent
commit
9a62a1f38f

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit 94048adac23b6e3aeabff09c274624580f010c7f
+Subproject commit e1f7b5c978357058469cae0458211057d9f4cbe8

+ 12 - 2
src/PixiEditor.UpdateInstaller.Exe/PixiEditor.UpdateInstaller.Exe.csproj

@@ -20,13 +20,23 @@
     <ProjectReference Include="..\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller.csproj"/>
   </ItemGroup>
   
-  <Target Name="Rename" AfterTargets="AfterBuild">
+  <Target Name="Rename" AfterTargets="AfterBuild" Condition="'$(OS)' == 'Windows_NT'">
     <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller.exe"/>
     <Message Text="Renamed build executable file." Importance="high"/>
   </Target>
 
-  <Target Name="Rename" AfterTargets="Publish">
+  <Target Name="Rename" AfterTargets="Publish" Condition="'$(OS)' == 'Windows_NT'">
     <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller.exe"/>
     <Message Text="Renamed published executable file." Importance="high"/>
   </Target>
+
+  <Target Name="Rename" AfterTargets="AfterBuild" Condition="'$(OS)' != 'Windows_NT'">
+    <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller"/>
+    <Message Text="Renamed build executable file." Importance="high"/>
+  </Target>
+
+  <Target Name="Rename" AfterTargets="Publish" Condition="'$(OS)' != 'Windows_NT'">
+    <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller"/>
+    <Message Text="Renamed published executable file." Importance="high"/>
+  </Target>
 </Project>

+ 16 - 1
src/PixiEditor.UpdateInstaller.Exe/Program.cs

@@ -40,11 +40,26 @@ finally
 
     if (startAfterUpdate)
     {
-        var files = Directory.GetFiles(controller.UpdateDirectory, "PixiEditor.exe");
+        string binaryName = OperatingSystem.IsWindows() ? "PixiEditor.exe" : "PixiEditor";
+        var files = Directory.GetFiles(controller.UpdateDirectory, binaryName);
         if (files.Length > 0)
         {
             string pixiEditorExecutablePath = files[0];
             Process.Start(pixiEditorExecutablePath);
         }
+        else
+        {
+            binaryName = OperatingSystem.IsWindows() ? "PixiEditor.Desktop.exe" : "PixiEditor.Desktop";
+            files = Directory.GetFiles(controller.UpdateDirectory, binaryName);
+            if (files.Length > 0)
+            {
+                string pixiEditorExecutablePath = files[0];
+                Process.Start(pixiEditorExecutablePath);
+            }
+            else
+            {
+                Console.WriteLine("PixiEditor executable not found.");
+            }
+        }
     }
 }

+ 2 - 1
src/PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller/ViewModels/UpdateController.cs

@@ -29,7 +29,8 @@ public class UpdateController
 
     public void InstallUpdate(StringBuilder log)
     {
-        string[] files = Directory.GetFiles(UpdateDownloader.DownloadLocation, "update-*.zip");
+        string extension = OperatingSystem.IsLinux() ? "tar.gz" : ".zip";
+        string[] files = Directory.GetFiles(UpdateDownloader.DownloadLocation, $"update-*{extension}");
         log.AppendLine($"Found {files.Length} update files.");
 
         if (files.Length > 0)

+ 5 - 5
src/PixiEditor.UpdateModule/UpdateDownloader.cs

@@ -11,9 +11,9 @@ public static class UpdateDownloader
 {
     public static string DownloadLocation { get; } = Path.Join(Path.GetTempPath(), "PixiEditor");
 
-    public static async Task DownloadReleaseZip(ReleaseInfo release)
+    public static async Task DownloadReleaseZip(ReleaseInfo release, string contentType, string extension)
     {
-        Asset? matchingAsset = GetMatchingAsset(release);
+        Asset? matchingAsset = GetMatchingAsset(release, contentType);
 
         if (matchingAsset == null)
         {
@@ -28,7 +28,7 @@ public static class UpdateDownloader
         {
             byte[] bytes = await response.Content.ReadAsByteArrayAsync();
             CreateTempDirectory();
-            await File.WriteAllBytesAsync(Path.Join(DownloadLocation, $"update-{release.TagName}.zip"), bytes);
+            await File.WriteAllBytesAsync(Path.Join(DownloadLocation, $"update-{release.TagName}.{extension}"), bytes);
         }
     }
 
@@ -61,7 +61,7 @@ public static class UpdateDownloader
         }
     }
 
-    private static Asset? GetMatchingAsset(ReleaseInfo release, string assetType = "zip")
+    private static Asset? GetMatchingAsset(ReleaseInfo release, string assetType)
     {
         if (release.TagName.StartsWith("1."))
         {
@@ -70,7 +70,7 @@ public static class UpdateDownloader
                                                       && x.Name.Contains(archOld));
         }
 
-        string arch = "x64";
+        string arch = OperatingSystem.IsWindows() ? "x64" : "amd64";
         string os = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" : "mac";
         return release.Assets.FirstOrDefault(x => x.ContentType.Contains(assetType)
                                                   && x.Name.Contains(arch) && x.Name.Contains(os));

+ 39 - 2
src/PixiEditor.UpdateModule/UpdateInstaller.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Diagnostics;
+using System.Formats.Tar;
 using System.IO;
 using System.IO.Compression;
 using System.Text;
@@ -35,10 +36,46 @@ public class UpdateInstaller
         
         log.AppendLine("Extracting files");
         
-        ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
+        if(Directory.Exists(UpdateFilesPath))
+        {
+            Directory.Delete(UpdateFilesPath, true);
+        }
+        
+        Directory.CreateDirectory(UpdateFilesPath);
         
+        bool isZip = ArchiveFileName.EndsWith(".zip");
+        if (isZip)
+        {
+            ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
+        }
+        else
+        {
+            using FileStream fs = new(ArchiveFileName, FileMode.Open, FileAccess.Read);
+            using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
+
+            TarFile.ExtractToDirectory(gz, UpdateFilesPath, overwriteFiles: false);        
+        }
+
+        string[] extractedFiles = Directory.GetFiles(UpdateFilesPath, "*", SearchOption.AllDirectories);
+        log.AppendLine($"Extracted {extractedFiles.Length} files to {UpdateFilesPath}");
         log.AppendLine("Files extracted");
-        string dirWithFiles = Directory.GetDirectories(UpdateFilesPath)[0];
+
+        string dirWithFiles = UpdateFilesPath;
+        string binName = OperatingSystem.IsWindows() ? "PixiEditor.exe" : "PixiEditor";
+        if (!File.Exists(Path.Combine(UpdateFilesPath, binName)))
+        {
+            dirWithFiles = Directory.GetDirectories(UpdateFilesPath)[0];
+        }
+        
+        string updaterFile = Path.Combine(dirWithFiles, "PixiEditor.UpdateInstaller" + (OperatingSystem.IsWindows() ? ".exe" : ""));
+
+        if (File.Exists(updaterFile))
+        {
+            string newName = Path.Combine(dirWithFiles, "PixiEditor.UpdateInstaller-update" + (OperatingSystem.IsWindows() ? ".exe" : ""));
+            File.Move(updaterFile, newName);
+            log.AppendLine($"Renamed {updaterFile} to {newName}");
+        }
+        
         log.AppendLine($"Copying files from {dirWithFiles} to {TargetDirectory}");
 
         try

+ 5 - 0
src/PixiEditor/Helpers/ProcessHelper.cs

@@ -7,6 +7,11 @@ internal static class ProcessHelper
 {
     public static Process RunAsAdmin(string path, string? args = null)
     {
+        if (IOperatingSystem.Current.IsLinux)
+        {
+            return IOperatingSystem.Current.ProcessUtility.ShellExecute(path, args);
+        }
+        
         return IOperatingSystem.Current.ProcessUtility.RunAsAdmin(path, args);
     }
 

+ 49 - 17
src/PixiEditor/ViewModels/SubViewModels/UpdateViewModel.cs

@@ -24,7 +24,6 @@ namespace PixiEditor.ViewModels.SubViewModels;
 
 internal class UpdateViewModel : SubViewModel<ViewModelMain>
 {
-    public const int MaxRetryCount = 3;
     public UpdateChecker UpdateChecker { get; set; }
 
     public List<UpdateChannel> UpdateChannels { get; } = new List<UpdateChannel>();
@@ -107,9 +106,15 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         get => _updateState == UpdateState.UpToDate;
     }
 
+    public string ZipExtension => IOperatingSystem.Current.IsLinux ? "tar.gz" : "zip";
+    public string ZipContentType => IOperatingSystem.Current.IsLinux ? "octet-stream" : "zip";
+    public string InstallerExtension => IOperatingSystem.Current.IsWindows ? "exe" : "dmg";
+
+    public string BinaryExtension => IOperatingSystem.Current.IsWindows ? ".exe" : string.Empty;
+
     public bool SelfUpdatingAvailable =>
 #if UPDATE
-        PixiEditorSettings.Update.CheckUpdatesOnStartup.Value && OsSupported();
+        PixiEditorSettings.Update.CheckUpdatesOnStartup.Value && OsSupported() && !InstallDirReadOnly();
 #else
         false;
 #endif
@@ -120,6 +125,15 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
     public UpdateViewModel(ViewModelMain owner)
         : base(owner)
     {
+        if (IOperatingSystem.Current.IsLinux)
+        {
+            if(File.Exists("no-updates"))
+            {
+                UpdateState = UpdateState.UnableToCheck;
+                return;
+            }
+        }
+        
         Owner.OnStartupEvent += Owner_OnStartupEvent;
         Owner.OnClose += Owner_OnClose;
         PixiEditorSettings.Update.UpdateChannel.ValueChanged += (_, value) =>
@@ -139,7 +153,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     public async Task CheckForUpdate()
     {
-        if (!IOperatingSystem.Current.IsWindows)
+        if (!OsSupported())
         {
             return;
         }
@@ -157,10 +171,12 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     private void Owner_OnClose()
     {
+#if RELEASE || DEVRELEASE
         if (UpdateState == UpdateState.ReadyToInstall)
         {
             Install(false);
         }
+#endif
     }
 
     public async Task Download()
@@ -182,9 +198,9 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
                 UpdateState = UpdateState.Downloading;
                 if (updateCompatible)
                 {
-                    await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo);
+                    await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo, ZipContentType, ZipExtension);
                 }
-                else
+                else if (IOperatingSystem.Current.IsWindows) // TODO: Add macos
                 {
                     await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
                 }
@@ -209,14 +225,16 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
     private bool AutoUpdateFileExists()
     {
         string path = Path.Join(UpdateDownloader.DownloadLocation,
-            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip");
+            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.{ZipExtension}");
         return File.Exists(path);
     }
 
     private bool UpdateInstallerFileExists()
     {
+        if (IOperatingSystem.Current.IsLinux) return false;
+
         string path = Path.Join(UpdateDownloader.DownloadLocation,
-            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe");
+            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.{InstallerExtension}");
         return File.Exists(path);
     }
 
@@ -228,7 +246,6 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     private void Install(bool startAfterUpdate)
     {
-#if RELEASE || DEVRELEASE
         string dir = AppDomain.CurrentDomain.BaseDirectory;
 
         UpdateDownloader.CreateTempDirectory();
@@ -236,7 +253,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             string.IsNullOrEmpty(UpdateChecker.LatestReleaseInfo.TagName)) return;
         bool updateFileExists = AutoUpdateFileExists();
         string exePath = Path.Join(UpdateDownloader.DownloadLocation,
-            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe");
+            $"update-{UpdateChecker.LatestReleaseInfo.TagName}.{InstallerExtension}");
 
         bool updateExeExists = File.Exists(exePath);
 
@@ -247,7 +264,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             updateExeExists = false;
         }
 
-        string updaterPath = Path.Join(dir, "PixiEditor.UpdateInstaller.exe");
+        string updaterPath = Path.Join(dir, $"PixiEditor.UpdateInstaller{BinaryExtension}");
 
         if (!updateFileExists && !updateExeExists)
         {
@@ -265,7 +282,6 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         {
             OpenExeInstaller(exePath);
         }
-#endif
     }
 
     private static void InstallHeadless(string updaterPath, bool startAfterUpdate)
@@ -283,10 +299,10 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         }
     }
 
-    private static void OpenExeInstaller(string updateExeFile)
+    private void OpenExeInstaller(string updateExeFile)
     {
         bool alreadyUpdated = VersionHelpers.GetCurrentAssemblyVersion().ToString() ==
-                              updateExeFile.Split('-')[1].Split(".exe")[0];
+                              updateExeFile.Split('-')[1].Split("." + InstallerExtension)[0];
 
         if (!alreadyUpdated)
         {
@@ -298,11 +314,26 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         }
     }
 
+    private static bool InstallDirReadOnly()
+    {
+        string installDir = AppDomain.CurrentDomain.BaseDirectory;
+        DirectoryInfo dirInfo = new DirectoryInfo(installDir);
+        return dirInfo.Attributes.HasFlag(FileAttributes.ReadOnly);
+    }
+
     private static void RestartToUpdate(string updateExeFile)
     {
         try
         {
-            IOperatingSystem.Current.ProcessUtility.RunAsAdmin(updateExeFile, null);
+            if (IOperatingSystem.Current.IsLinux)
+            {
+                IOperatingSystem.Current.ProcessUtility.ShellExecute(updateExeFile, null);
+            }
+            else
+            {
+                IOperatingSystem.Current.ProcessUtility.RunAsAdmin(updateExeFile, null);
+            }
+
             Shutdown();
         }
         catch (Win32Exception)
@@ -322,8 +353,9 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
     {
         try
         {
+            string binExtension = IOperatingSystem.Current.IsWindows ? ".exe" : string.Empty;
             ProcessHelper.RunAsAdmin(Path.Join(AppDomain.CurrentDomain.BaseDirectory,
-                "PixiEditor.UpdateInstaller.exe"));
+                $"PixiEditor.UpdateInstaller{binExtension}"));
             Shutdown();
         }
         catch (Win32Exception)
@@ -340,7 +372,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
     [Conditional("UPDATE")]
     private async void ConditionalUPDATE()
     {
-        if (PixiEditorSettings.Update.CheckUpdatesOnStartup.Value && OsSupported())
+        if (PixiEditorSettings.Update.CheckUpdatesOnStartup.Value && OsSupported() && !InstallDirReadOnly())
         {
             try
             {
@@ -374,7 +406,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
     private bool OsSupported()
     {
-        return IOperatingSystem.Current.IsWindows;
+        return IOperatingSystem.Current.IsWindows || IOperatingSystem.Current.IsLinux;
     }
 
     private void EnsureUpdateFilesDeleted()