diff --git a/IPASorter/FileClass.cs b/IPASorter/FileClass.cs index ac4228b..6e3b624 100644 --- a/IPASorter/FileClass.cs +++ b/IPASorter/FileClass.cs @@ -12,5 +12,11 @@ namespace IPASorter public string CFBundleIdentifier { get; set; } public string CFBundleVersion { get; set; } public string MinimumOSVersion { get; set; } + public string CFBundleDisplayName { get; set; } + } + + public class AppList + { + public IPAFile[] apps { get; set; } } } diff --git a/IPASorter/Program.cs b/IPASorter/Program.cs index 12ddf90..dbfd39a 100644 --- a/IPASorter/Program.cs +++ b/IPASorter/Program.cs @@ -7,6 +7,8 @@ using System.IO.Compression; using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace IPASorter @@ -26,16 +28,14 @@ namespace IPASorter // parse filepath if given string argsFilePath = args.Length != 0 ? args[0] : "./"; - if (!argsFilePath.EndsWith("/") || !argsFilePath.EndsWith("/")) argsFilePath += "/"; + if (!argsFilePath.EndsWith("/")) argsFilePath += "/"; // run steps FileScanner(argsFilePath); // MD5Eliminator(); obsolete - InfoPlistRenamer(); - if(args.Length > 1 && args[1] == "-si") - { - SortByiOSCompatibility(); - } + InfoPlistRenamer(argsFilePath); + SortByiOSCompatibility(argsFilePath); + GenerateJson(); Console.WriteLine("complete :)"); } @@ -43,12 +43,14 @@ namespace IPASorter // step 1 static void FileScanner(string path) { + Console.WriteLine("Scanning subdirectories for IPA files"); List tmp = Directory.GetFiles(path, "*.ipa", SearchOption.AllDirectories).ToList(); foreach (string s in tmp) { + Console.WriteLine($"Found {s}"); files.Add(new IPAFile { - fileName = s.Split('/')[s.Split('/').Length -1].Split('/')[s.Split('/')[s.Split('/').Length - 1].Split('/').Length - 1], + fileName = s.Split('/')[s.Split('/').Length -1], path = s, md5sum = CalculateMD5(s) }) ; @@ -56,9 +58,10 @@ namespace IPASorter } // step 2 - static void InfoPlistRenamer() + static void InfoPlistRenamer(string path) { Directory.CreateDirectory("./sortertemp"); + Directory.CreateDirectory($"{path}/incomplete"); foreach (IPAFile i in files) { Console.WriteLine($"fixing name of {i.fileName}"); @@ -66,17 +69,47 @@ namespace IPASorter // extract ipa Directory.CreateDirectory($"./sortertemp/{i.fileName}"); ZipFile.ExtractToDirectory(i.path, $"./sortertemp/{i.fileName}"); - string appPath = $"./sortertemp/{i.fileName}/Payload/{Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/')[Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/').Length - 1]}"; - // parse plist - Dictionary plist = (Dictionary)Plist.readPlist(appPath + "/Info.plist"); + Dictionary plist = new Dictionary(); + try + { + string appPath = $"./sortertemp/{i.fileName}/Payload/{Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/')[Directory.GetDirectories($"./sortertemp/{i.fileName}/Payload/")[0].Split('/').Length - 1]}"; + plist = (Dictionary)Plist.readPlist(appPath + "/Info.plist"); + } + catch(Exception) + { + Console.WriteLine($"{i.fileName} has a missing/damaged Info.plist. moving to the broken directory..."); + File.Move(i.path, $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}", true); + i.path = $"{path}/incomplete/{i.fileName.Replace(".ipa", $"-{i.md5sum}.ipa")}"; + continue; + } Directory.Delete($"./sortertemp/{i.fileName}", true); i.CFBundleIdentifier = plist["CFBundleIdentifier"].ToString(); - i.CFBundleVersion = plist["CFBundleVersion"].ToString(); - i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); + try + { + i.CFBundleDisplayName = RemoveIllegalFileNameChars(plist["CFBundleDisplayName"].ToString()); + } + catch (KeyNotFoundException) + { + i.CFBundleDisplayName = i.CFBundleIdentifier.Split('.')[2]; + } + string whichToUse = "CFBundleVersion"; + if (plist["CFBundleVersion"].ToString() == "1") + { + whichToUse = "CFBundleShortVersionString"; + } + i.CFBundleVersion = plist[whichToUse].ToString(); + try + { + i.MinimumOSVersion = plist["MinimumOSVersion"].ToString(); + } + catch (KeyNotFoundException) + { + i.MinimumOSVersion = "2.0"; + } // rename file - string newFileName = $"{plist["CFBundleIdentifier"]}-{plist["CFBundleVersion"]}-(iOS_{plist["MinimumOSVersion"]})-{i.md5sum}.ipa"; + string newFileName = $"{i.CFBundleDisplayName}-({i.CFBundleIdentifier})-{i.CFBundleVersion}-(iOS_{i.MinimumOSVersion})-{i.md5sum}.ipa"; File.Move(i.path, i.path.Replace(i.fileName, newFileName), true); i.path = i.path.Replace(i.fileName, newFileName); i.fileName = newFileName; @@ -84,13 +117,30 @@ namespace IPASorter Directory.Delete("./sortertemp", true); } - // optional step 3 - static void SortByiOSCompatibility() + // step 3 + static void SortByiOSCompatibility(string path) { + Console.WriteLine("Sorting apps by minimum iOS version"); + foreach(IPAFile i in files) + { + if (i.path == "DO NOT ENUMERATE") continue; + Directory.CreateDirectory($"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}"); + File.Move(i.path, $"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}/{i.fileName}", true); + i.path = $"{path}/iOS{i.MinimumOSVersion.Split('.')[0]}/{i.CFBundleIdentifier}/{i.fileName}"; + } } - // othet stuff + // step 4 + static void GenerateJson() + { + AppList apps = new AppList(); + apps.apps = files.ToArray(); + string appsJson = JsonSerializer.Serialize(apps); + File.WriteAllText("./apps.json", appsJson); + } + + // other stuff (hate) static string CalculateMD5(string fileName) { using (var md5 = MD5.Create()) @@ -102,6 +152,13 @@ namespace IPASorter } } } + + public static string RemoveIllegalFileNameChars(string input, string replacement = "") + { + var regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); + var r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch))); + return r.Replace(input, replacement); + } } } diff --git a/README.md b/README.md index 293ba5a..cb1c98c 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ Cross platform sorter for iOS app packages (IPAs) ## Command line arguments -`IPASorter ` -Running with no arguments will sort from the directory the program is in with no sorting options. -### Sorting Options -coming soon +`IPASorter ` or +`dotnet IPASorter.dll ` +Running with no arguments will sort from the directory the program is in. requires .net core 3.1 or later