Skip to content

Commit 70d67df

Browse files
committed
feat: Add option to query installed app path
1 parent 657f1de commit 70d67df

File tree

2 files changed

+67
-1
lines changed

2 files changed

+67
-1
lines changed

src/main/java/io/github/berstanio/pymobiledevice3/ipc/PyMobileDevice3IPC.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,44 @@ public CompletableFuture<DeviceInfo> getDevice(String uuid) {
209209
});
210210
}
211211

212+
/**
213+
* @param appPath The path to the app bundle, to retrieve the identifier from
214+
* @return The bundle identifier. Fails exceptionally, if no bundle identifier can be found
215+
*/
216+
public CompletableFuture<String> getBundleIdentifier(File appPath)
217+
{
218+
JSONObject object = new JSONObject();
219+
object.put("command", "get_bundle_identifier");
220+
object.put("app_path", appPath.getAbsolutePath());
221+
222+
return createRequest(object, (future, jsonObject) -> {
223+
future.complete(jsonObject.getString("result"));
224+
});
225+
}
226+
227+
/**
228+
* Gets the installed path of an app on a specific device
229+
*
230+
* @param info The device to query
231+
* @param bundleIdentifier The bundle identifier to search for
232+
* @return The on-device path or null, if the bundle is not installed
233+
*/
234+
public CompletableFuture<String> getInstalledPath(DeviceInfo info, String bundleIdentifier)
235+
{
236+
JSONObject object = new JSONObject();
237+
object.put("command", "get_installed_path");
238+
object.put("device_id", info.getUniqueDeviceId());
239+
object.put("bundle_identifier", bundleIdentifier);
240+
241+
return createRequest(object, (future, jsonObject) -> {
242+
if (jsonObject.get("state").equals("not_installed")) {
243+
future.complete(null);
244+
} else {
245+
future.complete(jsonObject.getString("result"));
246+
}
247+
});
248+
}
249+
212250
/**
213251
* @param deviceInfo The device to install to.
214252
* @param path .app bundle path

src/main/resources/handler.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from threading import Thread
1515
from time import sleep
16+
from typing import Union
1617

1718
import requests
1819
from packaging.version import Version
@@ -165,7 +166,7 @@ def get_device(id, device_id, ipc_client):
165166
with create_using_usbmux(device_id, autopair=False) as lockdown:
166167
reply = {"id": id, "state": "completed", "result": lockdown.short_info}
167168
write_dispatcher.write_reply(ipc_client, reply)
168-
except NoDeviceConnectedError | DeviceNotFoundError:
169+
except Union[NoDeviceConnectedError, DeviceNotFoundError]:
169170
reply = {"id": id, "state": "failed_expected"}
170171
write_dispatcher.write_reply(ipc_client, reply)
171172

@@ -199,6 +200,27 @@ def progress_handler(progress, *args):
199200

200201
print("Installed bundle: " + str(bundle_identifier))
201202

203+
def get_bundle_identifier(id, path, ipc_client):
204+
info_plist_path = Path(path) / "Info.plist"
205+
with open(info_plist_path, 'rb') as f:
206+
plist_data = plistlib.load(f)
207+
208+
bundle_identifier = plist_data["CFBundleIdentifier"]
209+
210+
reply = {"id": id, "state": "completed", "result": bundle_identifier}
211+
write_dispatcher.write_reply(ipc_client, reply)
212+
213+
def get_installed_path(id, lockdown_client, bundle_identifier, ipc_client):
214+
with InstallationProxyService(lockdown=lockdown_client) as installer:
215+
res = installer.lookup(options={"BundleIDs" : [bundle_identifier]})
216+
217+
if bundle_identifier not in res:
218+
reply = {"id": id, "state": "not_installed"}
219+
else:
220+
reply = {"id": id, "state": "completed", "result": res[bundle_identifier]["Path"]}
221+
222+
write_dispatcher.write_reply(ipc_client, reply)
223+
202224
def decode_plist(id, path, ipc_client):
203225
with open(path, 'rb') as f:
204226
plist_data = plistlib.load(f)
@@ -402,6 +424,9 @@ def handle_command(command, ipc_client):
402424
elif command_type == "get_version":
403425
get_version(id, ipc_client)
404426
return
427+
elif command_type == "get_bundle_identifier":
428+
get_bundle_identifier(id, res["app_path"], ipc_client)
429+
return
405430

406431
# Now come the device targetted functions
407432
device_id = res['device_id']
@@ -416,6 +441,9 @@ def handle_command(command, ipc_client):
416441
port = res['port'] if 'port' in res else 0
417442
debugserver_connect(id, lockdown, port, ipc_client)
418443
return
444+
elif command_type == "get_installed_path":
445+
get_installed_path(id, lockdown, res["bundle_identifier"], ipc_client)
446+
return
419447

420448
except Exception as e:
421449
reply = {"request": command, "state": "failed", "error": repr(e), "backtrace": traceback.format_exc()}

0 commit comments

Comments
 (0)