Skip to content

Instantly share code, notes, and snippets.

@TheFanatr
Last active September 21, 2018 03:54
Show Gist options
  • Save TheFanatr/af26760d66dc4a9501c355d4b08483da to your computer and use it in GitHub Desktop.
Save TheFanatr/af26760d66dc4a9501c355d4b08483da to your computer and use it in GitHub Desktop.
Approximately functional utlilities class for persisting data, containing possibly unstable code for non-Windows environments.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
namespace StandardStorage
{
/// <summary>
/// A collection of useful storage utilities for finding storage paths.
/// </summary>
public static class StorageUtilities
{
/// <summary>
/// Creates and returns the full path to an app-specific local user storage folder.
/// </summary>
public static string LocalUserAppDataPath => GetAppSpecificStoragePathFromBasePath(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
/// <summary>
/// Creates and returns the full path to an app-specific storage folder based on <paramref name="basePath"/>.
/// </summary>
/// <param name="basePath"></param>
/// <returns>returns a full path to the created storage folder.</returns>
internal static string GetAppSpecificStoragePathFromBasePath(string basePath) => Directory.CreateDirectory(Path.Combine(basePath, CompanyName, ProductName, ProductVersion)).FullName;
static string productVersion;
/// <summary>
/// Gets the product version of the current top-level assembly using this library.
/// </summary>
internal static string ProductVersion
{
get
{
if (productVersion == null)
{
// Try custom attribute.
object[] attrs = Assembly.GetEntryAssembly()?.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false);
productVersion = attrs != null && attrs.Length > 0
? ((AssemblyInformationalVersionAttribute)attrs[0]).InformationalVersion
: WindowsUtilities.GetAppFileVersionInfo().ProductVersion is string windowsProductVersion ? windowsProductVersion.Trim() : "1.0.0.0";
}
return productVersion;
}
}
static string productName;
/// <summary>
/// Gets the prodcut name of the current top-level assembly using this library.
/// </summary>
internal static string ProductName
{
get
{
if (productName == null)
{
// Try custom attribute.
if (Assembly.GetEntryAssembly()?.GetCustomAttributes(typeof(AssemblyProductAttribute), false) is object[] attrs && attrs.Length > 0 && ((AssemblyProductAttribute)attrs[0])?.Product is string attributesProductName && !String.IsNullOrWhiteSpace(attributesProductName))
productName = attributesProductName;
// Try win32 version info.
else if (GetAppFileVersionInfo()?.ProductName is string fileProductName && !String.IsNullOrWhiteSpace(fileProductName))
productName = fileProductName.Trim();
// Fake it with the main root namespace.
// WARNING: Does not work with MC++. See GetAppMainType.
else
{
Type t = GetAppMainType();
if (t != null)
{
string ns = t.Namespace;
productName = !String.IsNullOrWhiteSpace(ns)
? ns.LastIndexOf(".", StringComparison.CurrentCulture) is int lastDot && lastDot != -1 && lastDot < ns.Length - 1 ? ns.Substring(lastDot + 1) : ns
: t.Name;
}
}
}
return productName;
}
}
static string companyName;
/// <summary>
/// Gets the company name of the current top-level assembly using this library.
/// </summary>
internal static string CompanyName
{
get
{
return companyName =
(companyName is null)
? Assembly.GetEntryAssembly()?.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false) is object[] attrs && attrs.Length > 0
? ((AssemblyCompanyAttribute)attrs[0]).Company
: GetAppMainType()?.Namespace is string ns && !String.IsNullOrWhiteSpace(ns) ? ns.IndexOf(".", StringComparison.CurrentCulture) is int firstDot && firstDot != -1 ? ns.Substring(0, firstDot) : ns
: Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemPlatform == Microsoft.DotNet.PlatformAbstractions.Platform.Windows && WindowsUtilities.GetAppFileVersionInfo()?.CompanyName is string appFileVersionCompanyName && !String.IsNullOrWhiteSpace(appFileVersionCompanyName)
? appFileVersionCompanyName.Trim() : productName
: companyName;
}
}
static string executablePath;
/// <summary>
/// A full path to the current top-level executable assembly using this library.
/// </summary>
public static string ExecutablePath
{
get
{
if (executablePath is null)
{
executablePath = new Uri(Assembly.GetEntryAssembly()?.CodeBase) is Uri codeBase
?
codeBase.IsFile ? codeBase.LocalPath + Uri.UnescapeDataString(codeBase.Fragment) : codeBase.ToString()
:
WindowsUtilities.UnsafeGetFullPath(WindowsUtilities.GetModuleFileNameLongPath(new HandleRef(null, IntPtr.Zero)).ToString());
}
return executablePath;
}
}
protected static class WindowsUtilities
{
public static bool CurrentlyUsable => Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemPlatform == Microsoft.DotNet.PlatformAbstractions.Platform.Windows;
static FileVersionInfo appFileVersion;
internal static FileVersionInfo GetAppFileVersionInfo()
{
try
{
if (appFileVersion == null)
{
Type t = GetAppMainType();
if (t != null)
{
new FileIOPermission(PermissionState.None) { AllFiles = FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read }.Assert();
try { appFileVersion = FileVersionInfo.GetVersionInfo(t.Module.FullyQualifiedName); }
finally { CodeAccessPermission.RevertAssert(); }
}
else appFileVersion = FileVersionInfo.GetVersionInfo(ExecutablePath);
}
}
catch (DllNotFoundException)
{
return null;
}
return appFileVersion;
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);
/// <summary>
/// Gets the long path to the module pointed to by <paramref name="hModule"/>.
/// </summary>
/// <param name="hModule">A handle to the module that the long path is needed for.</param>
/// <returns>The long path to the module pointed to by <paramref name="hModule"/></returns>
internal static StringBuilder GetModuleFileNameLongPath(HandleRef hModule)
{
StringBuilder buffer = new StringBuilder(260);
int noOfTimes = 1;
int length = 0;
// Iterating by allocating chunk of memory each time we find the length is not sufficient.
// Performance should not be an issue for current MAX_PATH length due to this change.
while ((length = GetModuleFileName(hModule, buffer, buffer.Capacity)) == buffer.Capacity && Marshal.GetLastWin32Error() == 122 && buffer.Capacity < Int16.MaxValue)
{
noOfTimes += 2; // Increasing buffer size by 520 in each iteration.
buffer.EnsureCapacity(noOfTimes * 260 < Int16.MaxValue ? noOfTimes * 260 : Int16.MaxValue);
}
buffer.Length = length;
return buffer;
}
internal static string UnsafeGetFullPath(string fileName)
{
string full = fileName;
FileIOPermission fiop = new FileIOPermission(PermissionState.None) { AllFiles = FileIOPermissionAccess.PathDiscovery };
fiop.Assert();
try { full = Path.GetFullPath(fileName); }
finally { CodeAccessPermission.RevertAssert(); }
if (new Uri(full) is Uri exeUri && exeUri.Scheme == "file")
new FileIOPermission(FileIOPermissionAccess.PathDiscovery, full).Demand();
return full;
}
}
static Type mainType;
// Get Main type...This doesn't work in MC++ because Main is a global function and not
// a class static method (it doesn't belong to a Type).
static Type GetAppMainType() => mainType ?? (mainType = Assembly.GetEntryAssembly()?.EntryPoint?.ReflectedType);
}
}
@TheFanatr
Copy link
Author

This is here for safekeeping in case I ever decide to re-add platform-specific behaviour to StandardStorage's StorageUtilities class. StorageUtilities is supposed to be platform-agnostic, so I removed any code that performs platform invocations in order to hopefully increase the stability of the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment