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