mirror of
https://github.com/whoeevee/ivinject.git
synced 2026-01-09 00:25:03 +01:00
First commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user