»Core Development>Code coverage>Lib/msilib/__init__.py

Python code coverage for Lib/msilib/__init__.py

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