Browse Source

Merge pull request #956 from PixiEditor/autoupdate-macos

Autoupdate improvements
Krzysztof Krysiński 2 months ago
parent
commit
d22ebb5fff

+ 1 - 1
src/ColorPicker

@@ -1 +1 @@
-Subproject commit 78237e9c5d70ebd878b34f4dd63f54b25bafea23
+Subproject commit 56faff5afcc6fc96b44b5caa6f15d0b3a902cd0a

+ 3 - 2
src/PixiEditor.AnimationRenderer.FFmpeg/FFMpegRenderer.cs

@@ -173,11 +173,12 @@ public class FFMpegRenderer : IAnimationRenderer
                 return;
                 return;
             }
             }
 
 
-            IOperatingSystem.Current.ProcessUtility.Execute("chmod", $"+x {filePath}");
+            IOperatingSystem.Current.ProcessUtility.Execute("chmod", $"+x {filePath}").WaitForExit(500);
         }
         }
         catch (Exception e)
         catch (Exception e)
         {
         {
-            IOperatingSystem.Current.ProcessUtility.Execute("chmod", $"+x {filePath}");
+            IOperatingSystem.Current.ProcessUtility.Execute("chmod", $"+x {filePath}")
+                .WaitForExit(500);
         }
         }
     }
     }
 
 

+ 8 - 4
src/PixiEditor.MacOs/MacOsProcessUtility.cs

@@ -7,12 +7,16 @@ internal class MacOsProcessUtility : IProcessUtility
 {
 {
     public Process RunAsAdmin(string path, string args)
     public Process RunAsAdmin(string path, string args)
     {
     {
+        string script = $"""
+
+                                     do shell script "'{path}' {args}" with administrator privileges
+                         """;
         ProcessStartInfo startInfo = new ProcessStartInfo
         ProcessStartInfo startInfo = new ProcessStartInfo
         {
         {
-            FileName = path,
-            Verb = "runas",
-            Arguments = args,
-            UseShellExecute = true
+            FileName = "osascript",
+            ArgumentList = { "-e", script },
+            UseShellExecute = true,
+            CreateNoWindow = true,
         };
         };
 
 
         Process p = new Process();
         Process p = new Process();

+ 4 - 4
src/PixiEditor.UpdateInstaller.Exe/PixiEditor.UpdateInstaller.Exe.csproj

@@ -20,22 +20,22 @@
     <ProjectReference Include="..\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller.csproj"/>
     <ProjectReference Include="..\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller\PixiEditor.UpdateInstaller.csproj"/>
   </ItemGroup>
   </ItemGroup>
   
   
-  <Target Name="Rename" AfterTargets="AfterBuild" Condition="'$(OS)' == 'Windows_NT'">
+  <Target Name="RenameBuildWin" AfterTargets="AfterBuild" Condition="'$(RuntimeIdentifier)' =='win-x64' or '$(RuntimeIdentifier)'=='win-arm64'">
     <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller.exe"/>
     <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller.exe"/>
     <Message Text="Renamed build executable file." Importance="high"/>
     <Message Text="Renamed build executable file." Importance="high"/>
   </Target>
   </Target>
 
 
-  <Target Name="Rename" AfterTargets="Publish" Condition="'$(OS)' == 'Windows_NT'">
+  <Target Name="RenamePublishWin" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)'=='win-x64' or '$(RuntimeIdentifier)'=='win-arm64'">
     <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller.exe"/>
     <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe.exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller.exe"/>
     <Message Text="Renamed published executable file." Importance="high"/>
     <Message Text="Renamed published executable file." Importance="high"/>
   </Target>
   </Target>
 
 
-  <Target Name="Rename" AfterTargets="AfterBuild" Condition="'$(OS)' != 'Windows_NT'">
+  <Target Name="RenameBuildUnix" AfterTargets="AfterBuild" Condition="'$(RuntimeIdentifier)'!='win-x64' and '$(RuntimeIdentifier)'!='win-arm64'">
     <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller"/>
     <Move SourceFiles="$(OutDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(OutDir)PixiEditor.UpdateInstaller"/>
     <Message Text="Renamed build executable file." Importance="high"/>
     <Message Text="Renamed build executable file." Importance="high"/>
   </Target>
   </Target>
 
 
-  <Target Name="Rename" AfterTargets="Publish" Condition="'$(OS)' != 'Windows_NT'">
+  <Target Name="RenamePublishUnix" AfterTargets="Publish" Condition="'$(RuntimeIdentifier)'!='win-x64' and '$(RuntimeIdentifier)'!='win-arm64'">
     <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller"/>
     <Move SourceFiles="$(PublishDir)PixiEditor.UpdateInstaller.Exe" DestinationFiles="$(PublishDir)PixiEditor.UpdateInstaller"/>
     <Message Text="Renamed published executable file." Importance="high"/>
     <Message Text="Renamed published executable file." Importance="high"/>
   </Target>
   </Target>

+ 70 - 20
src/PixiEditor.UpdateInstaller.Exe/Program.cs

@@ -5,6 +5,7 @@ using PixiEditor.UpdateInstaller.ViewModels;
 UpdateController controller = new UpdateController();
 UpdateController controller = new UpdateController();
 StringBuilder log = new StringBuilder();
 StringBuilder log = new StringBuilder();
 bool startAfterUpdate = false;
 bool startAfterUpdate = false;
+string logDirectory = Path.Combine(Path.GetTempPath(), "PixiEditor");
 
 
 foreach (string arg in args)
 foreach (string arg in args)
 {
 {
@@ -20,46 +21,95 @@ try
 {
 {
     log.AppendLine($"{DateTime.Now}: Starting update installation...");
     log.AppendLine($"{DateTime.Now}: Starting update installation...");
     controller.InstallUpdate(log);
     controller.InstallUpdate(log);
+    log.AppendLine($"{DateTime.Now}: Update installation completed successfully.");
 }
 }
 catch (Exception ex)
 catch (Exception ex)
 {
 {
     log.AppendLine($"{DateTime.Now}: Error during update installation: {ex.Message}");
     log.AppendLine($"{DateTime.Now}: Error during update installation: {ex.Message}");
-    File.AppendAllText("ErrorLog.txt",
-        $"Error PixiEditor.UpdateInstaller: {DateTime.Now}\n{ex.Message}\n{ex.StackTrace}\n-----\n");
+    string errorLogPath = Path.Combine(logDirectory, "ErrorLog.txt");
+    File.AppendAllText(errorLogPath, $"Error PixiEditor.UpdateInstaller: {DateTime.Now}\n{ex.Message}\n{ex.StackTrace}\n-----\n");
 }
 }
 finally
 finally
 {
 {
+    if (startAfterUpdate)
+    {
+        log.AppendLine($"{DateTime.Now}: Starting PixiEditor after update.");
+        if (OperatingSystem.IsMacOS())
+        {
+            StartPixiEditorOnMacOS(controller);
+        }
+        else
+        {
+            string binaryName = OperatingSystem.IsWindows() ? "PixiEditor.exe" : "PixiEditor";
+            string path = Path.Join(controller.UpdateDirectory, binaryName);
+            if (File.Exists(path))
+            {
+                log.AppendLine($"{DateTime.Now}: Starting PixiEditor from {path}");
+                StartPixiEditor(path);
+            }
+            else
+            {
+                binaryName = OperatingSystem.IsWindows() ? "PixiEditor.Desktop.exe" : "PixiEditor.Desktop";
+                path = Path.Join(controller.UpdateDirectory, binaryName);
+                if (File.Exists(path))
+                {
+                    StartPixiEditor(path);
+                }
+                else
+                {
+                    log.AppendLine("PixiEditor executable not found.");
+                }
+            }
+        }
+    }
+    
     try
     try
     {
     {
-        File.WriteAllText("UpdateLog.txt", log.ToString());
+        string updateLogPath = Path.Combine(logDirectory, "UpdateLog.txt");
+        File.WriteAllText(updateLogPath, log.ToString());
     }
     }
     catch
     catch
     {
     {
         // probably permissions or disk full, the best we can do is to ignore this
         // probably permissions or disk full, the best we can do is to ignore this
     }
     }
 
 
-    if (startAfterUpdate)
+    void StartPixiEditor(string pixiEditorExecutablePath)
     {
     {
-        string binaryName = OperatingSystem.IsWindows() ? "PixiEditor.exe" : "PixiEditor";
-        var files = Directory.GetFiles(controller.UpdateDirectory, binaryName);
-        if (files.Length > 0)
+        if (OperatingSystem.IsWindows())
         {
         {
-            string pixiEditorExecutablePath = files[0];
-            Process.Start(pixiEditorExecutablePath);
+            Process.Start(new ProcessStartInfo(pixiEditorExecutablePath) { UseShellExecute = true });
         }
         }
-        else
+        else if (OperatingSystem.IsLinux())
         {
         {
-            binaryName = OperatingSystem.IsWindows() ? "PixiEditor.Desktop.exe" : "PixiEditor.Desktop";
-            files = Directory.GetFiles(controller.UpdateDirectory, binaryName);
-            if (files.Length > 0)
+            string display = Environment.GetEnvironmentVariable("DISPLAY");
+            Process.Start(new ProcessStartInfo
             {
             {
-                string pixiEditorExecutablePath = files[0];
-                Process.Start(pixiEditorExecutablePath);
-            }
-            else
-            {
-                Console.WriteLine("PixiEditor executable not found.");
-            }
+                FileName = "/bin/bash",
+                Arguments = "-c \"DISPLAY=" + display + " " + pixiEditorExecutablePath + "\" & disown",
+                UseShellExecute = false,
+            });
         }
         }
+        else
+        {
+            log.AppendLine($"{DateTime.Now}: Unsupported operating system for starting PixiEditor.");
+        }
+    }
+}
+
+void StartPixiEditorOnMacOS(UpdateController controller)
+{
+    string pixiEditorExecutablePath = Path.Combine(controller.UpdateDirectory, "PixiEditor.app");
+    if (Directory.Exists(pixiEditorExecutablePath))
+    {
+        log.AppendLine($"{DateTime.Now}: Starting PixiEditor with open -a PixiEditor");
+        Process.Start(new ProcessStartInfo
+        {
+            FileName = "open",
+            Arguments = $"-a PixiEditor",
+        });
+    }
+    else
+    {
+        log.AppendLine($"{DateTime.Now}: PixiEditor.app not found at {pixiEditorExecutablePath}");
     }
     }
 }
 }

+ 18 - 4
src/PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller/ViewModels/UpdateController.cs

@@ -13,10 +13,25 @@ public class UpdateController
         Current = this;
         Current = this;
 
 
         string updateDirectory = Path.GetDirectoryName(Extensions.GetExecutablePath());
         string updateDirectory = Path.GetDirectoryName(Extensions.GetExecutablePath());
+        if (OperatingSystem.IsMacOS())
+        {
+            updateDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+        }
+        else
+        {
+            string infoPath = Path.Join(Path.GetTempPath(), "PixiEditor", "update-location.txt");
+            if (File.Exists(infoPath))
+            {
+                try
+                {
+                    updateDirectory = File.ReadAllText(infoPath);
+                }
+                catch (Exception ex)
+                {
+                }
+            }
+        }
 
 
-#if DEBUG
-        updateDirectory = Path.GetDirectoryName(Environment.GetCommandLineArgs().FirstOrDefault());
-#endif
         UpdateDirectory = updateDirectory;
         UpdateDirectory = updateDirectory;
     }
     }
 
 
@@ -37,7 +52,6 @@ public class UpdateController
         {
         {
             Installer = new UpdateModule.UpdateInstaller(files[0], UpdateDirectory);
             Installer = new UpdateModule.UpdateInstaller(files[0], UpdateDirectory);
             log.AppendLine($"Installing update from {files[0]} to {UpdateDirectory}");
             log.AppendLine($"Installing update from {files[0]} to {UpdateDirectory}");
-            Console.WriteLine("Installing update, DO NOT CLOSE THIS WINDOW");
             Installer.Install(log);
             Installer.Install(log);
         }
         }
     }
     }

+ 5 - 0
src/PixiEditor.UpdateModule/UpdateChecker.cs

@@ -78,6 +78,11 @@ public class UpdateChecker
         LatestReleaseInfo = await GetLatestReleaseInfoAsync(Channel.ApiUrl);
         LatestReleaseInfo = await GetLatestReleaseInfoAsync(Channel.ApiUrl);
         return CheckUpdateAvailable(LatestReleaseInfo);
         return CheckUpdateAvailable(LatestReleaseInfo);
     }
     }
+    
+    public void SetLatestReleaseInfo(ReleaseInfo releaseInfo)
+    {
+        LatestReleaseInfo = releaseInfo;
+    }
 
 
     public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
     public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
     {
     {

+ 3 - 2
src/PixiEditor.UpdateModule/UpdateDownloader.cs

@@ -70,8 +70,9 @@ public static class UpdateDownloader
                                                       && x.Name.Contains(archOld));
                                                       && x.Name.Contains(archOld));
         }
         }
 
 
-        string arch = OperatingSystem.IsWindows() ? "x64" : "amd64";
-        string os = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" : "mac";
+        string arch = OperatingSystem.IsWindows() ? "x64" : 
+                      OperatingSystem.IsLinux() ? "amd64" : "universal";
+        string os = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" : "macos";
         return release.Assets.FirstOrDefault(x => x.ContentType.Contains(assetType)
         return release.Assets.FirstOrDefault(x => x.ContentType.Contains(assetType)
                                                   && x.Name.Contains(arch) && x.Name.Contains(os));
                                                   && x.Name.Contains(arch) && x.Name.Contains(os));
     }
     }

+ 92 - 22
src/PixiEditor.UpdateModule/UpdateInstaller.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.Formats.Tar;
 using System.Formats.Tar;
 using System.IO;
 using System.IO;
 using System.IO.Compression;
 using System.IO.Compression;
+using System.Linq;
 using System.Text;
 using System.Text;
 
 
 namespace PixiEditor.UpdateModule;
 namespace PixiEditor.UpdateModule;
@@ -17,7 +18,8 @@ public class UpdateInstaller
         TargetDirectory = targetDirectory;
         TargetDirectory = targetDirectory;
     }
     }
 
 
-    public static string UpdateFilesPath { get; set; } = Path.Join(UpdateDownloader.DownloadLocation, TargetDirectoryName);
+    public static string UpdateFilesPath { get; set; } =
+        Path.Join(UpdateDownloader.DownloadLocation, TargetDirectoryName);
 
 
     public string ArchiveFileName { get; set; }
     public string ArchiveFileName { get; set; }
 
 
@@ -26,34 +28,81 @@ public class UpdateInstaller
     public void Install(StringBuilder log)
     public void Install(StringBuilder log)
     {
     {
         var processes = Process.GetProcessesByName("PixiEditor.Desktop");
         var processes = Process.GetProcessesByName("PixiEditor.Desktop");
+        processes = processes.Concat(Process.GetProcessesByName("PixiEditor")).ToArray();
         log.AppendLine($"Found {processes.Length} PixiEditor processes running.");
         log.AppendLine($"Found {processes.Length} PixiEditor processes running.");
         if (processes.Length > 0)
         if (processes.Length > 0)
         {
         {
             log.AppendLine("Killing PixiEditor processes...");
             log.AppendLine("Killing PixiEditor processes...");
-            processes[0].WaitForExit();
+            foreach (var process in processes)
+            {
+                try
+                {
+                    log.AppendLine($"Killing process {process.ProcessName} with ID {process.Id}");
+                    process.Kill();
+                    process.WaitForExit();
+                }
+                catch (Exception ex)
+                {
+                    log.AppendLine($"Failed to kill process {process.ProcessName} with ID {process.Id}: {ex.Message}");
+                }
+            }
+
             log.AppendLine("Processes killed.");
             log.AppendLine("Processes killed.");
         }
         }
-        
+
         log.AppendLine("Extracting files");
         log.AppendLine("Extracting files");
-        
-        if(Directory.Exists(UpdateFilesPath))
+
+        if (Directory.Exists(UpdateFilesPath))
         {
         {
             Directory.Delete(UpdateFilesPath, true);
             Directory.Delete(UpdateFilesPath, true);
         }
         }
-        
+
         Directory.CreateDirectory(UpdateFilesPath);
         Directory.CreateDirectory(UpdateFilesPath);
-        
+
         bool isZip = ArchiveFileName.EndsWith(".zip");
         bool isZip = ArchiveFileName.EndsWith(".zip");
         if (isZip)
         if (isZip)
         {
         {
+            log.AppendLine($"Extracting {ArchiveFileName} to {UpdateFilesPath}");
             ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
             ZipFile.ExtractToDirectory(ArchiveFileName, UpdateFilesPath, true);
         }
         }
         else
         else
         {
         {
+            log.AppendLine($"Extracting {ArchiveFileName} to {UpdateFilesPath} using GZipStream");
             using FileStream fs = new(ArchiveFileName, FileMode.Open, FileAccess.Read);
             using FileStream fs = new(ArchiveFileName, FileMode.Open, FileAccess.Read);
             using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
             using GZipStream gz = new(fs, CompressionMode.Decompress, leaveOpen: true);
 
 
-            TarFile.ExtractToDirectory(gz, UpdateFilesPath, overwriteFiles: false);        
+            TarFile.ExtractToDirectory(gz, UpdateFilesPath, overwriteFiles: true);
+        }
+
+        if (OperatingSystem.IsMacOS())
+        {
+            string appFile = Directory.GetDirectories(UpdateFilesPath, "PixiEditor.app", SearchOption.TopDirectoryOnly)
+                .FirstOrDefault();
+            if (string.IsNullOrEmpty(appFile))
+            {
+                log.AppendLine("PixiEditor.app not found in the update files. Installation failed.");
+                string[] allFiles = Directory.GetFiles(UpdateFilesPath, "*.*", SearchOption.TopDirectoryOnly);
+                foreach (string file in allFiles)
+                {
+                    log.AppendLine($"Found file: {file}");
+                }
+
+                throw new FileNotFoundException("PixiEditor.app not found in the update files.");
+            }
+
+
+            log.AppendLine($"Moving {appFile} to {TargetDirectory}");
+            string targetAppDirectory = Path.Combine(TargetDirectory, "PixiEditor.app");
+            if (Directory.Exists(targetAppDirectory))
+            {
+                log.AppendLine($"Removing existing PixiEditor.app at {targetAppDirectory}");
+                Directory.Delete(targetAppDirectory, true);
+            }
+
+            Directory.Move(appFile, targetAppDirectory);
+
+            Cleanup(log);
+            return;
         }
         }
 
 
         string[] extractedFiles = Directory.GetFiles(UpdateFilesPath, "*", SearchOption.AllDirectories);
         string[] extractedFiles = Directory.GetFiles(UpdateFilesPath, "*", SearchOption.AllDirectories);
@@ -66,16 +115,7 @@ public class UpdateInstaller
         {
         {
             dirWithFiles = Directory.GetDirectories(UpdateFilesPath)[0];
             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}");
         log.AppendLine($"Copying files from {dirWithFiles} to {TargetDirectory}");
 
 
         try
         try
@@ -91,14 +131,44 @@ public class UpdateInstaller
 
 
         log.AppendLine("Files copied");
         log.AppendLine("Files copied");
         log.AppendLine("Deleting archive and update files");
         log.AppendLine("Deleting archive and update files");
-        
-        DeleteArchive();
+
+        Cleanup(log);
     }
     }
 
 
-    private void DeleteArchive()
+    private void Cleanup(StringBuilder logger)
     {
     {
         File.Delete(ArchiveFileName);
         File.Delete(ArchiveFileName);
         Directory.Delete(UpdateFilesPath, true);
         Directory.Delete(UpdateFilesPath, true);
+        string updateLocationFile = Path.Join(Path.GetTempPath(), "PixiEditor", "update-location.txt");
+        logger.AppendLine($"Looking for: {updateLocationFile}");
+        if (File.Exists(updateLocationFile))
+        {
+            try
+            {
+                logger.AppendLine($"Deleting update location file: {updateLocationFile}");
+                File.Delete(updateLocationFile);
+            }
+            catch (Exception ex)
+            {
+                logger.AppendLine($"Failed to delete update location file: {ex.Message}");
+            }
+        }
+
+        string updateInstallerFile = Path.Join(Path.GetTempPath(), "PixiEditor",
+            "PixiEditor.UpdateInstaller" + (OperatingSystem.IsWindows() ? ".exe" : ""));
+        logger.AppendLine($"Looking for: {updateInstallerFile}");
+        if (File.Exists(updateInstallerFile))
+        {
+            try
+            {
+                logger.AppendLine($"Deleting update installer file: {updateInstallerFile}");
+                File.Delete(updateInstallerFile);
+            }
+            catch (Exception ex)
+            {
+                logger.AppendLine($"Failed to delete update installer file: {ex.Message}");
+            }
+        }
     }
     }
 
 
     private void CopyFilesToDestination(string sourceDirectory, StringBuilder log)
     private void CopyFilesToDestination(string sourceDirectory, StringBuilder log)
@@ -123,12 +193,12 @@ public class UpdateInstaller
     {
     {
         string[] subDirs = Directory.GetDirectories(originDirectory);
         string[] subDirs = Directory.GetDirectories(originDirectory);
         log.AppendLine($"Found {subDirs.Length} subdirectories to copy.");
         log.AppendLine($"Found {subDirs.Length} subdirectories to copy.");
-        if(subDirs.Length == 0) return;
+        if (subDirs.Length == 0) return;
 
 
         foreach (string subDir in subDirs)
         foreach (string subDir in subDirs)
         {
         {
             string targetDirPath = Path.Join(targetDirectory, Path.GetFileName(subDir));
             string targetDirPath = Path.Join(targetDirectory, Path.GetFileName(subDir));
-            
+
             log.AppendLine($"Copying {subDir} to {targetDirPath}");
             log.AppendLine($"Copying {subDir} to {targetDirPath}");
 
 
             CopySubDirectories(subDir, targetDirPath, log);
             CopySubDirectories(subDir, targetDirPath, log);

+ 6 - 2
src/PixiEditor/Models/DocumentModels/Autosave/AutosaverSaveUserFileJob.cs

@@ -46,8 +46,12 @@ internal class AutosaverSaveUserFileJob(DocumentViewModel document) : IAutosaver
 
 
                 File.Copy(document.AutosaveViewModel.LastAutosavedPath, path, true);
                 File.Copy(document.AutosaveViewModel.LastAutosavedPath, path, true);
 
 
-                document.MarkAsSaved();
-                document.AutosaveViewModel.AddAutosaveHistoryEntry(AutosaveHistoryType.Periodic, AutosaveHistoryResult.SavedUserFile);
+                Dispatcher.UIThread.Invoke(() =>
+                {
+                    document.MarkAsSaved();
+                    document.AutosaveViewModel.AddAutosaveHistoryEntry(AutosaveHistoryType.Periodic,
+                        AutosaveHistoryResult.SavedUserFile);
+                });
                 return UserFileAutosaveResult.Success;
                 return UserFileAutosaveResult.Success;
             }
             }
             catch (Exception e) when (e is UnauthorizedAccessException or DirectoryNotFoundException)
             catch (Exception e) when (e is UnauthorizedAccessException or DirectoryNotFoundException)

+ 2 - 2
src/PixiEditor/Properties/AssemblyInfo.cs

@@ -43,5 +43,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.0.89")]
-[assembly: AssemblyFileVersion("2.0.0.89")]
+[assembly: AssemblyVersion("2.0.0.90")]
+[assembly: AssemblyFileVersion("2.0.0.90")]

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/ColorsViewModel.cs

@@ -426,7 +426,7 @@ internal class ColorsViewModel : SubViewModel<ViewModelMain>, IColorsHandler
     [Commands_Command.Internal("PixiEditor.CloseContextMenu")]
     [Commands_Command.Internal("PixiEditor.CloseContextMenu")]
     public void CloseContextMenu(XAML_ContextMenu menu)
     public void CloseContextMenu(XAML_ContextMenu menu)
     {
     {
-        menu.Close();
+        menu?.Close();
     }
     }
 
 
     public void SetupPaletteProviders(IServiceProvider services)
     public void SetupPaletteProviders(IServiceProvider services)

+ 77 - 37
src/PixiEditor/ViewModels/SubViewModels/UpdateViewModel.cs

@@ -3,6 +3,7 @@ using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Avalonia;
 using Avalonia;
@@ -166,6 +167,16 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             return;
             return;
         }
         }
 
 
+        if (updateAvailable)
+        {
+            if (!IOperatingSystem.Current.IsWindows && UpdateChecker.LatestReleaseInfo.TagName.StartsWith("1."))
+            {
+                // 1.0 is windows only
+                UpdateState = UpdateState.UpToDate;
+                return;
+            }
+        }
+
         UpdateState = updateAvailable ? UpdateState.UpdateAvailable : UpdateState.UpToDate;
         UpdateState = updateAvailable ? UpdateState.UpdateAvailable : UpdateState.UpToDate;
     }
     }
 
 
@@ -196,12 +207,12 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             try
             try
             {
             {
                 UpdateState = UpdateState.Downloading;
                 UpdateState = UpdateState.Downloading;
-                if (updateCompatible)
+                if (updateCompatible || !IOperatingSystem.Current.IsWindows)
                 {
                 {
                     await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo, ZipContentType,
                     await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo, ZipContentType,
                         ZipExtension);
                         ZipExtension);
                 }
                 }
-                else if (IOperatingSystem.Current.IsWindows) // TODO: Add macos
+                else if (IOperatingSystem.Current.IsWindows)
                 {
                 {
                     await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
                     await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
                 }
                 }
@@ -245,9 +256,17 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
         Install(true);
         Install(true);
     }
     }
 
 
+    [Command.Debug("PixiEditor.Update.DebugInstall", "Debug Install Update", "(DEBUG) Install update zip file without checking for updates")]
+    public void DebugInstall()
+    {
+        UpdateChecker.SetLatestReleaseInfo(new ReleaseInfo(true) { TagName = "2.2.2.2" });
+        Install(true);
+    }
+
     private void Install(bool startAfterUpdate)
     private void Install(bool startAfterUpdate)
     {
     {
-        string dir = AppDomain.CurrentDomain.BaseDirectory;
+        string dir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName) ??
+                     AppDomain.CurrentDomain.BaseDirectory;
 
 
         UpdateDownloader.CreateTempDirectory();
         UpdateDownloader.CreateTempDirectory();
         if (UpdateChecker.LatestReleaseInfo == null ||
         if (UpdateChecker.LatestReleaseInfo == null ||
@@ -267,6 +286,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
 
         string updaterPath = Path.Join(dir, $"PixiEditor.UpdateInstaller{BinaryExtension}");
         string updaterPath = Path.Join(dir, $"PixiEditor.UpdateInstaller{BinaryExtension}");
 
 
+
         if (!updateFileExists && !updateExeExists)
         if (!updateFileExists && !updateExeExists)
         {
         {
             EnsureUpdateFilesDeleted();
             EnsureUpdateFilesDeleted();
@@ -275,6 +295,23 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             return;
             return;
         }
         }
 
 
+        try
+        {
+            if (Path.Exists(updaterPath))
+            {
+                File.Copy(updaterPath, Path.Join(UpdateDownloader.DownloadLocation, $"PixiEditor.UpdateInstaller" + BinaryExtension),
+                    true);
+                updaterPath = Path.Join(UpdateDownloader.DownloadLocation, $"PixiEditor.UpdateInstaller" + BinaryExtension);
+                File.WriteAllText(Path.Join(UpdateDownloader.DownloadLocation, "update-location.txt"),
+                    Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty);
+            }
+        }
+        catch (IOException)
+        {
+            NoticeDialog.Show("COULD_NOT_UPDATE_WITHOUT_ADMIN", "INSUFFICIENT_PERMISSIONS");
+            return;
+        }
+
         if (updateFileExists && File.Exists(updaterPath))
         if (updateFileExists && File.Exists(updaterPath))
         {
         {
             InstallHeadless(updaterPath, startAfterUpdate);
             InstallHeadless(updaterPath, startAfterUpdate);
@@ -287,36 +324,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
 
     private static void InstallHeadless(string updaterPath, bool startAfterUpdate)
     private static void InstallHeadless(string updaterPath, bool startAfterUpdate)
     {
     {
-        try
-        {
-            if (IOperatingSystem.Current.IsLinux)
-            {
-                bool hasWritePermissions = !InstallDirReadOnly();
-                if (hasWritePermissions)
-                {
-                    IOperatingSystem.Current.ProcessUtility.ShellExecute(updaterPath, startAfterUpdate ? "--startOnSuccess" : null);
-                }
-                else
-                {
-                    NoticeDialog.Show(
-                        "COULD_NOT_UPDATE_WITHOUT_ADMIN",
-                        "INSUFFICIENT_PERMISSIONS");
-                    return;
-                }
-            }
-            else
-            {
-                ProcessHelper.RunAsAdmin(updaterPath, startAfterUpdate ? "--startOnSuccess" : null);
-            }
-
-            Shutdown();
-        }
-        catch (Win32Exception)
-        {
-            NoticeDialog.Show(
-                "COULD_NOT_UPDATE_WITHOUT_ADMIN",
-                "INSUFFICIENT_PERMISSIONS");
-        }
+        TryRestartToUpdate(updaterPath, startAfterUpdate ? "--startOnSuccess" : null);
     }
     }
 
 
     private void OpenExeInstaller(string updateExeFile)
     private void OpenExeInstaller(string updateExeFile)
@@ -342,6 +350,11 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
     }
     }
 
 
     private static void RestartToUpdate(string updateExeFile)
     private static void RestartToUpdate(string updateExeFile)
+    {
+        TryRestartToUpdate(updateExeFile, null);
+    }
+
+    private static void TryRestartToUpdate(string updateExeFile, string? args)
     {
     {
         try
         try
         {
         {
@@ -351,15 +364,42 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
                 bool hasWritePermissions = !InstallDirReadOnly();
                 bool hasWritePermissions = !InstallDirReadOnly();
                 if (hasWritePermissions)
                 if (hasWritePermissions)
                 {
                 {
-                    IOperatingSystem.Current.ProcessUtility.ShellExecute(updateExeFile, null);
+                    IOperatingSystem.Current.ProcessUtility.ShellExecute(updateExeFile, args);
+                    Shutdown();
+                }
+                else
+                {
+                    NoticeDialog.Show("COULD_NOT_UPDATE_WITHOUT_ADMIN", "INSUFFICIENT_PERMISSIONS");
+                    return;
                 }
                 }
             }
             }
             else
             else
             {
             {
-                IOperatingSystem.Current.ProcessUtility.RunAsAdmin(updateExeFile, null);
+                var proc = IOperatingSystem.Current.ProcessUtility.RunAsAdmin(updateExeFile, args);
+                if (IOperatingSystem.Current.IsMacOs)
+                {
+                    proc.WaitForExitAsync().ContinueWith(t =>
+                    {
+                        Dispatcher.UIThread.Invoke(() =>
+                        {
+
+                            if (t.IsCompletedSuccessfully && proc.ExitCode == 0)
+                            {
+                                Shutdown();
+                            }
+                            else
+                            {
+                                NoticeDialog.Show("COULD_NOT_UPDATE_WITHOUT_ADMIN", "INSUFFICIENT_PERMISSIONS");
+                            }
+                        });
+                    });
+                }
+                else
+                {
+                    Shutdown();
+                }
             }
             }
 
 
-            Shutdown();
         }
         }
         catch (Win32Exception)
         catch (Win32Exception)
         {
         {
@@ -431,7 +471,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
 
 
     private bool OsSupported()
     private bool OsSupported()
     {
     {
-        return IOperatingSystem.Current.IsWindows || IOperatingSystem.Current.IsLinux;
+        return IOperatingSystem.Current.IsWindows || IOperatingSystem.Current.IsLinux || IOperatingSystem.Current.IsMacOs;
     }
     }
 
 
     private void EnsureUpdateFilesDeleted()
     private void EnsureUpdateFilesDeleted()

+ 9 - 2
src/PixiEditor/Views/Visuals/PixiFilePreviewImage.cs

@@ -76,8 +76,15 @@ internal class PixiFilePreviewImage : TextureControl
 
 
         Dispatcher.UIThread.Post(() =>
         Dispatcher.UIThread.Post(() =>
         {
         {
-            var surface = LoadTexture(imageBytes);
-            SetImage(surface);
+            try
+            {
+                var surface = LoadTexture(imageBytes);
+                SetImage(surface);
+            }
+            catch (Exception e)
+            {
+                SetCorrupt();
+            }
         });
         });
     }
     }