blob: e5d73c7baa27abe68112914a7aad3ac9ca1f2654 [file] [log] [blame]
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00001# Microsoft Installer Library
2# (C) 2003 Martin v. Loewis
3
4import win32com.client.gencache
5import win32com.client
6import pythoncom, pywintypes
7from win32com.client import constants
Georg Brandl38feaf02008-05-25 07:45:51 +00008import re, string, os, sets, glob, subprocess, sys, winreg, struct
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00009
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000010# Partially taken from Wine
11datasizemask= 0x00ff
12type_valid= 0x0100
13type_localizable= 0x0200
14
15typemask= 0x0c00
16type_long= 0x0000
17type_short= 0x0400
18type_string= 0x0c00
19type_binary= 0x0800
20
21type_nullable= 0x1000
22type_key= 0x2000
23# XXX temporary, localizable?
24knownbits = datasizemask | type_valid | type_localizable | \
25 typemask | type_nullable | type_key
26
27# Summary Info Property IDs
28PID_CODEPAGE=1
29PID_TITLE=2
30PID_SUBJECT=3
31PID_AUTHOR=4
32PID_KEYWORDS=5
33PID_COMMENTS=6
34PID_TEMPLATE=7
35PID_LASTAUTHOR=8
36PID_REVNUMBER=9
37PID_LASTPRINTED=11
38PID_CREATE_DTM=12
39PID_LASTSAVE_DTM=13
40PID_PAGECOUNT=14
41PID_WORDCOUNT=15
42PID_CHARCOUNT=16
43PID_APPNAME=18
44PID_SECURITY=19
45
46def reset():
47 global _directories
48 _directories = sets.Set()
49
50def EnsureMSI():
51 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0)
52
53def EnsureMSM():
54 try:
55 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0)
56 except pywintypes.com_error:
57 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0)
58
59_Installer=None
60def MakeInstaller():
61 global _Installer
62 if _Installer is None:
63 EnsureMSI()
64 _Installer = win32com.client.Dispatch('WindowsInstaller.Installer',
65 resultCLSID='{000C1090-0000-0000-C000-000000000046}')
66 return _Installer
67
68_Merge=None
69def MakeMerge2():
70 global _Merge
71 if _Merge is None:
72 EnsureMSM()
73 _Merge = win32com.client.Dispatch("Msm.Merge2.1")
74 return _Merge
75
76class Table:
77 def __init__(self, name):
78 self.name = name
79 self.fields = []
80
81 def add_field(self, index, name, type):
82 self.fields.append((index,name,type))
83
84 def sql(self):
85 fields = []
86 keys = []
87 self.fields.sort()
88 fields = [None]*len(self.fields)
89 for index, name, type in self.fields:
90 index -= 1
91 unk = type & ~knownbits
92 if unk:
Collin Winter6afaeb72007-08-03 17:06:41 +000093 print("%s.%s unknown bits %x" % (self.name, name, unk))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000094 size = type & datasizemask
95 dtype = type & typemask
96 if dtype == type_string:
97 if size:
98 tname="CHAR(%d)" % size
99 else:
100 tname="CHAR"
101 elif dtype == type_short:
102 assert size==2
103 tname = "SHORT"
104 elif dtype == type_long:
105 assert size==4
106 tname="LONG"
107 elif dtype == type_binary:
108 assert size==0
109 tname="OBJECT"
110 else:
111 tname="unknown"
Collin Winter6afaeb72007-08-03 17:06:41 +0000112 print("%s.%sunknown integer type %d" % (self.name, name, size))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000113 if type & type_nullable:
114 flags = ""
115 else:
116 flags = " NOT NULL"
117 if type & type_localizable:
118 flags += " LOCALIZABLE"
119 fields[index] = "`%s` %s%s" % (name, tname, flags)
120 if type & type_key:
121 keys.append("`%s`" % name)
122 fields = ", ".join(fields)
123 keys = ", ".join(keys)
124 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
125
126 def create(self, db):
127 v = db.OpenView(self.sql())
128 v.Execute(None)
129 v.Close()
130
131class Binary:
132 def __init__(self, fname):
133 self.name = fname
134 def __repr__(self):
135 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
136
137def gen_schema(destpath, schemapath):
138 d = MakeInstaller()
139 schema = d.OpenDatabase(schemapath,
140 win32com.client.constants.msiOpenDatabaseModeReadOnly)
141
142 # XXX ORBER BY
143 v=schema.OpenView("SELECT * FROM _Columns")
144 curtable=None
145 tables = []
146 v.Execute(None)
147 f = open(destpath, "wt")
148 f.write("from msilib import Table\n")
149 while 1:
150 r=v.Fetch()
151 if not r:break
152 name=r.StringData(1)
153 if curtable != name:
154 f.write("\n%s = Table('%s')\n" % (name,name))
155 curtable = name
156 tables.append(name)
157 f.write("%s.add_field(%d,'%s',%d)\n" %
158 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4)))
159 v.Close()
160
161 f.write("\ntables=[%s]\n\n" % (", ".join(tables)))
162
163 # Fill the _Validation table
164 f.write("_Validation_records = [\n")
165 v = schema.OpenView("SELECT * FROM _Validation")
166 v.Execute(None)
167 while 1:
168 r = v.Fetch()
169 if not r:break
170 # Table, Column, Nullable
Georg Brandlbf82e372008-05-16 17:02:34 +0000171 f.write("(%r,%r,%r," %
172 (r.StringData(1), r.StringData(2), r.StringData(3)))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000173 def put_int(i):
174 if r.IsNull(i):f.write("None, ")
175 else:f.write("%d," % r.IntegerData(i))
176 def put_str(i):
177 if r.IsNull(i):f.write("None, ")
Georg Brandlbf82e372008-05-16 17:02:34 +0000178 else:f.write("%r," % r.StringData(i))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000179 put_int(4) # MinValue
180 put_int(5) # MaxValue
181 put_str(6) # KeyTable
182 put_int(7) # KeyColumn
183 put_str(8) # Category
184 put_str(9) # Set
185 put_str(10)# Description
186 f.write("),\n")
187 f.write("]\n\n")
188
Tim Peters94607dd2004-08-22 19:42:56 +0000189 f.close()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000190
191def gen_sequence(destpath, msipath):
192 dir = os.path.dirname(destpath)
193 d = MakeInstaller()
194 seqmsi = d.OpenDatabase(msipath,
195 win32com.client.constants.msiOpenDatabaseModeReadOnly)
196
197 v = seqmsi.OpenView("SELECT * FROM _Tables");
198 v.Execute(None)
199 f = open(destpath, "w")
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000200 f.write("import msilib,os;dirname=os.path.dirname(__file__)\n")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000201 tables = []
202 while 1:
203 r = v.Fetch()
204 if not r:break
205 table = r.StringData(1)
206 tables.append(table)
207 f.write("%s = [\n" % table)
208 v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table)
209 v1.Execute(None)
210 info = v1.ColumnInfo(constants.msiColumnInfoTypes)
211 while 1:
212 r = v1.Fetch()
213 if not r:break
214 rec = []
215 for i in range(1,r.FieldCount+1):
216 if r.IsNull(i):
217 rec.append(None)
218 elif info.StringData(i)[0] in "iI":
219 rec.append(r.IntegerData(i))
220 elif info.StringData(i)[0] in "slSL":
221 rec.append(r.StringData(i))
222 elif info.StringData(i)[0]=="v":
223 size = r.DataSize(i)
224 bytes = r.ReadStream(i, size, constants.msiReadStreamBytes)
225 bytes = bytes.encode("latin-1") # binary data represented "as-is"
226 if table == "Binary":
227 fname = rec[0]+".bin"
228 open(os.path.join(dir,fname),"wb").write(bytes)
229 rec.append(Binary(fname))
230 else:
231 rec.append(bytes)
232 else:
Georg Brandlbf82e372008-05-16 17:02:34 +0000233 raise ValueError("Unsupported column type", info.StringData(i))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000234 f.write(repr(tuple(rec))+",\n")
235 v1.Close()
236 f.write("]\n\n")
237 v.Close()
Georg Brandlbf82e372008-05-16 17:02:34 +0000238 f.write("tables=%s\n" % repr(list(map(str,tables))))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000239 f.close()
240
241class _Unspecified:pass
242def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
243 "Change the sequence number of an action in a sequence list"
244 for i in range(len(seq)):
245 if seq[i][0] == action:
246 if cond is _Unspecified:
247 cond = seq[i][1]
248 if seqno is _Unspecified:
249 seqno = seq[i][2]
250 seq[i] = (action, cond, seqno)
251 return
Collin Wintera817e582007-08-22 23:05:06 +0000252 raise ValueError("Action not found in sequence")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000253
254def add_data(db, table, values):
255 d = MakeInstaller()
256 v = db.OpenView("SELECT * FROM `%s`" % table)
257 count = v.ColumnInfo(0).FieldCount
258 r = d.CreateRecord(count)
259 for value in values:
260 assert len(value) == count, value
261 for i in range(count):
262 field = value[i]
Georg Brandlbf82e372008-05-16 17:02:34 +0000263 if isinstance(field, int):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000264 r.SetIntegerData(i+1,field)
Georg Brandlbf82e372008-05-16 17:02:34 +0000265 elif isinstance(field, str):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000266 r.SetStringData(i+1,field)
267 elif field is None:
268 pass
269 elif isinstance(field, Binary):
270 r.SetStream(i+1, field.name)
271 else:
Collin Wintera817e582007-08-22 23:05:06 +0000272 raise TypeError("Unsupported type %s" % field.__class__.__name__)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000273 v.Modify(win32com.client.constants.msiViewModifyInsert, r)
274 r.ClearData()
275 v.Close()
276
277def add_stream(db, name, path):
278 d = MakeInstaller()
279 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
280 r = d.CreateRecord(1)
281 r.SetStream(1, path)
282 v.Execute(r)
283 v.Close()
284
285def init_database(name, schema,
286 ProductName, ProductCode, ProductVersion,
287 Manufacturer):
288 try:
289 os.unlink(name)
290 except OSError:
291 pass
292 ProductCode = ProductCode.upper()
293 d = MakeInstaller()
294 # Create the database
295 db = d.OpenDatabase(name,
296 win32com.client.constants.msiOpenDatabaseModeCreate)
297 # Create the tables
298 for t in schema.tables:
299 t.create(db)
300 # Fill the validation table
301 add_data(db, "_Validation", schema._Validation_records)
302 # Initialize the summary information, allowing atmost 20 properties
303 si = db.GetSummaryInformation(20)
304 si.SetProperty(PID_TITLE, "Installation Database")
305 si.SetProperty(PID_SUBJECT, ProductName)
306 si.SetProperty(PID_AUTHOR, Manufacturer)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000307 si.SetProperty(PID_TEMPLATE, msi_type)
Martin v. Löwis1e3a2642004-09-10 11:55:32 +0000308 si.SetProperty(PID_REVNUMBER, gen_uuid())
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000309 si.SetProperty(PID_WORDCOUNT, 2) # long file names, compressed, original media
310 si.SetProperty(PID_PAGECOUNT, 200)
311 si.SetProperty(PID_APPNAME, "Python MSI Library")
312 # XXX more properties
313 si.Persist()
314 add_data(db, "Property", [
315 ("ProductName", ProductName),
316 ("ProductCode", ProductCode),
317 ("ProductVersion", ProductVersion),
318 ("Manufacturer", Manufacturer),
319 ("ProductLanguage", "1033")])
320 db.Commit()
321 return db
322
323def add_tables(db, module):
324 for table in module.tables:
325 add_data(db, table, getattr(module, table))
326
327def make_id(str):
328 #str = str.replace(".", "_") # colons are allowed
329 str = str.replace(" ", "_")
330 str = str.replace("-", "_")
331 if str[0] in string.digits:
332 str = "_"+str
333 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
334 return str
335
336def gen_uuid():
337 return str(pythoncom.CreateGuid())
338
339class CAB:
340 def __init__(self, name):
341 self.name = name
342 self.file = open(name+".txt", "wt")
343 self.filenames = sets.Set()
344 self.index = 0
345
346 def gen_id(self, dir, file):
347 logical = _logical = make_id(file)
348 pos = 1
349 while logical in self.filenames:
350 logical = "%s.%d" % (_logical, pos)
351 pos += 1
352 self.filenames.add(logical)
353 return logical
354
355 def append(self, full, file, logical = None):
356 if os.path.isdir(full):
357 return
358 if not logical:
359 logical = self.gen_id(dir, file)
360 self.index += 1
361 if full.find(" ")!=-1:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000362 self.file.write('"%s" %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000363 else:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000364 self.file.write('%s %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000365 return self.index, logical
366
367 def commit(self, db):
368 self.file.close()
369 try:
370 os.unlink(self.name+".cab")
371 except OSError:
372 pass
373 for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000374 (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
375 (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
376 (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
377 ]:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000378 try:
Georg Brandl38feaf02008-05-25 07:45:51 +0000379 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, k)
380 dir = winreg.QueryValueEx(key, v)[0]
381 winreg.CloseKey(key)
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000382 except (WindowsError, IndexError):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000383 continue
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000384 cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
385 if not os.path.exists(cabarc):
386 continue
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000387 break
388 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000389 print("WARNING: cabarc.exe not found in registry")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000390 cabarc = "cabarc.exe"
Guido van Rossum360e4b82007-05-14 22:51:27 +0000391 cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
392 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000393 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
394 for line in p.stdout:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000395 if line.startswith(" -- adding "):
396 sys.stdout.write(".")
397 else:
398 sys.stdout.write(line)
399 sys.stdout.flush()
400 if not os.path.exists(self.name+".cab"):
Collin Wintera817e582007-08-22 23:05:06 +0000401 raise IOError("cabarc failed")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000402 add_data(db, "Media",
403 [(1, self.index, None, "#"+self.name, None, None)])
404 add_stream(db, self.name, self.name+".cab")
405 os.unlink(self.name+".txt")
406 os.unlink(self.name+".cab")
407 db.Commit()
408
409_directories = sets.Set()
410class Directory:
411 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
412 """Create a new directory in the Directory table. There is a current component
413 at each point in time for the directory, which is either explicitly created
414 through start_component, or implicitly when files are added for the first
415 time. Files are added into the current component, and into the cab file.
416 To create a directory, a base directory object needs to be specified (can be
417 None), the path to the physical directory, and a logical directory name.
418 Default specifies the DefaultDir slot in the directory table. componentflags
419 specifies the default flags that new components get."""
420 index = 1
421 _logical = make_id(_logical)
422 logical = _logical
423 while logical in _directories:
424 logical = "%s%d" % (_logical, index)
425 index += 1
426 _directories.add(logical)
427 self.db = db
428 self.cab = cab
429 self.basedir = basedir
430 self.physical = physical
431 self.logical = logical
432 self.component = None
433 self.short_names = sets.Set()
434 self.ids = sets.Set()
435 self.keyfiles = {}
436 self.componentflags = componentflags
437 if basedir:
438 self.absolute = os.path.join(basedir.absolute, physical)
439 blogical = basedir.logical
440 else:
441 self.absolute = physical
442 blogical = None
443 add_data(db, "Directory", [(logical, blogical, default)])
444
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000445 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000446 """Add an entry to the Component table, and make this component the current for this
447 directory. If no component name is given, the directory name is used. If no feature
448 is given, the current feature is used. If no flags are given, the directory's default
449 flags are used. If no keyfile is given, the KeyPath is left null in the Component
450 table."""
451 if flags is None:
452 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000453 if uuid is None:
454 uuid = gen_uuid()
455 else:
456 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000457 if component is None:
458 component = self.logical
459 self.component = component
460 if Win64:
461 flags |= 256
462 if keyfile:
463 keyid = self.cab.gen_id(self.absolute, keyfile)
464 self.keyfiles[keyfile] = keyid
465 else:
466 keyid = None
467 add_data(self.db, "Component",
468 [(component, uuid, self.logical, flags, None, keyid)])
469 if feature is None:
470 feature = current_feature
471 add_data(self.db, "FeatureComponents",
472 [(feature.id, component)])
473
474 def make_short(self, file):
475 parts = file.split(".")
476 if len(parts)>1:
477 suffix = parts[-1].upper()
478 else:
479 suffix = None
480 prefix = parts[0].upper()
481 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
482 if suffix:
483 file = prefix+"."+suffix
484 else:
485 file = prefix
486 assert file not in self.short_names
487 else:
488 prefix = prefix[:6]
489 if suffix:
490 suffix = suffix[:3]
491 pos = 1
492 while 1:
493 if suffix:
494 file = "%s~%d.%s" % (prefix, pos, suffix)
495 else:
496 file = "%s~%d" % (prefix, pos)
497 if file not in self.short_names: break
498 pos += 1
499 assert pos < 10000
500 if pos in (10, 100, 1000):
501 prefix = prefix[:-1]
502 self.short_names.add(file)
503 assert not re.search(r'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names
504 return file
505
506 def add_file(self, file, src=None, version=None, language=None):
507 """Add a file to the current component of the directory, starting a new one
508 one if there is no current component. By default, the file name in the source
509 and the file table will be identical. If the src file is specified, it is
510 interpreted relative to the current directory. Optionally, a version and a
511 language can be specified for the entry in the File table."""
512 if not self.component:
513 self.start_component(self.logical, current_feature)
514 if not src:
515 # Allow relative paths for file if src is not specified
516 src = file
517 file = os.path.basename(file)
518 absolute = os.path.join(self.absolute, src)
519 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
Georg Brandlbf82e372008-05-16 17:02:34 +0000520 if file in self.keyfiles:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000521 logical = self.keyfiles[file]
522 else:
523 logical = None
524 sequence, logical = self.cab.append(absolute, file, logical)
525 assert logical not in self.ids
526 self.ids.add(logical)
527 short = self.make_short(file)
528 full = "%s|%s" % (short, file)
529 filesize = os.stat(absolute).st_size
530 # constants.msidbFileAttributesVital
531 # Compressed omitted, since it is the database default
532 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000533 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000534 add_data(self.db, "File",
535 [(logical, self.component, full, filesize, version,
536 language, attributes, sequence)])
537 if not version:
538 # Add hash if the file is not versioned
539 filehash = MakeInstaller().FileHash(absolute, 0)
540 add_data(self.db, "MsiFileHash",
541 [(logical, 0, filehash.IntegerData(1),
542 filehash.IntegerData(2), filehash.IntegerData(3),
543 filehash.IntegerData(4))])
544 # Automatically remove .pyc/.pyo files on uninstall (2)
545 # XXX: adding so many RemoveFile entries makes installer unbelievably
546 # slow. So instead, we have to use wildcard remove entries
547 # if file.endswith(".py"):
548 # add_data(self.db, "RemoveFile",
549 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
550 # self.logical, 2),
551 # (logical+"o", self.component, "%sO|%so" % (short, file),
552 # self.logical, 2)])
553
554 def glob(self, pattern, exclude = None):
555 """Add a list of files to the current component as specified in the
556 glob pattern. Individual files can be excluded in the exclude list."""
557 files = glob.glob1(self.absolute, pattern)
558 for f in files:
559 if exclude and f in exclude: continue
560 self.add_file(f)
561 return files
562
563 def remove_pyc(self):
564 "Remove .pyc/.pyo files on uninstall"
565 add_data(self.db, "RemoveFile",
566 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
567 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
568
Martin v. Löwis90cc5ab2008-06-02 10:04:16 +0000569 def removefile(self, key, pattern):
570 "Add a RemoveFile entry"
571 add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
572
573
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000574class Feature:
575 def __init__(self, db, id, title, desc, display, level = 1,
576 parent=None, directory = None, attributes=0):
577 self.id = id
578 if parent:
579 parent = parent.id
580 add_data(db, "Feature",
581 [(id, parent, title, desc, display,
582 level, directory, attributes)])
583 def set_current(self):
584 global current_feature
585 current_feature = self
586
587class Control:
588 def __init__(self, dlg, name):
589 self.dlg = dlg
590 self.name = name
591
592 def event(self, ev, arg, cond = "1", order = None):
593 add_data(self.dlg.db, "ControlEvent",
594 [(self.dlg.name, self.name, ev, arg, cond, order)])
595
596 def mapping(self, ev, attr):
597 add_data(self.dlg.db, "EventMapping",
598 [(self.dlg.name, self.name, ev, attr)])
599
600 def condition(self, action, condition):
601 add_data(self.dlg.db, "ControlCondition",
602 [(self.dlg.name, self.name, action, condition)])
603
604class RadioButtonGroup(Control):
605 def __init__(self, dlg, name, property):
606 self.dlg = dlg
607 self.name = name
608 self.property = property
609 self.index = 1
610
611 def add(self, name, x, y, w, h, text, value = None):
612 if value is None:
613 value = name
614 add_data(self.dlg.db, "RadioButton",
615 [(self.property, self.index, value,
616 x, y, w, h, text, None)])
617 self.index += 1
618
619class Dialog:
620 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
621 self.db = db
622 self.name = name
623 self.x, self.y, self.w, self.h = x,y,w,h
624 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
625
626 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
627 add_data(self.db, "Control",
628 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
629 return Control(self, name)
630
631 def text(self, name, x, y, w, h, attr, text):
632 return self.control(name, "Text", x, y, w, h, attr, None,
633 text, None, None)
634
635 def bitmap(self, name, x, y, w, h, text):
636 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000637
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000638 def line(self, name, x, y, w, h):
639 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
640
641 def pushbutton(self, name, x, y, w, h, attr, text, next):
642 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
643
644 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
645 add_data(self.db, "Control",
646 [(self.name, name, "RadioButtonGroup",
647 x, y, w, h, attr, prop, text, next, None)])
648 return RadioButtonGroup(self, name, prop)
649
650 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000651 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000652
653def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000654 header = open(path, "rb").read(1000)
655 # offset of PE header is at offset 0x3c
656 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000657 assert header[pe_offset:pe_offset+4] == "PE\0\0"
658 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
659 return machine
660
661def set_arch_from_file(path):
662 global msi_type, Win64, arch_ext
663 machine = pe_type(path)
664 if machine == 0x14c:
665 # i386
666 msi_type = "Intel"
667 Win64 = 0
668 arch_ext = ''
669 elif machine == 0x200:
670 # Itanium
671 msi_type = "Intel64"
672 Win64 = 1
673 arch_ext = '.ia64'
674 elif machine == 0x8664:
675 # AMD64
676 msi_type = "x64"
677 Win64 = 1
678 arch_ext = '.amd64'
679 else:
Collin Wintera817e582007-08-22 23:05:06 +0000680 raise ValueError("Unsupported architecture")
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000681 msi_type += ";1033"