Skip to content

Commit 45da117

Browse files
Support bzip2 & xz + ui updates
1 parent 48c7910 commit 45da117

3 files changed

Lines changed: 104 additions & 44 deletions

File tree

DebToIPA.xcodeproj/project.pbxproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@
299299
ENABLE_PREVIEWS = YES;
300300
GENERATE_INFOPLIST_FILE = YES;
301301
INFOPLIST_KEY_CFBundleDisplayName = DebToIPA;
302+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
302303
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
303304
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
304305
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -309,7 +310,7 @@
309310
"$(inherited)",
310311
"@executable_path/Frameworks",
311312
);
312-
MARKETING_VERSION = 0.3;
313+
MARKETING_VERSION = 1.0;
313314
PRODUCT_BUNDLE_IDENTIFIER = "net.sourceloc.deb-to-ipa-app";
314315
PRODUCT_NAME = "$(TARGET_NAME)";
315316
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -330,6 +331,7 @@
330331
ENABLE_PREVIEWS = YES;
331332
GENERATE_INFOPLIST_FILE = YES;
332333
INFOPLIST_KEY_CFBundleDisplayName = DebToIPA;
334+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
333335
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
334336
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
335337
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -340,7 +342,7 @@
340342
"$(inherited)",
341343
"@executable_path/Frameworks",
342344
);
343-
MARKETING_VERSION = 0.3;
345+
MARKETING_VERSION = 1.0;
344346
PRODUCT_BUNDLE_IDENTIFIER = "net.sourceloc.deb-to-ipa-app";
345347
PRODUCT_NAME = "$(TARGET_NAME)";
346348
SWIFT_EMIT_LOC_STRINGS = YES;

DebToIPA/ContentView.swift

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,52 @@ import SwiftUI
1010
struct ContentView: View {
1111
@State private var isImporting = false
1212
@State private var alertMessage = ""
13+
@State private var alertTitle = ""
1314
@State private var showingAlert = false
1415
@State private var converting = false
16+
@State private var performCleanup = true
17+
@State private var statusText = ""
1518

1619
var body: some View {
1720
NavigationView {
1821
VStack {
19-
ProgressView()
20-
.padding()
21-
.opacity(converting ? 1 : 0)
22-
Button("Import .deb", action: {
22+
HStack {
23+
if converting {
24+
ProgressView()
25+
.padding()
26+
}
27+
Text(statusText)
28+
.foregroundColor(.secondary)
29+
.font(.caption)
30+
}
31+
Button("Convert .deb app", action: {
2332
isImporting.toggle()
2433
})
25-
.padding(10)
26-
.background(Color.blue)
27-
.cornerRadius(8)
28-
.foregroundColor(.white)
29-
Text("By sourcelocation\n\nCredits: itsnebulalol,\nLebJe/ArArchiveKit,\nmarmelroy/Zip,\ntsolomko/SWCompression")
30-
.foregroundColor(Color.secondary)
31-
.font(.caption)
32-
.multilineTextAlignment(.center)
33-
34+
.padding(10)
35+
.background(Color.blue)
36+
.cornerRadius(8)
37+
.foregroundColor(.white)
38+
HStack(alignment: .center) {
39+
Text("By sourcelocation")
40+
.foregroundColor(.secondary)
41+
.font(.caption)
42+
Button {
43+
alert("\nCredits: itsnebulalol,\nLebJe/ArArchiveKit,\nmarmelroy/Zip,\ntsolomko/SWCompression", title: "Info")
44+
} label: {
45+
Image(systemName: "info.circle")
46+
}
47+
}
48+
HStack {
49+
Text("Clean after running")
50+
.font(.body)
51+
Toggle("Cleanup", isOn: $performCleanup)
52+
.labelsHidden()
53+
}
54+
.padding()
3455
}
3556
.navigationTitle("DebToIPA")
3657
}
58+
.navigationViewStyle(StackNavigationViewStyle())
3759
.fileImporter(
3860
isPresented: $isImporting,
3961
allowedContentTypes: [.init(filenameExtension: "deb")!],
@@ -44,44 +66,52 @@ struct ContentView: View {
4466
}
4567
.alert(isPresented: $showingAlert) {
4668
Alert(
47-
title: Text("Error"),
69+
title: Text(alertTitle),
4870
message: Text(alertMessage)
4971
)
5072
}
5173
}
5274

5375
func convert(url: URL) {
5476
converting = true
55-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
77+
DispatchQueue.global(qos: .userInitiated).async {
5678
do {
5779
guard url.startAccessingSecurityScopedResource() else { throw ConversionError.noPermission }
58-
let ipa = try DebToIPA.convert(url)
59-
share(ipa)
80+
let ipa = try DebToIPA.convert(url, statusUpdate: { message in
81+
DispatchQueue.main.async {
82+
statusText = message
83+
}
84+
})
85+
DispatchQueue.main.async {
86+
share(ipa)
87+
}
6088
} catch let error {
61-
print(error)
62-
if let convError = error as? ConversionError {
63-
switch convError {
64-
case .unsupportedApp:
65-
alert("The .deb you imported is UNSUPPORTED and CANNOT be converted to .ipa, as it doesn't have Applications folder.")
66-
case .noDataFound:
67-
alert("Data wasn't found in .deb. Are you sure the .deb you imported isn't corrupted?")
68-
case .noApplication:
69-
alert("The .deb you imported is UNSUPPORTED and CANNOT be converted to .ipa, as it doesn't have a .app file inside it.")
70-
case .noPermission:
71-
alert("No permission to view the file")
72-
case .unknownFiletypeInsideTar:
73-
alert("Unknown filetype inside tar. This is a bug with the app. Please create an issue on Github with the name of tweak/app. Thanks!")
74-
case .unsupportedCompression:
75-
alert("Unsupported compression. This is a bug with the app. Please create an issue on Github with the name of tweak/app. Thanks!")
89+
DispatchQueue.main.async {
90+
print(error)
91+
if let convError = error as? ConversionError {
92+
switch convError {
93+
case .unsupportedApp:
94+
alert("The .deb you imported is UNSUPPORTED and CANNOT be converted to .ipa, as it doesn't have Applications folder.")
95+
case .noDataFound:
96+
alert("Data wasn't found in .deb. Are you sure the .deb you imported isn't corrupted?")
97+
case .noApplication:
98+
alert("The .deb you imported is UNSUPPORTED and CANNOT be converted to .ipa, as it doesn't have a .app file inside it.")
99+
case .noPermission:
100+
alert("No permission to view the file")
101+
case .unknownFiletypeInsideTar:
102+
alert("Unknown filetype inside tar. This is a bug with the app. Please create an issue on Github with the name of tweak/app. Thanks!")
103+
case .unsupportedCompression:
104+
alert("Unsupported compression. This is a bug with the app. Please create an issue on Github with the name of tweak/app. Thanks!")
105+
}
106+
} else {
107+
alert("Unknown error.\n" + error.localizedDescription)
76108
}
77-
} else {
78-
alert("Unknown error.\n" + error.localizedDescription)
79109
}
80110
}
81111
url.stopAccessingSecurityScopedResource()
82112

83113
converting = false
84-
})
114+
}
85115
}
86116

87117
func share(_ url: URL) {
@@ -91,14 +121,23 @@ struct ContentView: View {
91121
shareActivity.popoverPresentationController?.sourceView = vc.view
92122
shareActivity.popoverPresentationController?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height, width: 0, height: 0)
93123
shareActivity.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
124+
shareActivity.completionWithItemsHandler = { (type,completed, returnedItems, activityError) in
125+
if performCleanup {
126+
do {
127+
try DebToIPA.cleanup()
128+
} catch {
129+
alert("Cleanup failed. Reinstall is most likely needed. " + error.localizedDescription)
130+
}
131+
}
132+
}
94133
DispatchQueue.main.async {
95-
vc.present(shareActivity, animated: true, completion: {
96-
})
134+
vc.present(shareActivity, animated: true)
97135
}
98136
}
99137
}
100138

101-
func alert(_ message: String) {
139+
func alert(_ message: String, title: String = "Error") {
140+
alertTitle = title
102141
alertMessage = message
103142
showingAlert.toggle()
104143
}

DebToIPA/DebToIPA.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,79 @@ class DebToIPA {
1515
private static var tempDir: URL { fm.temporaryDirectory }
1616

1717
/// Converts .deb app to .ipa, returns url of .ipa
18-
static func convert(_ url: URL) throws -> URL {
18+
static func convert(_ url: URL, statusUpdate: (String) -> ()) throws -> URL {
1919
try cleanup()
2020
let payloadDir = tempDir.appendingPathComponent("Payload")
2121

2222
// Extract .deb
23-
let appURLs = try extractDeb(url)
23+
let appURLs = try extractDeb(url, statusUpdate: statusUpdate)
2424

25+
statusUpdate("Creating .ipa archive")
2526
// Create .ipa archive
2627
try fm.createDirectory(at: payloadDir, withIntermediateDirectories: true)
2728
for url in appURLs {
2829
try fm.moveItem(at: url, to: payloadDir.appendingPathComponent( url.lastPathComponent))
2930
}
3031

3132
// Create archive of ipa folder
33+
statusUpdate("Zipping app")
3234
let zipFilePath = try Zip.quickZipFiles([payloadDir], fileName: url.deletingPathExtension().lastPathComponent) // Zip
3335

3436
// Rename
37+
statusUpdate("Renaming .zip to .ipa")
3538
let destIpaURL = zipFilePath.deletingPathExtension().appendingPathExtension("ipa")
3639
try? fm.removeItem(at: destIpaURL)
3740
try fm.moveItem(at: zipFilePath, to: destIpaURL)
3841

42+
statusUpdate("Done.")
3943
return zipFilePath.deletingPathExtension().appendingPathExtension("ipa")
4044
}
4145

4246

4347
/// Extracts deb and returns .app urls
44-
static func extractDeb(_ url: URL) throws -> [URL] {
48+
static func extractDeb(_ url: URL, statusUpdate: (String) -> ()) throws -> [URL] {
4549
let extractedDir = tempDir.appendingPathComponent("extracted")
4650
let appsDir = tempDir.appendingPathComponent( "extracted/Applications/")
51+
statusUpdate("Reading .deb")
4752
let reader = try ArArchiveReader(archive: Array<UInt8>(Data(contentsOf: url)))
4853
var foundData = false
4954
for (header, dataInts) in reader {
5055
guard header.name.contains("data.tar") else { continue }
5156
let dataURL = tempDir.appendingPathComponent(header.name)
5257

5358
// Write data to disk
59+
statusUpdate("Creating data from ints")
5460
let data = Data(dataInts)
5561
try data.write(to: dataURL, options: .atomic)
5662

63+
statusUpdate("Decompressing data archive")
5764
let decompressedData: Data?
5865
switch DecompressionMethod(rawValue: header.name.components(separatedBy: ".").last ?? "") {
5966
case .lzma:
6067
foundData = true
68+
statusUpdate("Using LZMA")
6169
decompressedData = try LZMA.decompress(data: data)
6270
case .gz:
6371
foundData = true
72+
statusUpdate("Using GzipArchive")
6473
decompressedData = try GzipArchive.unarchive(archive:data)
74+
case .bzip2:
75+
foundData = true
76+
statusUpdate("Using BZip2")
77+
decompressedData = try BZip2.decompress(data:data)
78+
case .xz:
79+
foundData = true
80+
statusUpdate("Using XZArchive")
81+
decompressedData = try XZArchive.unarchive(archive:data)
6582
case .none:
6683
throw ConversionError.unsupportedCompression
6784
}
6885

86+
statusUpdate("Opening .tar")
6987
try decompressedData!.write(to: extractedDir.appendingPathExtension("tar"))
7088
let tarContainer = try TarContainer.open(container: decompressedData!)
7189

90+
statusUpdate("Creating files")
7291
for entry in tarContainer {
7392
if entry.info.type == .directory {
7493
try fm.createDirectory(at: extractedDir.appendingPathComponent(entry.info.name), withIntermediateDirectories: true)
@@ -96,7 +115,7 @@ class DebToIPA {
96115
}
97116

98117
enum DecompressionMethod: String {
99-
case gz, lzma // todo
118+
case gz, lzma, bzip2, xz
100119
}
101120

102121
enum ConversionError: Error {

0 commit comments

Comments
 (0)