1
|
|
|
#!/usr/bin/env python3 |
2
|
4 |
|
"""This module is used for miscellaneous utilities.""" |
3
|
|
|
|
4
|
4 |
|
import os # path work |
5
|
4 |
|
import argparse # argument parser for filters |
6
|
4 |
|
import platform # platform info |
7
|
4 |
|
import glob # cap grabbing |
8
|
4 |
|
import hashlib # base url creation |
9
|
4 |
|
import threading # get thread for spinner |
10
|
4 |
|
import time # spinner delay |
11
|
4 |
|
import sys # streams, version info |
12
|
4 |
|
import itertools # spinners gonna spin |
13
|
4 |
|
import subprocess # loader verification |
14
|
4 |
|
from bbarchivist import bbconstants # cap location, version, filename bits |
15
|
4 |
|
from bbarchivist import compat # backwards compat |
16
|
4 |
|
from bbarchivist import dummy # useless stdout |
17
|
4 |
|
from bbarchivist import exceptions # exceptions |
18
|
4 |
|
from bbarchivist import iniconfig # config parsing |
19
|
|
|
|
20
|
4 |
|
__author__ = "Thurask" |
21
|
4 |
|
__license__ = "WTFPL v2" |
22
|
4 |
|
__copyright__ = "Copyright 2015-2016 Thurask" |
23
|
|
|
|
24
|
|
|
|
25
|
4 |
|
def grab_cap(): |
26
|
|
|
""" |
27
|
|
|
Figure out where cap is, local, specified or system-supplied. |
28
|
|
|
""" |
29
|
4 |
|
try: |
30
|
4 |
|
capfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CAP.filename))[0] |
31
|
4 |
|
except IndexError: |
32
|
|
|
try: |
33
|
|
|
cappath = cappath_config_loader() |
34
|
|
|
capfile = glob.glob(cappath)[0] |
35
|
|
|
except IndexError: |
36
|
|
|
cappath_config_writer(bbconstants.CAP.location) |
37
|
|
|
return bbconstants.CAP.location # no ini cap |
38
|
|
|
else: |
39
|
|
|
cappath_config_writer(os.path.abspath(capfile)) |
40
|
|
|
return os.path.abspath(capfile) # ini cap |
41
|
|
|
else: |
42
|
4 |
|
return os.path.abspath(capfile) # local cap |
43
|
|
|
|
44
|
|
|
|
45
|
4 |
|
def grab_cfp(): |
46
|
|
|
""" |
47
|
|
|
Figure out where cfp is, local or system-supplied. |
48
|
|
|
""" |
49
|
4 |
|
try: |
50
|
4 |
|
cfpfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CFP.filename))[0] |
51
|
4 |
|
except IndexError: |
52
|
4 |
|
cfpfile = bbconstants.CFP.location # system cfp |
53
|
4 |
|
return os.path.abspath(cfpfile) # local cfp |
54
|
|
|
|
55
|
|
|
|
56
|
4 |
|
def new_enough(minver): |
57
|
|
|
""" |
58
|
|
|
Check if we're at or above a minimum Python version. |
59
|
|
|
|
60
|
|
|
:param minver: Minimum Python version (3.minver). |
61
|
|
|
:type minver: int |
62
|
|
|
""" |
63
|
4 |
|
return False if minver > sys.version_info[1] else True |
64
|
|
|
|
65
|
|
|
|
66
|
4 |
|
def fsizer(file_size): |
67
|
|
|
""" |
68
|
|
|
Raw byte file size to human-readable string. |
69
|
|
|
|
70
|
|
|
:param file_size: Number to parse. |
71
|
|
|
:type file_size: float |
72
|
|
|
""" |
73
|
4 |
|
if file_size is None: |
74
|
4 |
|
file_size = 0 |
75
|
4 |
|
fsize = float(file_size) |
76
|
4 |
|
for sfix in ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']: |
77
|
4 |
|
if fsize < 1024.0: |
78
|
4 |
|
size = "{0:3.2f}{1}".format(fsize, sfix) |
79
|
4 |
|
break |
80
|
|
|
else: |
81
|
4 |
|
fsize /= 1024.0 |
82
|
|
|
else: |
83
|
4 |
|
size = "{0:3.2f}{1}".format(fsize, 'YB') |
84
|
4 |
|
return size |
85
|
|
|
|
86
|
|
|
|
87
|
4 |
|
def signed_file_args(files): |
88
|
|
|
""" |
89
|
|
|
Check if there are between 1 and 6 files supplied to argparse. |
90
|
|
|
|
91
|
|
|
:param files: List of signed files, between 1 and 6 strings. |
92
|
|
|
:type files: list(str) |
93
|
|
|
""" |
94
|
4 |
|
filelist = [file for file in files if file] |
95
|
4 |
|
if not 1 <= len(filelist) <= 6: |
96
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Requires 1-6 signed files") |
97
|
4 |
|
return files |
98
|
|
|
|
99
|
|
|
|
100
|
4 |
|
def file_exists(file): |
101
|
|
|
""" |
102
|
|
|
Check if file exists, raise argparse error if it doesn't. |
103
|
|
|
|
104
|
|
|
:param file: Path to a file, including extension. |
105
|
|
|
:type file: str |
106
|
|
|
""" |
107
|
4 |
|
if not os.path.exists(file): |
108
|
4 |
|
raise argparse.ArgumentError(argument=None, message="{0} not found.".format(file)) |
109
|
4 |
|
return file |
110
|
|
|
|
111
|
|
|
|
112
|
4 |
|
def positive_integer(input_int): |
113
|
|
|
""" |
114
|
|
|
Check if number > 0, raise argparse error if it isn't. |
115
|
|
|
|
116
|
|
|
:param input_int: Integer to check. |
117
|
|
|
:type input_int: str |
118
|
|
|
""" |
119
|
4 |
|
if int(input_int) <= 0: |
120
|
4 |
|
info = "{0} is not >=0.".format(str(input_int)) |
121
|
4 |
|
raise argparse.ArgumentError(argument=None, message=info) |
122
|
4 |
|
return int(input_int) |
123
|
|
|
|
124
|
|
|
|
125
|
4 |
|
def valid_method(method): |
126
|
|
|
""" |
127
|
|
|
Check if compression method is valid, raise argparse error if it isn't. |
128
|
|
|
|
129
|
|
|
:param method: Compression method to check. |
130
|
|
|
:type method: str |
131
|
|
|
""" |
132
|
4 |
|
methodlist = bbconstants.METHODS |
133
|
4 |
|
if not new_enough(3): |
134
|
4 |
|
methodlist = [x for x in bbconstants.METHODS if x != "txz"] |
135
|
4 |
|
if method not in methodlist: |
136
|
4 |
|
info = "Invalid method {0}.".format(method) |
137
|
4 |
|
raise argparse.ArgumentError(argument=None, message=info) |
138
|
4 |
|
return method |
139
|
|
|
|
140
|
|
|
|
141
|
4 |
|
def valid_carrier(mcc_mnc): |
142
|
|
|
""" |
143
|
|
|
Check if MCC/MNC is valid (1-3 chars), raise argparse error if it isn't. |
144
|
|
|
|
145
|
|
|
:param mcc_mnc: MCC/MNC to check. |
146
|
|
|
:type mcc_mnc: str |
147
|
|
|
""" |
148
|
4 |
|
if not str(mcc_mnc).isdecimal(): |
149
|
4 |
|
infod = "Non-integer {0}.".format(str(mcc_mnc)) |
150
|
4 |
|
raise argparse.ArgumentError(argument=None, message=infod) |
151
|
4 |
|
if len(str(mcc_mnc)) > 3 or len(str(mcc_mnc)) == 0: |
152
|
4 |
|
infol = "{0} is invalid.".format(str(mcc_mnc)) |
153
|
4 |
|
raise argparse.ArgumentError(argument=None, message=infol) |
154
|
|
|
else: |
155
|
4 |
|
return mcc_mnc |
156
|
|
|
|
157
|
|
|
|
158
|
4 |
|
def escreens_pin(pin): |
159
|
|
|
""" |
160
|
|
|
Check if given PIN is valid, raise argparse error if it isn't. |
161
|
|
|
|
162
|
|
|
:param pin: PIN to check. |
163
|
|
|
:type pin: str |
164
|
|
|
""" |
165
|
4 |
|
if len(pin) == 8: |
166
|
4 |
|
try: |
167
|
4 |
|
int(pin, 16) # hexadecimal-ness |
168
|
4 |
|
except ValueError: |
169
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Invalid PIN.") |
170
|
|
|
else: |
171
|
4 |
|
return pin.lower() |
172
|
|
|
else: |
173
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Invalid PIN.") |
174
|
|
|
|
175
|
|
|
|
176
|
4 |
|
def escreens_duration(duration): |
177
|
|
|
""" |
178
|
|
|
Check if Engineering Screens duration is valid. |
179
|
|
|
|
180
|
|
|
:param duration: Duration to check. |
181
|
|
|
:type duration: int |
182
|
|
|
""" |
183
|
4 |
|
if int(duration) in (1, 3, 6, 15, 30): |
184
|
4 |
|
return int(duration) |
185
|
|
|
else: |
186
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Invalid duration.") |
187
|
|
|
|
188
|
|
|
|
189
|
4 |
|
def droidlookup_hashtype(method): |
190
|
|
|
""" |
191
|
|
|
Check if Android autoloader lookup hash type is valid. |
192
|
|
|
|
193
|
|
|
:param method: None for regular OS links, "sha256/512" for SHA256 or 512 hash. |
194
|
|
|
:type method: str |
195
|
|
|
""" |
196
|
4 |
|
if method.lower() in ("sha512", "sha256"): |
197
|
4 |
|
return method.lower() |
198
|
|
|
else: |
199
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Invalid type.") |
200
|
|
|
|
201
|
|
|
|
202
|
4 |
|
def droidlookup_devicetype(device): |
203
|
|
|
""" |
204
|
|
|
Check if Android autoloader device type is valid. |
205
|
|
|
|
206
|
|
|
:param device: Android autoloader types to check. |
207
|
|
|
:type device: str |
208
|
|
|
""" |
209
|
4 |
|
devices = ("Priv", "DTEK50", "DTEK60") |
210
|
4 |
|
if device is None: |
211
|
4 |
|
return None |
212
|
|
|
else: |
213
|
4 |
|
for dev in devices: |
214
|
4 |
|
if dev.lower() == device.lower(): |
215
|
4 |
|
return dev |
216
|
4 |
|
raise argparse.ArgumentError(argument=None, message="Invalid device.") |
217
|
|
|
|
218
|
|
|
|
219
|
4 |
|
def s2b(input_check): |
220
|
|
|
""" |
221
|
|
|
Return Boolean interpretation of string input. |
222
|
|
|
|
223
|
|
|
:param input_check: String to check if it means True or False. |
224
|
|
|
:type input_check: str |
225
|
|
|
""" |
226
|
4 |
|
return str(input_check).lower() in ("yes", "true", "t", "1", "y") |
227
|
|
|
|
228
|
|
|
|
229
|
4 |
|
def is_amd64(): |
230
|
|
|
""" |
231
|
|
|
Check if script is running on an AMD64 system. |
232
|
|
|
""" |
233
|
4 |
|
return platform.machine().endswith("64") |
234
|
|
|
|
235
|
|
|
|
236
|
4 |
|
def is_windows(): |
237
|
|
|
""" |
238
|
|
|
Check if script is running on Windows. |
239
|
|
|
""" |
240
|
4 |
|
return platform.system() == "Windows" |
241
|
|
|
|
242
|
|
|
|
243
|
4 |
|
def talkaprint(msg, talkative=False): |
244
|
|
|
""" |
245
|
|
|
Print only if asked to. |
246
|
|
|
|
247
|
|
|
:param msg: Message to print. |
248
|
|
|
:type msg: str |
249
|
|
|
|
250
|
|
|
:param talkative: Whether to output to screen. False by default. |
251
|
|
|
:type talkative: bool |
252
|
|
|
""" |
253
|
4 |
|
if talkative: |
254
|
4 |
|
print(msg) |
255
|
|
|
|
256
|
|
|
|
257
|
4 |
|
def get_seven_zip(talkative=False): |
258
|
|
|
""" |
259
|
|
|
Return name of 7-Zip executable. |
260
|
|
|
On POSIX, it MUST be 7za. |
261
|
|
|
On Windows, it can be installed or supplied with the script. |
262
|
|
|
:func:`win_seven_zip` is used to determine if it's installed. |
263
|
|
|
|
264
|
|
|
:param talkative: Whether to output to screen. False by default. |
265
|
|
|
:type talkative: bool |
266
|
|
|
""" |
267
|
4 |
|
return win_seven_zip(talkative) if is_windows() else "7za" |
268
|
|
|
|
269
|
|
|
|
270
|
4 |
|
def win_seven_zip(talkative=False): |
271
|
|
|
""" |
272
|
|
|
For Windows, check where 7-Zip is ("where", pretty much). |
273
|
|
|
Consult registry first for any installed instances of 7-Zip. |
274
|
|
|
|
275
|
|
|
:param talkative: Whether to output to screen. False by default. |
276
|
|
|
:type talkative: bool |
277
|
|
|
""" |
278
|
|
|
talkaprint("CHECKING INSTALLED FILES...", talkative) |
279
|
|
|
try: |
280
|
|
|
import winreg # windows registry |
281
|
|
|
hk7z = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\7-Zip") |
282
|
|
|
path = winreg.QueryValueEx(hk7z, "Path") |
283
|
|
|
except OSError as exc: |
284
|
|
|
if talkative: |
285
|
|
|
exceptions.handle_exception(exc, xit=None) |
286
|
|
|
talkaprint("TRYING LOCAL FILES...", talkative) |
287
|
|
|
return win_seven_zip_local(talkative) |
288
|
|
|
else: |
289
|
|
|
talkaprint("7ZIP USING INSTALLED FILES", talkative) |
290
|
|
|
return '"{0}"'.format(os.path.join(path[0], "7z.exe")) |
291
|
|
|
|
292
|
|
|
|
293
|
4 |
|
def win_seven_zip_local(talkative=False): |
294
|
|
|
""" |
295
|
|
|
If 7-Zip isn't in the registry, fall back onto supplied executables. |
296
|
|
|
If *those* aren't there, return "error". |
297
|
|
|
|
298
|
|
|
:param talkative: Whether to output to screen. False by default. |
299
|
|
|
:type talkative: bool |
300
|
|
|
""" |
301
|
|
|
filecount = len([x for x in os.listdir(os.getcwd()) if x in ["7za.exe", "7z.exe"]]) |
302
|
|
|
if filecount == 2: |
303
|
|
|
talkaprint("7ZIP USING LOCAL FILES", talkative) |
304
|
|
|
szexe = "7za.64.exe" if is_amd64() else "7za.exe" |
305
|
|
|
else: |
306
|
|
|
talkaprint("NO LOCAL FILES", talkative) |
307
|
|
|
szexe = "error" |
308
|
|
|
return szexe |
309
|
|
|
|
310
|
|
|
|
311
|
4 |
|
def get_core_count(): |
312
|
|
|
""" |
313
|
|
|
Find out how many CPU cores this system has. |
314
|
|
|
""" |
315
|
4 |
|
try: |
316
|
4 |
|
cores = str(compat.enum_cpus()) # 3.4 and up |
317
|
|
|
except NotImplementedError: |
318
|
|
|
cores = "1" # 3.2-3.3 |
319
|
|
|
else: |
320
|
4 |
|
if compat.enum_cpus() is None: |
321
|
|
|
cores = "1" |
322
|
4 |
|
return cores |
323
|
|
|
|
324
|
|
|
|
325
|
4 |
|
def prep_seven_zip_path(path, talkative=False): |
326
|
|
|
""" |
327
|
|
|
Print p7zip path on POSIX, or notify if not there. |
328
|
|
|
|
329
|
|
|
:param path: Path to use. |
330
|
|
|
:type path: str |
331
|
|
|
|
332
|
|
|
:param talkative: Whether to output to screen. False by default. |
333
|
|
|
:type talkative: bool |
334
|
|
|
""" |
335
|
4 |
|
if path is None: |
336
|
4 |
|
talkaprint("NO 7ZIP\nPLEASE INSTALL p7zip", talkative) |
337
|
4 |
|
return False |
338
|
|
|
else: |
339
|
4 |
|
talkaprint("7ZIP FOUND AT {0}".format(path), talkative) |
340
|
4 |
|
return True |
341
|
|
|
|
342
|
|
|
|
343
|
4 |
|
def prep_seven_zip_posix(talkative=False): |
344
|
|
|
""" |
345
|
|
|
Check for p7zip on POSIX. |
346
|
|
|
|
347
|
|
|
:param talkative: Whether to output to screen. False by default. |
348
|
|
|
:type talkative: bool |
349
|
|
|
""" |
350
|
4 |
|
try: |
351
|
4 |
|
path = compat.where_which("7za") |
352
|
4 |
|
except ImportError: |
353
|
4 |
|
talkaprint("PLEASE INSTALL SHUTILWHICH WITH PIP", talkative) |
354
|
4 |
|
return False |
355
|
|
|
else: |
356
|
4 |
|
return prep_seven_zip_path(path, talkative) |
357
|
|
|
|
358
|
|
|
|
359
|
4 |
|
def prep_seven_zip(talkative=False): |
360
|
|
|
""" |
361
|
|
|
Check for presence of 7-Zip. |
362
|
|
|
On POSIX, check for p7zip. |
363
|
|
|
On Windows, check for 7-Zip. |
364
|
|
|
|
365
|
|
|
:param talkative: Whether to output to screen. False by default. |
366
|
|
|
:type talkative: bool |
367
|
|
|
""" |
368
|
4 |
|
if is_windows(): |
369
|
|
|
return get_seven_zip(talkative) != "error" |
370
|
|
|
else: |
371
|
4 |
|
return prep_seven_zip_posix(talkative) |
372
|
|
|
|
373
|
|
|
|
374
|
4 |
|
def increment(version, inc=3): |
375
|
|
|
""" |
376
|
|
|
Increment version by given number. For repeated lookups. |
377
|
|
|
|
378
|
|
|
:param version: w.x.y.ZZZZ, becomes w.x.y.(ZZZZ + increment). |
379
|
|
|
:type version: str |
380
|
|
|
|
381
|
|
|
:param inc: What to increment by. Default is 3. |
382
|
|
|
:type inc: str |
383
|
|
|
""" |
384
|
4 |
|
splitos = version.split(".") |
385
|
4 |
|
splitos[3] = int(splitos[3]) |
386
|
4 |
|
if splitos[3] > 9996: # prevent overflow |
387
|
4 |
|
splitos[3] = 0 |
388
|
4 |
|
splitos[3] += int(inc) |
389
|
4 |
|
splitos[3] = str(splitos[3]) |
390
|
4 |
|
return ".".join(splitos) |
391
|
|
|
|
392
|
|
|
|
393
|
4 |
|
def stripper(name): |
394
|
|
|
""" |
395
|
|
|
Strip fluff from bar filename. |
396
|
|
|
|
397
|
|
|
:param name: Bar filename, must contain '-nto+armle-v7+signed.bar'. |
398
|
|
|
:type name: str |
399
|
|
|
""" |
400
|
4 |
|
return name.replace("-nto+armle-v7+signed.bar", "") |
401
|
|
|
|
402
|
|
|
|
403
|
4 |
|
def create_base_url(softwareversion): |
404
|
|
|
""" |
405
|
|
|
Make the root URL for production server files. |
406
|
|
|
|
407
|
|
|
:param softwareversion: Software version to hash. |
408
|
|
|
:type softwareversion: str |
409
|
|
|
""" |
410
|
|
|
# Hash software version |
411
|
4 |
|
swhash = hashlib.sha1(softwareversion.encode('utf-8')) |
412
|
4 |
|
hashedsoftwareversion = swhash.hexdigest() |
413
|
|
|
# Root of all urls |
414
|
4 |
|
baseurl = "http://cdn.fs.sl.blackberry.com/fs/qnx/production/{0}".format(hashedsoftwareversion) |
415
|
4 |
|
return baseurl |
416
|
|
|
|
417
|
|
|
|
418
|
4 |
|
def format_app_name(appname): |
419
|
|
|
""" |
420
|
|
|
Convert long reverse DNS name to short name. |
421
|
|
|
|
422
|
|
|
:param appname: Application name (ex. sys.pim.calendar -> "calendar") |
423
|
|
|
:type appname: str |
424
|
|
|
""" |
425
|
4 |
|
final = appname.split(".")[-1] |
426
|
4 |
|
return final |
427
|
|
|
|
428
|
|
|
|
429
|
4 |
|
def create_bar_url(softwareversion, appname, appversion, clean=False): |
430
|
|
|
""" |
431
|
|
|
Make the URL for any production server file. |
432
|
|
|
|
433
|
|
|
:param softwareversion: Software version to hash. |
434
|
|
|
:type softwareversion: str |
435
|
|
|
|
436
|
|
|
:param appname: Application name, preferably like on server. |
437
|
|
|
:type appname: str |
438
|
|
|
|
439
|
|
|
:param appversion: Application version. |
440
|
|
|
:type appversion: str |
441
|
|
|
|
442
|
|
|
:param clean: Whether or not to clean up app name. Default is False. |
443
|
|
|
:type clean: bool |
444
|
|
|
""" |
445
|
4 |
|
baseurl = create_base_url(softwareversion) |
446
|
4 |
|
if clean: |
447
|
4 |
|
appname = format_app_name(appname) |
448
|
4 |
|
return "{0}/{1}-{2}-nto+armle-v7+signed.bar".format(baseurl, appname, appversion) |
449
|
|
|
|
450
|
|
|
|
451
|
4 |
|
def generate_urls(softwareversion, osversion, radioversion, core=False): |
452
|
|
|
""" |
453
|
|
|
Generate a list of OS URLs and a list of radio URLs based on input. |
454
|
|
|
|
455
|
|
|
:param softwareversion: Software version to hash. |
456
|
|
|
:type softwareversion: str |
457
|
|
|
|
458
|
|
|
:param osversion: OS version. |
459
|
|
|
:type osversion: str |
460
|
|
|
|
461
|
|
|
:param radioversion: Radio version. |
462
|
|
|
:type radioversion: str |
463
|
|
|
|
464
|
|
|
:param core: Whether or not to return core URLs as well. |
465
|
|
|
:type core: bool |
466
|
|
|
""" |
467
|
4 |
|
osurls = [ |
468
|
|
|
create_bar_url(softwareversion, "winchester.factory_sfi.desktop", osversion), |
469
|
|
|
create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion), |
470
|
|
|
create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion), |
471
|
|
|
create_bar_url(softwareversion, "qc8974.factory_sfi.desktop", osversion) |
472
|
|
|
] |
473
|
4 |
|
radiourls = [ |
474
|
|
|
create_bar_url(softwareversion, "m5730", radioversion), |
475
|
|
|
create_bar_url(softwareversion, "qc8960", radioversion), |
476
|
|
|
create_bar_url(softwareversion, "qc8960.omadm", radioversion), |
477
|
|
|
create_bar_url(softwareversion, "qc8960.wtr", radioversion), |
478
|
|
|
create_bar_url(softwareversion, "qc8960.wtr5", radioversion), |
479
|
|
|
create_bar_url(softwareversion, "qc8930.wtr5", radioversion), |
480
|
|
|
create_bar_url(softwareversion, "qc8974.wtr2", radioversion) |
481
|
|
|
] |
482
|
4 |
|
coreurls = [] |
483
|
4 |
|
splitos = [int(i) for i in osversion.split(".")] |
484
|
4 |
|
osurls[2] = filter_1031(osurls[2], splitos, 5) |
485
|
4 |
|
osurls[3] = filter_1031(osurls[3], splitos, 6) |
486
|
4 |
|
if core: |
487
|
4 |
|
coreurls = [x.replace(".desktop", "") for x in osurls] |
488
|
4 |
|
return osurls, radiourls, coreurls |
489
|
|
|
|
490
|
|
|
|
491
|
4 |
|
def filter_1031(osurl, splitos, device): |
492
|
|
|
""" |
493
|
|
|
Modify URLs to reflect changes in 10.3.1+. |
494
|
|
|
|
495
|
|
|
:param osurl: OS URL to modify. |
496
|
|
|
:type osurl: str |
497
|
|
|
|
498
|
|
|
:param splitos: OS version, split and cast to int: [10, 3, 2, 2876] |
499
|
|
|
:type splitos: list(int) |
500
|
|
|
|
501
|
|
|
:param device: Device to use. |
502
|
|
|
:type device: int |
503
|
|
|
""" |
504
|
4 |
|
if (splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= 1): |
505
|
4 |
|
if device == 5: |
506
|
4 |
|
osurl = osurl.replace("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30") |
507
|
4 |
|
elif device == 6: |
508
|
4 |
|
osurl = osurl.replace("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974") |
509
|
4 |
|
return osurl |
510
|
|
|
|
511
|
|
|
|
512
|
4 |
|
def generate_lazy_urls(softwareversion, osversion, radioversion, device): |
513
|
|
|
""" |
514
|
|
|
Generate a pair of OS/radio URLs based on input. |
515
|
|
|
|
516
|
|
|
:param softwareversion: Software version to hash. |
517
|
|
|
:type softwareversion: str |
518
|
|
|
|
519
|
|
|
:param osversion: OS version. |
520
|
|
|
:type osversion: str |
521
|
|
|
|
522
|
|
|
:param radioversion: Radio version. |
523
|
|
|
:type radioversion: str |
524
|
|
|
|
525
|
|
|
:param device: Device to use. |
526
|
|
|
:type device: int |
527
|
|
|
""" |
528
|
4 |
|
splitos = [int(i) for i in osversion.split(".")] |
529
|
4 |
|
rads = ["m5730", "qc8960", "qc8960.omadm", "qc8960.wtr", |
530
|
|
|
"qc8960.wtr5", "qc8930.wtr4", "qc8974.wtr2"] |
531
|
4 |
|
oses = ["winchester.factory", "qc8960.factory", "qc8960.verizon", |
532
|
|
|
"qc8974.factory"] |
533
|
4 |
|
maps = {0:0, 1:1, 2:2, 3:1, 4:1, 5:1, 6:3} |
534
|
4 |
|
osurl = create_bar_url(softwareversion, "{0}_sfi.desktop".format(oses[maps[device]]), osversion) |
535
|
4 |
|
radiourl = create_bar_url(softwareversion, rads[device], radioversion) |
536
|
4 |
|
osurl = filter_1031(osurl, splitos, device) |
537
|
4 |
|
return osurl, radiourl |
538
|
|
|
|
539
|
|
|
|
540
|
4 |
|
def bulk_urls(softwareversion, osversion, radioversion, core=False, altsw=None): |
541
|
|
|
""" |
542
|
|
|
Generate all URLs, plus extra Verizon URLs. |
543
|
|
|
|
544
|
|
|
:param softwareversion: Software version to hash. |
545
|
|
|
:type softwareversion: str |
546
|
|
|
|
547
|
|
|
:param osversion: OS version. |
548
|
|
|
:type osversion: str |
549
|
|
|
|
550
|
|
|
:param radioversion: Radio version. |
551
|
|
|
:type radioversion: str |
552
|
|
|
|
553
|
|
|
:param device: Device to use. |
554
|
|
|
:type device: int |
555
|
|
|
|
556
|
|
|
:param core: Whether or not to return core URLs as well. |
557
|
|
|
:type core: bool |
558
|
|
|
|
559
|
|
|
:param altsw: Radio software release, if not the same as OS. |
560
|
|
|
:type altsw: str |
561
|
|
|
""" |
562
|
4 |
|
baseurl = create_base_url(softwareversion) |
563
|
4 |
|
osurls, radurls, coreurls = generate_urls(softwareversion, osversion, radioversion, core) |
564
|
4 |
|
vzwos, vzwrad = generate_lazy_urls(softwareversion, osversion, radioversion, 2) |
565
|
4 |
|
osurls.append(vzwos) |
566
|
4 |
|
radurls.append(vzwrad) |
567
|
4 |
|
vzwcore = vzwos.replace("sfi.desktop", "sfi") |
568
|
4 |
|
if core: |
569
|
4 |
|
coreurls.append(vzwcore) |
570
|
4 |
|
osurls = list(set(osurls)) # pop duplicates |
571
|
4 |
|
radurls = list(set(radurls)) |
572
|
4 |
|
if core: |
573
|
4 |
|
coreurls = list(set(coreurls)) |
574
|
4 |
|
if altsw is not None: |
575
|
4 |
|
altbase = create_base_url(altsw) |
576
|
4 |
|
radiourls2 = [] |
577
|
4 |
|
for rad in radurls: |
578
|
4 |
|
radiourls2.append(rad.replace(baseurl, altbase)) |
579
|
4 |
|
radurls = radiourls2 |
580
|
4 |
|
del radiourls2 |
581
|
4 |
|
return osurls, coreurls, radurls |
582
|
|
|
|
583
|
|
|
|
584
|
4 |
|
def line_begin(): |
585
|
|
|
""" |
586
|
|
|
Go to beginning of line, to overwrite whatever's there. |
587
|
|
|
""" |
588
|
4 |
|
sys.stdout.write("\r") |
589
|
4 |
|
sys.stdout.flush() |
590
|
|
|
|
591
|
|
|
|
592
|
4 |
|
def spinner_clear(): |
593
|
|
|
""" |
594
|
|
|
Get rid of any spinner residue left in stdout. |
595
|
|
|
""" |
596
|
4 |
|
sys.stdout.write("\b \b") |
597
|
4 |
|
sys.stdout.flush() |
598
|
|
|
|
599
|
|
|
|
600
|
4 |
|
class Spinner(object): |
601
|
|
|
""" |
602
|
|
|
A basic spinner using itertools. No need for progress. |
603
|
|
|
""" |
604
|
|
|
|
605
|
4 |
|
def __init__(self): |
606
|
4 |
|
self.wheel = itertools.cycle(['-', '/', '|', '\\']) |
607
|
4 |
|
self.file = dummy.UselessStdout() |
608
|
|
|
|
609
|
4 |
|
def after(self): |
610
|
|
|
""" |
611
|
|
|
Iterate over itertools.cycle, write to file. |
612
|
|
|
""" |
613
|
4 |
|
try: |
614
|
4 |
|
self.file.write(next(self.wheel)) |
615
|
4 |
|
self.file.flush() |
616
|
4 |
|
self.file.write("\b\r") |
617
|
4 |
|
self.file.flush() |
618
|
|
|
except (KeyboardInterrupt, SystemExit): |
619
|
|
|
self.stop() |
620
|
|
|
|
621
|
4 |
|
def stop(self): |
622
|
|
|
""" |
623
|
|
|
Kill output. |
624
|
|
|
""" |
625
|
4 |
|
self.file = dummy.UselessStdout() |
626
|
|
|
|
627
|
|
|
|
628
|
4 |
|
class SpinManager(object): |
629
|
|
|
""" |
630
|
|
|
Wraps around the itertools spinner, runs it in another thread. |
631
|
|
|
""" |
632
|
|
|
|
633
|
4 |
|
def __init__(self): |
634
|
4 |
|
spinner = Spinner() |
635
|
4 |
|
self.spinner = spinner |
636
|
4 |
|
self.thread = threading.Thread(target=self.loop, args=()) |
637
|
4 |
|
self.thread.daemon = True |
638
|
4 |
|
self.scanning = False |
639
|
4 |
|
self.spinner.file = dummy.UselessStdout() |
640
|
|
|
|
641
|
4 |
|
def start(self): |
642
|
|
|
""" |
643
|
|
|
Begin the spinner. |
644
|
|
|
""" |
645
|
4 |
|
self.spinner.file = sys.stderr |
646
|
4 |
|
self.scanning = True |
647
|
4 |
|
self.thread.start() |
648
|
|
|
|
649
|
4 |
|
def loop(self): |
650
|
|
|
""" |
651
|
|
|
Spin if scanning, clean up if not. |
652
|
|
|
""" |
653
|
4 |
|
while self.scanning: |
654
|
4 |
|
time.sleep(0.5) |
655
|
4 |
|
try: |
656
|
4 |
|
line_begin() |
657
|
4 |
|
self.spinner.after() |
658
|
|
|
except (KeyboardInterrupt, SystemExit): |
659
|
|
|
self.scanning = False |
660
|
|
|
self.stop() |
661
|
|
|
|
662
|
4 |
|
def stop(self): |
663
|
|
|
""" |
664
|
|
|
Stop the spinner. |
665
|
|
|
""" |
666
|
4 |
|
self.spinner.stop() |
667
|
4 |
|
self.scanning = False |
668
|
4 |
|
spinner_clear() |
669
|
4 |
|
line_begin() |
670
|
4 |
|
if not is_windows(): |
671
|
4 |
|
print("\n") |
672
|
|
|
|
673
|
|
|
|
674
|
4 |
|
def return_and_delete(target): |
675
|
|
|
""" |
676
|
|
|
Read text file, then delete it. Return contents. |
677
|
|
|
|
678
|
|
|
:param target: Text file to read. |
679
|
|
|
:type target: str |
680
|
|
|
""" |
681
|
4 |
|
with open(target, "r") as thefile: |
682
|
4 |
|
content = thefile.read() |
683
|
4 |
|
os.remove(target) |
684
|
4 |
|
return content |
685
|
|
|
|
686
|
|
|
|
687
|
4 |
|
def verify_loader_integrity(loaderfile): |
688
|
|
|
""" |
689
|
|
|
Test for created loader integrity. Windows-only. |
690
|
|
|
|
691
|
|
|
:param loaderfile: Path to loader. |
692
|
|
|
:type loaderfile: str |
693
|
|
|
""" |
694
|
4 |
|
if not is_windows(): |
695
|
4 |
|
pass |
696
|
|
|
else: |
697
|
4 |
|
excode = None |
698
|
4 |
|
try: |
699
|
4 |
|
with open(os.devnull, 'rb') as dnull: |
700
|
4 |
|
cmd = "{0} fileinfo".format(loaderfile) |
701
|
4 |
|
excode = subprocess.call(cmd, stdout=dnull, stderr=subprocess.STDOUT) |
702
|
4 |
|
except OSError: |
703
|
4 |
|
excode = -1 |
704
|
4 |
|
return excode == 0 # 0 if OK, non-zero if something broke |
705
|
|
|
|
706
|
|
|
|
707
|
4 |
|
def verify_bulk_loaders(ldir): |
708
|
|
|
""" |
709
|
|
|
Run :func:`verify_loader_integrity` for all files in a dir. |
710
|
|
|
|
711
|
|
|
:param ldir: Directory to use. |
712
|
|
|
:type ldir: str |
713
|
|
|
""" |
714
|
4 |
|
if not is_windows(): |
715
|
4 |
|
pass |
716
|
|
|
else: |
717
|
4 |
|
files = [os.path.join(ldir, file) for file in os.listdir(ldir) if not os.path.isdir(file)] |
718
|
4 |
|
brokens = [] |
719
|
4 |
|
for file in files: |
720
|
4 |
|
fname = os.path.basename(file) |
721
|
4 |
|
if fname.endswith(".exe") and fname.startswith(bbconstants.PREFIXES): |
722
|
4 |
|
print("TESTING: {0}".format(fname)) |
723
|
4 |
|
if not verify_loader_integrity(file): |
724
|
4 |
|
brokens.append(fname) |
725
|
4 |
|
return brokens |
726
|
|
|
|
727
|
|
|
|
728
|
4 |
|
def workers(input_data): |
729
|
|
|
""" |
730
|
|
|
Count number of CPU workers, smaller of number of threads and length of data. |
731
|
|
|
|
732
|
|
|
:param input_data: Input data, some iterable. |
733
|
|
|
:type input_data: list |
734
|
|
|
""" |
735
|
4 |
|
runners = len(input_data) if len(input_data) < compat.enum_cpus() else compat.enum_cpus() |
736
|
4 |
|
return runners |
737
|
|
|
|
738
|
|
|
|
739
|
4 |
|
def prep_logfile(): |
740
|
|
|
""" |
741
|
|
|
Prepare log file, labeling it with current date. Select folder based on frozen status. |
742
|
|
|
""" |
743
|
4 |
|
logfile = "{0}.txt".format(time.strftime("%Y_%m_%d_%H%M%S")) |
744
|
4 |
|
root = os.getcwd() if getattr(sys, 'frozen', False) else os.path.expanduser("~") |
745
|
4 |
|
basefolder = os.path.join(root, "lookuplogs") |
746
|
4 |
|
os.makedirs(basefolder, exist_ok=True) |
747
|
4 |
|
record = os.path.join(basefolder, logfile) |
748
|
4 |
|
open(record, "w").close() |
749
|
4 |
|
return record |
750
|
|
|
|
751
|
|
|
|
752
|
4 |
|
def prepends(file, pre, suf): |
753
|
|
|
""" |
754
|
|
|
Check if filename starts with/ends with stuff. |
755
|
|
|
|
756
|
|
|
:param file: File to check. |
757
|
|
|
:type file: str |
758
|
|
|
|
759
|
|
|
:param pre: Prefix(es) to check. |
760
|
|
|
:type pre: str or list or tuple |
761
|
|
|
|
762
|
|
|
:param suf: Suffix(es) to check. |
763
|
|
|
:type suf: str or list or tuple |
764
|
|
|
""" |
765
|
4 |
|
return file.startswith(pre) and file.endswith(suf) |
766
|
|
|
|
767
|
|
|
|
768
|
4 |
|
def lprint(iterable): |
769
|
|
|
""" |
770
|
|
|
A oneliner for 'for item in x: print item'. |
771
|
|
|
|
772
|
|
|
:param iterable: Iterable to print. |
773
|
|
|
:type iterable: list/tuple |
774
|
|
|
""" |
775
|
4 |
|
for item in iterable: |
776
|
4 |
|
print(item) |
777
|
|
|
|
778
|
|
|
|
779
|
4 |
|
def cappath_config_loader(homepath=None): |
780
|
|
|
""" |
781
|
|
|
Read a ConfigParser file to get cap preferences. |
782
|
|
|
|
783
|
|
|
:param homepath: Folder containing ini file. Default is user directory. |
784
|
|
|
:type homepath: str |
785
|
|
|
""" |
786
|
4 |
|
capini = iniconfig.generic_loader('cappath', homepath) |
787
|
4 |
|
cappath = capini.get('path', fallback=bbconstants.CAP.location) |
788
|
4 |
|
return cappath |
789
|
|
|
|
790
|
|
|
|
791
|
4 |
|
def cappath_config_writer(cappath=None, homepath=None): |
792
|
|
|
""" |
793
|
|
|
Write a ConfigParser file to store cap preferences. |
794
|
|
|
|
795
|
|
|
:param cappath: Method to use. |
796
|
|
|
:type cappath: str |
797
|
|
|
|
798
|
|
|
:param homepath: Folder containing ini file. Default is user directory. |
799
|
|
|
:type homepath: str |
800
|
|
|
""" |
801
|
4 |
|
cappath = grab_cap() if cappath is None else cappath |
802
|
4 |
|
results = {"path": cappath} |
803
|
4 |
|
iniconfig.generic_writer("cappath", results, homepath) |
804
|
|
|
|
805
|
|
|
|
806
|
4 |
|
def cond_do(dofunc, goargs, restargs=[], condition=True): |
|
|
|
|
807
|
|
|
""" |
808
|
|
|
Do a function, check a condition, then do same function but swap first argument. |
809
|
|
|
|
810
|
|
|
:param dofunc: Function to do. |
811
|
|
|
:type dofunc: function |
812
|
|
|
|
813
|
|
|
:param goargs: List of variable arguments. |
814
|
|
|
:type goargs: list(str) |
815
|
|
|
|
816
|
|
|
:param restargs: Rest of arguments, which are constant. |
817
|
|
|
:type restargs: list(str) |
818
|
|
|
|
819
|
|
|
:param condition: Condition to check in order to use secondarg. |
820
|
|
|
:type condition: bool |
821
|
|
|
""" |
822
|
4 |
|
dofunc(goargs[0], *restargs) |
823
|
4 |
|
if condition: |
824
|
4 |
|
dofunc(goargs[1], *restargs) |
825
|
|
|
|
826
|
|
|
|
827
|
4 |
|
def cond_check(dofunc, goargs, restargs=[], condition=True, checkif=True, checkifnot=True): |
|
|
|
|
828
|
|
|
""" |
829
|
|
|
Do :func:`cond_do` based on a condition, then do it again based on a second condition. |
830
|
|
|
|
831
|
|
|
:param dofunc: Function to do. |
832
|
|
|
:type dofunc: function |
833
|
|
|
|
834
|
|
|
:param goargs: List of variable arguments. |
835
|
|
|
:type goargs: list(str) |
836
|
|
|
|
837
|
|
|
:param restargs: Rest of arguments, which are constant. |
838
|
|
|
:type restargs: list(str) |
839
|
|
|
|
840
|
|
|
:param condition: Condition to check in order to use secondarg. |
841
|
|
|
:type condition: bool |
842
|
|
|
|
843
|
|
|
:param checkif: Do :func:`cond_do` if this is True. |
844
|
|
|
:type checkif: bool |
845
|
|
|
|
846
|
|
|
:param checkifnot: Do :func:`cond_do` if this is False. |
847
|
|
|
:type checkifnot: bool |
848
|
|
|
""" |
849
|
4 |
|
if checkif: |
850
|
4 |
|
cond_do(dofunc, goargs[0:2], restargs, condition) |
851
|
|
|
if not checkifnot: |
852
|
|
|
cond_do(dofunc, goargs[2:4], restargs, condition) |
|
|
|
|
Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.