First commit

This commit is contained in:
eevee
2024-11-03 18:29:07 +03:00
commit db63ef6ff2
41 changed files with 1742 additions and 0 deletions
@@ -0,0 +1,36 @@
using Claunia.PropertyList;
using static ivinject.Features.Packaging.Models.InfoPlistDictionaryKeys;
namespace ivinject.Features.Packaging;
internal static class InfoPlistDictionaryExtensions
{
internal static string? BundleIdentifier(this NSDictionary dictionary) =>
dictionary.TryGetValue(CoreFoundationBundleIdentifierKey, out var bundleIdObject)
? ((NSString)bundleIdObject).Content
: null;
internal static string? WatchKitCompanionAppBundleIdentifier(this NSDictionary dictionary) =>
dictionary.TryGetValue(WatchKitCompanionAppBundleIdentifierKey, out var bundleIdObject)
? ((NSString)bundleIdObject).Content
: null;
internal static NSDictionary? Extension(this NSDictionary dictionary) =>
dictionary.TryGetValue(NextStepExtensionKey, out var extension)
? (NSDictionary)extension
: null;
internal static string ExtensionPointIdentifier(this NSDictionary dictionary) =>
((NSString)dictionary[NextStepExtensionPointIdentifierKey]).Content;
internal static NSDictionary ExtensionAttributes(this NSDictionary dictionary) =>
(NSDictionary)dictionary[NextStepExtensionAttributesKey];
internal static string WatchKitAppBundleIdentifier(this NSDictionary dictionary) =>
((NSString)dictionary[WatchKitAppBundleIdentifierKey]).Content;
internal static string BundleExecutable(this NSDictionary dictionary) =>
((NSString)dictionary[CoreFoundationBundleExecutableKey]).Content;
internal static async Task SaveToFile(this NSDictionary dictionary, string filePath) =>
await File.WriteAllTextAsync(filePath, dictionary.ToXmlPropertyList());
}
@@ -0,0 +1,7 @@
namespace ivinject.Features.Packaging.Models;
internal class DirectoryNames
{
internal const string FrameworksDirectoryName = "Frameworks";
internal const string PlugInsDirectoryName = "PlugIns";
}
@@ -0,0 +1,16 @@
namespace ivinject.Features.Packaging.Models;
internal static class InfoPlistDictionaryKeys
{
internal const string UiKitSupportedDevicesKey = "UISupportedDevices";
internal const string UiKitSupportsDocumentBrowserKey = "UISupportsDocumentBrowser";
internal const string UiKitFileSharingEnabledKey = "UIFileSharingEnabled";
internal const string CoreFoundationBundleIdentifierKey = "CFBundleIdentifier";
internal const string CoreFoundationBundleExecutableKey = "CFBundleExecutable";
internal const string WatchKitCompanionAppBundleIdentifierKey = "WKCompanionAppBundleIdentifier";
internal const string NextStepExtensionKey = "NSExtension";
internal const string NextStepExtensionPointIdentifierKey = "NSExtensionPointIdentifier";
internal const string NextStepExtensionAttributesKey = "NSExtensionAttributes";
internal const string WatchKitAppBundleIdentifierKey = "WKAppBundleIdentifier";
}
@@ -0,0 +1,10 @@
using static ivinject.Features.Packaging.Models.DirectoryNames;
namespace ivinject.Features.Packaging.Models;
internal class IviDirectoriesInfo(string bundleDirectory)
{
internal string BundleDirectory { get; } = bundleDirectory;
internal string FrameworksDirectory { get; } = Path.Combine(bundleDirectory, FrameworksDirectoryName);
internal string PlugInsDirectory { get; } = Path.Combine(bundleDirectory, PlugInsDirectoryName);
}
@@ -0,0 +1,12 @@
using ivinject.Common.Models;
namespace ivinject.Features.Packaging.Models;
internal class IviPackageInfo(string mainBinary, string bundleIdentifier, IviDirectoriesInfo directoriesInfo)
{
internal IviMachOBinary MainBinary { get; } = new(
Path.Combine(directoriesInfo.BundleDirectory, mainBinary)
);
internal string BundleIdentifier { get; } = bundleIdentifier;
internal IviDirectoriesInfo DirectoriesInfo { get; } = directoriesInfo;
}
+67
View File
@@ -0,0 +1,67 @@
using System.IO.Compression;
using Claunia.PropertyList;
using ivinject.Features.Packaging.Models;
using Microsoft.Extensions.Logging;
using static ivinject.Common.DirectoryExtensions;
namespace ivinject.Features.Packaging;
internal partial class PackageManager(ILogger logger) : IDisposable
{
private readonly string _tempDirectory = TempDirectoryPath();
private string TempPayloadDirectory => Path.Combine(_tempDirectory, "Payload");
private string _bundleDirectory = null!;
private FileInfo _infoDictionaryFile = null!;
private NSDictionary _infoDictionary = null!;
internal IviPackageInfo PackageInfo { get; private set; } = null!;
private void LoadPackageInfo()
{
_infoDictionaryFile = new FileInfo(
Path.Combine(_bundleDirectory, "Info.plist")
);
_infoDictionary = (NSDictionary)PropertyListParser.Parse(_infoDictionaryFile);
PackageInfo = new IviPackageInfo(
_infoDictionary.BundleExecutable(),
((NSString)_infoDictionary["CFBundleIdentifier"]).Content,
new IviDirectoriesInfo(_bundleDirectory)
);
}
private void ProcessAppPackage(string targetAppPackage)
{
var directoryInfo = new DirectoryInfo(targetAppPackage);
if (directoryInfo.Exists)
{
var packageName = directoryInfo.Name;
_bundleDirectory = Path.Combine(TempPayloadDirectory, packageName);
CopyDirectory(targetAppPackage, _bundleDirectory, true);
logger.LogInformation("Copied {}", packageName);
return;
}
var fileInfo = new FileInfo(targetAppPackage);
var fileName = fileInfo.Name;
ZipFile.ExtractToDirectory(targetAppPackage, _tempDirectory);
logger.LogInformation("Extracted {}", fileName);
_bundleDirectory = Directory.GetDirectories(TempPayloadDirectory)[0];
}
internal void LoadAppPackage(string targetAppPackage)
{
ProcessAppPackage(targetAppPackage);
LoadPackageInfo();
}
public void Dispose() => Directory.Delete(_tempDirectory, true);
}
@@ -0,0 +1,104 @@
using Claunia.PropertyList;
using ivinject.Features.Command.Models;
using Microsoft.Extensions.Logging;
using static ivinject.Features.Packaging.Models.InfoPlistDictionaryKeys;
namespace ivinject.Features.Packaging;
internal partial class PackageManager
{
private void RemoveSupportedDevices()
{
if (_infoDictionary.Remove(UiKitSupportedDevicesKey))
logger.LogInformation("Removed supported devices property");
else
logger.LogWarning("Unable to remove supported devices property. The key is likely not present.");
}
private void EnableDocumentSupport()
{
_infoDictionary[UiKitSupportsDocumentBrowserKey] = new NSNumber(true);
_infoDictionary[UiKitFileSharingEnabledKey] = new NSNumber(true);
logger.LogInformation("Enabled documents support for the application");
}
private void RemoveDirectories(IEnumerable<string> directories)
{
foreach (var directory in directories)
{
Directory.Delete(Path.Combine(_bundleDirectory, directory), true);
logger.LogInformation("Removed {} directory from the app package", directory);
}
}
private static void ReplaceWatchKitIdentifiers(
NSDictionary dictionary,
string customBundleId,
string packageBundleId
)
{
if (dictionary.WatchKitCompanionAppBundleIdentifier() is not null)
dictionary[WatchKitCompanionAppBundleIdentifierKey] = new NSString(customBundleId);
if (dictionary.Extension() is not { } extension
|| extension.ExtensionPointIdentifier() != "com.apple.watchkit")
return;
var attributes = extension.ExtensionAttributes();
var watchKitAppBundleId = attributes.WatchKitAppBundleIdentifier();
attributes[WatchKitAppBundleIdentifierKey] = new NSString(
watchKitAppBundleId.Replace(packageBundleId, customBundleId)
);
}
private async Task ReplaceBundleIdentifiers(string customBundleId)
{
var packageBundleId = PackageInfo.BundleIdentifier;
var replacedCount = 0;
var infoPlistFiles = Directory.EnumerateFiles(
_bundleDirectory,
"Info.plist",
SearchOption.AllDirectories
);
foreach (var file in infoPlistFiles)
{
var dictionary = (NSDictionary)PropertyListParser.Parse(file);
ReplaceWatchKitIdentifiers(dictionary, customBundleId, packageBundleId);
if (dictionary.BundleIdentifier() is not { } bundleId
|| !bundleId.Contains(packageBundleId))
continue;
var newBundleId = bundleId.Replace(packageBundleId, customBundleId);
dictionary[CoreFoundationBundleIdentifierKey] = new NSString(newBundleId);
await dictionary.SaveToFile(file);
replacedCount++;
}
logger.LogInformation("Replaced bundle identifier of {} bundles", replacedCount);
}
internal async Task PerformPackageModifications(IviPackagingInfo packagingInfo)
{
RemoveDirectories(packagingInfo.DirectoriesToRemove);
if (packagingInfo.RemoveSupportedDevices)
RemoveSupportedDevices();
if (packagingInfo.EnableDocumentsSupport)
EnableDocumentSupport();
await _infoDictionary.SaveToFile(_infoDictionaryFile.FullName);
if (packagingInfo.CustomBundleId is not { } customBundleId)
return;
await ReplaceBundleIdentifiers(customBundleId);
}
}
@@ -0,0 +1,81 @@
using System.IO.Compression;
using Microsoft.Extensions.Logging;
using static ivinject.Common.DirectoryExtensions;
namespace ivinject.Features.Packaging;
internal partial class PackageManager
{
private bool CopyAppPackage(string outputAppPackage, bool overwrite, ref bool isOverwritten)
{
var packageDirectory = new DirectoryInfo(outputAppPackage);
if (packageDirectory.Exists)
{
if (overwrite)
{
packageDirectory.Delete(true);
isOverwritten = true;
}
else
{
return false;
}
}
CopyDirectory(_bundleDirectory, packageDirectory.FullName, true);
logger.LogInformation("{} {}", isOverwritten ? "Replaced" : "Copied", packageDirectory.Name);
return true;
}
private bool CreateAppArchive(
string outputAppPackage,
bool overwrite,
CompressionLevel compressionLevel,
ref bool isOverwritten
)
{
var packageFile = new FileInfo(outputAppPackage);
if (packageFile.Exists)
{
if (overwrite)
{
packageFile.Delete();
isOverwritten = true;
}
else
{
return false;
}
}
foreach (var dotFile in Directory.EnumerateFiles(TempPayloadDirectory, ".*"))
{
var fileInfo = new FileInfo(dotFile);
File.Delete(fileInfo.FullName);
logger.LogWarning("Removed {} from the app package", fileInfo.Name);
}
ZipFile.CreateFromDirectory(
TempPayloadDirectory,
packageFile.FullName,
compressionLevel,
true
);
logger.LogInformation("{} {}", isOverwritten ? "Replaced" : "Created", packageFile.Name);
return true;
}
internal bool CreateAppPackage(string outputAppPackage, bool overwrite, CompressionLevel compressionLevel)
{
var isOverwritten = false;
return outputAppPackage.EndsWith(".app")
? CopyAppPackage(outputAppPackage, overwrite, ref isOverwritten)
: CreateAppArchive(outputAppPackage, overwrite, compressionLevel, ref isOverwritten);
}
}