Issue #2183: Simplify and optimize bytecode for list comprehensions.
[python.git] / Lib / msilib / __init__.py
blobb13d0306f12ea995813f16f87ba4b1cea5846dfb
1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2005 Martin v. Löwis
3 # Licensed to PSF under a Contributor Agreement.
4 from _msi import *
5 import os, string, re
7 Win64=0
9 # Partially taken from Wine
10 datasizemask= 0x00ff
11 type_valid= 0x0100
12 type_localizable= 0x0200
14 typemask= 0x0c00
15 type_long= 0x0000
16 type_short= 0x0400
17 type_string= 0x0c00
18 type_binary= 0x0800
20 type_nullable= 0x1000
21 type_key= 0x2000
22 # XXX temporary, localizable?
23 knownbits = datasizemask | type_valid | type_localizable | \
24 typemask | type_nullable | type_key
26 class Table:
27 def __init__(self, name):
28 self.name = name
29 self.fields = []
31 def add_field(self, index, name, type):
32 self.fields.append((index,name,type))
34 def sql(self):
35 fields = []
36 keys = []
37 self.fields.sort()
38 fields = [None]*len(self.fields)
39 for index, name, type in self.fields:
40 index -= 1
41 unk = type & ~knownbits
42 if unk:
43 print "%s.%s unknown bits %x" % (self.name, name, unk)
44 size = type & datasizemask
45 dtype = type & typemask
46 if dtype == type_string:
47 if size:
48 tname="CHAR(%d)" % size
49 else:
50 tname="CHAR"
51 elif dtype == type_short:
52 assert size==2
53 tname = "SHORT"
54 elif dtype == type_long:
55 assert size==4
56 tname="LONG"
57 elif dtype == type_binary:
58 assert size==0
59 tname="OBJECT"
60 else:
61 tname="unknown"
62 print "%s.%sunknown integer type %d" % (self.name, name, size)
63 if type & type_nullable:
64 flags = ""
65 else:
66 flags = " NOT NULL"
67 if type & type_localizable:
68 flags += " LOCALIZABLE"
69 fields[index] = "`%s` %s%s" % (name, tname, flags)
70 if type & type_key:
71 keys.append("`%s`" % name)
72 fields = ", ".join(fields)
73 keys = ", ".join(keys)
74 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
76 def create(self, db):
77 v = db.OpenView(self.sql())
78 v.Execute(None)
79 v.Close()
81 class _Unspecified:pass
82 def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
83 "Change the sequence number of an action in a sequence list"
84 for i in range(len(seq)):
85 if seq[i][0] == action:
86 if cond is _Unspecified:
87 cond = seq[i][1]
88 if seqno is _Unspecified:
89 seqno = seq[i][2]
90 seq[i] = (action, cond, seqno)
91 return
92 raise ValueError, "Action not found in sequence"
94 def add_data(db, table, values):
95 v = db.OpenView("SELECT * FROM `%s`" % table)
96 count = v.GetColumnInfo(MSICOLINFO_NAMES).GetFieldCount()
97 r = CreateRecord(count)
98 for value in values:
99 assert len(value) == count, value
100 for i in range(count):
101 field = value[i]
102 if isinstance(field, (int, long)):
103 r.SetInteger(i+1,field)
104 elif isinstance(field, basestring):
105 r.SetString(i+1,field)
106 elif field is None:
107 pass
108 elif isinstance(field, Binary):
109 r.SetStream(i+1, field.name)
110 else:
111 raise TypeError, "Unsupported type %s" % field.__class__.__name__
112 try:
113 v.Modify(MSIMODIFY_INSERT, r)
114 except Exception, e:
115 raise MSIError("Could not insert "+repr(values)+" into "+table)
117 r.ClearData()
118 v.Close()
121 def add_stream(db, name, path):
122 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
123 r = CreateRecord(1)
124 r.SetStream(1, path)
125 v.Execute(r)
126 v.Close()
128 def init_database(name, schema,
129 ProductName, ProductCode, ProductVersion,
130 Manufacturer):
131 try:
132 os.unlink(name)
133 except OSError:
134 pass
135 ProductCode = ProductCode.upper()
136 # Create the database
137 db = OpenDatabase(name, MSIDBOPEN_CREATE)
138 # Create the tables
139 for t in schema.tables:
140 t.create(db)
141 # Fill the validation table
142 add_data(db, "_Validation", schema._Validation_records)
143 # Initialize the summary information, allowing atmost 20 properties
144 si = db.GetSummaryInformation(20)
145 si.SetProperty(PID_TITLE, "Installation Database")
146 si.SetProperty(PID_SUBJECT, ProductName)
147 si.SetProperty(PID_AUTHOR, Manufacturer)
148 if Win64:
149 si.SetProperty(PID_TEMPLATE, "Intel64;1033")
150 else:
151 si.SetProperty(PID_TEMPLATE, "Intel;1033")
152 si.SetProperty(PID_REVNUMBER, gen_uuid())
153 si.SetProperty(PID_WORDCOUNT, 2) # long file names, compressed, original media
154 si.SetProperty(PID_PAGECOUNT, 200)
155 si.SetProperty(PID_APPNAME, "Python MSI Library")
156 # XXX more properties
157 si.Persist()
158 add_data(db, "Property", [
159 ("ProductName", ProductName),
160 ("ProductCode", ProductCode),
161 ("ProductVersion", ProductVersion),
162 ("Manufacturer", Manufacturer),
163 ("ProductLanguage", "1033")])
164 db.Commit()
165 return db
167 def add_tables(db, module):
168 for table in module.tables:
169 add_data(db, table, getattr(module, table))
171 def make_id(str):
172 #str = str.replace(".", "_") # colons are allowed
173 str = str.replace(" ", "_")
174 str = str.replace("-", "_")
175 if str[0] in string.digits:
176 str = "_"+str
177 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
178 return str
180 def gen_uuid():
181 return "{"+UuidCreate().upper()+"}"
183 class CAB:
184 def __init__(self, name):
185 self.name = name
186 self.files = []
187 self.filenames = set()
188 self.index = 0
190 def gen_id(self, file):
191 logical = _logical = make_id(file)
192 pos = 1
193 while logical in self.filenames:
194 logical = "%s.%d" % (_logical, pos)
195 pos += 1
196 self.filenames.add(logical)
197 return logical
199 def append(self, full, file, logical):
200 if os.path.isdir(full):
201 return
202 if not logical:
203 logical = self.gen_id(file)
204 self.index += 1
205 self.files.append((full, logical))
206 return self.index, logical
208 def commit(self, db):
209 from tempfile import mktemp
210 filename = mktemp()
211 FCICreate(filename, self.files)
212 add_data(db, "Media",
213 [(1, self.index, None, "#"+self.name, None, None)])
214 add_stream(db, self.name, filename)
215 os.unlink(filename)
216 db.Commit()
218 _directories = set()
219 class Directory:
220 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
221 """Create a new directory in the Directory table. There is a current component
222 at each point in time for the directory, which is either explicitly created
223 through start_component, or implicitly when files are added for the first
224 time. Files are added into the current component, and into the cab file.
225 To create a directory, a base directory object needs to be specified (can be
226 None), the path to the physical directory, and a logical directory name.
227 Default specifies the DefaultDir slot in the directory table. componentflags
228 specifies the default flags that new components get."""
229 index = 1
230 _logical = make_id(_logical)
231 logical = _logical
232 while logical in _directories:
233 logical = "%s%d" % (_logical, index)
234 index += 1
235 _directories.add(logical)
236 self.db = db
237 self.cab = cab
238 self.basedir = basedir
239 self.physical = physical
240 self.logical = logical
241 self.component = None
242 self.short_names = set()
243 self.ids = set()
244 self.keyfiles = {}
245 self.componentflags = componentflags
246 if basedir:
247 self.absolute = os.path.join(basedir.absolute, physical)
248 blogical = basedir.logical
249 else:
250 self.absolute = physical
251 blogical = None
252 add_data(db, "Directory", [(logical, blogical, default)])
254 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
255 """Add an entry to the Component table, and make this component the current for this
256 directory. If no component name is given, the directory name is used. If no feature
257 is given, the current feature is used. If no flags are given, the directory's default
258 flags are used. If no keyfile is given, the KeyPath is left null in the Component
259 table."""
260 if flags is None:
261 flags = self.componentflags
262 if uuid is None:
263 uuid = gen_uuid()
264 else:
265 uuid = uuid.upper()
266 if component is None:
267 component = self.logical
268 self.component = component
269 if Win64:
270 flags |= 256
271 if keyfile:
272 keyid = self.cab.gen_id(self.absolute, keyfile)
273 self.keyfiles[keyfile] = keyid
274 else:
275 keyid = None
276 add_data(self.db, "Component",
277 [(component, uuid, self.logical, flags, None, keyid)])
278 if feature is None:
279 feature = current_feature
280 add_data(self.db, "FeatureComponents",
281 [(feature.id, component)])
283 def make_short(self, file):
284 parts = file.split(".")
285 if len(parts)>1:
286 suffix = parts[-1].upper()
287 else:
288 suffix = None
289 prefix = parts[0].upper()
290 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
291 if suffix:
292 file = prefix+"."+suffix
293 else:
294 file = prefix
295 assert file not in self.short_names
296 else:
297 prefix = prefix[:6]
298 if suffix:
299 suffix = suffix[:3]
300 pos = 1
301 while 1:
302 if suffix:
303 file = "%s~%d.%s" % (prefix, pos, suffix)
304 else:
305 file = "%s~%d" % (prefix, pos)
306 if file not in self.short_names: break
307 pos += 1
308 assert pos < 10000
309 if pos in (10, 100, 1000):
310 prefix = prefix[:-1]
311 self.short_names.add(file)
312 assert not re.search(r'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names
313 return file
315 def add_file(self, file, src=None, version=None, language=None):
316 """Add a file to the current component of the directory, starting a new one
317 one if there is no current component. By default, the file name in the source
318 and the file table will be identical. If the src file is specified, it is
319 interpreted relative to the current directory. Optionally, a version and a
320 language can be specified for the entry in the File table."""
321 if not self.component:
322 self.start_component(self.logical, current_feature, 0)
323 if not src:
324 # Allow relative paths for file if src is not specified
325 src = file
326 file = os.path.basename(file)
327 absolute = os.path.join(self.absolute, src)
328 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
329 if self.keyfiles.has_key(file):
330 logical = self.keyfiles[file]
331 else:
332 logical = None
333 sequence, logical = self.cab.append(absolute, file, logical)
334 assert logical not in self.ids
335 self.ids.add(logical)
336 short = self.make_short(file)
337 full = "%s|%s" % (short, file)
338 filesize = os.stat(absolute).st_size
339 # constants.msidbFileAttributesVital
340 # Compressed omitted, since it is the database default
341 # could add r/o, system, hidden
342 attributes = 512
343 add_data(self.db, "File",
344 [(logical, self.component, full, filesize, version,
345 language, attributes, sequence)])
346 #if not version:
347 # # Add hash if the file is not versioned
348 # filehash = FileHash(absolute, 0)
349 # add_data(self.db, "MsiFileHash",
350 # [(logical, 0, filehash.IntegerData(1),
351 # filehash.IntegerData(2), filehash.IntegerData(3),
352 # filehash.IntegerData(4))])
353 # Automatically remove .pyc/.pyo files on uninstall (2)
354 # XXX: adding so many RemoveFile entries makes installer unbelievably
355 # slow. So instead, we have to use wildcard remove entries
356 if file.endswith(".py"):
357 add_data(self.db, "RemoveFile",
358 [(logical+"c", self.component, "%sC|%sc" % (short, file),
359 self.logical, 2),
360 (logical+"o", self.component, "%sO|%so" % (short, file),
361 self.logical, 2)])
362 return logical
364 def glob(self, pattern, exclude = None):
365 """Add a list of files to the current component as specified in the
366 glob pattern. Individual files can be excluded in the exclude list."""
367 files = glob.glob1(self.absolute, pattern)
368 for f in files:
369 if exclude and f in exclude: continue
370 self.add_file(f)
371 return files
373 def remove_pyc(self):
374 "Remove .pyc/.pyo files on uninstall"
375 add_data(self.db, "RemoveFile",
376 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
377 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
379 class Binary:
380 def __init__(self, fname):
381 self.name = fname
382 def __repr__(self):
383 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
385 class Feature:
386 def __init__(self, db, id, title, desc, display, level = 1,
387 parent=None, directory = None, attributes=0):
388 self.id = id
389 if parent:
390 parent = parent.id
391 add_data(db, "Feature",
392 [(id, parent, title, desc, display,
393 level, directory, attributes)])
394 def set_current(self):
395 global current_feature
396 current_feature = self
398 class Control:
399 def __init__(self, dlg, name):
400 self.dlg = dlg
401 self.name = name
403 def event(self, event, argument, condition = "1", ordering = None):
404 add_data(self.dlg.db, "ControlEvent",
405 [(self.dlg.name, self.name, event, argument,
406 condition, ordering)])
408 def mapping(self, event, attribute):
409 add_data(self.dlg.db, "EventMapping",
410 [(self.dlg.name, self.name, event, attribute)])
412 def condition(self, action, condition):
413 add_data(self.dlg.db, "ControlCondition",
414 [(self.dlg.name, self.name, action, condition)])
416 class RadioButtonGroup(Control):
417 def __init__(self, dlg, name, property):
418 self.dlg = dlg
419 self.name = name
420 self.property = property
421 self.index = 1
423 def add(self, name, x, y, w, h, text, value = None):
424 if value is None:
425 value = name
426 add_data(self.dlg.db, "RadioButton",
427 [(self.property, self.index, value,
428 x, y, w, h, text, None)])
429 self.index += 1
431 class Dialog:
432 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
433 self.db = db
434 self.name = name
435 self.x, self.y, self.w, self.h = x,y,w,h
436 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
438 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
439 add_data(self.db, "Control",
440 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
441 return Control(self, name)
443 def text(self, name, x, y, w, h, attr, text):
444 return self.control(name, "Text", x, y, w, h, attr, None,
445 text, None, None)
447 def bitmap(self, name, x, y, w, h, text):
448 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
450 def line(self, name, x, y, w, h):
451 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
453 def pushbutton(self, name, x, y, w, h, attr, text, next):
454 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
456 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
457 add_data(self.db, "Control",
458 [(self.name, name, "RadioButtonGroup",
459 x, y, w, h, attr, prop, text, next, None)])
460 return RadioButtonGroup(self, name, prop)
462 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
463 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)