Privet \ Hello,
Thanks a lot for great FarNet library!!!
I just upgraded from FarNet 5.0.32 to latest one 5.0.59 in order to support latest versions of Far > 4159.
In previous version of FarNet my custom editor\viewer worked just fine.
For simplicity you can think of it as a viewer of files encoded in base64 format.
The logic of my custom editor (viewer) is all in Invoke() method and aux static functions (see snippet code below).
Basically:
* Ask user if he \ she wants to convert the file, open as it or Cancel the operation, here I'm using Far.Api.Message() function
* If Yes convert the file by creating *NEW* file in %TEMP% folder
* Close current editor by calling editor.Close()
* Open *NEW* file by calling:
IEditor editor = Far.Api.CreateEditor();
editor.Title = Path.GetFileName(filename);
editor.FileName = filename;
editor.DeleteSource = DeleteSource.File;
editor.Open();
* If user want to open as is just continue without any conversation
* If cancel call editor.Close()
In 5.0.32 and Far 38xx (both x86 and x64) the above code worked just perfect.
Now, I'm having the following issue with new FarNet 5.0.59 and Far 4160 x64 (see image below):
* Far.Api.Message() for some reason causes editor Invoke() to be called *AGAIN* (on the same thread), here callstack:
> Sha.dll!Sha.Base64Editor.Invoke(object sender, FarNet.ModuleEditorEventArgs e) Line 21 C#
[External Code]
Sha.dll!Sha.Base64Editor.Invoke(object sender, FarNet.ModuleEditorEventArgs e) Line 21 C#
[External Code]
Line 21 is Far.Api.Message()
Below please find sample code which you can compile and deploy to repro the problem:
* Set a breakpoint on Far.Api.Message() and hit F10
Thanks a lot for your help,
Eli (Илья) Zeitlin from Microsoft
- TwoMessageBoxes.PNG (31.2 KiB) Viewed 12436 times
Code: Select all
using FarNet;
using FarNet.Tools;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Sha
{
[System.Runtime.InteropServices.Guid("53CD6F79-51A9-4D61-80D9-38ABF266F965")]
[ModuleEditor(Name = "Base64 Editor", Mask = "*.base64*")]
public class Base64Editor : ModuleEditor
{
// Redirect F4 on .base64_* file to our editor
public override void Invoke(object sender, ModuleEditorEventArgs e)
{
var editor = (IEditor)sender;
switch (Far.Api.Message(string.Format(
"Do you want to convert base64 file?\n",
Path.GetFileName(editor.FileName)), "Base64 Editor", MessageOptions.None, new string[] { "Yes", "Open as is", "Cancel" }))
{
case 0: // Convert
editor.Close();
OpenTempFileInEditor(ConvertBase64BondFileToBinaryBondFile(editor.FileName));
return;
case 1: // Open as is
break;
default: // ESC
editor.Close();
break;
}
}
public static void OpenTempFileInEditor(string filename)
{
if (IsFileExistAndNotEmpty(filename))
{
IEditor editor = Far.Api.CreateEditor();
editor.Title = Path.GetFileName(filename);
editor.FileName = filename;
editor.DeleteSource = DeleteSource.File;
editor.Open();
}
}
// Show progress box during conversion opeartions
private static void UpdateProgress(ProgressBox pb, TimeSpan refreshRate, string srcFile, string destFile, long entityIndex, long completed, long size)
{
if (pb.ElapsedFromShow > refreshRate)
{
pb.Activity = string.Format("From: {0}\nTo: {1}\nEntities: {2}", srcFile, destFile, entityIndex);
if (size > 0) pb.SetProgressValue(completed, size);
pb.ShowProgress();
}
}
/// <summary>
/// This function will convert *.base64 format file to %TEMP%\*.base64.bond file
/// It will delete the source *.base64 file if is located under %TEMP% folder on success
/// </summary>
/// <param name="filename">Full path to *.base64 file</param>
/// <returns>Full path to generated .bond file</returns>
private static string ConvertBase64BondFileToBinaryBondFile(string filename)
{
TimeSpan refreshRate = TimeSpan.FromMilliseconds(50); // 20 times in 1 sec
string tempBondFilename = Path.Combine(Path.GetTempPath(), Path.GetFileName(filename) + ".bond");
long entityIndex = 0;
var pb = new ProgressBox("Converting base64 Bond to binary Bond") { LineCount = 4 };
UpdateProgress(pb, refreshRate, filename, tempBondFilename, entityIndex, 0, 0);
try
{
using (var fsIn = new FileStream(filename, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
{
var reader = new StreamReader(fsIn);
using (var fsOut = new FileStream(tempBondFilename, FileMode.Create))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
entityIndex++;
UpdateProgress(pb, refreshRate, filename, tempBondFilename, entityIndex, fsIn.Position, fsIn.Length);
try
{
var entityByte = Convert.FromBase64String(line);
fsOut.Write(entityByte, 0, entityByte.Length);
}
catch (FormatException) // Skip errors for first and last lines. Example first line in last 1 mb from file or last line in first 1 mb from file
{
if ((entityIndex > 1) && (!reader.EndOfStream))
{
throw;
}
}
if (UIUserStop())
{
break;
}
}
}
}
// .bond file is under %TEMP% let's delete it
if (filename.StartsWith(Path.GetTempPath(), StringComparison.CurrentCultureIgnoreCase))
{
File.Delete(filename);
}
}
catch (Exception e)
{
if (IsFileExistAndNotEmpty(tempBondFilename))
{
Far.Api.ShowError("Conversion of base64 Bond to binary Bond failed", e);
}
}
pb.Dispose();
Far.Api.UI.Redraw();
return tempBondFilename;
}
/// <summary>
/// Returns True if ESC was pressed, otherwise False
/// </summary>
/// <returns>Returns True if ESC was pressed, otherwise False</returns>
public static bool UIUserStop()
{
if ((Console.KeyAvailable) && (Console.ReadKey().Key == ConsoleKey.Escape))
{
return (0 == Far.Api.Message("Are you sure you want to cancel this operation?", "", MessageOptions.YesNo));
}
return false;
}
public static bool IsFileExistAndNotEmpty(string filename)
{
return (File.Exists(filename) && (new FileInfo(filename).Length > 0));
}
}
}