1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2005 Martin v. Löwis
3 # Licensed to PSF under a Contributor Agreement.
5 import os
, string
, re
, sys
7 AMD64
= "AMD64" in sys
.version
8 Itanium
= "Itanium" in sys
.version
9 Win64
= AMD64
or Itanium
11 # Partially taken from Wine
14 type_localizable
= 0x0200
24 # XXX temporary, localizable?
25 knownbits
= datasizemask | type_valid | type_localizable | \
26 typemask | type_nullable | type_key
29 def __init__(self
, name
):
33 def add_field(self
, index
, name
, type):
34 self
.fields
.append((index
,name
,type))
40 fields
= [None]*len(self
.fields
)
41 for index
, name
, type in self
.fields
:
43 unk
= type & ~knownbits
45 print "%s.%s unknown bits %x" % (self
.name
, name
, unk
)
46 size
= type & datasizemask
47 dtype
= type & typemask
48 if dtype
== type_string
:
50 tname
="CHAR(%d)" % size
53 elif dtype
== type_short
:
56 elif dtype
== type_long
:
59 elif dtype
== type_binary
:
64 print "%s.%sunknown integer type %d" % (self
.name
, name
, size
)
65 if type & type_nullable
:
69 if type & type_localizable
:
70 flags
+= " LOCALIZABLE"
71 fields
[index
] = "`%s` %s%s" % (name
, tname
, flags
)
73 keys
.append("`%s`" % name
)
74 fields
= ", ".join(fields
)
75 keys
= ", ".join(keys
)
76 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self
.name
, fields
, keys
)
79 v
= db
.OpenView(self
.sql())
83 class _Unspecified
:pass
84 def change_sequence(seq
, action
, seqno
=_Unspecified
, cond
= _Unspecified
):
85 "Change the sequence number of an action in a sequence list"
86 for i
in range(len(seq
)):
87 if seq
[i
][0] == action
:
88 if cond
is _Unspecified
:
90 if seqno
is _Unspecified
:
92 seq
[i
] = (action
, cond
, seqno
)
94 raise ValueError, "Action not found in sequence"
96 def add_data(db
, table
, values
):
97 v
= db
.OpenView("SELECT * FROM `%s`" % table
)
98 count
= v
.GetColumnInfo(MSICOLINFO_NAMES
).GetFieldCount()
99 r
= CreateRecord(count
)
101 assert len(value
) == count
, value
102 for i
in range(count
):
104 if isinstance(field
, (int, long)):
105 r
.SetInteger(i
+1,field
)
106 elif isinstance(field
, basestring
):
107 r
.SetString(i
+1,field
)
110 elif isinstance(field
, Binary
):
111 r
.SetStream(i
+1, field
.name
)
113 raise TypeError, "Unsupported type %s" % field
.__class
__.__name
__
115 v
.Modify(MSIMODIFY_INSERT
, r
)
117 raise MSIError("Could not insert "+repr(values
)+" into "+table
)
123 def add_stream(db
, name
, path
):
124 v
= db
.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name
)
130 def init_database(name
, schema
,
131 ProductName
, ProductCode
, ProductVersion
,
137 ProductCode
= ProductCode
.upper()
138 # Create the database
139 db
= OpenDatabase(name
, MSIDBOPEN_CREATE
)
141 for t
in schema
.tables
:
143 # Fill the validation table
144 add_data(db
, "_Validation", schema
._Validation
_records
)
145 # Initialize the summary information, allowing atmost 20 properties
146 si
= db
.GetSummaryInformation(20)
147 si
.SetProperty(PID_TITLE
, "Installation Database")
148 si
.SetProperty(PID_SUBJECT
, ProductName
)
149 si
.SetProperty(PID_AUTHOR
, Manufacturer
)
151 si
.SetProperty(PID_TEMPLATE
, "Intel64;1033")
153 si
.SetProperty(PID_TEMPLATE
, "x64;1033")
155 si
.SetProperty(PID_TEMPLATE
, "Intel;1033")
156 si
.SetProperty(PID_REVNUMBER
, gen_uuid())
157 si
.SetProperty(PID_WORDCOUNT
, 2) # long file names, compressed, original media
158 si
.SetProperty(PID_PAGECOUNT
, 200)
159 si
.SetProperty(PID_APPNAME
, "Python MSI Library")
160 # XXX more properties
162 add_data(db
, "Property", [
163 ("ProductName", ProductName
),
164 ("ProductCode", ProductCode
),
165 ("ProductVersion", ProductVersion
),
166 ("Manufacturer", Manufacturer
),
167 ("ProductLanguage", "1033")])
171 def add_tables(db
, module
):
172 for table
in module
.tables
:
173 add_data(db
, table
, getattr(module
, table
))
176 #str = str.replace(".", "_") # colons are allowed
177 str = str.replace(" ", "_")
178 str = str.replace("-", "_")
179 if str[0] in string
.digits
:
181 assert re
.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
185 return "{"+UuidCreate().upper()+"}"
188 def __init__(self
, name
):
191 self
.filenames
= set()
194 def gen_id(self
, file):
195 logical
= _logical
= make_id(file)
197 while logical
in self
.filenames
:
198 logical
= "%s.%d" % (_logical
, pos
)
200 self
.filenames
.add(logical
)
203 def append(self
, full
, file, logical
):
204 if os
.path
.isdir(full
):
207 logical
= self
.gen_id(file)
209 self
.files
.append((full
, logical
))
210 return self
.index
, logical
212 def commit(self
, db
):
213 from tempfile
import mktemp
215 FCICreate(filename
, self
.files
)
216 add_data(db
, "Media",
217 [(1, self
.index
, None, "#"+self
.name
, None, None)])
218 add_stream(db
, self
.name
, filename
)
224 def __init__(self
, db
, cab
, basedir
, physical
, _logical
, default
, componentflags
=None):
225 """Create a new directory in the Directory table. There is a current component
226 at each point in time for the directory, which is either explicitly created
227 through start_component, or implicitly when files are added for the first
228 time. Files are added into the current component, and into the cab file.
229 To create a directory, a base directory object needs to be specified (can be
230 None), the path to the physical directory, and a logical directory name.
231 Default specifies the DefaultDir slot in the directory table. componentflags
232 specifies the default flags that new components get."""
234 _logical
= make_id(_logical
)
236 while logical
in _directories
:
237 logical
= "%s%d" % (_logical
, index
)
239 _directories
.add(logical
)
242 self
.basedir
= basedir
243 self
.physical
= physical
244 self
.logical
= logical
245 self
.component
= None
246 self
.short_names
= set()
249 self
.componentflags
= componentflags
251 self
.absolute
= os
.path
.join(basedir
.absolute
, physical
)
252 blogical
= basedir
.logical
254 self
.absolute
= physical
256 add_data(db
, "Directory", [(logical
, blogical
, default
)])
258 def start_component(self
, component
= None, feature
= None, flags
= None, keyfile
= None, uuid
=None):
259 """Add an entry to the Component table, and make this component the current for this
260 directory. If no component name is given, the directory name is used. If no feature
261 is given, the current feature is used. If no flags are given, the directory's default
262 flags are used. If no keyfile is given, the KeyPath is left null in the Component
265 flags
= self
.componentflags
270 if component
is None:
271 component
= self
.logical
272 self
.component
= component
276 keyid
= self
.cab
.gen_id(self
.absolute
, keyfile
)
277 self
.keyfiles
[keyfile
] = keyid
280 add_data(self
.db
, "Component",
281 [(component
, uuid
, self
.logical
, flags
, None, keyid
)])
283 feature
= current_feature
284 add_data(self
.db
, "FeatureComponents",
285 [(feature
.id, component
)])
287 def make_short(self
, file):
288 parts
= file.split(".")
290 suffix
= parts
[-1].upper()
293 prefix
= parts
[0].upper()
294 if len(prefix
) <= 8 and (not suffix
or len(suffix
)<=3):
296 file = prefix
+"."+suffix
299 assert file not in self
.short_names
307 file = "%s~%d.%s" % (prefix
, pos
, suffix
)
309 file = "%s~%d" % (prefix
, pos
)
310 if file not in self
.short_names
: break
313 if pos
in (10, 100, 1000):
315 self
.short_names
.add(file)
316 assert not re
.search(r
'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names
319 def add_file(self
, file, src
=None, version
=None, language
=None):
320 """Add a file to the current component of the directory, starting a new one
321 one if there is no current component. By default, the file name in the source
322 and the file table will be identical. If the src file is specified, it is
323 interpreted relative to the current directory. Optionally, a version and a
324 language can be specified for the entry in the File table."""
325 if not self
.component
:
326 self
.start_component(self
.logical
, current_feature
, 0)
328 # Allow relative paths for file if src is not specified
330 file = os
.path
.basename(file)
331 absolute
= os
.path
.join(self
.absolute
, src
)
332 assert not re
.search(r
'[\?|><:/*]"', file) # restrictions on long names
333 if file in self
.keyfiles
:
334 logical
= self
.keyfiles
[file]
337 sequence
, logical
= self
.cab
.append(absolute
, file, logical
)
338 assert logical
not in self
.ids
339 self
.ids
.add(logical
)
340 short
= self
.make_short(file)
341 full
= "%s|%s" % (short
, file)
342 filesize
= os
.stat(absolute
).st_size
343 # constants.msidbFileAttributesVital
344 # Compressed omitted, since it is the database default
345 # could add r/o, system, hidden
347 add_data(self
.db
, "File",
348 [(logical
, self
.component
, full
, filesize
, version
,
349 language
, attributes
, sequence
)])
351 # # Add hash if the file is not versioned
352 # filehash = FileHash(absolute, 0)
353 # add_data(self.db, "MsiFileHash",
354 # [(logical, 0, filehash.IntegerData(1),
355 # filehash.IntegerData(2), filehash.IntegerData(3),
356 # filehash.IntegerData(4))])
357 # Automatically remove .pyc/.pyo files on uninstall (2)
358 # XXX: adding so many RemoveFile entries makes installer unbelievably
359 # slow. So instead, we have to use wildcard remove entries
360 if file.endswith(".py"):
361 add_data(self
.db
, "RemoveFile",
362 [(logical
+"c", self
.component
, "%sC|%sc" % (short
, file),
364 (logical
+"o", self
.component
, "%sO|%so" % (short
, file),
368 def glob(self
, pattern
, exclude
= None):
369 """Add a list of files to the current component as specified in the
370 glob pattern. Individual files can be excluded in the exclude list."""
371 files
= glob
.glob1(self
.absolute
, pattern
)
373 if exclude
and f
in exclude
: continue
377 def remove_pyc(self
):
378 "Remove .pyc/.pyo files on uninstall"
379 add_data(self
.db
, "RemoveFile",
380 [(self
.component
+"c", self
.component
, "*.pyc", self
.logical
, 2),
381 (self
.component
+"o", self
.component
, "*.pyo", self
.logical
, 2)])
384 def __init__(self
, fname
):
387 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self
.name
390 def __init__(self
, db
, id, title
, desc
, display
, level
= 1,
391 parent
=None, directory
= None, attributes
=0):
395 add_data(db
, "Feature",
396 [(id, parent
, title
, desc
, display
,
397 level
, directory
, attributes
)])
398 def set_current(self
):
399 global current_feature
400 current_feature
= self
403 def __init__(self
, dlg
, name
):
407 def event(self
, event
, argument
, condition
= "1", ordering
= None):
408 add_data(self
.dlg
.db
, "ControlEvent",
409 [(self
.dlg
.name
, self
.name
, event
, argument
,
410 condition
, ordering
)])
412 def mapping(self
, event
, attribute
):
413 add_data(self
.dlg
.db
, "EventMapping",
414 [(self
.dlg
.name
, self
.name
, event
, attribute
)])
416 def condition(self
, action
, condition
):
417 add_data(self
.dlg
.db
, "ControlCondition",
418 [(self
.dlg
.name
, self
.name
, action
, condition
)])
420 class RadioButtonGroup(Control
):
421 def __init__(self
, dlg
, name
, property):
424 self
.property = property
427 def add(self
, name
, x
, y
, w
, h
, text
, value
= None):
430 add_data(self
.dlg
.db
, "RadioButton",
431 [(self
.property, self
.index
, value
,
432 x
, y
, w
, h
, text
, None)])
436 def __init__(self
, db
, name
, x
, y
, w
, h
, attr
, title
, first
, default
, cancel
):
439 self
.x
, self
.y
, self
.w
, self
.h
= x
,y
,w
,h
440 add_data(db
, "Dialog", [(name
, x
,y
,w
,h
,attr
,title
,first
,default
,cancel
)])
442 def control(self
, name
, type, x
, y
, w
, h
, attr
, prop
, text
, next
, help):
443 add_data(self
.db
, "Control",
444 [(self
.name
, name
, type, x
, y
, w
, h
, attr
, prop
, text
, next
, help)])
445 return Control(self
, name
)
447 def text(self
, name
, x
, y
, w
, h
, attr
, text
):
448 return self
.control(name
, "Text", x
, y
, w
, h
, attr
, None,
451 def bitmap(self
, name
, x
, y
, w
, h
, text
):
452 return self
.control(name
, "Bitmap", x
, y
, w
, h
, 1, None, text
, None, None)
454 def line(self
, name
, x
, y
, w
, h
):
455 return self
.control(name
, "Line", x
, y
, w
, h
, 1, None, None, None, None)
457 def pushbutton(self
, name
, x
, y
, w
, h
, attr
, text
, next
):
458 return self
.control(name
, "PushButton", x
, y
, w
, h
, attr
, None, text
, next
, None)
460 def radiogroup(self
, name
, x
, y
, w
, h
, attr
, prop
, text
, next
):
461 add_data(self
.db
, "Control",
462 [(self
.name
, name
, "RadioButtonGroup",
463 x
, y
, w
, h
, attr
, prop
, text
, next
, None)])
464 return RadioButtonGroup(self
, name
, prop
)
466 def checkbox(self
, name
, x
, y
, w
, h
, attr
, prop
, text
, next
):
467 return self
.control(name
, "CheckBox", x
, y
, w
, h
, attr
, prop
, text
, next
, None)