Refactor InteropFactory

This commit is contained in:
Michael Bucari-Tovo 2023-02-18 10:43:13 -07:00
parent 14b63c0883
commit a00849fb6f
12 changed files with 41 additions and 159 deletions

View File

@ -2,7 +2,7 @@
"WindowsClassic": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip", "WindowsClassic": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-classic\\.zip",
"WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip", "WindowsAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-win(dows)?-chardonnay\\.zip",
"LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-amd64\\.deb", "LinuxAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-amd64\\.deb",
"MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-x64\\.tgz" "MacOSAvalonia": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-x64\\.tgz",
"LinuxAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-arm64\\.deb", "LinuxAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-linux-arm64\\.deb",
"MacOSAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-arm64\\.tgz" "MacOSAvalonia_Arm64": "Libation\\.\\d+\\.\\d+\\.\\d+-macOS-arm64\\.tgz"
} }

View File

@ -53,7 +53,7 @@ if [ $? -ne 0 ]
fi fi
delfiles=('libmp3lame.x64.dylib' 'libmp3lame.arm64.dylib' 'ffmpegaac.x64.dylib' 'ffmpegaac.arm64.dylib' 'libmp3lame.x64.dll' 'ffmpegaac.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.x86.dll' 'LinuxConfigApp' 'LinuxConfigApp.runtimeconfig.json' 'LinuxConfigApp.deps.json') delfiles=('libmp3lame.arm64.dylib' 'libmp3lame.x64.dylib' 'libmp3lame.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.arm64.dylib' 'ffmpegaac.x64.dylib' 'ffmpegaac.x64.dll' 'ffmpegaac.x86.dll' 'LinuxConfigApp' 'LinuxConfigApp.deps.json' 'LinuxConfigApp.runtimeconfig.json')
if [[ "$ARCH" == "arm64" ]] if [[ "$ARCH" == "arm64" ]]
then then
delfiles+=('libmp3lame.x64.so' 'ffmpegaac.x64.so') delfiles+=('libmp3lame.x64.so' 'ffmpegaac.x64.so')

View File

@ -79,7 +79,7 @@ echo "Set CFBundleVersion to $VERSION"
sed -i -e "s/VERSION_STRING/$VERSION/" $BUNDLE_CONTENTS/Info.plist sed -i -e "s/VERSION_STRING/$VERSION/" $BUNDLE_CONTENTS/Info.plist
delfiles=('libmp3lame.x64.so' 'ffmpegaac.x64.so' 'libmp3lame.arm64.so' 'ffmpegaac.arm64.so' 'libmp3lame.x64.dll' 'ffmpegaac.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.x86.dll' 'ffmpegaac.x86.dll' 'MacOSConfigApp' 'MacOSConfigApp.runtimeconfig.json' 'MacOSConfigApp.deps.json') delfiles=( 'libmp3lame.arm64.so' 'libmp3lame.x64.so' 'libmp3lame.x64.dll' 'libmp3lame.x86.dll' 'ffmpegaac.arm64.so' 'ffmpegaac.x64.so' 'ffmpegaac.x64.dll' 'ffmpegaac.x86.dll' 'MacOSConfigApp' 'MacOSConfigApp.deps.json' 'MacOSConfigApp.runtimeconfig.json')
if [[ "$ARCH" == "arm64" ]] if [[ "$ARCH" == "arm64" ]]
then then
delfiles+=('libmp3lame.x64.dylib' 'ffmpegaac.x64.dylib') delfiles+=('libmp3lame.x64.dylib' 'ffmpegaac.x64.dylib')

View File

@ -1,29 +0,0 @@
using System;
namespace AppScaffolding
{
public abstract class OSConfigBase
{
public abstract Type InteropFunctionsType { get; }
public virtual Type[] ReferencedTypes { get; } = new Type[0];
public void Run()
{
//Each of these types belongs to a different windows-only assembly that's needed by
//the WinInterop methods. By referencing these types in main we force the runtime to
//load their assemblies before execution reaches inside main. This allows the calling
//process to find these assemblies in its module list.
_ = ReferencedTypes;
_ = InteropFunctionsType;
//Wait for the calling process to be ready to read the WriteLine()
Console.ReadLine();
// Signal the calling process that execution has reached inside main, and that all referenced assemblies have been loaded.
Console.WriteLine();
// Wait for the calling process to finish reading the process module list, then exit.
Console.ReadLine();
}
}
}

View File

@ -24,18 +24,15 @@ namespace LibationAvalonia
//We can do this because we're already executing inside the sandbox. //We can do this because we're already executing inside the sandbox.
//Any process created in the sandbox executes in the same sandbox. //Any process created in the sandbox executes in the same sandbox.
//Unfortunately, all sandbox files are read/execute, so no writing! //Unfortunately, all sandbox files are read/execute, so no writing!
Process.Start("Hangover");
Assembly asm = Assembly.GetExecutingAssembly();
string path = Path.GetDirectoryName(asm.Location);
Process.Start("Hangover" + (Configuration.IsWindows ? ".exe" : ""));
return; return;
} }
if (Configuration.IsMacOs && args?.Length > 0 && args[0] == "cli") if (Configuration.IsMacOs && args?.Length > 0 && args[0] == "cli")
{ {
//Open a new Terminal in the sandbox //Open a new Terminal in the sandbox
Assembly asm2 = Assembly.GetExecutingAssembly(); Process.Start(
string libationProgramFiles = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",
Process.Start("/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", $"\"{libationProgramFiles}\""); $"\"{Configuration.ProcessDirectory}\"");
return; return;
} }

View File

@ -9,8 +9,9 @@ namespace LibationFileManager
{ {
public partial class Configuration public partial class Configuration
{ {
public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}"; public static string ProcessDirectory { get; } = Path.GetDirectoryName(Exe.FileLocationOnDisk);
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES_KEY)); public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}";
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation")); public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation")); public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation")); public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation"));

View File

@ -76,7 +76,7 @@ namespace LibationFileManager
//Possible appsettings.json locations, in order of preference. //Possible appsettings.json locations, in order of preference.
string[] possibleAppsettingsFiles = new[] string[] possibleAppsettingsFiles = new[]
{ {
Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), appsettings_filename), Path.Combine(ProcessDirectory, appsettings_filename),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation", appsettings_filename), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation", appsettings_filename),
Path.Combine(UserProfile, appsettings_filename), Path.Combine(UserProfile, appsettings_filename),
Path.Combine(Path.GetTempPath(), "Libation", appsettings_filename) Path.Combine(Path.GetTempPath(), "Libation", appsettings_filename)

View File

@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using Dinah.Core; using Dinah.Core;
namespace LibationFileManager namespace LibationFileManager
@ -25,21 +23,29 @@ namespace LibationFileManager
instance ??= instance ??=
InteropFunctionsType is null InteropFunctionsType is null
? new NullInteropFunctions() ? new NullInteropFunctions()
//: values is null || values.Length == 0 ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions
: Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions; : Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions;
return instance; return instance;
} }
#region load types #region load types
public static Func<string, bool> MatchesOS { get; } private const string CONFIG_APP_ENDING = "ConfigApp.dll";
public static Func<string, bool> MatchesOS { get; }
= Configuration.IsWindows ? a => Path.GetFileName(a).StartsWithInsensitive("win") = Configuration.IsWindows ? a => Path.GetFileName(a).StartsWithInsensitive("win")
: Configuration.IsLinux ? a => Path.GetFileName(a).StartsWithInsensitive("linux") : Configuration.IsLinux ? a => Path.GetFileName(a).StartsWithInsensitive("linux")
: Configuration.IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || Path.GetFileName(a).StartsWithInsensitive("osx") : Configuration.IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || Path.GetFileName(a).StartsWithInsensitive("osx")
: _ => false; : _ => false;
private const string CONFIG_APP_ENDING = "ConfigApp.dll"; private static readonly EnumerationOptions enumerationOptions = new()
private static List<ProcessModule> ModuleList { get; } = new(); {
MatchType = MatchType.Simple,
MatchCasing = MatchCasing.CaseInsensitive,
IgnoreInaccessible = true,
RecurseSubdirectories = false,
ReturnSpecialDirectories = false
};
static InteropFactory() static InteropFactory()
{ {
// searches file names for potential matches; doesn't run anything // searches file names for potential matches; doesn't run anything
@ -52,94 +58,36 @@ namespace LibationFileManager
return; return;
} }
/*
* Commented code used to locate assemblies from the *ConfigApp.exe's module list.
* Use this method to locate dependencies when they are not in Libation's program files directory.
#if DEBUG
// runs the exe and gets the exe's loaded modules
ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp))
.OrderBy(x => x.ModuleName)
.ToList();
#endif
*/
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
var configAppAssembly = Assembly.LoadFrom(configApp); var configAppAssembly = Assembly.LoadFrom(configApp);
var type = typeof(IInteropFunctions); var type = typeof(IInteropFunctions);
InteropFunctionsType = configAppAssembly InteropFunctionsType = configAppAssembly
.GetTypes() .GetTypes()
.FirstOrDefault(t => type.IsAssignableFrom(t)); .FirstOrDefault(type.IsAssignableFrom);
} }
private static string getOSConfigApp() private static string getOSConfigApp()
{ {
var here = Path.GetDirectoryName(Environment.ProcessPath);
// find '*ConfigApp.dll' files // find '*ConfigApp.dll' files
var appName = var appName =
Directory.EnumerateFiles(here, $"*{CONFIG_APP_ENDING}", SearchOption.TopDirectoryOnly) Directory.EnumerateFiles(Configuration.ProcessDirectory, $"*{CONFIG_APP_ENDING}", enumerationOptions)
// sanity check. shouldn't ever be true
.Except(new[] { Environment.ProcessPath })
.FirstOrDefault(exe => MatchesOS(exe)); .FirstOrDefault(exe => MatchesOS(exe));
return appName; return appName;
} }
/*
* Use this method to locate dependencies when they are not in Libation's program files directory.
*
private static List<ProcessModule> LoadModuleList(string exeName)
{
var proc = new Process
{
StartInfo = new()
{
FileName = exeName,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
}
};
var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
proc.OutputDataReceived += (_, _) => waitHandle.Set();
proc.Start();
proc.BeginOutputReadLine();
//Let the win process know we're ready to receive its standard output
proc.StandardInput.WriteLine();
if (!waitHandle.WaitOne(2000))
throw new Exception("Failed to start program");
//The win process has finished loading and is now waiting inside Main().
//Copy it process module list.
var modules = proc.Modules.Cast<ProcessModule>().ToList();
//Let the win process know we're done reading its module list
proc.StandardInput.WriteLine();
return modules;
}
*/
private static Dictionary<string, Assembly> lowEffortCache { get; } = new(); private static Dictionary<string, Assembly> lowEffortCache { get; } = new();
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{ {
// e.g. "System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" var asmName = new AssemblyName(args.Name);
var asmName = args.Name.Split(',')[0] + ".dll"; var here = Configuration.ProcessDirectory;
var here = Path.GetDirectoryName(Environment.ProcessPath);
var key = $"{asmName}|{here}"; var key = $"{asmName}|{here}";
if (lowEffortCache.TryGetValue(key, out var value)) if (lowEffortCache.TryGetValue(key, out var value))
return value; return value;
var assembly = CurrentDomain_AssemblyResolve_internal(asmName: asmName, here: here); var assembly = CurrentDomain_AssemblyResolve_internal(asmName, here: here);
lowEffortCache[key] = assembly; lowEffortCache[key] = assembly;
//Let the runtime handle any dll not found exceptions. //Let the runtime handle any dll not found exceptions.
@ -149,27 +97,14 @@ namespace LibationFileManager
return assembly; return assembly;
} }
private static Assembly CurrentDomain_AssemblyResolve_internal(string asmName, string here) private static Assembly CurrentDomain_AssemblyResolve_internal(AssemblyName asmName, string here)
{ {
/*
* Commented code used to locate assemblies from the *ConfigApp.exe's module list.
* Use this method to locate dependencies when they are not in Libation's program files directory.
#if DEBUG
var modulePath = ModuleList.SingleOrDefault(m => m.ModuleName.EqualsInsensitive(asmName))?.FileName;
#else
*/
// find the requested assembly in the program files directory // find the requested assembly in the program files directory
var modulePath = var modulePath =
Directory.EnumerateFiles(here, asmName, SearchOption.TopDirectoryOnly) Directory.EnumerateFiles(here, $"{asmName.Name}.dll", enumerationOptions)
.SingleOrDefault(); .SingleOrDefault();
//#endif return modulePath is null ? null : Assembly.LoadFrom(modulePath);
if (modulePath is null)
return null;
return Assembly.LoadFrom(modulePath);
} }
#endregion #endregion

View File

@ -1,6 +1,5 @@
using LibationFileManager; using LibationFileManager;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection;
namespace LinuxConfigApp namespace LinuxConfigApp
{ {
@ -39,8 +38,6 @@ namespace LinuxConfigApp
//prompt across multiple distributions and desktop environments. //prompt across multiple distributions and desktop environments.
const string runasroot = "runasroot.sh"; const string runasroot = "runasroot.sh";
var asmDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string command = $"{exe ?? ""} {args ?? ""}".Trim(); string command = $"{exe ?? ""} {args ?? ""}".Trim();
foreach (var console in consoleCommands) foreach (var console in consoleCommands)
@ -55,7 +52,7 @@ namespace LinuxConfigApp
$"Running '{exe}' as root", // console title $"Running '{exe}' as root", // console title
console[2], console[2],
"/bin/sh", "/bin/sh",
Path.Combine(asmDir, runasroot), //script file Path.Combine(Configuration.ProcessDirectory, runasroot), //script file
"Installing libation.deb", //command title "Installing libation.deb", //command title
command, // command to execute vis /bin/sh command, // command to execute vis /bin/sh
$"Please run '{command}' manually" // error message to display in the terminal $"Please run '{command}' manually" // error message to display in the terminal

View File

@ -1,11 +1,7 @@
using AppScaffolding; namespace LinuxConfigApp
namespace LinuxConfigApp
{ {
class Program : OSConfigBase class Program
{ {
public override Type InteropFunctionsType => typeof(LinuxInterop); static void Main() { }
static void Main() => new Program().Run();
} }
} }

View File

@ -1,11 +1,7 @@
using AppScaffolding; namespace MacOSConfigApp
namespace MacOSConfigApp
{ {
class Program : OSConfigBase class Program
{ {
public override Type InteropFunctionsType => typeof(MacOSInterop); static void Main() { }
static void Main() => new Program().Run();
} }
} }

View File

@ -1,18 +1,7 @@
using AppScaffolding;
namespace WindowsConfigApp namespace WindowsConfigApp
{ {
class Program : OSConfigBase class Program
{ {
public override Type InteropFunctionsType => typeof(WinInterop); static void Main() { }
public override Type[] ReferencedTypes => new Type[]
{
typeof(Bitmap),
typeof(Dinah.Core.WindowsDesktop.GitClient),
typeof(Accessibility.IAccIdentity),
typeof(Microsoft.Win32.SystemEvents)
};
static void Main() => new Program().Run();
} }
} }