|
@@ -20,6 +20,9 @@ using PixiEditor.Models.IO;
|
|
using PixiEditor.Models.UserData;
|
|
using PixiEditor.Models.UserData;
|
|
using Drawie.Numerics;
|
|
using Drawie.Numerics;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
+using PixiEditor.Extensions.CommonApi.UserPreferences;
|
|
|
|
+using PixiEditor.Models.DocumentModels.Autosave;
|
|
|
|
+using PixiEditor.Models.ExceptionHandling;
|
|
using PixiEditor.Models.IO.CustomDocumentFormats;
|
|
using PixiEditor.Models.IO.CustomDocumentFormats;
|
|
using PixiEditor.OperatingSystem;
|
|
using PixiEditor.OperatingSystem;
|
|
using PixiEditor.Parser;
|
|
using PixiEditor.Parser;
|
|
@@ -100,6 +103,11 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
}
|
|
}
|
|
|
|
|
|
private void OpenHelloTherePopup()
|
|
private void OpenHelloTherePopup()
|
|
|
|
+ {
|
|
|
|
+ new HelloTherePopup(this).Show();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void Owner_OnStartupEvent()
|
|
{
|
|
{
|
|
List<string> args = StartupArgs.Args;
|
|
List<string> args = StartupArgs.Args;
|
|
string file = args.FirstOrDefault(x => Importer.IsSupportedFile(x) && File.Exists(x));
|
|
string file = args.FirstOrDefault(x => Importer.IsSupportedFile(x) && File.Exists(x));
|
|
@@ -114,7 +122,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
|
|
|
|
if (lastCrash == null)
|
|
if (lastCrash == null)
|
|
{
|
|
{
|
|
- MaybeReopenTempAutosavedFiles();
|
|
|
|
|
|
+ TryReopenTempAutosavedFiles();
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
@@ -135,23 +143,6 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
CrashHelper.SendExceptionInfo(exc);
|
|
CrashHelper.SendExceptionInfo(exc);
|
|
}
|
|
}
|
|
|
|
|
|
- if (file != null)
|
|
|
|
- {
|
|
|
|
- OpenFromPath(file);
|
|
|
|
- }
|
|
|
|
- else if ((Owner.DocumentManagerSubViewModel.Documents.Count == 0 && !args.Contains("--crash")) && !args.Contains("--openedInExisting"))
|
|
|
|
- {
|
|
|
|
- if (preferences!.GetPreference("ShowStartupWindow", true))
|
|
|
|
- {
|
|
|
|
- OpenHelloTherePopup();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private void Owner_OnStartupEvent()
|
|
|
|
- {
|
|
|
|
- List<string> args = StartupArgs.Args;
|
|
|
|
- string file = args.FirstOrDefault(x => Importer.IsSupportedFile(x) && File.Exists(x));
|
|
|
|
if (file != null)
|
|
if (file != null)
|
|
{
|
|
{
|
|
OpenFromPath(file);
|
|
OpenFromPath(file);
|
|
@@ -159,7 +150,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
else if ((Owner.DocumentManagerSubViewModel.Documents.Count == 0 && !args.Contains("--crash")) &&
|
|
else if ((Owner.DocumentManagerSubViewModel.Documents.Count == 0 && !args.Contains("--crash")) &&
|
|
!args.Contains("--openedInExisting"))
|
|
!args.Contains("--openedInExisting"))
|
|
{
|
|
{
|
|
- if (PixiEditorSettings.StartupWindow.ShowStartupWindow.Value)
|
|
|
|
|
|
+ if (preferences!.GetPreference("ShowStartupWindow", true))
|
|
{
|
|
{
|
|
OpenHelloTherePopup();
|
|
OpenHelloTherePopup();
|
|
}
|
|
}
|
|
@@ -239,25 +230,24 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Tries to open the passed file if it isn't already open
|
|
/// Tries to open the passed file if it isn't already open
|
|
/// </summary>
|
|
/// </summary>
|
|
- public void OpenFromPath(string path, bool associatePath = true)
|
|
|
|
|
|
+ public DocumentViewModel OpenFromPath(string path, bool associatePath = true)
|
|
{
|
|
{
|
|
if (MakeExistingDocumentActiveIfOpened(path))
|
|
if (MakeExistingDocumentActiveIfOpened(path))
|
|
- return;
|
|
|
|
|
|
+ return null;
|
|
|
|
|
|
try
|
|
try
|
|
{
|
|
{
|
|
if (path.EndsWith(".pixi"))
|
|
if (path.EndsWith(".pixi"))
|
|
{
|
|
{
|
|
- OpenDotPixi(path, associatePath);
|
|
|
|
- }
|
|
|
|
- else if (IsCustomFormat(path))
|
|
|
|
- {
|
|
|
|
- OpenCustomFormat(path, associatePath);
|
|
|
|
|
|
+ return OpenDotPixi(path, associatePath);
|
|
}
|
|
}
|
|
- else
|
|
|
|
|
|
+
|
|
|
|
+ if (IsCustomFormat(path))
|
|
{
|
|
{
|
|
- OpenRegularImage(path, associatePath);
|
|
|
|
|
|
+ return OpenCustomFormat(path, associatePath);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return OpenRegularImage(path, associatePath);
|
|
}
|
|
}
|
|
catch (RecoverableException ex)
|
|
catch (RecoverableException ex)
|
|
{
|
|
{
|
|
@@ -267,6 +257,8 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
{
|
|
{
|
|
NoticeDialog.Show("OLD_FILE_FORMAT_DESCRIPTION", "OLD_FILE_FORMAT");
|
|
NoticeDialog.Show("OLD_FILE_FORMAT_DESCRIPTION", "OLD_FILE_FORMAT");
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
private bool IsCustomFormat(string path)
|
|
private bool IsCustomFormat(string path)
|
|
@@ -275,7 +267,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
return documentBuilders.Any(x => x.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
|
|
return documentBuilders.Any(x => x.Extensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
}
|
|
|
|
|
|
- private void OpenCustomFormat(string path, bool associatePath)
|
|
|
|
|
|
+ private DocumentViewModel? OpenCustomFormat(string path, bool associatePath)
|
|
{
|
|
{
|
|
IDocumentBuilder builder = documentBuilders.First(x =>
|
|
IDocumentBuilder builder = documentBuilders.First(x =>
|
|
x.Extensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase));
|
|
x.Extensions.Contains(Path.GetExtension(path), StringComparer.OrdinalIgnoreCase));
|
|
@@ -283,7 +275,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
if (!File.Exists(path))
|
|
if (!File.Exists(path))
|
|
{
|
|
{
|
|
NoticeDialog.Show("FILE_NOT_FOUND", "FAILED_TO_OPEN_FILE");
|
|
NoticeDialog.Show("FILE_NOT_FOUND", "FAILED_TO_OPEN_FILE");
|
|
- return;
|
|
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
try
|
|
try
|
|
@@ -297,6 +289,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
}
|
|
}
|
|
|
|
|
|
AddRecentlyOpened(document.FullFilePath);
|
|
AddRecentlyOpened(document.FullFilePath);
|
|
|
|
+ return document;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
{
|
|
{
|
|
@@ -304,12 +297,14 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
Console.WriteLine(ex);
|
|
Console.WriteLine(ex);
|
|
CrashHelper.SendExceptionInfo(ex);
|
|
CrashHelper.SendExceptionInfo(ex);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Opens a .pixi file from path, creates a document from it, and adds it to the system
|
|
/// Opens a .pixi file from path, creates a document from it, and adds it to the system
|
|
/// </summary>
|
|
/// </summary>
|
|
- private void OpenDotPixi(string path, bool associatePath = true)
|
|
|
|
|
|
+ private DocumentViewModel OpenDotPixi(string path, bool associatePath = true)
|
|
{
|
|
{
|
|
DocumentViewModel document = Importer.ImportDocument(path, associatePath);
|
|
DocumentViewModel document = Importer.ImportDocument(path, associatePath);
|
|
|
|
|
|
@@ -318,26 +313,40 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
|
|
|
|
var fileSize = new FileInfo(path).Length;
|
|
var fileSize = new FileInfo(path).Length;
|
|
Analytics.SendOpenFile(PixiFileType.PixiFile, fileSize, document.SizeBindable);
|
|
Analytics.SendOpenFile(PixiFileType.PixiFile, fileSize, document.SizeBindable);
|
|
|
|
+
|
|
|
|
+ return document;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Opens a .pixi file from path, creates a document from it, and adds it to the system
|
|
/// Opens a .pixi file from path, creates a document from it, and adds it to the system
|
|
/// </summary>
|
|
/// </summary>
|
|
- public void OpenRecoveredDotPixi(string? originalPath, byte[] dotPixiBytes)
|
|
|
|
|
|
+ public void OpenRecoveredDotPixi(string? originalPath, string? autosavePath, Guid? autosaveGuid, byte[] dotPixiBytes)
|
|
{
|
|
{
|
|
DocumentViewModel document = Importer.ImportDocument(dotPixiBytes, originalPath);
|
|
DocumentViewModel document = Importer.ImportDocument(dotPixiBytes, originalPath);
|
|
document.MarkAsUnsaved();
|
|
document.MarkAsUnsaved();
|
|
|
|
+
|
|
|
|
+ if (autosavePath != null)
|
|
|
|
+ {
|
|
|
|
+ document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(autosaveGuid!.Value, autosavePath);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ AddDocumentViewModelToTheSystem(document);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void OpenFromPixiBytes(byte[] bytes)
|
|
|
|
+ {
|
|
|
|
+ DocumentViewModel document = Importer.ImportDocument(bytes, null);
|
|
AddDocumentViewModelToTheSystem(document);
|
|
AddDocumentViewModelToTheSystem(document);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Opens a regular image file from path, creates a document from it, and adds it to the system.
|
|
/// Opens a regular image file from path, creates a document from it, and adds it to the system.
|
|
/// </summary>
|
|
/// </summary>
|
|
- private void OpenRegularImage(string path, bool associatePath)
|
|
|
|
|
|
+ private DocumentViewModel OpenRegularImage(string path, bool associatePath)
|
|
{
|
|
{
|
|
var image = Importer.ImportImage(path, VecI.NegativeOne);
|
|
var image = Importer.ImportImage(path, VecI.NegativeOne);
|
|
|
|
|
|
- if (image == null) return;
|
|
|
|
|
|
+ if (image == null) return null;
|
|
|
|
|
|
var doc = NewDocument(b => b
|
|
var doc = NewDocument(b => b
|
|
.WithSize(image.Size)
|
|
.WithSize(image.Size)
|
|
@@ -369,8 +378,58 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
CrashHelper.SendExceptionInfo(new InvalidFileTypeException(default,
|
|
CrashHelper.SendExceptionInfo(new InvalidFileTypeException(default,
|
|
$"Invalid file type '{fileType}'"));
|
|
$"Invalid file type '{fileType}'"));
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ return doc;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ public void OpenFromReport(CrashReport report, out bool showMissingFilesDialog)
|
|
|
|
+ {
|
|
|
|
+ var documents = report.RecoverDocuments(out var info);
|
|
|
|
+
|
|
|
|
+ var i = 0;
|
|
|
|
+
|
|
|
|
+ Exception firstException = null;
|
|
|
|
+ Exception secondException = null;
|
|
|
|
+ Exception thirdException = null;
|
|
|
|
+
|
|
|
|
+ foreach (var document in documents)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ OpenRecoveredDotPixi(document.OriginalPath, document.AutosavePath,
|
|
|
|
+ AutosaveHelper.GetAutosaveGuid(document.AutosavePath), document.GetRecoveredBytes());
|
|
|
|
+ i++;
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ firstException = e;
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ OpenFromPath(document.AutosavePath, false);
|
|
|
|
+ }
|
|
|
|
+ catch (Exception deepE)
|
|
|
|
+ {
|
|
|
|
+ secondException = deepE;
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ OpenRecoveredDotPixi(document.OriginalPath, document.AutosavePath,
|
|
|
|
+ AutosaveHelper.GetAutosaveGuid(document.AutosavePath), document.GetAutosaveBytes());
|
|
|
|
+ }
|
|
|
|
+ catch (Exception veryDeepE)
|
|
|
|
+ {
|
|
|
|
+ thirdException = veryDeepE;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var exceptions = new[] { firstException, secondException, thirdException };
|
|
|
|
+ CrashHelper.SendExceptionInfo(new AggregateException(exceptions.Where(x => x != null).ToArray()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ showMissingFilesDialog = documents.Count != i;
|
|
|
|
+ }
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Opens a regular image file from path, creates a document from it, and adds it to the system.
|
|
/// Opens a regular image file from path, creates a document from it, and adds it to the system.
|
|
@@ -582,6 +641,120 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void TryReopenTempAutosavedFiles()
|
|
|
|
+ {
|
|
|
|
+ var preferences = Owner.Preferences;
|
|
|
|
+
|
|
|
|
+ // Todo sure, no session saving, but shouldn't we still load backups in case of unexpected shutdown?
|
|
|
|
+ // it probably should be handled elsewhere
|
|
|
|
+ if (!preferences.GetPreference<bool>(PreferencesConstants.SaveSessionStateEnabled, PreferencesConstants.SaveSessionStateDefault))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ var history =
|
|
|
|
+ preferences.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
|
|
|
|
+
|
|
|
|
+ // There are no autosave attempts .. but what if the user has just launched pixieditor for the first time,
|
|
|
|
+ // and it unexpectedly closed before auto saving anything. They could've still had some files open, and they won't be reopened in this session
|
|
|
|
+ // I'll say this is by design
|
|
|
|
+ if (history is null || history.Count == 0)
|
|
|
|
+ return;
|
|
|
|
+ var lastSession = history[^1];
|
|
|
|
+ if (lastSession.AutosaveEntries.Count == 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ List<List<AutosaveHistoryEntry>> perDocumentHistories = (
|
|
|
|
+ from entry in lastSession.AutosaveEntries
|
|
|
|
+ group entry by entry.TempFileGuid
|
|
|
|
+ into entryGroup
|
|
|
|
+ select entryGroup.OrderBy(a => a.DateTime).ToList()
|
|
|
|
+ ).ToList();
|
|
|
|
+
|
|
|
|
+ /*bool shutdownWasUnexpected = lastSession.AutosaveEntries.All(a => a.Type != AutosaveHistoryType.OnClose);
|
|
|
|
+ if (shutdownWasUnexpected)
|
|
|
|
+ {
|
|
|
|
+ List<List<AutosaveHistoryEntry>> lastBackups = (
|
|
|
|
+ from entry in lastSession.AutosaveEntries
|
|
|
|
+ group entry by entry.TempFileGuid into entryGroup
|
|
|
|
+ select entryGroup.OrderBy(a => a.DateTime).ToList()
|
|
|
|
+ ).ToList();
|
|
|
|
+ // todo notify about files getting recovered after unexpected shutdown
|
|
|
|
+ // also separate this out into a function
|
|
|
|
+ return;
|
|
|
|
+ }*/
|
|
|
|
+
|
|
|
|
+ foreach (var documentHistory in perDocumentHistories)
|
|
|
|
+ {
|
|
|
|
+ AutosaveHistoryEntry lastEntry = documentHistory[^1];
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ if (lastEntry.Type != AutosaveHistoryType.OnClose)
|
|
|
|
+ {
|
|
|
|
+ // unexpected shutdown happened, this file wasn't saved on close, but we supposedly have a backup
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ switch (lastEntry.Result)
|
|
|
|
+ {
|
|
|
|
+ case AutosaveHistoryResult.SavedBackup:
|
|
|
|
+ LoadFromAutosave(lastEntry);
|
|
|
|
+ break;
|
|
|
|
+ case AutosaveHistoryResult.SavedUserFile:
|
|
|
|
+ case AutosaveHistoryResult.NothingToSave:
|
|
|
|
+ // load from user file
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ throw new ArgumentOutOfRangeException();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ CrashHelper.SendExceptionInfo(e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Owner.AutosaveViewModel.CleanupAutosavedFilesAndHistory();
|
|
|
|
+
|
|
|
|
+ /*foreach (var file in files)
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ if (file.AutosavePath != null)
|
|
|
|
+ {
|
|
|
|
+ var document = OpenFromPath(file.AutosavePath, false);
|
|
|
|
+ document.FullFilePath = file.OriginalPath;
|
|
|
|
+
|
|
|
|
+ if (file.AutosavePath != null)
|
|
|
|
+ {
|
|
|
|
+ document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(
|
|
|
|
+ AutosaveHelper.GetAutosaveGuid(file.AutosavePath)!.Value, file.AutosavePath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ OpenFromPath(file.OriginalPath);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ CrashHelper.SendExceptionInfo(e);
|
|
|
|
+ }
|
|
|
|
+ }*/
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void LoadFromAutosave(AutosaveHistoryEntry entry)
|
|
|
|
+ {
|
|
|
|
+ string path = AutosaveHelper.GetAutosavePath(entry.TempFileGuid);
|
|
|
|
+ if (path == null)
|
|
|
|
+ {
|
|
|
|
+ // TODO: Notify
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var document = OpenFromPath(path, false);
|
|
|
|
+ document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(entry.TempFileGuid, path);
|
|
|
|
+ }
|
|
|
|
+
|
|
private List<RecentlyOpenedDocument> GetRecentlyOpenedDocuments()
|
|
private List<RecentlyOpenedDocument> GetRecentlyOpenedDocuments()
|
|
{
|
|
{
|
|
var paths = PixiEditorSettings.File.RecentlyOpened.Value.Take(PixiEditorSettings.File.MaxOpenedRecently.Value);
|
|
var paths = PixiEditorSettings.File.RecentlyOpened.Value.Take(PixiEditorSettings.File.MaxOpenedRecently.Value);
|