logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

__init__.py (17591B)


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