diff options
Diffstat (limited to 'src/installation_proxy.c')
-rw-r--r-- | src/installation_proxy.c | 1232 |
1 files changed, 753 insertions, 479 deletions
diff --git a/src/installation_proxy.c b/src/installation_proxy.c index 4a76dd2..ec19da0 100644 --- a/src/installation_proxy.c +++ b/src/installation_proxy.c | |||
@@ -2,63 +2,210 @@ | |||
2 | * installation_proxy.c | 2 | * installation_proxy.c |
3 | * com.apple.mobile.installation_proxy service implementation. | 3 | * com.apple.mobile.installation_proxy service implementation. |
4 | * | 4 | * |
5 | * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. | 5 | * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. |
6 | * Copyright (c) 2010-2013 Nikias Bassen, All Rights Reserved. | ||
6 | * | 7 | * |
7 | * This library is free software; you can redistribute it and/or | 8 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public | 9 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either | 10 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. | 11 | * version 2.1 of the License, or (at your option) any later version. |
11 | * | 12 | * |
12 | * This library is distributed in the hope that it will be useful, | 13 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. | 16 | * Lesser General Public License for more details. |
16 | * | 17 | * |
17 | * You should have received a copy of the GNU Lesser General Public | 18 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library; if not, write to the Free Software | 19 | * License along with this library; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | */ | 21 | */ |
21 | 22 | ||
23 | #ifdef HAVE_CONFIG_H | ||
24 | #include <config.h> | ||
25 | #endif | ||
22 | #include <string.h> | 26 | #include <string.h> |
23 | #include <stdlib.h> | 27 | #include <stdlib.h> |
28 | #include <inttypes.h> | ||
24 | #include <unistd.h> | 29 | #include <unistd.h> |
25 | #include <plist/plist.h> | 30 | #include <plist/plist.h> |
26 | 31 | ||
27 | #include "installation_proxy.h" | 32 | #include "installation_proxy.h" |
28 | #include "property_list_service.h" | 33 | #include "property_list_service.h" |
29 | #include "debug.h" | 34 | #include "common/debug.h" |
35 | |||
36 | typedef enum { | ||
37 | INSTPROXY_COMMAND_TYPE_ASYNC, | ||
38 | INSTPROXY_COMMAND_TYPE_SYNC | ||
39 | } instproxy_command_type_t; | ||
30 | 40 | ||
31 | struct instproxy_status_data { | 41 | struct instproxy_status_data { |
32 | instproxy_client_t client; | 42 | instproxy_client_t client; |
43 | plist_t command; | ||
33 | instproxy_status_cb_t cbfunc; | 44 | instproxy_status_cb_t cbfunc; |
34 | char *operation; | ||
35 | void *user_data; | 45 | void *user_data; |
36 | }; | 46 | }; |
37 | 47 | ||
38 | /** | 48 | /** |
49 | * Converts an error string identifier to a instproxy_error_t value. | ||
50 | * Used internally to get correct error codes from a response. | ||
51 | * | ||
52 | * @param name The error name to convert. | ||
53 | * @param error_detail Pointer to store error detail text if available. The | ||
54 | * caller is reponsible for freeing the allocated buffer after use. If NULL | ||
55 | * is passed no error detail will be returned. | ||
56 | * | ||
57 | * @return A matching instproxy_error_t error code or | ||
58 | * INSTPROXY_E_UNKNOWN_ERROR otherwise. | ||
59 | */ | ||
60 | static instproxy_error_t instproxy_strtoerr(const char* name) | ||
61 | { | ||
62 | instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; | ||
63 | |||
64 | if (strcmp(name, "AlreadyArchived") == 0) { | ||
65 | err = INSTPROXY_E_ALREADY_ARCHIVED; | ||
66 | } else if (strcmp(name, "APIInternalError") == 0) { | ||
67 | err = INSTPROXY_E_API_INTERNAL_ERROR; | ||
68 | } else if (strcmp(name, "ApplicationAlreadyInstalled") == 0) { | ||
69 | err = INSTPROXY_E_APPLICATION_ALREADY_INSTALLED; | ||
70 | } else if (strcmp(name, "ApplicationMoveFailed") == 0) { | ||
71 | err = INSTPROXY_E_APPLICATION_MOVE_FAILED; | ||
72 | } else if (strcmp(name, "ApplicationSINFCaptureFailed") == 0) { | ||
73 | err = INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED; | ||
74 | } else if (strcmp(name, "ApplicationSandboxFailed") == 0) { | ||
75 | err = INSTPROXY_E_APPLICATION_SANDBOX_FAILED; | ||
76 | } else if (strcmp(name, "ApplicationVerificationFailed") == 0) { | ||
77 | err = INSTPROXY_E_APPLICATION_VERIFICATION_FAILED; | ||
78 | } else if (strcmp(name, "ArchiveDestructionFailed") == 0) { | ||
79 | err = INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED; | ||
80 | } else if (strcmp(name, "BundleVerificationFailed") == 0) { | ||
81 | err = INSTPROXY_E_BUNDLE_VERIFICATION_FAILED; | ||
82 | } else if (strcmp(name, "CarrierBundleCopyFailed") == 0) { | ||
83 | err = INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED; | ||
84 | } else if (strcmp(name, "CarrierBundleDirectoryCreationFailed") == 0) { | ||
85 | err = INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED; | ||
86 | } else if (strcmp(name, "CarrierBundleMissingSupportedSIMs") == 0) { | ||
87 | err = INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS; | ||
88 | } else if (strcmp(name, "CommCenterNotificationFailed") == 0) { | ||
89 | err = INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED; | ||
90 | } else if (strcmp(name, "ContainerCreationFailed") == 0) { | ||
91 | err = INSTPROXY_E_CONTAINER_CREATION_FAILED; | ||
92 | } else if (strcmp(name, "ContainerP0wnFailed") == 0) { | ||
93 | err = INSTPROXY_E_CONTAINER_P0WN_FAILED; | ||
94 | } else if (strcmp(name, "ContainerRemovalFailed") == 0) { | ||
95 | err = INSTPROXY_E_CONTAINER_REMOVAL_FAILED; | ||
96 | } else if (strcmp(name, "EmbeddedProfileInstallFailed") == 0) { | ||
97 | err = INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED; | ||
98 | } else if (strcmp(name, "ExecutableTwiddleFailed") == 0) { | ||
99 | err = INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED; | ||
100 | } else if (strcmp(name, "ExistenceCheckFailed") == 0) { | ||
101 | err = INSTPROXY_E_EXISTENCE_CHECK_FAILED; | ||
102 | } else if (strcmp(name, "InstallMapUpdateFailed") == 0) { | ||
103 | err = INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED; | ||
104 | } else if (strcmp(name, "ManifestCaptureFailed") == 0) { | ||
105 | err = INSTPROXY_E_MANIFEST_CAPTURE_FAILED; | ||
106 | } else if (strcmp(name, "MapGenerationFailed") == 0) { | ||
107 | err = INSTPROXY_E_MAP_GENERATION_FAILED; | ||
108 | } else if (strcmp(name, "MissingBundleExecutable") == 0) { | ||
109 | err = INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE; | ||
110 | } else if (strcmp(name, "MissingBundleIdentifier") == 0) { | ||
111 | err = INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER; | ||
112 | } else if (strcmp(name, "MissingBundlePath") == 0) { | ||
113 | err = INSTPROXY_E_MISSING_BUNDLE_PATH; | ||
114 | } else if (strcmp(name, "MissingContainer") == 0) { | ||
115 | err = INSTPROXY_E_MISSING_CONTAINER; | ||
116 | } else if (strcmp(name, "NotificationFailed") == 0) { | ||
117 | err = INSTPROXY_E_NOTIFICATION_FAILED; | ||
118 | } else if (strcmp(name, "PackageExtractionFailed") == 0) { | ||
119 | err = INSTPROXY_E_PACKAGE_EXTRACTION_FAILED; | ||
120 | } else if (strcmp(name, "PackageInspectionFailed") == 0) { | ||
121 | err = INSTPROXY_E_PACKAGE_INSPECTION_FAILED; | ||
122 | } else if (strcmp(name, "PackageMoveFailed") == 0) { | ||
123 | err = INSTPROXY_E_PACKAGE_MOVE_FAILED; | ||
124 | } else if (strcmp(name, "PathConversionFailed") == 0) { | ||
125 | err = INSTPROXY_E_PATH_CONVERSION_FAILED; | ||
126 | } else if (strcmp(name, "RestoreContainerFailed") == 0) { | ||
127 | err = INSTPROXY_E_RESTORE_CONTAINER_FAILED; | ||
128 | } else if (strcmp(name, "SeatbeltProfileRemovalFailed") == 0) { | ||
129 | err = INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED; | ||
130 | } else if (strcmp(name, "StageCreationFailed") == 0) { | ||
131 | err = INSTPROXY_E_STAGE_CREATION_FAILED; | ||
132 | } else if (strcmp(name, "SymlinkFailed") == 0) { | ||
133 | err = INSTPROXY_E_SYMLINK_FAILED; | ||
134 | } else if (strcmp(name, "UnknownCommand") == 0) { | ||
135 | err = INSTPROXY_E_UNKNOWN_COMMAND; | ||
136 | } else if (strcmp(name, "iTunesArtworkCaptureFailed") == 0) { | ||
137 | err = INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED; | ||
138 | } else if (strcmp(name, "iTunesMetadataCaptureFailed") == 0) { | ||
139 | err = INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED; | ||
140 | } else if (strcmp(name, "DeviceOSVersionTooLow") == 0) { | ||
141 | err = INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW; | ||
142 | } else if (strcmp(name, "DeviceFamilyNotSupported") == 0) { | ||
143 | err = INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED; | ||
144 | } else if (strcmp(name, "PackagePatchFailed") == 0) { | ||
145 | err = INSTPROXY_E_PACKAGE_PATCH_FAILED; | ||
146 | } else if (strcmp(name, "IncorrectArchitecture") == 0) { | ||
147 | err = INSTPROXY_E_INCORRECT_ARCHITECTURE; | ||
148 | } else if (strcmp(name, "PluginCopyFailed") == 0) { | ||
149 | err = INSTPROXY_E_PLUGIN_COPY_FAILED; | ||
150 | } else if (strcmp(name, "BreadcrumbFailed") == 0) { | ||
151 | err = INSTPROXY_E_BREADCRUMB_FAILED; | ||
152 | } else if (strcmp(name, "BreadcrumbUnlockFailed") == 0) { | ||
153 | err = INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED; | ||
154 | } else if (strcmp(name, "GeoJSONCaptureFailed") == 0) { | ||
155 | err = INSTPROXY_E_GEOJSON_CAPTURE_FAILED; | ||
156 | } else if (strcmp(name, "NewsstandArtworkCaptureFailed") == 0) { | ||
157 | err = INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED; | ||
158 | } else if (strcmp(name, "MissingCommand") == 0) { | ||
159 | err = INSTPROXY_E_MISSING_COMMAND; | ||
160 | } else if (strcmp(name, "NotEntitled") == 0) { | ||
161 | err = INSTPROXY_E_NOT_ENTITLED; | ||
162 | } else if (strcmp(name, "MissingPackagePath") == 0) { | ||
163 | err = INSTPROXY_E_MISSING_PACKAGE_PATH; | ||
164 | } else if (strcmp(name, "MissingContainerPath") == 0) { | ||
165 | err = INSTPROXY_E_MISSING_CONTAINER_PATH; | ||
166 | } else if (strcmp(name, "MissingApplicationIdentifier") == 0) { | ||
167 | err = INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER; | ||
168 | } else if (strcmp(name, "MissingAttributeValue") == 0) { | ||
169 | err = INSTPROXY_E_MISSING_ATTRIBUTE_VALUE; | ||
170 | } else if (strcmp(name, "LookupFailed") == 0) { | ||
171 | err = INSTPROXY_E_LOOKUP_FAILED; | ||
172 | } else if (strcmp(name, "DictCreationFailed") == 0) { | ||
173 | err = INSTPROXY_E_DICT_CREATION_FAILED; | ||
174 | } else if (strcmp(name, "InstallProhibited") == 0) { | ||
175 | err = INSTPROXY_E_INSTALL_PROHIBITED; | ||
176 | } else if (strcmp(name, "UninstallProhibited") == 0) { | ||
177 | err = INSTPROXY_E_UNINSTALL_PROHIBITED; | ||
178 | } else if (strcmp(name, "MissingBundleVersion") == 0) { | ||
179 | err = INSTPROXY_E_MISSING_BUNDLE_VERSION; | ||
180 | } | ||
181 | |||
182 | return err; | ||
183 | } | ||
184 | |||
185 | /** | ||
39 | * Locks an installation_proxy client, used for thread safety. | 186 | * Locks an installation_proxy client, used for thread safety. |
40 | * | 187 | * |
41 | * @param client The installation_proxy client to lock | 188 | * @param client The installation_proxy client to lock |
42 | */ | 189 | */ |
43 | static void instproxy_lock(instproxy_client_t client) | 190 | static void instproxy_lock(instproxy_client_t client) |
44 | { | 191 | { |
45 | debug_info("InstallationProxy: Locked"); | 192 | debug_info("Locked"); |
46 | g_mutex_lock(client->mutex); | 193 | mutex_lock(&client->mutex); |
47 | } | 194 | } |
48 | 195 | ||
49 | /** | 196 | /** |
50 | * Unlocks an installation_proxy client, used for thread safety. | 197 | * Unlocks an installation_proxy client, used for thread safety. |
51 | * | 198 | * |
52 | * @param client The installation_proxy client to lock | 199 | * @param client The installation_proxy client to lock |
53 | */ | 200 | */ |
54 | static void instproxy_unlock(instproxy_client_t client) | 201 | static void instproxy_unlock(instproxy_client_t client) |
55 | { | 202 | { |
56 | debug_info("InstallationProxy: Unlocked"); | 203 | debug_info("Unlocked"); |
57 | g_mutex_unlock(client->mutex); | 204 | mutex_unlock(&client->mutex); |
58 | } | 205 | } |
59 | 206 | ||
60 | /** | 207 | /** |
61 | * Convert a property_list_service_error_t value to an instproxy_error_t value. | 208 | * Converts a property_list_service_error_t value to an instproxy_error_t value. |
62 | * Used internally to get correct error codes. | 209 | * Used internally to get correct error codes. |
63 | * | 210 | * |
64 | * @param err A property_list_service_error_t error code | 211 | * @param err A property_list_service_error_t error code |
@@ -77,186 +224,80 @@ static instproxy_error_t instproxy_error(property_list_service_error_t err) | |||
77 | return INSTPROXY_E_PLIST_ERROR; | 224 | return INSTPROXY_E_PLIST_ERROR; |
78 | case PROPERTY_LIST_SERVICE_E_MUX_ERROR: | 225 | case PROPERTY_LIST_SERVICE_E_MUX_ERROR: |
79 | return INSTPROXY_E_CONN_FAILED; | 226 | return INSTPROXY_E_CONN_FAILED; |
227 | case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: | ||
228 | return INSTPROXY_E_RECEIVE_TIMEOUT; | ||
80 | default: | 229 | default: |
81 | break; | 230 | break; |
82 | } | 231 | } |
83 | return INSTPROXY_E_UNKNOWN_ERROR; | 232 | return INSTPROXY_E_UNKNOWN_ERROR; |
84 | } | 233 | } |
85 | 234 | ||
86 | /** | 235 | instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client) |
87 | * Connects to the installation_proxy service on the specified device. | ||
88 | * | ||
89 | * @param device The device to connect to | ||
90 | * @param port Destination port (usually given by lockdownd_start_service). | ||
91 | * @param client Pointer that will be set to a newly allocated | ||
92 | * instproxy_client_t upon successful return. | ||
93 | * | ||
94 | * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value | ||
95 | * when an error occured. | ||
96 | */ | ||
97 | instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client) | ||
98 | { | 236 | { |
99 | /* makes sure thread environment is available */ | ||
100 | if (!g_thread_supported()) | ||
101 | g_thread_init(NULL); | ||
102 | |||
103 | if (!device) | ||
104 | return INSTPROXY_E_INVALID_ARG; | ||
105 | |||
106 | property_list_service_client_t plistclient = NULL; | 237 | property_list_service_client_t plistclient = NULL; |
107 | if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { | 238 | instproxy_error_t err = instproxy_error(property_list_service_client_new(device, service, &plistclient)); |
108 | return INSTPROXY_E_CONN_FAILED; | 239 | if (err != INSTPROXY_E_SUCCESS) { |
240 | return err; | ||
109 | } | 241 | } |
110 | 242 | ||
111 | instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); | 243 | instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); |
112 | client_loc->parent = plistclient; | 244 | client_loc->parent = plistclient; |
113 | client_loc->mutex = g_mutex_new(); | 245 | mutex_init(&client_loc->mutex); |
114 | client_loc->status_updater = NULL; | 246 | client_loc->receive_status_thread = THREAD_T_NULL; |
115 | 247 | ||
116 | *client = client_loc; | 248 | *client = client_loc; |
117 | return INSTPROXY_E_SUCCESS; | 249 | return INSTPROXY_E_SUCCESS; |
118 | } | 250 | } |
119 | 251 | ||
120 | /** | 252 | instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label) |
121 | * Disconnects an installation_proxy client from the device and frees up the | 253 | { |
122 | * installation_proxy client data. | 254 | instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; |
123 | * | 255 | service_client_factory_start_service(device, INSTPROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(instproxy_client_new), &err); |
124 | * @param client The installation_proxy client to disconnect and free. | 256 | return err; |
125 | * | 257 | } |
126 | * @return INSTPROXY_E_SUCCESS on success | 258 | |
127 | * or INSTPROXY_E_INVALID_ARG if client is NULL. | ||
128 | */ | ||
129 | instproxy_error_t instproxy_client_free(instproxy_client_t client) | 259 | instproxy_error_t instproxy_client_free(instproxy_client_t client) |
130 | { | 260 | { |
131 | if (!client) | 261 | if (!client) |
132 | return INSTPROXY_E_INVALID_ARG; | 262 | return INSTPROXY_E_INVALID_ARG; |
133 | 263 | ||
134 | property_list_service_client_free(client->parent); | 264 | property_list_service_client_t parent = client->parent; |
135 | client->parent = NULL; | 265 | client->parent = NULL; |
136 | if (client->status_updater) { | 266 | if (client->receive_status_thread) { |
137 | debug_info("joining status_updater"); | 267 | debug_info("joining receive_status_thread"); |
138 | g_thread_join(client->status_updater); | 268 | thread_join(client->receive_status_thread); |
139 | } | 269 | thread_free(client->receive_status_thread); |
140 | if (client->mutex) { | 270 | client->receive_status_thread = THREAD_T_NULL; |
141 | g_mutex_free(client->mutex); | ||
142 | } | 271 | } |
272 | property_list_service_client_free(parent); | ||
273 | mutex_destroy(&client->mutex); | ||
143 | free(client); | 274 | free(client); |
144 | 275 | ||
145 | return INSTPROXY_E_SUCCESS; | 276 | return INSTPROXY_E_SUCCESS; |
146 | } | 277 | } |
147 | 278 | ||
148 | /** | 279 | /** |
149 | * Send a command with specified options to the device. | 280 | * Sends a command to the device. |
150 | * Only used internally. | 281 | * Only used internally. |
151 | * | 282 | * |
152 | * @param client The connected installation_proxy client. | 283 | * @param client The connected installation_proxy client. |
153 | * @param command The command to execute. Required. | 284 | * @param command The command to execute. Required. |
154 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
155 | * @param appid The ApplicationIdentifier to add or NULL if not required. | ||
156 | * @param package_path The installation package path or NULL if not required. | ||
157 | * | 285 | * |
158 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | 286 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if |
159 | * an error occured. | 287 | * an error occurred. |
160 | */ | 288 | */ |
161 | static instproxy_error_t instproxy_send_command(instproxy_client_t client, const char *command, plist_t client_options, const char *appid, const char *package_path) | 289 | static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) |
162 | { | 290 | { |
163 | if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) | 291 | if (!client || !command) |
164 | return INSTPROXY_E_INVALID_ARG; | 292 | return INSTPROXY_E_INVALID_ARG; |
165 | 293 | ||
166 | plist_t dict = plist_new_dict(); | 294 | instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); |
167 | if (appid) { | ||
168 | plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); | ||
169 | } | ||
170 | if (client_options && (plist_dict_get_size(client_options) > 0)) { | ||
171 | plist_dict_insert_item(dict, "ClientOptions", plist_copy(client_options)); | ||
172 | } | ||
173 | plist_dict_insert_item(dict, "Command", plist_new_string(command)); | ||
174 | if (package_path) { | ||
175 | plist_dict_insert_item(dict, "PackagePath", plist_new_string(package_path)); | ||
176 | } | ||
177 | |||
178 | instproxy_error_t err = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); | ||
179 | plist_free(dict); | ||
180 | return err; | ||
181 | } | ||
182 | 295 | ||
183 | /** | ||
184 | * List installed applications. This function runs synchronously. | ||
185 | * | ||
186 | * @param client The connected installation_proxy client | ||
187 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
188 | * Valid client options include: | ||
189 | * "ApplicationType" -> "User" | ||
190 | * "ApplicationType" -> "System" | ||
191 | * @param result Pointer that will be set to a plist that will hold an array | ||
192 | * of PLIST_DICT holding information about the applications found. | ||
193 | * | ||
194 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
195 | * an error occured. | ||
196 | */ | ||
197 | instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) | ||
198 | { | ||
199 | if (!client || !client->parent || !result) | ||
200 | return INSTPROXY_E_INVALID_ARG; | ||
201 | |||
202 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
203 | |||
204 | instproxy_lock(client); | ||
205 | res = instproxy_send_command(client, "Browse", client_options, NULL, NULL); | ||
206 | if (res != INSTPROXY_E_SUCCESS) { | 296 | if (res != INSTPROXY_E_SUCCESS) { |
207 | debug_info("could not send plist"); | 297 | debug_info("could not send command plist, error %d", res); |
208 | goto leave_unlock; | 298 | return res; |
209 | } | ||
210 | |||
211 | int browsing = 0; | ||
212 | plist_t apps_array = plist_new_array(); | ||
213 | plist_t dict = NULL; | ||
214 | |||
215 | do { | ||
216 | browsing = 0; | ||
217 | dict = NULL; | ||
218 | res = instproxy_error(property_list_service_receive_plist(client->parent, &dict)); | ||
219 | if (res != INSTPROXY_E_SUCCESS) { | ||
220 | break; | ||
221 | } | ||
222 | if (dict) { | ||
223 | uint64_t i; | ||
224 | uint64_t current_amount = 0; | ||
225 | char *status = NULL; | ||
226 | plist_t camount = plist_dict_get_item(dict, "CurrentAmount"); | ||
227 | plist_t pstatus = plist_dict_get_item(dict, "Status"); | ||
228 | if (camount) { | ||
229 | plist_get_uint_val(camount, ¤t_amount); | ||
230 | } | ||
231 | if (current_amount > 0) { | ||
232 | plist_t current_list = plist_dict_get_item(dict, "CurrentList"); | ||
233 | for (i = 0; current_list && (i < current_amount); i++) { | ||
234 | plist_t item = plist_array_get_item(current_list, i); | ||
235 | plist_array_append_item(apps_array, plist_copy(item)); | ||
236 | } | ||
237 | } | ||
238 | if (pstatus) { | ||
239 | plist_get_string_val(pstatus, &status); | ||
240 | } | ||
241 | if (status) { | ||
242 | if (!strcmp(status, "BrowsingApplications")) { | ||
243 | browsing = 1; | ||
244 | } else if (!strcmp(status, "Complete")) { | ||
245 | debug_info("Browsing applications completed"); | ||
246 | res = INSTPROXY_E_SUCCESS; | ||
247 | } | ||
248 | free(status); | ||
249 | } | ||
250 | plist_free(dict); | ||
251 | } | ||
252 | } while (browsing); | ||
253 | |||
254 | if (res == INSTPROXY_E_SUCCESS) { | ||
255 | *result = apps_array; | ||
256 | } | 299 | } |
257 | 300 | ||
258 | leave_unlock: | ||
259 | instproxy_unlock(client); | ||
260 | return res; | 301 | return res; |
261 | } | 302 | } |
262 | 303 | ||
@@ -269,78 +310,99 @@ leave_unlock: | |||
269 | * | 310 | * |
270 | * @param client The connected installation proxy client | 311 | * @param client The connected installation proxy client |
271 | * @param status_cb Pointer to a callback function or NULL | 312 | * @param status_cb Pointer to a callback function or NULL |
272 | * @param operation Operation name. Will be passed to the callback function | 313 | * @param command Operation specificiation in plist. Will be passed to the |
273 | * in async mode or shown in debug messages in sync mode. | 314 | * status_cb callback. |
274 | * @param user_data Callback data passed to status_cb. | 315 | * @param user_data Callback data passed to status_cb. |
275 | */ | 316 | */ |
276 | static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) | 317 | static instproxy_error_t instproxy_receive_status_loop(instproxy_client_t client, plist_t command, instproxy_status_cb_t status_cb, void *user_data) |
277 | { | 318 | { |
278 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | 319 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
279 | int ok = 1; | 320 | int complete = 0; |
280 | plist_t dict = NULL; | 321 | plist_t node = NULL; |
322 | char* command_name = NULL; | ||
323 | char* status_name = NULL; | ||
324 | char* error_name = NULL; | ||
325 | char* error_description = NULL; | ||
326 | uint64_t error_code = 0; | ||
327 | #ifndef STRIP_DEBUG_CODE | ||
328 | int percent_complete = 0; | ||
329 | #endif | ||
330 | |||
331 | instproxy_command_get_name(command, &command_name); | ||
281 | 332 | ||
282 | do { | 333 | do { |
334 | /* receive status response */ | ||
283 | instproxy_lock(client); | 335 | instproxy_lock(client); |
284 | res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); | 336 | res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); |
285 | instproxy_unlock(client); | 337 | instproxy_unlock(client); |
286 | if (res != INSTPROXY_E_SUCCESS) { | 338 | |
339 | /* break out if we have a communication problem */ | ||
340 | if (res != INSTPROXY_E_SUCCESS && res != INSTPROXY_E_RECEIVE_TIMEOUT) { | ||
287 | debug_info("could not receive plist, error %d", res); | 341 | debug_info("could not receive plist, error %d", res); |
288 | break; | 342 | break; |
289 | } | 343 | } |
290 | if (dict) { | 344 | |
291 | /* invoke callback function */ | 345 | /* parse status response */ |
292 | if (status_cb) { | 346 | if (node) { |
293 | status_cb(operation, dict, user_data); | 347 | /* check status for possible error to allow reporting it and aborting it gracefully */ |
348 | res = instproxy_status_get_error(node, &error_name, &error_description, &error_code); | ||
349 | if (res != INSTPROXY_E_SUCCESS) { | ||
350 | debug_info("command: %s, error %d, code 0x%08"PRIx64", name: %s, description: \"%s\"", command_name, res, error_code, error_name, error_description ? error_description: "N/A"); | ||
351 | complete = 1; | ||
352 | } | ||
353 | |||
354 | if (error_name) { | ||
355 | free(error_name); | ||
356 | error_name = NULL; | ||
294 | } | 357 | } |
295 | /* check for 'Error', so we can abort cleanly */ | 358 | |
296 | plist_t err = plist_dict_get_item(dict, "Error"); | 359 | if (error_description) { |
297 | if (err) { | 360 | free(error_description); |
361 | error_description = NULL; | ||
362 | } | ||
363 | |||
364 | /* check status from response */ | ||
365 | instproxy_status_get_name(node, &status_name); | ||
366 | if (!status_name) { | ||
367 | debug_info("ignoring message without Status key:"); | ||
368 | debug_plist(node); | ||
369 | } else { | ||
370 | if (!strcmp(status_name, "Complete")) { | ||
371 | complete = 1; | ||
372 | } else { | ||
373 | res = INSTPROXY_E_OP_IN_PROGRESS; | ||
374 | } | ||
298 | #ifndef STRIP_DEBUG_CODE | 375 | #ifndef STRIP_DEBUG_CODE |
299 | char *err_msg = NULL; | 376 | percent_complete = -1; |
300 | plist_get_string_val(err, &err_msg); | 377 | instproxy_status_get_percent_complete(node, &percent_complete); |
301 | if (err_msg) { | 378 | if (percent_complete >= 0) { |
302 | debug_info("(%s): ERROR: %s", operation, err_msg); | 379 | debug_info("command: %s, status: %s, percent (%d%%)", command_name, status_name, percent_complete); |
303 | free(err_msg); | 380 | } else { |
381 | debug_info("command: %s, status: %s", command_name, status_name); | ||
304 | } | 382 | } |
305 | #endif | 383 | #endif |
306 | ok = 0; | 384 | free(status_name); |
307 | res = INSTPROXY_E_OP_FAILED; | 385 | status_name = NULL; |
308 | } | 386 | } |
309 | /* get 'Status' */ | 387 | |
310 | plist_t status = plist_dict_get_item(dict, "Status"); | 388 | /* invoke status callback function */ |
311 | if (status) { | 389 | if (status_cb) { |
312 | char *status_msg = NULL; | 390 | status_cb(command, node, user_data); |
313 | plist_get_string_val(status, &status_msg); | ||
314 | if (status_msg) { | ||
315 | if (!strcmp(status_msg, "Complete")) { | ||
316 | ok = 0; | ||
317 | res = INSTPROXY_E_SUCCESS; | ||
318 | } | ||
319 | #ifndef STRIP_DEBUG_CODE | ||
320 | plist_t npercent = plist_dict_get_item(dict, "PercentComplete"); | ||
321 | if (npercent) { | ||
322 | uint64_t val = 0; | ||
323 | int percent; | ||
324 | plist_get_uint_val(npercent, &val); | ||
325 | percent = val; | ||
326 | debug_info("(%s): %s (%d%%)", operation, status_msg, percent); | ||
327 | } else { | ||
328 | debug_info("(%s): %s", operation, status_msg); | ||
329 | } | ||
330 | #endif | ||
331 | free(status_msg); | ||
332 | } | ||
333 | } | 391 | } |
334 | plist_free(dict); | 392 | |
335 | dict = NULL; | 393 | plist_free(node); |
394 | node = NULL; | ||
336 | } | 395 | } |
337 | } while (ok && client->parent); | 396 | } while (!complete && client->parent); |
397 | |||
398 | if (command_name) | ||
399 | free(command_name); | ||
338 | 400 | ||
339 | return res; | 401 | return res; |
340 | } | 402 | } |
341 | 403 | ||
342 | /** | 404 | /** |
343 | * Internally used status updater thread function that will call the specified | 405 | * Internally used "receive status" thread function that will call the specified |
344 | * callback function when status update messages (or error messages) are | 406 | * callback function when status update messages (or error messages) are |
345 | * received. | 407 | * received. |
346 | * | 408 | * |
@@ -349,20 +411,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, | |||
349 | * | 411 | * |
350 | * @return Always NULL. | 412 | * @return Always NULL. |
351 | */ | 413 | */ |
352 | static gpointer instproxy_status_updater(gpointer arg) | 414 | static void* instproxy_receive_status_loop_thread(void* arg) |
353 | { | 415 | { |
354 | struct instproxy_status_data *data = (struct instproxy_status_data*)arg; | 416 | struct instproxy_status_data *data = (struct instproxy_status_data*)arg; |
355 | 417 | ||
356 | /* run until the operation is complete or an error occurs */ | 418 | /* run until the command is complete or an error occurs */ |
357 | (void)instproxy_perform_operation(data->client, data->cbfunc, data->operation, data->user_data); | 419 | (void)instproxy_receive_status_loop(data->client, data->command, data->cbfunc, data->user_data); |
358 | 420 | ||
359 | /* cleanup */ | 421 | /* cleanup */ |
360 | instproxy_lock(data->client); | 422 | instproxy_lock(data->client); |
423 | |||
361 | debug_info("done, cleaning up."); | 424 | debug_info("done, cleaning up."); |
362 | if (data->operation) { | 425 | |
363 | free(data->operation); | 426 | if (data->command) { |
427 | plist_free(data->command); | ||
428 | } | ||
429 | |||
430 | if (data->client->receive_status_thread) { | ||
431 | thread_free(data->client->receive_status_thread); | ||
432 | data->client->receive_status_thread = THREAD_T_NULL; | ||
364 | } | 433 | } |
365 | data->client->status_updater = NULL; | 434 | |
366 | instproxy_unlock(data->client); | 435 | instproxy_unlock(data->client); |
367 | free(data); | 436 | free(data); |
368 | 437 | ||
@@ -370,379 +439,493 @@ static gpointer instproxy_status_updater(gpointer arg) | |||
370 | } | 439 | } |
371 | 440 | ||
372 | /** | 441 | /** |
373 | * Internally used helper function that creates a status updater thread which | 442 | * Internally used helper function that creates a "receive status" thread which |
374 | * will call the passed callback function when status updates occur. | 443 | * will call the passed callback function when a status is received. |
375 | * If status_cb is NULL no thread will be created, but the operation will | 444 | * |
376 | * run synchronously until it completes or an error occurs. | 445 | * If async is 0 no thread will be created and the command will run |
446 | * synchronously until it completes or an error occurs. | ||
377 | * | 447 | * |
378 | * @param client The connected installation proxy client | 448 | * @param client The connected installation proxy client |
379 | * @param status_cb Pointer to a callback function or NULL | 449 | * @param command Operation name. Will be passed to the callback function |
380 | * @param operation Operation name. Will be passed to the callback function | ||
381 | * in async mode or shown in debug messages in sync mode. | 450 | * in async mode or shown in debug messages in sync mode. |
451 | * @param async A boolean indicating if receive loop should be run | ||
452 | * asynchronously or block. | ||
453 | * @param status_cb Pointer to a callback function or NULL. | ||
382 | * @param user_data Callback data passed to status_cb. | 454 | * @param user_data Callback data passed to status_cb. |
383 | * | 455 | * |
384 | * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or | 456 | * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or |
385 | * when the operation completed successfully (sync). | 457 | * when the command completed successfully (sync). |
386 | * An INSTPROXY_E_* error value is returned if an error occured. | 458 | * An INSTPROXY_E_* error value is returned if an error occurred. |
387 | */ | 459 | */ |
388 | static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) | 460 | static instproxy_error_t instproxy_receive_status_loop_with_callback(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) |
389 | { | 461 | { |
462 | if (!client || !client->parent || !command) { | ||
463 | return INSTPROXY_E_INVALID_ARG; | ||
464 | } | ||
465 | |||
466 | if (client->receive_status_thread) { | ||
467 | return INSTPROXY_E_OP_IN_PROGRESS; | ||
468 | } | ||
469 | |||
390 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | 470 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
391 | if (status_cb) { | 471 | if (async == INSTPROXY_COMMAND_TYPE_ASYNC) { |
392 | /* async mode */ | 472 | /* async mode */ |
393 | struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); | 473 | struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); |
394 | if (data) { | 474 | if (data) { |
395 | data->client = client; | 475 | data->client = client; |
476 | data->command = plist_copy(command); | ||
396 | data->cbfunc = status_cb; | 477 | data->cbfunc = status_cb; |
397 | data->operation = strdup(operation); | ||
398 | data->user_data = user_data; | 478 | data->user_data = user_data; |
399 | 479 | ||
400 | client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); | 480 | if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) { |
401 | if (client->status_updater) { | ||
402 | res = INSTPROXY_E_SUCCESS; | 481 | res = INSTPROXY_E_SUCCESS; |
403 | } | 482 | } |
404 | } | 483 | } |
405 | } else { | 484 | } else { |
406 | /* sync mode */ | 485 | /* sync mode as a fallback */ |
407 | res = instproxy_perform_operation(client, NULL, operation, NULL); | 486 | res = instproxy_receive_status_loop(client, command, status_cb, user_data); |
408 | } | 487 | } |
488 | |||
409 | return res; | 489 | return res; |
410 | } | 490 | } |
411 | 491 | ||
412 | |||
413 | /** | 492 | /** |
414 | * Internal function used by instproxy_install and instproxy_upgrade. | 493 | * Internal core function to send a command and process the response. |
415 | * | 494 | * |
416 | * @param client The connected installation_proxy client | 495 | * @param client The connected installation_proxy client |
417 | * @param pkg_path Path of the installation package (inside the AFC jail) | 496 | * @param command The command specification dictionary. |
418 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | 497 | * @param async A boolean indicating whether the receive loop should be run |
419 | * @param status_cb Callback function for progress and status information. If | 498 | * asynchronously or block until completing the command. |
420 | * NULL is passed, this function will run synchronously. | 499 | * @param status_cb Callback function to call if a command status is received. |
421 | * @param command The command to execute. | ||
422 | * @param user_data Callback data passed to status_cb. | 500 | * @param user_data Callback data passed to status_cb. |
423 | * | 501 | * |
424 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | 502 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if |
425 | * an error occured. | 503 | * an error occurred. |
426 | */ | 504 | */ |
427 | static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, const char *command, void *user_data) | 505 | static instproxy_error_t instproxy_perform_command(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) |
428 | { | 506 | { |
429 | if (!client || !client->parent || !pkg_path) { | 507 | if (!client || !client->parent || !command) { |
430 | return INSTPROXY_E_INVALID_ARG; | 508 | return INSTPROXY_E_INVALID_ARG; |
431 | } | 509 | } |
432 | if (client->status_updater) { | 510 | |
511 | if (client->receive_status_thread) { | ||
433 | return INSTPROXY_E_OP_IN_PROGRESS; | 512 | return INSTPROXY_E_OP_IN_PROGRESS; |
434 | } | 513 | } |
435 | 514 | ||
515 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
516 | |||
517 | /* send command */ | ||
436 | instproxy_lock(client); | 518 | instproxy_lock(client); |
437 | instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path); | 519 | res = instproxy_send_command(client, command); |
438 | instproxy_unlock(client); | 520 | instproxy_unlock(client); |
439 | 521 | ||
440 | if (res != INSTPROXY_E_SUCCESS) { | 522 | /* loop until status or error is received */ |
441 | debug_info("could not send plist, error %d", res); | 523 | res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data); |
442 | return res; | ||
443 | } | ||
444 | 524 | ||
445 | return instproxy_create_status_updater(client, status_cb, command, user_data); | 525 | return res; |
446 | } | 526 | } |
447 | 527 | ||
448 | /** | 528 | instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) |
449 | * Install an application on the device. | ||
450 | * | ||
451 | * @param client The connected installation_proxy client | ||
452 | * @param pkg_path Path of the installation package (inside the AFC jail) | ||
453 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
454 | * Valid options include: | ||
455 | * "iTunesMetadata" -> PLIST_DATA | ||
456 | * "ApplicationSINF" -> PLIST_DATA | ||
457 | * "PackageType" -> "Developer" | ||
458 | * If PackageType -> Developer is specified, then pkg_path points to | ||
459 | * an .app directory instead of an install package. | ||
460 | * @param status_cb Callback function for progress and status information. If | ||
461 | * NULL is passed, this function will run synchronously. | ||
462 | * @param user_data Callback data passed to status_cb. | ||
463 | * | ||
464 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
465 | * an error occured. | ||
466 | * | ||
467 | * @note If a callback function is given (async mode), this function returns | ||
468 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
469 | * created successfully; any error occuring during the operation has to be | ||
470 | * handled inside the specified callback function. | ||
471 | */ | ||
472 | instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
473 | { | 529 | { |
474 | return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install", user_data); | 530 | if (!client || !client->parent || !status_cb) |
531 | return INSTPROXY_E_INVALID_ARG; | ||
532 | |||
533 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
534 | |||
535 | plist_t command = plist_new_dict(); | ||
536 | plist_dict_set_item(command, "Command", plist_new_string("Browse")); | ||
537 | if (client_options) | ||
538 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
539 | |||
540 | res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, (void*)user_data); | ||
541 | |||
542 | plist_free(command); | ||
543 | |||
544 | return res; | ||
475 | } | 545 | } |
476 | 546 | ||
477 | /** | 547 | static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *user_data) |
478 | * Upgrade an application on the device. This function is nearly the same as | ||
479 | * instproxy_install; the difference is that the installation progress on the | ||
480 | * device is faster if the application is already installed. | ||
481 | * | ||
482 | * @param client The connected installation_proxy client | ||
483 | * @param pkg_path Path of the installation package (inside the AFC jail) | ||
484 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
485 | * Valid options include: | ||
486 | * "iTunesMetadata" -> PLIST_DATA | ||
487 | * "ApplicationSINF" -> PLIST_DATA | ||
488 | * "PackageType" -> "Developer" | ||
489 | * If PackageType -> Developer is specified, then pkg_path points to | ||
490 | * an .app directory instead of an install package. | ||
491 | * @param status_cb Callback function for progress and status information. If | ||
492 | * NULL is passed, this function will run synchronously. | ||
493 | * @param user_data Callback data passed to status_cb. | ||
494 | * | ||
495 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
496 | * an error occured. | ||
497 | * | ||
498 | * @note If a callback function is given (async mode), this function returns | ||
499 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
500 | * created successfully; any error occuring during the operation has to be | ||
501 | * handled inside the specified callback function. | ||
502 | */ | ||
503 | instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
504 | { | 548 | { |
505 | return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade", user_data); | 549 | plist_t *result_array = (plist_t*)user_data; |
550 | uint64_t current_amount = 0; | ||
551 | plist_t current_list = NULL; | ||
552 | uint64_t i; | ||
553 | |||
554 | instproxy_status_get_current_list(status, NULL, NULL, ¤t_amount, ¤t_list); | ||
555 | |||
556 | debug_info("current_amount: %d", current_amount); | ||
557 | |||
558 | if (current_amount > 0) { | ||
559 | for (i = 0; current_list && (i < current_amount); i++) { | ||
560 | plist_t item = plist_array_get_item(current_list, i); | ||
561 | plist_array_append_item(*result_array, plist_copy(item)); | ||
562 | } | ||
563 | } | ||
564 | |||
565 | if (current_list) | ||
566 | plist_free(current_list); | ||
506 | } | 567 | } |
507 | 568 | ||
508 | /** | 569 | instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) |
509 | * Uninstall an application from the device. | ||
510 | * | ||
511 | * @param client The connected installation proxy client | ||
512 | * @param appid ApplicationIdentifier of the app to uninstall | ||
513 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
514 | * Currently there are no known client options, so pass NULL here. | ||
515 | * @param status_cb Callback function for progress and status information. If | ||
516 | * NULL is passed, this function will run synchronously. | ||
517 | * @param user_data Callback data passed to status_cb. | ||
518 | * | ||
519 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
520 | * an error occured. | ||
521 | * | ||
522 | * @note If a callback function is given (async mode), this function returns | ||
523 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
524 | * created successfully; any error occuring during the operation has to be | ||
525 | * handled inside the specified callback function. | ||
526 | */ | ||
527 | instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
528 | { | 570 | { |
529 | if (!client || !client->parent || !appid) { | 571 | if (!client || !client->parent || !result) |
530 | return INSTPROXY_E_INVALID_ARG; | 572 | return INSTPROXY_E_INVALID_ARG; |
531 | } | ||
532 | |||
533 | if (client->status_updater) { | ||
534 | return INSTPROXY_E_OP_IN_PROGRESS; | ||
535 | } | ||
536 | 573 | ||
537 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | 574 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
538 | plist_t dict = plist_new_dict(); | ||
539 | plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); | ||
540 | plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall")); | ||
541 | 575 | ||
542 | instproxy_lock(client); | 576 | plist_t result_array = plist_new_array(); |
543 | res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL); | ||
544 | instproxy_unlock(client); | ||
545 | 577 | ||
546 | plist_free(dict); | 578 | plist_t command = plist_new_dict(); |
579 | plist_dict_set_item(command, "Command", plist_new_string("Browse")); | ||
580 | if (client_options) | ||
581 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
547 | 582 | ||
548 | if (res != INSTPROXY_E_SUCCESS) { | 583 | res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_append_current_list_to_result_cb, (void*)&result_array); |
549 | debug_info("could not send plist, error %d", res); | 584 | |
550 | return res; | 585 | if (res == INSTPROXY_E_SUCCESS) { |
586 | *result = result_array; | ||
587 | } else { | ||
588 | plist_free(result_array); | ||
551 | } | 589 | } |
552 | 590 | ||
553 | return instproxy_create_status_updater(client, status_cb, "Uninstall", user_data); | 591 | plist_free(command); |
592 | |||
593 | return res; | ||
554 | } | 594 | } |
555 | 595 | ||
556 | /** | 596 | static void instproxy_copy_lookup_result_cb(plist_t command, plist_t status, void *user_data) |
557 | * List archived applications. This function runs synchronously. | 597 | { |
558 | * | 598 | plist_t* result = (plist_t*)user_data; |
559 | * @see instproxy_archive | 599 | |
560 | * | 600 | plist_t node = plist_dict_get_item(status, "LookupResult"); |
561 | * @param client The connected installation_proxy client | 601 | if (node) { |
562 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | 602 | *result = plist_copy(node); |
563 | * Currently there are no known client options, so pass NULL here. | 603 | } |
564 | * @param result Pointer that will be set to a plist containing a PLIST_DICT | 604 | } |
565 | * holding information about the archived applications found. | 605 | |
566 | * | 606 | instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result) |
567 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
568 | * an error occured. | ||
569 | */ | ||
570 | instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) | ||
571 | { | 607 | { |
608 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
609 | int i = 0; | ||
610 | plist_t lookup_result = NULL; | ||
611 | plist_t command = NULL; | ||
612 | plist_t appid_array = NULL; | ||
613 | plist_t node = NULL; | ||
614 | |||
572 | if (!client || !client->parent || !result) | 615 | if (!client || !client->parent || !result) |
573 | return INSTPROXY_E_INVALID_ARG; | 616 | return INSTPROXY_E_INVALID_ARG; |
574 | 617 | ||
575 | instproxy_lock(client); | 618 | command = plist_new_dict(); |
576 | instproxy_error_t res = instproxy_send_command(client, "LookupArchives", client_options, NULL, NULL); | 619 | plist_dict_set_item(command, "Command", plist_new_string("Lookup")); |
620 | if (client_options) { | ||
621 | node = plist_copy(client_options); | ||
622 | } else if (appids) { | ||
623 | node = plist_new_dict(); | ||
624 | } | ||
577 | 625 | ||
578 | if (res != INSTPROXY_E_SUCCESS) { | 626 | /* add bundle identifiers to client options */ |
579 | debug_info("could not send plist, error %d", res); | 627 | if (appids) { |
580 | goto leave_unlock; | 628 | appid_array = plist_new_array(); |
629 | while (appids[i]) { | ||
630 | plist_array_append_item(appid_array, plist_new_string(appids[i])); | ||
631 | i++; | ||
632 | } | ||
633 | plist_dict_set_item(node, "BundleIDs", appid_array); | ||
581 | } | 634 | } |
582 | 635 | ||
583 | res = instproxy_error(property_list_service_receive_plist(client->parent, result)); | 636 | if (node) { |
584 | if (res != INSTPROXY_E_SUCCESS) { | 637 | plist_dict_set_item(command, "ClientOptions", node); |
585 | debug_info("could not receive plist, error %d", res); | ||
586 | goto leave_unlock; | ||
587 | } | 638 | } |
588 | 639 | ||
589 | res = INSTPROXY_E_SUCCESS; | 640 | res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); |
641 | |||
642 | if (res == INSTPROXY_E_SUCCESS) { | ||
643 | *result = lookup_result; | ||
644 | } else { | ||
645 | plist_free(lookup_result); | ||
646 | } | ||
647 | |||
648 | plist_free(command); | ||
649 | |||
650 | return res; | ||
651 | } | ||
652 | |||
653 | instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
654 | { | ||
655 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
656 | |||
657 | plist_t command = plist_new_dict(); | ||
658 | plist_dict_set_item(command, "Command", plist_new_string("Install")); | ||
659 | if (client_options) | ||
660 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
661 | plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); | ||
662 | |||
663 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
664 | |||
665 | plist_free(command); | ||
666 | |||
667 | return res; | ||
668 | } | ||
669 | |||
670 | instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
671 | { | ||
672 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
673 | |||
674 | plist_t command = plist_new_dict(); | ||
675 | plist_dict_set_item(command, "Command", plist_new_string("Upgrade")); | ||
676 | if (client_options) | ||
677 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
678 | plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); | ||
679 | |||
680 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
681 | |||
682 | plist_free(command); | ||
683 | |||
684 | return res; | ||
685 | } | ||
686 | |||
687 | instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
688 | { | ||
689 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
690 | |||
691 | plist_t command = plist_new_dict(); | ||
692 | plist_dict_set_item(command, "Command", plist_new_string("Uninstall")); | ||
693 | if (client_options) | ||
694 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
695 | plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); | ||
696 | |||
697 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
698 | |||
699 | plist_free(command); | ||
700 | |||
701 | return res; | ||
702 | } | ||
703 | |||
704 | instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) | ||
705 | { | ||
706 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
707 | |||
708 | plist_t command = plist_new_dict(); | ||
709 | plist_dict_set_item(command, "Command", plist_new_string("LookupArchives")); | ||
710 | if (client_options) | ||
711 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
712 | |||
713 | res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)result); | ||
714 | |||
715 | plist_free(command); | ||
590 | 716 | ||
591 | leave_unlock: | ||
592 | instproxy_unlock(client); | ||
593 | return res; | 717 | return res; |
594 | } | 718 | } |
595 | 719 | ||
596 | /** | ||
597 | * Archive an application on the device. | ||
598 | * This function tells the device to make an archive of the specified | ||
599 | * application. This results in the device creating a ZIP archive in the | ||
600 | * 'ApplicationArchives' directory and uninstalling the application. | ||
601 | * | ||
602 | * @param client The connected installation proxy client | ||
603 | * @param appid ApplicationIdentifier of the app to archive. | ||
604 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
605 | * Valid options include: | ||
606 | * "SkipUninstall" -> Boolean | ||
607 | * "ArchiveType" -> "ApplicationOnly" | ||
608 | * @param status_cb Callback function for progress and status information. If | ||
609 | * NULL is passed, this function will run synchronously. | ||
610 | * @param user_data Callback data passed to status_cb. | ||
611 | * | ||
612 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
613 | * an error occured. | ||
614 | * | ||
615 | * @note If a callback function is given (async mode), this function returns | ||
616 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
617 | * created successfully; any error occuring during the operation has to be | ||
618 | * handled inside the specified callback function. | ||
619 | */ | ||
620 | instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | 720 | instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) |
621 | { | 721 | { |
622 | if (!client || !client->parent || !appid) | 722 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
723 | |||
724 | plist_t command = plist_new_dict(); | ||
725 | plist_dict_set_item(command, "Command", plist_new_string("Archive")); | ||
726 | if (client_options) | ||
727 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
728 | plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); | ||
729 | |||
730 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
731 | |||
732 | plist_free(command); | ||
733 | |||
734 | return res; | ||
735 | } | ||
736 | |||
737 | instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
738 | { | ||
739 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
740 | |||
741 | plist_t command = plist_new_dict(); | ||
742 | plist_dict_set_item(command, "Command", plist_new_string("Restore")); | ||
743 | if (client_options) | ||
744 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
745 | plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); | ||
746 | |||
747 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
748 | |||
749 | plist_free(command); | ||
750 | |||
751 | return res; | ||
752 | } | ||
753 | |||
754 | instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
755 | { | ||
756 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
757 | |||
758 | plist_t command = plist_new_dict(); | ||
759 | plist_dict_set_item(command, "Command", plist_new_string("RemoveArchive")); | ||
760 | if (client_options) | ||
761 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
762 | plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); | ||
763 | |||
764 | res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); | ||
765 | |||
766 | plist_free(command); | ||
767 | |||
768 | return res; | ||
769 | } | ||
770 | |||
771 | instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result) | ||
772 | { | ||
773 | if (!client || !capabilities || !result) | ||
623 | return INSTPROXY_E_INVALID_ARG; | 774 | return INSTPROXY_E_INVALID_ARG; |
624 | 775 | ||
625 | if (client->status_updater) { | 776 | plist_t lookup_result = NULL; |
626 | return INSTPROXY_E_OP_IN_PROGRESS; | 777 | |
778 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; | ||
779 | |||
780 | plist_t command = plist_new_dict(); | ||
781 | plist_dict_set_item(command, "Command", plist_new_string("CheckCapabilitiesMatch")); | ||
782 | if (client_options) | ||
783 | plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); | ||
784 | |||
785 | if (capabilities) { | ||
786 | int i = 0; | ||
787 | plist_t capabilities_array = plist_new_array(); | ||
788 | while (capabilities[i]) { | ||
789 | plist_array_append_item(capabilities_array, plist_new_string(capabilities[i])); | ||
790 | i++; | ||
791 | } | ||
792 | plist_dict_set_item(command, "Capabilities", capabilities_array); | ||
627 | } | 793 | } |
628 | 794 | ||
629 | instproxy_lock(client); | 795 | res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); |
630 | instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL); | ||
631 | instproxy_unlock(client); | ||
632 | 796 | ||
633 | if (res != INSTPROXY_E_SUCCESS) { | 797 | if (res == INSTPROXY_E_SUCCESS) { |
634 | debug_info("could not send plist, error %d", res); | 798 | *result = lookup_result; |
635 | return res; | 799 | } else { |
800 | plist_free(lookup_result); | ||
636 | } | 801 | } |
637 | return instproxy_create_status_updater(client, status_cb, "Archive", user_data); | 802 | |
803 | plist_free(command); | ||
804 | |||
805 | return res; | ||
638 | } | 806 | } |
639 | 807 | ||
640 | /** | 808 | instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code) |
641 | * Restore a previously archived application on the device. | ||
642 | * This function is the counterpart to instproxy_archive. | ||
643 | * @see instproxy_archive | ||
644 | * | ||
645 | * @param client The connected installation proxy client | ||
646 | * @param appid ApplicationIdentifier of the app to restore. | ||
647 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
648 | * Currently there are no known client options, so pass NULL here. | ||
649 | * @param status_cb Callback function for progress and status information. If | ||
650 | * NULL is passed, this function will run synchronously. | ||
651 | * @param user_data Callback data passed to status_cb. | ||
652 | * | ||
653 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
654 | * an error occured. | ||
655 | * | ||
656 | * @note If a callback function is given (async mode), this function returns | ||
657 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
658 | * created successfully; any error occuring during the operation has to be | ||
659 | * handled inside the specified callback function. | ||
660 | */ | ||
661 | instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
662 | { | 809 | { |
663 | if (!client || !client->parent || !appid) | 810 | instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
811 | |||
812 | if (!status || !name) | ||
664 | return INSTPROXY_E_INVALID_ARG; | 813 | return INSTPROXY_E_INVALID_ARG; |
665 | 814 | ||
666 | if (client->status_updater) { | 815 | plist_t node = plist_dict_get_item(status, "Error"); |
667 | return INSTPROXY_E_OP_IN_PROGRESS; | 816 | if (node) { |
817 | plist_get_string_val(node, name); | ||
818 | } else { | ||
819 | /* no error here */ | ||
820 | res = INSTPROXY_E_SUCCESS; | ||
668 | } | 821 | } |
669 | 822 | ||
670 | instproxy_lock(client); | 823 | if (code != NULL) { |
671 | instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL); | 824 | *code = 0; |
672 | instproxy_unlock(client); | 825 | node = plist_dict_get_item(status, "ErrorDetail"); |
826 | if (node) { | ||
827 | plist_get_uint_val(node, code); | ||
828 | *code &= 0xffffffff; | ||
829 | } | ||
830 | } | ||
673 | 831 | ||
674 | if (res != INSTPROXY_E_SUCCESS) { | 832 | if (description != NULL) { |
675 | debug_info("could not send plist, error %d", res); | 833 | node = plist_dict_get_item(status, "ErrorDescription"); |
676 | return res; | 834 | if (node) { |
835 | plist_get_string_val(node, description); | ||
836 | } | ||
837 | } | ||
838 | |||
839 | if (*name) { | ||
840 | res = instproxy_strtoerr(*name); | ||
677 | } | 841 | } |
678 | return instproxy_create_status_updater(client, status_cb, "Restore", user_data); | 842 | |
843 | return res; | ||
679 | } | 844 | } |
680 | 845 | ||
681 | /** | 846 | void instproxy_status_get_name(plist_t status, char **name) |
682 | * Removes a previously archived application from the device. | ||
683 | * This function removes the ZIP archive from the 'ApplicationArchives' | ||
684 | * directory. | ||
685 | * | ||
686 | * @param client The connected installation proxy client | ||
687 | * @param appid ApplicationIdentifier of the archived app to remove. | ||
688 | * @param client_options The client options to use, as PLIST_DICT, or NULL. | ||
689 | * Currently there are no known client options, so passing NULL is fine. | ||
690 | * @param status_cb Callback function for progress and status information. If | ||
691 | * NULL is passed, this function will run synchronously. | ||
692 | * @param user_data Callback data passed to status_cb. | ||
693 | * | ||
694 | * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if | ||
695 | * an error occured. | ||
696 | * | ||
697 | * @note If a callback function is given (async mode), this function returns | ||
698 | * INSTPROXY_E_SUCCESS immediately if the status updater thread has been | ||
699 | * created successfully; any error occuring during the operation has to be | ||
700 | * handled inside the specified callback function. | ||
701 | */ | ||
702 | instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) | ||
703 | { | 847 | { |
704 | if (!client || !client->parent || !appid) | 848 | if (name) { |
705 | return INSTPROXY_E_INVALID_ARG; | 849 | plist_t node = plist_dict_get_item(status, "Status"); |
850 | if (node) { | ||
851 | plist_get_string_val(node, name); | ||
852 | } else { | ||
853 | *name = NULL; | ||
854 | } | ||
855 | } | ||
856 | } | ||
706 | 857 | ||
707 | if (client->status_updater) { | 858 | void instproxy_status_get_percent_complete(plist_t status, int *percent) |
708 | return INSTPROXY_E_OP_IN_PROGRESS; | 859 | { |
860 | uint64_t val = 0; | ||
861 | if (percent) { | ||
862 | plist_t node = plist_dict_get_item(status, "PercentComplete"); | ||
863 | if (node) { | ||
864 | plist_get_uint_val(node, &val); | ||
865 | *percent = val; | ||
866 | } | ||
709 | } | 867 | } |
868 | } | ||
710 | 869 | ||
711 | instproxy_lock(client); | 870 | void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list) |
712 | instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); | 871 | { |
713 | instproxy_unlock(client); | 872 | plist_t node = NULL; |
873 | |||
874 | if (status && plist_get_node_type(status) == PLIST_DICT) { | ||
875 | /* command specific logic: parse browsed list */ | ||
876 | if (list != NULL) { | ||
877 | node = plist_dict_get_item(status, "CurrentList"); | ||
878 | if (node) { | ||
879 | *current_amount = plist_array_get_size(node); | ||
880 | *list = plist_copy(node); | ||
881 | } | ||
882 | } | ||
714 | 883 | ||
715 | if (res != INSTPROXY_E_SUCCESS) { | 884 | if (total != NULL) { |
716 | debug_info("could not send plist, error %d", res); | 885 | node = plist_dict_get_item(status, "Total"); |
717 | return res; | 886 | if (node) { |
887 | plist_get_uint_val(node, total); | ||
888 | } | ||
889 | } | ||
890 | |||
891 | if (current_amount != NULL) { | ||
892 | node = plist_dict_get_item(status, "CurrentAmount"); | ||
893 | if (node) { | ||
894 | plist_get_uint_val(node, current_amount); | ||
895 | } | ||
896 | } | ||
897 | |||
898 | if (current_index != NULL) { | ||
899 | node = plist_dict_get_item(status, "CurrentIndex"); | ||
900 | if (node) { | ||
901 | plist_get_uint_val(node, current_index); | ||
902 | } | ||
903 | } | ||
718 | } | 904 | } |
719 | return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data); | ||
720 | } | 905 | } |
721 | 906 | ||
722 | /** | 907 | void instproxy_command_get_name(plist_t command, char** name) |
723 | * Create a new client_options plist. | 908 | { |
724 | * | 909 | if (name) { |
725 | * @return A new plist_t of type PLIST_DICT. | 910 | plist_t node = plist_dict_get_item(command, "Command"); |
726 | */ | 911 | if (node) { |
727 | plist_t instproxy_client_options_new() | 912 | plist_get_string_val(node, name); |
913 | } else { | ||
914 | *name = NULL; | ||
915 | } | ||
916 | } | ||
917 | } | ||
918 | |||
919 | plist_t instproxy_client_options_new(void) | ||
728 | { | 920 | { |
729 | return plist_new_dict(); | 921 | return plist_new_dict(); |
730 | } | 922 | } |
731 | 923 | ||
732 | /** | ||
733 | * Add one or more new key:value pairs to the given client_options. | ||
734 | * | ||
735 | * @param client_options The client options to modify. | ||
736 | * @param ... KEY, VALUE, [KEY, VALUE], NULL | ||
737 | * | ||
738 | * @note The keys and values passed are expected to be strings, except for | ||
739 | * "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type | ||
740 | * PLIST_DATA as value, or "SkipUninstall" needing int as value. | ||
741 | */ | ||
742 | void instproxy_client_options_add(plist_t client_options, ...) | 924 | void instproxy_client_options_add(plist_t client_options, ...) |
743 | { | 925 | { |
744 | if (!client_options) | 926 | if (!client_options) |
745 | return; | 927 | return; |
928 | |||
746 | va_list args; | 929 | va_list args; |
747 | va_start(args, client_options); | 930 | va_start(args, client_options); |
748 | char *arg = va_arg(args, char*); | 931 | char *arg = va_arg(args, char*); |
@@ -750,21 +933,21 @@ void instproxy_client_options_add(plist_t client_options, ...) | |||
750 | char *key = strdup(arg); | 933 | char *key = strdup(arg); |
751 | if (!strcmp(key, "SkipUninstall")) { | 934 | if (!strcmp(key, "SkipUninstall")) { |
752 | int intval = va_arg(args, int); | 935 | int intval = va_arg(args, int); |
753 | plist_dict_insert_item(client_options, key, plist_new_bool(intval)); | 936 | plist_dict_set_item(client_options, key, plist_new_bool(intval)); |
754 | } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata")) { | 937 | } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes") || !strcmp(key, "BundleIDs")) { |
755 | plist_t plistval = va_arg(args, plist_t); | 938 | plist_t plistval = va_arg(args, plist_t); |
756 | if (!plistval) { | 939 | if (!plistval) { |
757 | free(key); | 940 | free(key); |
758 | break; | 941 | break; |
759 | } | 942 | } |
760 | plist_dict_insert_item(client_options, key, plist_copy(plistval)); | 943 | plist_dict_set_item(client_options, key, plist_copy(plistval)); |
761 | } else { | 944 | } else { |
762 | char *strval = va_arg(args, char*); | 945 | char *strval = va_arg(args, char*); |
763 | if (!strval) { | 946 | if (!strval) { |
764 | free(key); | 947 | free(key); |
765 | break; | 948 | break; |
766 | } | 949 | } |
767 | plist_dict_insert_item(client_options, key, plist_new_string(strval)); | 950 | plist_dict_set_item(client_options, key, plist_new_string(strval)); |
768 | } | 951 | } |
769 | free(key); | 952 | free(key); |
770 | arg = va_arg(args, char*); | 953 | arg = va_arg(args, char*); |
@@ -772,15 +955,106 @@ void instproxy_client_options_add(plist_t client_options, ...) | |||
772 | va_end(args); | 955 | va_end(args); |
773 | } | 956 | } |
774 | 957 | ||
775 | /** | 958 | void instproxy_client_options_set_return_attributes(plist_t client_options, ...) |
776 | * Free client_options plist. | 959 | { |
777 | * | 960 | if (!client_options) |
778 | * @param client_options The client options plist to free. Does nothing if NULL | 961 | return; |
779 | * is passed. | 962 | |
780 | */ | 963 | plist_t return_attributes = plist_new_array(); |
964 | |||
965 | va_list args; | ||
966 | va_start(args, client_options); | ||
967 | char *arg = va_arg(args, char*); | ||
968 | while (arg) { | ||
969 | char *attribute = strdup(arg); | ||
970 | plist_array_append_item(return_attributes, plist_new_string(attribute)); | ||
971 | free(attribute); | ||
972 | arg = va_arg(args, char*); | ||
973 | } | ||
974 | va_end(args); | ||
975 | |||
976 | plist_dict_set_item(client_options, "ReturnAttributes", return_attributes); | ||
977 | } | ||
978 | |||
781 | void instproxy_client_options_free(plist_t client_options) | 979 | void instproxy_client_options_free(plist_t client_options) |
782 | { | 980 | { |
783 | if (client_options) { | 981 | if (client_options) { |
784 | plist_free(client_options); | 982 | plist_free(client_options); |
785 | } | 983 | } |
786 | } | 984 | } |
985 | |||
986 | instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path) | ||
987 | { | ||
988 | if (!client || !client->parent || !bundle_id) | ||
989 | return INSTPROXY_E_INVALID_ARG; | ||
990 | |||
991 | plist_t apps = NULL; | ||
992 | |||
993 | // create client options for any application types | ||
994 | plist_t client_opts = instproxy_client_options_new(); | ||
995 | instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL); | ||
996 | |||
997 | // only return attributes we need | ||
998 | instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", "Path", NULL); | ||
999 | |||
1000 | // only query for specific appid | ||
1001 | const char* appids[] = {bundle_id, NULL}; | ||
1002 | |||
1003 | // query device for list of apps | ||
1004 | instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps); | ||
1005 | |||
1006 | instproxy_client_options_free(client_opts); | ||
1007 | |||
1008 | if (ierr != INSTPROXY_E_SUCCESS) { | ||
1009 | return ierr; | ||
1010 | } | ||
1011 | |||
1012 | plist_t app_found = plist_access_path(apps, 1, bundle_id); | ||
1013 | if (!app_found) { | ||
1014 | if (apps) | ||
1015 | plist_free(apps); | ||
1016 | *path = NULL; | ||
1017 | return INSTPROXY_E_OP_FAILED; | ||
1018 | } | ||
1019 | |||
1020 | char* path_str = NULL; | ||
1021 | plist_t path_p = plist_dict_get_item(app_found, "Path"); | ||
1022 | if (path_p) { | ||
1023 | plist_get_string_val(path_p, &path_str); | ||
1024 | } | ||
1025 | |||
1026 | char* exec_str = NULL; | ||
1027 | plist_t exec_p = plist_dict_get_item(app_found, "CFBundleExecutable"); | ||
1028 | if (exec_p) { | ||
1029 | plist_get_string_val(exec_p, &exec_str); | ||
1030 | } | ||
1031 | |||
1032 | if (!path_str) { | ||
1033 | debug_info("app path not found"); | ||
1034 | return INSTPROXY_E_OP_FAILED; | ||
1035 | } | ||
1036 | |||
1037 | if (!exec_str) { | ||
1038 | debug_info("bundle executable not found"); | ||
1039 | return INSTPROXY_E_OP_FAILED; | ||
1040 | } | ||
1041 | |||
1042 | plist_free(apps); | ||
1043 | |||
1044 | char* ret = (char*)malloc(strlen(path_str) + 1 + strlen(exec_str) + 1); | ||
1045 | strcpy(ret, path_str); | ||
1046 | strcat(ret, "/"); | ||
1047 | strcat(ret, exec_str); | ||
1048 | |||
1049 | *path = ret; | ||
1050 | |||
1051 | if (path_str) { | ||
1052 | free(path_str); | ||
1053 | } | ||
1054 | |||
1055 | if (exec_str) { | ||
1056 | free(exec_str); | ||
1057 | } | ||
1058 | |||
1059 | return INSTPROXY_E_SUCCESS; | ||
1060 | } | ||