1 # -*- coding: iso-8859-1 -*-
2 # Copyright (C) 2005 Martin v. Löwis
3 # Licensed to PSF under a Contributor Agreement.
9 # Partially taken from Wine
12 type_localizable
= 0x0200
22 # XXX temporary, localizable?
23 knownbits
= datasizemask | type_valid | type_localizable | \
24 typemask | type_nullable | type_key
27 def __init__(self
, name
):
31 def add_field(self
, index
, name
, type):
32 self
.fields
.append((index
,name
,type))
38 fields
= [None]*len(self
.fields
)
39 for index
, name
, type in self
.fields
:
41 unk
= type & ~knownbits
43 print "%s.%s unknown bits %x" % (self
.name
, name
, unk
)
44 size
= type & datasizemask
45 dtype
= type & typemask
46 if dtype
== type_string
:
48 tname
="CHAR(%d)" % size
51 elif dtype
== type_short
:
54 elif dtype
== type_long
:
57 elif dtype
== type_binary
:
62 print "%s.%sunknown integer type %d" % (self
.name
, name
, size
)
63 if type & type_nullable
:
67 if type & type_localizable
:
68 flags
+= " LOCALIZABLE"
69 fields
[index
] = "`%s` %s%s" % (name
, tname
, flags
)
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
)
77 v
= db
.OpenView(self
.sql())
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
:
88 if seqno
is _Unspecified
:
90 seq
[i
] = (action
, cond
, seqno
)
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
)
99 assert len(value
) == count
, value
100 for i
in range(count
):
102 if isinstance(field
, (int, long)):
103 r
.SetInteger(i
+1,field
)
104 elif isinstance(field
, basestring
):
105 r
.SetString(i
+1,field
)
108 elif isinstance(field
, Binary
):
109 r
.SetStream(i
+1, field
.name
)
111 raise TypeError, "Unsupported type %s" % field
.__class
__.__name
__
113 v
.Modify(MSIMODIFY_INSERT
, r
)
115 raise MSIError("Could not insert "+repr(values
)+" into "+table
)
121 def add_stream(db
, name
, path
):
122 v
= db
.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name
)
128 def init_database(name
, schema
,
129 ProductName
, ProductCode
, ProductVersion
,
135 ProductCode
= ProductCode
.upper()
136 # Create the database
137 db
= OpenDatabase(name
, MSIDBOPEN_CREATE
)
139 for t
in schema
.tables
:
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
)
149 si
.SetProperty(PID_TEMPLATE
, "Intel64;1033")
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
158 add_data(db
, "Property", [
159 ("ProductName", ProductName
),
160 ("ProductCode", ProductCode
),
161 ("ProductVersion", ProductVersion
),
162 ("Manufacturer", Manufacturer
),
163 ("ProductLanguage", "1033")])
167 def add_tables(db
, module
):
168 for table
in module
.tables
:
169 add_data(db
, table
, getattr(module
, table
))
172 #str = str.replace(".", "_") # colons are allowed
173 str = str.replace(" ", "_")
174 str = str.replace("-", "_")
175 if str[0] in string
.digits
:
177 assert re
.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
181 return "{"+UuidCreate().upper()+"}"
184 def __init__(self
, name
):
187 self
.filenames
= set()
190 def gen_id(self
, file):
191 logical
= _logical
= make_id(file)
193 while logical
in self
.filenames
:
194 logical
= "%s.%d" % (_logical
, pos
)
196 self
.filenames
.add(logical
)
199 def append(self
, full
, file, logical
):
200 if os
.path
.isdir(full
):
203 logical
= self
.gen_id(file)
205 self
.files
.append((full
, logical
))
206 return self
.index
, logical
208 def commit(self
, db
):
209 from tempfile
import 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
)
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."""
230 _logical
= make_id(_logical
)
232 while logical
in _directories
:
233 logical
= "%s%d" % (_logical
, index
)
235 _directories
.add(logical
)
238 self
.basedir
= basedir
239 self
.physical
= physical
240 self
.logical
= logical
241 self
.component
= None
242 self
.short_names
= set()
245 self
.componentflags
= componentflags
247 self
.absolute
= os
.path
.join(basedir
.absolute
, physical
)
248 blogical
= basedir
.logical
250 self
.absolute
= physical
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
261 flags
= self
.componentflags
266 if component
is None:
267 component
= self
.logical
268 self
.component
= component
272 keyid
= self
.cab
.gen_id(self
.absolute
, keyfile
)
273 self
.keyfiles
[keyfile
] = keyid
276 add_data(self
.db
, "Component",
277 [(component
, uuid
, self
.logical
, flags
, None, keyid
)])
279 feature
= current_feature
280 add_data(self
.db
, "FeatureComponents",
281 [(feature
.id, component
)])
283 def make_short(self
, file):
284 parts
= file.split(".")
286 suffix
= parts
[-1].upper()
289 prefix
= parts
[0].upper()
290 if len(prefix
) <= 8 and (not suffix
or len(suffix
)<=3):
292 file = prefix
+"."+suffix
295 assert file not in self
.short_names
303 file = "%s~%d.%s" % (prefix
, pos
, suffix
)
305 file = "%s~%d" % (prefix
, pos
)
306 if file not in self
.short_names
: break
309 if pos
in (10, 100, 1000):
311 self
.short_names
.add(file)
312 assert not re
.search(r
'[\?|><:/*"+,;=\[\]]', file) # restrictions on short names
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)
324 # Allow relative paths for file if src is not specified
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]
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
343 add_data(self
.db
, "File",
344 [(logical
, self
.component
, full
, filesize
, version
,
345 language
, attributes
, sequence
)])
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),
360 (logical
+"o", self
.component
, "%sO|%so" % (short
, file),
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
)
369 if exclude
and f
in exclude
: continue
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)])
380 def __init__(self
, fname
):
383 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self
.name
386 def __init__(self
, db
, id, title
, desc
, display
, level
= 1,
387 parent
=None, directory
= None, attributes
=0):
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
399 def __init__(self
, dlg
, 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):
420 self
.property = property
423 def add(self
, name
, x
, y
, w
, h
, text
, value
= None):
426 add_data(self
.dlg
.db
, "RadioButton",
427 [(self
.property, self
.index
, value
,
428 x
, y
, w
, h
, text
, None)])
432 def __init__(self
, db
, name
, x
, y
, w
, h
, attr
, title
, first
, default
, cancel
):
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,
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)