-
Notifications
You must be signed in to change notification settings - Fork 1
/
install_addon.py
372 lines (279 loc) · 9.79 KB
/
install_addon.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
import sys
import os
import shutil
import subprocess
import zipfile
# install_addon.py Version 20180803
#
# This script covers majority of the add-on packages I downloaded from the web.
# It assumes that the user data is under 'User' or 'user' sub-directory somewhere in the .zip file.
# Windows doesn't care about capitalization, but Linux and macOS do.
# Therefore, this script looks into .lst files and correct capitalization if the file name in the .lst file does not match
# the capitalization of the actual file.
#
# Now at least I can test that the program does not crash or freeze if I install add-on packages in a batch!
#
# Of course you need to have Python installed on your system to use this script.
# This script has been tested with Python 3.6. It may run with Python 2.x, but I haven't tested.
#
# BSD License. Free for re-distribution. Please feel free to customize for your own add-on package and bundle with package.
#
# Usage:
# python install_addon.py package_zip_file.zip
# or
# python install_addon.py package_zip_file.zip install_directory
#
# If you do not specify the install_directory, the script installs to the default YSFLIGHT user directory (~/Documents/YSFLIGHT.COM/YSFLIGHT)
#
#
#
# By the way, I'm wondering why am I not writing this in C++ despite I am primarily a C++ programmer. I love C++ much better than Python.
# One reason could be C++ Standard Template Library does not have a function to get file listing from the file system.
# Basic string operations, splitting, replacing etc. are not part of STL either, although it is very easy to write.
# I wish C++ standard takes a file-system and basic string operations in the future into STL.
# I can do the same if I link a set of my own class libraries, but then I cannot say easily "please feel free to customize and bundle."
#
# ... I think I soon need to write it in C++ anyway for iOS and Android. But, for those purposes I'll use my own class libraries.
def DefaultFileList():
return [
]
def IsCommandAvailable(cmd):
# From http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
for path in os.environ["PATH"].split(os.pathsep):
path=path.strip('"')
#print("Searching "+cmd+" in "+path)
exe_file=os.path.join(path,cmd)
if os.path.isfile(exe_file) and os.access(exe_file,os.X_OK):
return True
#try:
# subprocess.Popen([cmd]).communicate()
#except:
# return False
return False
# srcPath/abc will be copied to dstPath/abc
# Also returns a list of copied files.
def ForceCopyTree(srcPath,instDir,dstPath):
if not os.path.isdir(dstPath):
os.makedirs(dstPath)
copiedList=[]
for fName in os.listdir(srcPath):
srcFul=os.path.join(srcPath,fName)
dstFul=os.path.join(instDir,dstPath,fName)
if os.path.isfile(dstFul):
os.remove(dstFul)
if os.path.isdir(srcFul):
if not os.path.isdir(dstFul):
os.makedirs(dstFul)
copiedList=copiedList+ForceCopyTree(srcFul,instDir,os.path.join(dstPath,fName))
else:
shutil.copyfile(srcFul,dstFul)
copiedList.append(os.path.join(dstPath,fName).replace('\\','/'))
return copiedList
# srcPath will be copied to dstPath. dstPath must be a complete file name, not a directory name.
def ForceCopyFile(srcPath,dstPath):
if os.path.isfile(dstPath):
os.remove(dstPath)
shutil.copyfile(srcPath,dstPath)
def FindUserDir(dir):
for fName in os.listdir(dir):
ful=os.path.join(dir,fName)
if fName=="User" or fName=='user':
return [ful,fName]
elif os.path.isdir(ful):
found=FindUserDir(ful)
if []!=found:
return found
return []
def FindListFile(dir):
lstFile=[]
for fName in os.listdir(dir):
ful=os.path.join(dir,fName)
if os.path.isdir(ful):
lstFile=lstFile+FindListFile(ful)
else:
ext=os.path.splitext(fName)[1].lower()
if ext==".lst":
lstFile.append([ful,fName])
return lstFile
# Need to nuke a working directory after installation.
# Also I don't want to make a copied files read-only.
def MakeTreeWritable(dir):
for fName in os.listdir(dir):
ful=os.path.join(dir,fName)
os.chmod(ful,0o777)
if os.path.isdir(ful):
MakeTreeWritable(ful)
def InstallAddOn(zipFName,instDir):
airDir=os.path.join(instDir,"aircraft")
gndDir=os.path.join(instDir,"ground")
scnDir=os.path.join(instDir,"scenery")
for dir in [airDir,gndDir,scnDir]:
if not os.path.isdir(dir):
os.makedirs(dir)
workDir=os.path.join(instDir,"tempDir")
if os.path.isdir(workDir):
shutil.rmtree(workDir)
os.makedirs(workDir)
pushd=os.getcwd()
print("****************************************************************")
print("Zip File: "+zipFName)
print("****************************************************************")
zip=zipfile.ZipFile(zipFName,"r")
os.chdir(workDir)
zip.extractall()
MakeTreeWritable(os.getcwd())
userDir=FindUserDir(workDir)
print("Found user dir: ")
print(userDir)
lstFile=FindListFile(workDir)
print("Found list files:")
print(lstFile)
if []==userDir:
print("Cannot find a user directory. Aborting installation.")
return
dataFile=ForceCopyTree(userDir[0],instDir,userDir[1])
leftUninstalled=[]
installedAirList=[]
installedGndList=[]
installedScnList=[]
for lst in lstFile:
if lst[1]=='scenary.lst' or lst[1]=='scenery.lst' or lst[1]=='aircraft.lst' or lst[1]=='ground.lst':
# Sorry for misspelling 'scenary'!
print("Skipping a generic .lst name:"+lst[1])
print("Probably intended to overwrite the default .lst file?")
continue
if lst[1].startswith("air"):
print("Installing "+lst[1]+" to aircraft")
ForceCopyFile(lst[0],os.path.join(airDir,lst[1]))
installedAirList.append(os.path.join(airDir,lst[1]))
elif lst[1].startswith("gro"):
print("Installing "+lst[1]+" to ground")
ForceCopyFile(lst[0],os.path.join(gndDir,lst[1]))
installedGndList.append(os.path.join(gndDir,lst[1]))
elif lst[1].startswith("sce"):
print("Installing "+lst[1]+" to scenery")
ForceCopyFile(lst[0],os.path.join(scnDir,lst[1]))
installedScnList.append(os.path.join(scnDir,lst[1]))
else:
leftUninstalled.append(lst[1])
print("Warning! Cannot identify the .lst file type ("+lst[1]+")");
print(" This .lst file hasn't been installed.")
if 0<len(leftUninstalled):
print("Following .lst files haven't been installed.")
print(leftUninstalled)
FixCapitalization(instDir,installedAirList,installedGndList,installedScnList,dataFile)
os.chdir(pushd)
shutil.rmtree(workDir)
def InstallMultiAddOn(zipDirName,instDir):
for zipFName in os.listdir(zipDirName):
ext=os.path.splitext(zipFName)[1].lower()
if ext=='.zip':
zipFul=os.path.join(zipDirName,zipFName)
print("["+zipFName+"]")
InstallAddOn(zipFul,instDir)
################################################################################
def TryCorrectFileName(fName,lowerToActual):
actual=lowerToActual.get(fName.lower().replace('"',''))
if actual==None:
if fName.startswith("aircraft/") or fName.startswith("ground/") or fName.startswith("scenery/"):
print("File "+fName+" is probably a reference to a default file.")
else:
print("Warning: File "+fName+" does not exist.")
return (False,fName)
if None!=actual and fName!=actual:
print("Correcting Capitalization: "+fName+" to "+actual)
return (True,actual)
else:
return (False,fName)
def FixCapitalizationPerDatFile(datFName,lowerToActual,keywordDict):
try:
fp=open(datFName,"r")
except:
print("File "+datFName+" does not exist.")
return
updated=False
fileContent=[]
for s in fp:
argv=s.split()
if 0<len(argv) and None!=keywordDict.get(argv[0].upper()):
(tf,newFName)=TryCorrectFileName(argv[1],lowerToActual)
if True==tf:
updated=True;
s=s.replace(argv[1],newFName)
fileContent.append(s)
fp.close()
if True==updated:
print("Writing "+datFName)
fp=open(datFName,"w")
for s in fileContent:
fp.write(s+"\n")
fp.close()
def FixCapitalizationPerListFile(listFName,lowerToActual,skipFirstArg):
txt=[]
ifp=open(listFName,"r")
for s in ifp:
txt.append(s)
ifp.close()
newTxt=[]
updated=False
for s in txt:
argv=s.split()
first=True
for arg in argv:
if True==first and True==skipFirstArg:
first=False
continue
(tf,actual)=TryCorrectFileName(arg,lowerToActual)
if True==tf:
updated=True
print("Correcting Capitalization: "+arg+" to "+actual)
s=s.replace(arg,actual)
first=False
newTxt.append(s)
if True==updated:
print("Updating: "+listFName)
ofp=open(listFName,"w")
for s in newTxt:
ofp.write(s+"\n")
ofp.close()
def FixCapitalization(instDir,airListFName,gndListFName,scnListFName,dataFile):
lowerToActual=dict()
for f in dataFile:
lowerToActual[f.lower()]=f
for fName in airListFName:
FixCapitalizationPerListFile(fName,lowerToActual,False)
for fName in gndListFName:
FixCapitalizationPerListFile(fName,lowerToActual,False)
try:
fp=open(fName,"r")
except:
print("Cannot open "+fName)
continue
for s in fp:
argv=s.split()
if 0<len(argv):
datFName=os.path.join(instDir,argv[0])
datFName=os.path.expanduser(datFName)
FixCapitalizationPerDatFile(datFName,lowerToActual,{"CARRIER":0})
fp.close()
for fName in scnListFName:
FixCapitalizationPerListFile(fName,lowerToActual,True)
################################################################################
def main():
if len(sys.argv)<2:
print("Usage: python install_addon.py input_zip_file.zip install_destination_directory")
print(" or,")
print(" python install_addon.py input_zip_file.zip")
print(" If you don't specify the destination, it uses ~/YSFLIGHT.COM/YSFLIGHT as the")
print(" default YSFLIGHT user-data install location.")
elif 3<=len(sys.argv):
if not os.path.isdir(sys.argv[2]):
os.makedirs(sys.argv[2])
instDir=sys.argv[2]
else:
instDir=os.path.join("~","Documents","YSFLIGHT.COM","YSFLIGHT")
instDir=os.path.expanduser(instDir)
InstallAddOn(sys.argv[1],instDir)
print("Installation done.")
if __name__=="__main__":
main()