symbian-qemu-0.9.1-12/python-win32-2.6.1/lib/msilib/__init__.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     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
       
     6 
       
     7 Win64=0
       
     8 
       
     9 # Partially taken from Wine
       
    10 datasizemask=      0x00ff
       
    11 type_valid=        0x0100
       
    12 type_localizable=  0x0200
       
    13 
       
    14 typemask=          0x0c00
       
    15 type_long=         0x0000
       
    16 type_short=        0x0400
       
    17 type_string=       0x0c00
       
    18 type_binary=       0x0800
       
    19 
       
    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
       
    25 
       
    26 class Table:
       
    27     def __init__(self, name):
       
    28         self.name = name
       
    29         self.fields = []
       
    30 
       
    31     def add_field(self, index, name, type):
       
    32         self.fields.append((index,name,type))
       
    33 
       
    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)
       
    75 
       
    76     def create(self, db):
       
    77         v = db.OpenView(self.sql())
       
    78         v.Execute(None)
       
    79         v.Close()
       
    80 
       
    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"
       
    93 
       
    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)
       
   116 
       
   117         r.ClearData()
       
   118     v.Close()
       
   119 
       
   120 
       
   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()
       
   127 
       
   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
       
   166 
       
   167 def add_tables(db, module):
       
   168     for table in module.tables:
       
   169         add_data(db, table, getattr(module, table))
       
   170 
       
   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
       
   179 
       
   180 def gen_uuid():
       
   181     return "{"+UuidCreate().upper()+"}"
       
   182 
       
   183 class CAB:
       
   184     def __init__(self, name):
       
   185         self.name = name
       
   186         self.files = []
       
   187         self.filenames = set()
       
   188         self.index = 0
       
   189 
       
   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
       
   198 
       
   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
       
   207 
       
   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()
       
   217 
       
   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)])
       
   253 
       
   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)])
       
   282 
       
   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
       
   314 
       
   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
       
   363 
       
   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
       
   372 
       
   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)])
       
   378 
       
   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
       
   384 
       
   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
       
   397 
       
   398 class Control:
       
   399     def __init__(self, dlg, name):
       
   400         self.dlg = dlg
       
   401         self.name = name
       
   402 
       
   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)])
       
   407 
       
   408     def mapping(self, event, attribute):
       
   409         add_data(self.dlg.db, "EventMapping",
       
   410                  [(self.dlg.name, self.name, event, attribute)])
       
   411 
       
   412     def condition(self, action, condition):
       
   413         add_data(self.dlg.db, "ControlCondition",
       
   414                  [(self.dlg.name, self.name, action, condition)])
       
   415 
       
   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
       
   422 
       
   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
       
   430 
       
   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)])
       
   437 
       
   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)
       
   442 
       
   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)
       
   446 
       
   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)
       
   449 
       
   450     def line(self, name, x, y, w, h):
       
   451         return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
       
   452 
       
   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)
       
   455 
       
   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)
       
   461 
       
   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)