/
Jinn.py
539 lines (459 loc) · 19.9 KB
/
Jinn.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
import sys
import options
import g
import os
import copy
import platform
# Redundant import just for pyinstaller
import encodings
from feedback import FeedbackMechanisms
from manifest.Manifest import Manifest
from feedback.ConsoleFeedback import ConsoleFeedback
from feedback.UIFeedback import UIFeedback
from feedback.FeedbackBase import FeedbackBase
from feedback.LogLevels import LogLevels
from helpers.FileSystemHelper import FileSystemHelper
from env.enums import OperatingSystem, Architecture
class Jinn(FileSystemHelper):
# Header for messages
header = """
.---.
| |
'---'.--. _..._ _..._
.---.|__| .' '. .' '.
| |.--.. .-. .. .-. .
| || || ' ' || ' ' |
| || || | | || | | |
| || || | | || | | |
| || || | | || | | |
| ||__|| | | || | | |
__.' ' | | | || | | |
| ' | | | || | | |
|____.' '--' '--''--' '--'
A Java installer"""
def __init__(self):
self.new_manifest = None
self.manifest = None
self.args = ""
# Setup feedback mechanism
global feedback
if (options.interface == FeedbackMechanisms.CMD):
g.feedback = ConsoleFeedback()
elif (options.interface == FeedbackMechanisms.UI):
g.feedback = UIFeedback()
else:
# Not specified, need something to stop errors, so us this
g.feedback = FeedbackBase()
# Setup min log level
g.feedback.minLogLevel = LogLevels.getLevelFromString(options.min_log_level)
self.os = self.getOperatingSystem()
self.arch = self.getArchitecture()
g.feedback.log(LogLevels.INFO, "Started up. OS: %s; Architecture: %s" % (OperatingSystem().getOperatingSystem(self.os), Architecture().getArchitecture(self.arch)))
"""
Gets the current OS
"""
def getOperatingSystem(self):
p = platform.system()
if p == "Windows":
return OperatingSystem.WIN
elif p == "Darwin":
return OperatingSystem.OSX
elif p == "Linux":
return OperatingSystem.LIN
else:
g.feedback.log(LogLevels.ERROR, "Unable to work with operating system %s" % p)
raise OperatingSystemNotFoundException(p)
"""
Gets the current architecture
"""
def getArchitecture(self):
if self.os is OperatingSystem.WIN:
g.feedback.log(LogLevels.DEBUG, "Getting architecture, we are windows, so asking the registry")
return self.getArchitectureWindows()
else:
g.feedback.log(LogLevels.DEBUG, "Working out if we are running 32 or 64 bit")
if(sys.maxsize > 2**32):
g.feedback.log(LogLevels.DEBUG, "Decided 64 bit")
return Architecture.x64
else:
g.feedback.log(LogLevels.DEBUG, "Decided 32 bit")
return Architecture.x32
"""
Windows special case for getting architecture
"""
def getArchitectureWindows(self):
from _winreg import *
softwarekey = OpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE")
microsoftkey = OpenKey(softwarekey, "Microsoft")
windowskey = OpenKey(microsoftkey, "Windows NT")
versionkey = OpenKey(windowskey, "CurrentVersion")
value = QueryValueEx(versionkey, "BuildLabEx")[0]
CloseKey(versionkey)
CloseKey(windowskey)
CloseKey(microsoftkey)
CloseKey(softwarekey)
if "amd64" in value:
g.feedback.log(LogLevels.DEBUG, "From value %s, decided we are 64 bit" % value)
return Architecture.x64
else:
g.feedback.log(LogLevels.DEBUG, "From value %s, decided we are 32 bit" % value)
return Architecture.x32
"""
Loads the manifest file
"""
def loadManifest(self):
g.feedback.log(LogLevels.INFO, "Loading new manifest from %s" % options.manifest)
self.new_manifest = Manifest(self.os, self.arch, options.manifest, options.manifest_is_url)
if self.isInstalled():
manifest_file = ".jinn" + self.sep() + "current_manifest.json"
g.feedback.log(LogLevels.INFO, "Loading manifest from %s" % manifest_file)
self.manifest = Manifest(self.os, self.arch, manifest_file, False, True)
else:
g.feedback.log(LogLevels.INFO, "Loading manifest from new manifest, as not installed")
self.manifest = self.new_manifest
"""
A helper which sets up the system before a run
"""
def setupSystem(self):
g.feedback.log(LogLevels.INFO, "Setting up the system")
# Make sure we are installed
if not self.isInstalled():
g.feedback.log(LogLevels.INFO, "We aren't installed, so installing")
status = self.doInstall()
if status != 0:
g.feedback.log(LogLevels.ERROR, "Tried installing but failed horribly")
g.feedback.userMessage("Installation failed (1) - please contact distributor")
return status
g.feedback.log(LogLevels.INFO, "Installation succeeded")
else:
self.loadManifest()
return self.doUpdate()
"""
Runs the default action in the jinn
"""
def runDefaultAction(self):
g.feedback.log(LogLevels.DEBUG, "Running default action")
self.setupSystem()
g.feedback.log(LogLevels.DEBUG, "Default action execution commencing")
try:
self.manifest.runDefaultAction(self.args)
g.feedback.log(LogLevels.DEBUG, "Run default action executed successfully")
return 0
except Exception as e:
g.feedback.log(LogLevels.ERROR, "Running default action threw exception %s" % e)
return 1
"""
Runs a specific action within the jinn
"""
def runAction(self, action):
g.feedback.log(LogLevels.INFO, "Running %s action" % action)
self.setupSystem()
g.feedback.log(LogLevels.DEBUG, "Run action %s" % action)
try:
self.manifest.runAction(action, self.args)
g.feedback.log(LogLevels.INFO, "Running action %s successfully completed" % action)
return 0
except Exception as e:
g.feedback.log(LogLevels.ERROR, "Running action %s threw exception %s" % (action, e))
return 1
"""
Are we in dev mode?
"""
def isDevMode(self):
isDev = options.version is "DEV"
g.feedback.log(LogLevels.WARN, "Dev mode? %s" % str(isDev))
return isDev
"""
Checks the manifests for updates
"""
def doUpdate(self):
g.feedback.log(LogLevels.DEBUG, "Starting update procedure")
# No update if the version number is the same
if self.manifest.jinn.version == self.new_manifest.jinn.version:
g.feedback.log(LogLevels.DEBUG, "New and current manifest versions are %s and %s, so skipping update as they are identical" % (self.manifest.jinn.version, self.new_manifest.jinn.version))
return True
g.feedback.log(LogLevels.DEBUG, "Setting installation status across manifests")
# Initially, set the resources that are installed on the new manifest already
if not self.new_manifest.setInstallStatus(self.manifest.resources):
g.feedback.log(LogLevels.ERROR, "Failed to set install status")
return False
g.feedback.log(LogLevels.INFO, "Installing new resources")
# First, install resources that are new
if not self.new_manifest.installNewResources(self.manifest.resources):
g.feedback.log(LogLevels.ERROR, "Failed to install new resources")
return False
g.feedback.log(LogLevels.INFO, "Uninstalling removed resources")
# Second, uninstall resources that are gone
if not self.new_manifest.uninstallRemovedResources(self.manifest.resources):
g.feedback.log(LogLevels.ERROR, "Removing resources failed")
return False
g.feedback.log(LogLevels.INFO, "Updating changed resources")
# Third, update resources that are new version
if not self.new_manifest.updateResources(self.manifest.resources):
g.feedback.log(LogLevels.ERROR, "Updating changed resources failed")
return False
g.feedback.log(LogLevels.DEBUG, "Saving manifest")
# Finally, transition the manifest over to the new one
self.manifest = self.new_manifest
if not self.manifest.save():
g.feedback.log(LogLevels.ERROR, "Unable to save updated manifest")
return False
# Done!
g.feedback.log(LogLevels.DEBUG, "Update complete")
return True
"""
Copy this executable to the target directory, then run it
"""
def doCopy(self):
g.feedback.log(LogLevels.DEBUG, "We are not in the correct directory, so installing to the correct location")
if self.os is OperatingSystem.OSX:
g.feedback.log(LogLevels.DEBUG, "Copying in OS X mode")
targetDir = self.getInstallTargetDirectory()
copyTargetDir = targetDir.rsplit("/", 2)[0]
currentDir = self.getCurrentDirectory() + self.sep() + ".." + self.sep() + ".."
executable = targetDir + self.sep() + self.getExecutableName()
g.feedback.log(LogLevels.DEBUG, "Target executable: %s" % executable)
if not self.exists(executable):
g.feedback.log(LogLevels.DEBUG, "Target executable does not exist, copying")
if not self.makeDirectory(copyTargetDir):
return False
if not self.copyDir(currentDir, copyTargetDir):
return False
else:
g.feedback.log(LogLevels.DEBUG, "Target executable exists, so running it")
cmd = executable + " -install"
g.feedback.log(LogLevels.DEBUG, "Executing command: %s" % cmd)
res = os.system(cmd)
if res < 1:
g.feedback.log(LogLevels.DEBUG, "Executed command successfully")
return 0
else:
g.feedback.log(LogLevels.ERROR, "Executing command %s failed with code %s" % (cmd, str(res)))
return 1
else:
g.feedback.log(LogLevels.DEBUG, "Copying in Windows / Linux mode")
targetFile = self.getInstallTargetFile()
g.feedback.log(LogLevels.DEBUG, "Target file is %s" % targetFile)
d = self.getInstallTargetDirectory()
g.feedback.log(LogLevels.DEBUG, "Target directory is %s" % d)
if not self.exists(targetFile):
# Make the jinn install directory
if not self.makeDirectory(d):
g.feedback.log(LogLevels.ERROR, "Unable to make directory %s to install to" % d)
return 1
# Copy this binary into it
frm = self.getCurrentFile()
to = targetFile
g.feedback.log(LogLevels.DEBUG, "Copying from %s to %s" % (frm, to))
if not self.copyFile(frm, to):
g.feedback.log(LogLevels.ERROR, "Unable to copy %s to %s" % (frm,to))
return 1
# Change into that directory
g.feedback.log(LogLevels.DEBUG, "Changing to %s" % d)
if not self.changeDirectory(d):
g.feedback.log(LogLevels.ERROR, "Unable to change to the InstallTargetDirectory %s" % d)
return 1
# Run the new executable
g.feedback.log(LogLevels.DEBUG, "Code copied to %s, executing" % targetFile)
cmd = self.getExecutableName() + " -install"
if self.os == OperatingSystem.LIN or self.os == OperatingSystem.OSX:
cmd = "./" + cmd
g.feedback.log(LogLevels.DEBUG, "Run command: %s" % cmd)
os.system(cmd)
return 0
"""
Runs an installation of this jinn
"""
def doInstall(self):
g.feedback.log(LogLevels.INFO, "Beginning installation")
# We need the manifest first of all
self.loadManifest()
# Hard code here to not happen for dev
correctDir = self.isCorrectDirectory()
g.feedback.log(LogLevels.DEBUG, "Are we in correct directory? %s" % str(correctDir))
if not correctDir and not self.isDevMode():
return self.doCopy()
# Make sure we are in the right directory
installDir = self.getInstallTargetDirectory()
g.feedback.log(LogLevels.DEBUG, "Target install dir is %s" % installDir)
if not self.changeDirectory(installDir) and not self.isDevMode():
g.feedback.log(LogLevels.ERROR, "Unable to change to where we thought we were installed, %s" % installDir)
return 1
if self.isInstalled():
g.feedback.log(LogLevels.ERROR, "This jinn is already installed")
g.feedback.userMessage("Installation failed (3) - please contact distributor")
return 1
g.feedback.log(LogLevels.DEBUG, "Installing")
if not self.makeDirectory(".jinn"):
g.feedback.log(LogLevels.ERROR, "Unable to make .jinn directory")
return 1
if not self.manifest.save():
g.feedback.log(LogLevels.ERROR, "Unable to save the manifest")
return 1
try:
if self.manifest.installResources():
g.feedback.log(LogLevels.DEBUG, "Install resources succeeded")
return 0
else:
g.feedback.log(LogLevels.ERROR, "Install resources failed")
return 1
except Exception as e:
g.feedback.log(LogLevels.ERROR, "Unable to install resources: %s" % e)
return 1
"""
Runs uninstallation
"""
def doUninstall(self):
g.feedback.log(LogLevels.INFO, "Doing uninstallation")
if not self.isInstalled():
g.feedback.log(LogLevels.ERROR, "This jinn is not installed, so cannot be uninstalled")
g.feedback.userMessage("Uninstallation failed (4) - please contact distributor")
return 1
g.feedback.log(LogLevels.DEBUG, "Uninstalling")
self.loadManifest()
try:
if self.manifest.uninstallResources():
if self.delete(".jinn"):
g.feedback.userMessage("Uninstallation finished. To completely erase this application, please delete the directory %s" % self.getInstallTargetDirectory())
return 0
else:
g.feedback.log(LogLevels.ERROR, "Failed to delete .jinn directory, will think its still installed")
return 1
else:
g.feedback.log(LogLevels.ERROR, "Uninstallation failed")
return 1
except Exception as e:
g.feedback.log(LogLevels.ERROR, "Unable to uninstall: %s" % e)
return 1
"""
Returns the install target directory
"""
def getInstallTargetDirectory(self):
if self.isDevMode():
return self.getCurrentDirectory()
elif self.os is OperatingSystem.OSX:
return self.getHomeDirectory() + self.sep() + "Applications" + self.sep() + self.manifest.jinn.name + ".app" + self.sep() + "Contents" + self.sep() + "MacOS"
else:
return self.getHomeDirectory() + self.getDirectorySeparator() + self.manifest.jinn.name
"""
Get the name of the executable
"""
def getExecutableName(self):
if self.os == OperatingSystem.WIN:
return self.manifest.jinn.name + ".jinn.exe"
elif self.os == OperatingSystem.LIN:
return self.manifest.jinn.name + ".jinn"
elif self.os == OperatingSystem.OSX:
return "jinn"
return None
"""
Get the file we wish to install to
"""
def getInstallTargetFile(self):
return self.getInstallTargetDirectory() + self.getDirectorySeparator() + self.getExecutableName()
"""
Check whether or not we are running from the right place
"""
def isCorrectDirectory(self):
currentdir = self.getCurrentDirectory()
targetdir = self.getInstallTargetDirectory()
g.feedback.log(LogLevels.DEBUG, "Current directory: %s" % currentdir)
g.feedback.log(LogLevels.DEBUG, "Target dir: %s" % targetdir)
return targetdir == currentdir
"""
Take the sys args and turn them into a string stored on the object
for later use. Offset is how many to skip off the front.
Automatically skips the first one, the current script
"""
def processArgs(self, offset = 0):
args = copy.copy(sys.argv)
args.pop(0)
i = 0
while i < offset:
args.pop(0)
i += 1
self.args = " ".join(map(str, args))
g.feedback.log(LogLevels.DEBUG, "System args: %s" % self.args)
"""
Check whether or not this jinn is currently installed
"""
def isInstalled(self):
return self.directoryExists(".jinn")
"""
Output the version information
"""
def doVersion(self):
g.feedback.userMessage("""
%s
Version: %s
""" % (self.header, options.version))
return 0
"""
Output the help content
"""
def doHelp(self):
g.feedback.userMessage("""
%s
Created by import.io
Options:
./jinn
Run the jinn with the default action
./jinn -install
Run the jinn installation
./jinn -uninstall
Uninstall the jinn
./jinn -help
Display this helpful help dialog
./jinn -action (actionname)
Run the jinn action specified by (actionname)
./jinn -version
Print the version string
""" % self.header)
return 0
"""
Runs the jinn feature we need to perform
"""
def do(self):
# Change to the current run directory
self.changeDirectory(self.getPathFromFilePath(sys.argv[0]))
# Analyse the sys args to figure out what to do
if len(sys.argv) < 2:
# No extra args, if we are installed we want to run the default action.
# Otherwise this is the first run from download, so do install
if self.isInstalled():
return self.runDefaultAction()
else:
return self.doInstall()
elif sys.argv[1] == "-install":
return self.doInstall()
elif sys.argv[1] == "-uninstall":
return self.doUninstall()
elif sys.argv[1] == "-help":
return self.doHelp()
elif sys.argv[1] == "-version":
return self.doVersion()
elif sys.argv[1] == "-action":
if len(sys.argv) < 3:
g.feedback.userMessage("For -action, you must specify an action - try -help")
return 1
self.processArgs(2)
return self.runAction(sys.argv[2])
else:
# There are some args, we don't recognise the first, so must want to run default action with some args
self.processArgs()
return self.runDefaultAction()
class OperatingSystemNotFoundException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
"""
Main function that is run when the code is started from this file
"""
def main():
g.jinn = Jinn()
return g.jinn.do()
if __name__ == '__main__':
status = main()
g.feedback.userMessage("Jinn exiting with code %s" % str(status))
sys.exit(status)