[Libreoffice-commits] core.git: Branch 'aoo/trunk' - instsetoo_native/data instsetoo_native/util solenv/bin

Andre Fischer af at apache.org
Wed Dec 4 02:07:52 PST 2013


 instsetoo_native/data/releases.xml                     |  275 +-
 instsetoo_native/util/makefile.mk                      |  113 -
 solenv/bin/make_installer.pl                           |  133 +
 solenv/bin/modules/installer/globals.pm                |    9 
 solenv/bin/modules/installer/logger.pm                 |   10 
 solenv/bin/modules/installer/parameter.pm              |    4 
 solenv/bin/modules/installer/patch/FileSequenceList.pm |  119 -
 solenv/bin/modules/installer/patch/InstallationSet.pm  |  401 +++-
 solenv/bin/modules/installer/patch/Msi.pm              |  291 ++-
 solenv/bin/modules/installer/patch/MsiRow.pm           |    9 
 solenv/bin/modules/installer/patch/MsiTable.pm         |  230 ++
 solenv/bin/modules/installer/patch/ReleasesList.pm     |  283 ++-
 solenv/bin/modules/installer/patch/Tools.pm            |   19 
 solenv/bin/modules/installer/patch/Version.pm          |   35 
 solenv/bin/modules/installer/systemactions.pm          |   52 
 solenv/bin/modules/installer/windows/component.pm      |  308 ++-
 solenv/bin/modules/installer/windows/directory.pm      |  642 ++++--
 solenv/bin/modules/installer/windows/file.pm           |  648 +++++--
 solenv/bin/modules/installer/windows/msiglobal.pm      |  255 +-
 solenv/bin/modules/installer/windows/registry.pm       |  169 +
 solenv/bin/patch_tool.pl                               | 1571 +++++++++++++++++
 solenv/bin/release_prepare.pl                          |  141 -
 22 files changed, 4625 insertions(+), 1092 deletions(-)

New commits:
commit 1138b2a95c8c985e5682ab4b25b15b00e96cc8e5
Author: Andre Fischer <af at apache.org>
Date:   Wed Dec 4 08:51:30 2013 +0000

    123531: Reimplemented support for creating patches on Windows.

diff --git a/instsetoo_native/data/releases.xml b/instsetoo_native/data/releases.xml
index 34c4d07..a8635f6 100644
--- a/instsetoo_native/data/releases.xml
+++ b/instsetoo_native/data/releases.xml
@@ -21,296 +21,357 @@
  ***********************************************************-->
 <releases>
   <release>
-    <version>4.0.0</version>
-    <download>
+    <version>
+      <major>4</major>
+      <minor>0</minor>
+      <micro>0</micro>
+    </version>
+    <downloads>
       <package-format>msi</package-format>
       <url-template>
         http://archive.apache.org/dist/openoffice/4.0.0/binaries/%L/Apache_OpenOffice_4.0.0_Win_x86_install_%L.exe
       </url-template>
+      <upgrade-code>7C35B9AB-2CE3-4C18-BE7C-5B97EA089EB3</upgrade-code>
+      <build-id>9702</build-id>
       <item>
         <language>ast</language>
         <checksum type="sha256">415e2f3cf4d99a3f6ad495e33b89c716ef6966c9c4d7df90ad6e004fcf0222f4</checksum>
         <size>154219869</size>
+        <product-code>59496534-E113-4A02-AC23-DD77E6079398</product-code>
       </item>
       <item>
         <language>cs</language>
         <checksum type="sha256">a90a2c00c58f3e30654b89b4a363b099b0a7d1f829ebf9c7c18333d387f4bf41</checksum>
         <size>130773239</size>
+        <product-code>A81E275C-C1D1-473D-90D9-7EAE310550C7</product-code>
       </item>
       <item>
         <language>de</language>
         <checksum type="sha256">5569634ac40b42a710c666d0511f6aa4dbcb05d1a525e06f6be038e828c507f0</checksum>
         <size>162401424</size>
+        <product-code>B28DBCBA-60F8-40ED-B35B-F510C327946C</product-code>
       </item>
       <item>
         <language>el</language>
         <checksum type="sha256">cc5cde6314cea634200da6861da527c11ca7d74338074cdd4e5a22e1ffd9ef7c</checksum>
         <size>139633387</size>
+        <product-code>43A3EED6-E15A-4EBA-9FBA-E0A0D1793EDA</product-code>
       </item>
       <item>
         <language>en-GB</language>
         <checksum type="sha256">7f83397877b72e7f2da38f11b76c3f46e30b93713b40becba699d21f913847e0</checksum>
         <size>136201626</size>
-      </item>
-      <item>
-        <language>en-US</language>
-        <checksum type="sha256">8db246183f549c298d731cf61771f8f8adb37028d8ecc737fe1ad452899adb2d</checksum>
-        <size>143436858</size>
+        <product-code>EA1DC8F8-C357-44CA-A332-AB9762DF698C</product-code>
       </item>
       <item>
         <language>es</language>
         <checksum type="sha256">eb991c71b436c4d740f3d2b5441716fc321bb57eb7f36c7019ec86feebb7f5d4</checksum>
         <size>132424454</size>
+        <product-code>FBD275C7-DD8C-4056-BD4F-5ECC1A56DE6A</product-code>
       </item>
       <item>
         <language>fi</language>
         <checksum type="sha256">fa2b9015864651be44afc940988ed20c6093bc8707aea72ab1b75cf147d81ed2</checksum>
         <size>138474963</size>
+        <product-code>1D365623-A8E7-4ACA-8CBB-431A6CCA1F84</product-code>
       </item>
       <item>
         <language>fr</language>
         <checksum type="sha256">b4b20eac7f6220a4a6250e79d950a328d08b927b8856d762b0b1e033833e198f</checksum>
         <size>133397214</size>
+        <product-code>4F8C9861-DDCF-4EE8-978C-35B691C406B3</product-code>
       </item>
       <item>
         <language>gd</language>
         <checksum type="sha256">c2e5ac75f899efab61f039a193a68ec2f4d092fd43c6eb46d8b2d34d8166ce6e</checksum>
         <size>154472734</size>
+        <product-code>D67CE80F-3DD6-4433-ACE3-C591B8CA013B</product-code>
       </item>
       <item>
         <language>gl</language>
         <checksum type="sha256">bac4915fd47b9e219d72a02a268fa590096c0884ebafd966b10e9b77fe16eee0</checksum>
         <size>137952793</size>
+        <product-code>529BC9F1-8478-4F1E-9FCD-144ED91B529B</product-code>
       </item>
       <item>
         <language>hu</language>
         <checksum type="sha256">2e01d00431692cb6071cf520e71b900dba28a846dccf4ed3a85c245761c98906</checksum>
         <size>133240224</size>
+        <product-code>FAA13518-DA5A-429B-990F-896589C83900</product-code>
       </item>
       <item>
         <language>it</language>
         <checksum type="sha256">8663c01de8f62b26a7864ac4da6abd316a1c2e00b35c60372cad5287120fe409</checksum>
         <size>138823014</size>
+        <product-code>B73A5540-CC29-489E-B513-B58EEDEB3A69</product-code>
       </item>
       <item>
         <language>ja</language>
         <checksum type="sha256">559f165ee9ff516a80573befaf7d0936ca81fa89aca655213b4f265eb7c7059a</checksum>
         <size>136416679</size>
+        <product-code>B611F56C-12F5-4AC2-8395-F6E7E79A2FE7</product-code>
       </item>
       <item>
         <language>ko</language>
         <checksum type="sha256">daa7f3b51e6ffb0b541a98fbcdc0bad53eea579a7d74c9acbdd73ad660b117b1</checksum>
         <size>131617051</size>
+        <product-code>FAF9DE58-C459-44C8-A627-DCD83BC071BA</product-code>
       </item>
       <item>
         <language>nl</language>
         <checksum type="sha256">fcc6e55c7a1407be8e8f2faf7305ebb9a8f7e1bea882e5d917b5846a2c0ee955</checksum>
         <size>138526756</size>
+        <product-code>EB29A33D-3425-486D-B602-E464ADE4D71C</product-code>
       </item>
       <item>
         <language>pt</language>
         <checksum type="sha256">58307e0eeb485840e1462a23f8741e793422ccdc468123f85876ae287482a987</checksum>
         <size>130929247</size>
+        <product-code>497906E9-6B2F-45C4-BA2B-B662DCFDE940</product-code>
       </item>
       <item>
         <language>pt-BR</language>
         <checksum type="sha256">08ef83f28e138dc0bc6bdf761e4676eb13c2b30717ce47e0becec2de90c73f7a</checksum>
         <size>130814647</size>
+        <product-code>8DCC7121-A76E-4493-A90E-06E06485E4FF</product-code>
       </item>
       <item>
         <language>ru</language>
         <checksum type="sha256">dda689b273eb9ddadfb156a08df8e73c3b41e3cc8178a16546e282f8e1c54d77</checksum>
         <size>137558077</size>
+        <product-code>970C0E46-87BD-4210-9F24-CCD5A902F7D3</product-code>
       </item>
       <item>
         <language>sk</language>
         <checksum type="sha256">d2722d2569178838a57705e76062603f995f71d5219e8f910bc7a6b46fa6039a</checksum>
         <size>131199460</size>
+        <product-code>B645BA5F-A4CD-44B8-BE37-A0F6BDF4A6D3</product-code>
       </item>
       <item>
         <language>sl</language>
         <checksum type="sha256">eecea1c75833a873a3cc00f81c85f411319b085728224c716ac90159622c0c3e</checksum>
         <size>132340393</size>
+        <product-code>0489E54B-1826-45CB-8197-794634889904</product-code>
       </item>
       <item>
         <language>ta</language>
         <checksum type="sha256">4051b4831a960a8f27a3f56f32d7bd5a7bbdfcf859238a149aca9cd69ae0121c</checksum>
         <size>136961005</size>
+        <product-code>99F5B898-533E-4248-90D6-AA7202A3325C</product-code>
       </item>
       <item>
         <language>zh-CN</language>
         <checksum type="sha256">94424b9c24d3237e70d6452da8366cf9c6617a46fa171901db093b1a1166934d</checksum>
         <size>131840961</size>
+        <product-code>BFB77D24-4983-4FD8-99A8-164206DEFBEE</product-code>
       </item>
-    </download>
+    </downloads>
   </release>
   <release>
-    <version>4.0.1</version>
-    <download>
+    <version>
+      <major>4</major>
+      <minor>0</minor>
+      <micro>1</micro>
+    </version>
+    <downloads>
       <package-format>msi</package-format>
       <url-template>
         http://archive.apache.org/dist/openoffice/4.0.1/binaries/%L/Apache_OpenOffice_4.0.1_Win_x86_install_%L.exe
       </url-template>
+      <upgrade-code>7C35B9AB-2CE3-4C18-BE7C-5B97EA089EB3</upgrade-code>
+      <build-id>9714</build-id>
       <item>
-    <language>ast</language>
-    <checksum type="sha256">9854b6a99c6e56902e30ec01009e294aafe091e4733e9b8933690ee0365d6754</checksum>
-    <size>154320289</size>
+        <language>ast</language>
+        <checksum type="sha256">9854b6a99c6e56902e30ec01009e294aafe091e4733e9b8933690ee0365d6754</checksum>
+        <size>154320289</size>
+        <product-code>0D9885DA-8FEC-410B-A1A9-0282C58E04FC</product-code>
       </item>
       <item>
-    <language>cs</language>
-    <checksum type="sha256">b23c5dc07a6e521a7ad24e7c1d131c96ea3a6fdf3a2f96020b5cad2e7ebe0253</checksum>
-    <size>130785256</size>
+        <language>cs</language>
+        <checksum type="sha256">b23c5dc07a6e521a7ad24e7c1d131c96ea3a6fdf3a2f96020b5cad2e7ebe0253</checksum>
+        <size>130785256</size>
+        <product-code>220C463A-2890-4C7F-B97C-C49FE175B849</product-code>
       </item>
       <item>
-    <language>de</language>
-    <checksum type="sha256">fbbe39def767e6ecd16c7b6802f35d6e4b035c4b72328bbf0a8f045cf585aaa7</checksum>
-    <size>163606685</size>
+        <language>de</language>
+        <checksum type="sha256">fbbe39def767e6ecd16c7b6802f35d6e4b035c4b72328bbf0a8f045cf585aaa7</checksum>
+        <size>163606685</size>
+        <product-code>0AEC308E-7EB3-47F7-BB59-F2C9C6166B27</product-code>
       </item>
       <item>
-    <language>el</language>
-    <checksum type="sha256">e59f47b986c4bcd8f21062446b299f68ac7ed941356813b0e2dbd651fb59c847</checksum>
-    <size>139717373</size>
+        <language>el</language>
+        <checksum type="sha256">e59f47b986c4bcd8f21062446b299f68ac7ed941356813b0e2dbd651fb59c847</checksum>
+        <size>139717373</size>
+        <product-code>8022138A-6A6B-45CC-A2F2-81E7E21A4284</product-code>
       </item>
       <item>
-    <language>en-GB</language>
-    <checksum type="sha256">75f06dbe9f13804ea9f3ef20d831e300a55df1bf1a5b656d2422206a8a0d8bda</checksum>
-    <size>136295104</size>
+        <language>en-GB</language>
+        <checksum type="sha256">75f06dbe9f13804ea9f3ef20d831e300a55df1bf1a5b656d2422206a8a0d8bda</checksum>
+        <size>136295104</size>
+        <product-code>24B89186-2A56-4D28-B930-6F4FCF224E2F</product-code>
       </item>
       <item>
-    <language>en-US</language>
-    <checksum type="sha256">3b68145a33fa83d246febb3b7551fb0cbf57363bc772401ac0c37cfc1cde21b3</checksum>
-    <size>143485940</size>
+        <language>en-US</language>
+        <checksum type="sha256">3b68145a33fa83d246febb3b7551fb0cbf57363bc772401ac0c37cfc1cde21b3</checksum>
+        <size>143485940</size>
+        <product-code>47F460DA-D1BE-4D85-8DF2-AA1F31D3445F</product-code>
       </item>
       <item>
-    <language>es</language>
-    <checksum type="sha256">5136276a370378d11327b9a0bd074d269a49e797c7186f2e3cc9cf6c0bbc20fc</checksum>
-    <size>132478227</size>
+        <language>es</language>
+        <checksum type="sha256">5136276a370378d11327b9a0bd074d269a49e797c7186f2e3cc9cf6c0bbc20fc</checksum>
+        <size>132478227</size>
+        <product-code>52F63384-0FE8-41F5-B9C1-3331BE2E74F1</product-code>
       </item>
       <item>
-    <language>eu</language>
-    <checksum type="sha256">aae1ff61af9ac117637842ccc1c28221620c79b05ffbb1fb47d8a6fcbe3b1700</checksum>
-    <size>131476228</size>
+        <language>eu</language>
+        <checksum type="sha256">aae1ff61af9ac117637842ccc1c28221620c79b05ffbb1fb47d8a6fcbe3b1700</checksum>
+        <size>131476228</size>
+        <product-code>3EEA17A1-B7AC-40BC-BDA8-ED58A5695225</product-code>
       </item>
       <item>
-    <language>fi</language>
-    <checksum type="sha256">65ff3209a51afefde11dd1326921340ebe035f09626d8884f37a0f72f8c2785b</checksum>
-    <size>138523539</size>
+        <language>fi</language>
+        <checksum type="sha256">65ff3209a51afefde11dd1326921340ebe035f09626d8884f37a0f72f8c2785b</checksum>
+        <size>138523539</size>
+        <product-code>955C3F64-C693-41E6-B9D5-A505A5C41B52</product-code>
       </item>
       <item>
-    <language>fr</language>
-    <checksum type="sha256">70c97dc59412a8e4aeb87e51e8714cbba776397beb6a774804591653150e78d5</checksum>
-    <size>134622711</size>
+        <language>fr</language>
+        <checksum type="sha256">70c97dc59412a8e4aeb87e51e8714cbba776397beb6a774804591653150e78d5</checksum>
+        <size>134622711</size>
+        <product-code>8D5D54B8-3D29-4AB4-8DA8-1868DAF941D8</product-code>
       </item>
       <item>
-    <language>gd</language>
-    <checksum type="sha256">a57115a636f4004de2df9599e72bc4d07937b65a6bb99fe126dc18a788a57142</checksum>
-    <size>154574682</size>
+        <language>gd</language>
+        <checksum type="sha256">a57115a636f4004de2df9599e72bc4d07937b65a6bb99fe126dc18a788a57142</checksum>
+        <size>154574682</size>
+        <product-code>17010592-9593-4061-B5C8-21CED2273B3C</product-code>
       </item>
       <item>
-    <language>gl</language>
-    <checksum type="sha256">4c8fe9b42193dc37dc6a2b979b790ea6317ea25a6fa27fcb9007036c5725111d</checksum>
-    <size>138019151</size>
+        <language>gl</language>
+        <checksum type="sha256">4c8fe9b42193dc37dc6a2b979b790ea6317ea25a6fa27fcb9007036c5725111d</checksum>
+        <size>138019151</size>
+        <product-code>61A540F1-3DB4-4215-9259-21BD0D98746D</product-code>
       </item>
       <item>
-    <language>hu</language>
-    <checksum type="sha256">97765ef536ed1e3f07220fe4cb90520efdfb376fab751ad2cac7390316f14e65</checksum>
-    <size>133302800</size>
+        <language>hu</language>
+        <checksum type="sha256">97765ef536ed1e3f07220fe4cb90520efdfb376fab751ad2cac7390316f14e65</checksum>
+        <size>133302800</size>
+        <product-code>BE64FC93-72C0-4540-BC12-B8FEAE8CA33B</product-code>
       </item>
       <item>
-    <language>it</language>
-    <checksum type="sha256">c6fa3e0e9bef615d804b5d24d3f2cc49f7655aee01ef34cecb9b520a47249d02</checksum>
-    <size>138894766</size>
+        <language>it</language>
+        <checksum type="sha256">c6fa3e0e9bef615d804b5d24d3f2cc49f7655aee01ef34cecb9b520a47249d02</checksum>
+        <size>138894766</size>
+        <product-code>03F15CFC-BA7D-48B8-AA16-7F152BA27547</product-code>
       </item>
       <item>
-    <language>ja</language>
-    <checksum type="sha256">93d20ae5f96f4a93c705894849f01b6501604bf003e9b66f7d0dbdc6f2282965</checksum>
-    <size>136444096</size>
+        <language>ja</language>
+        <checksum type="sha256">93d20ae5f96f4a93c705894849f01b6501604bf003e9b66f7d0dbdc6f2282965</checksum>
+        <size>136444096</size>
+        <product-code>BA498C26-5210-4F14-BE9C-63041FCD306E</product-code>
       </item>
       <item>
-    <language>km</language>
-    <checksum type="sha256">4fc263c631d4ba797633d28ca529202b600d5fe676a8c215904d12d08ed8cc49</checksum>
-    <size>151866955</size>
+        <language>km</language>
+        <checksum type="sha256">4fc263c631d4ba797633d28ca529202b600d5fe676a8c215904d12d08ed8cc49</checksum>
+        <size>151866955</size>
+        <product-code>D10262B9-30E7-4EF8-8BF1-3C7BEDBEF4EE</product-code>
       </item>
       <item>
-    <language>ko</language>
-    <checksum type="sha256">2abd13afe2978c4300d872a06dcec00ae23592ed563be3d0d046ef81738d7a87</checksum>
-    <size>131671507</size>
+        <language>ko</language>
+        <checksum type="sha256">2abd13afe2978c4300d872a06dcec00ae23592ed563be3d0d046ef81738d7a87</checksum>
+        <size>131671507</size>
+        <product-code>697857AD-7EF1-4A6E-8F5B-D53409B97FB0</product-code>
       </item>
       <item>
-    <language>lt</language>
-    <checksum type="sha256">4ac6c2c88edb5254e0cf93f49daaf5e411ea0168d6c431d11da5d89638977cff</checksum>
-    <size>136935582</size>
+        <language>lt</language>
+        <checksum type="sha256">4ac6c2c88edb5254e0cf93f49daaf5e411ea0168d6c431d11da5d89638977cff</checksum>
+        <size>136935582</size>
+        <product-code>4DDF6CE7-9E5E-4192-9996-288AB7B23644</product-code>
       </item>
       <item>
-    <language>nl</language>
-    <checksum type="sha256">85292dad5aa80711c126091d3565dc9cd2219d818a59cbd5c7c9a4bc5282ebb8</checksum>
-    <size>139734741</size>
+        <language>nl</language>
+        <checksum type="sha256">85292dad5aa80711c126091d3565dc9cd2219d818a59cbd5c7c9a4bc5282ebb8</checksum>
+        <size>139734741</size>
+        <product-code>EA9BAE1A-2D68-4160-81E6-14B712435D66</product-code>
       </item>
       <item>
-    <language>pl</language>
-    <checksum type="sha256">c71c63d0c0e76f28b7d3bebb39e1cb9c0ca35e29557c53528f631e6c1aeca04c</checksum>
-    <size>133661993</size>
+        <language>pl</language>
+        <checksum type="sha256">c71c63d0c0e76f28b7d3bebb39e1cb9c0ca35e29557c53528f631e6c1aeca04c</checksum>
+        <size>133661993</size>
+        <product-code>DA0106A3-216E-48DE-9CF6-655DA8FC1D22</product-code>
       </item>
       <item>
-    <language>pt-BR</language>
-    <checksum type="sha256">e6baed3d30d4b18e32e21ab4fcd22446ac1f8a40efe49a4f105e9c3fc0ba1611</checksum>
-    <size>130866989</size>
+        <language>pt</language>
+        <checksum type="sha256">dd855dc99fc41fd509e938881397798fa2e9ed92e663cb8b9ea6c356d5d5a096</checksum>
+        <size>130989882</size>
+        <product-code>08D91641-36A2-4DA8-BA5B-6DFF68BBFE04</product-code>
       </item>
       <item>
-    <language>pt</language>
-    <checksum type="sha256">dd855dc99fc41fd509e938881397798fa2e9ed92e663cb8b9ea6c356d5d5a096</checksum>
-    <size>130989882</size>
+        <language>pt-BR</language>
+        <checksum type="sha256">e6baed3d30d4b18e32e21ab4fcd22446ac1f8a40efe49a4f105e9c3fc0ba1611</checksum>
+        <size>130866989</size>
+        <product-code>50FA6B86-D3C4-4961-A58F-1A061B2DCE04</product-code>
       </item>
       <item>
-    <language>ru</language>
-    <checksum type="sha256">7e965822f8dfb0aa4a67bf5bff8ddb852901c672886bb9f2ee275c5c976c0a48</checksum>
-    <size>137584051</size>
+        <language>ru</language>
+        <checksum type="sha256">7e965822f8dfb0aa4a67bf5bff8ddb852901c672886bb9f2ee275c5c976c0a48</checksum>
+        <size>137584051</size>
+        <product-code>85B491F5-56FA-483B-92EE-C6F90CCDBA74</product-code>
       </item>
       <item>
-    <language>sk</language>
-    <checksum type="sha256">4400eb30ca5072b175da7963049bb0ecca761af73b7dbccb9a0b4cd789b26042</checksum>
-    <size>131265676</size>
+        <language>sk</language>
+        <checksum type="sha256">4400eb30ca5072b175da7963049bb0ecca761af73b7dbccb9a0b4cd789b26042</checksum>
+        <size>131265676</size>
+        <product-code>A00F439C-600D-4220-96CF-C6F1F8C32633</product-code>
       </item>
       <item>
-    <language>sl</language>
-    <checksum type="sha256">83d384d9e50ddcb9c1d74069d8796e5f37bed55b5da6f43c326c0784a6e61cef</checksum>
-    <size>132368586</size>
+        <language>sl</language>
+        <checksum type="sha256">83d384d9e50ddcb9c1d74069d8796e5f37bed55b5da6f43c326c0784a6e61cef</checksum>
+        <size>132368586</size>
+        <product-code>0BFA9E1C-B3EB-4E6C-812F-CAA1D88F0F09</product-code>
       </item>
       <item>
-    <language>sr</language>
-    <checksum type="sha256">a2d2e043c1c3fa9a90924ae29138641b238d7749984f873a8672e2844a55e3d9</checksum>
-    <size>136961170</size>
+        <language>sr</language>
+        <checksum type="sha256">a2d2e043c1c3fa9a90924ae29138641b238d7749984f873a8672e2844a55e3d9</checksum>
+        <size>136961170</size>
+        <product-code>02E47753-869E-4757-92FA-478E8EF0FEA3</product-code>
       </item>
       <item>
-    <language>sv</language>
-    <checksum type="sha256">15cbdb4f5a7ecf253f31981d0203c08e279a26603ca737e64f740524e1d672ca</checksum>
-    <size>131330363</size>
+        <language>sv</language>
+        <checksum type="sha256">15cbdb4f5a7ecf253f31981d0203c08e279a26603ca737e64f740524e1d672ca</checksum>
+        <size>131330363</size>
+        <product-code>46BCB691-9148-4FCB-B215-CCDF70B5D95A</product-code>
       </item>
       <item>
-    <language>ta</language>
-    <checksum type="sha256">352083d6d2fc6c39027a03fbbb6449d4a67955b370c5a8efba0407b74a456bc9</checksum>
-    <size>137001517</size>
+        <language>ta</language>
+        <checksum type="sha256">352083d6d2fc6c39027a03fbbb6449d4a67955b370c5a8efba0407b74a456bc9</checksum>
+        <size>137001517</size>
+        <product-code>1297D7DE-BCDB-4B37-81AC-03C34DCF0A9B</product-code>
       </item>
       <item>
-    <language>tr</language>
-    <checksum type="sha256">bdfbf83cc905bf44f086ea51295899a042667fa6334e6378ef5b5b354843ba37</checksum>
-    <size>130397087</size>
+        <language>tr</language>
+        <checksum type="sha256">bdfbf83cc905bf44f086ea51295899a042667fa6334e6378ef5b5b354843ba37</checksum>
+        <size>130397087</size>
+        <product-code>7242DE31-0571-4C24-A142-E60AB70D6982</product-code>
       </item>
       <item>
-    <language>vi</language>
-    <checksum type="sha256">0483c20036f47738ae86a19d0ab4e66eff8d8f5226f716970a4ec7f56a78bff6</checksum>
-    <size>131526617</size>
+        <language>vi</language>
+        <checksum type="sha256">0483c20036f47738ae86a19d0ab4e66eff8d8f5226f716970a4ec7f56a78bff6</checksum>
+        <size>131526617</size>
+        <product-code>37188DD9-F074-40CC-BCE4-EB0E7EA664BD</product-code>
       </item>
       <item>
-    <language>zh-CN</language>
-    <checksum type="sha256">f2966f3c251cf31a24d7931950838c04e410935dd15a6fdd9241acf81bc5e784</checksum>
-    <size>131863915</size>
+        <language>zh-CN</language>
+        <checksum type="sha256">f2966f3c251cf31a24d7931950838c04e410935dd15a6fdd9241acf81bc5e784</checksum>
+        <size>131863915</size>
+        <product-code>2E6C4507-4909-44A6-AD5C-0FC1FDCDCFA1</product-code>
       </item>
       <item>
-    <language>zh-TW</language>
-    <checksum type="sha256">a194cfb2dc2cbcae2e89740485ebfcbf605b7c80a5bbdffb699e447c53698e53</checksum>
-    <size>131990994</size>
+        <language>zh-TW</language>
+        <checksum type="sha256">a194cfb2dc2cbcae2e89740485ebfcbf605b7c80a5bbdffb699e447c53698e53</checksum>
+        <size>131990994</size>
+        <product-code>7890D527-C555-46DA-B60F-173675BFCC19</product-code>
       </item>
-    </download>
+    </downloads>
   </release>
 </releases>
\ No newline at end of file
diff --git a/instsetoo_native/util/makefile.mk b/instsetoo_native/util/makefile.mk
index c608103..5e6cc6e 100644
--- a/instsetoo_native/util/makefile.mk
+++ b/instsetoo_native/util/makefile.mk
@@ -19,8 +19,6 @@
 #  
 #**************************************************************
 
-
-
 PRJ=..
 PRJNAME=instsetoo_native
 TARGET=util
@@ -74,6 +72,10 @@ help .PHONY :
     @echo "    sdkoo"
     @echo "    sdkoodev"
     @echo 
+    @echo "experimental targets:"
+    @echo "    patch_create           create a patch for updating an installed office (Windows only)"
+    @echo "    patch_apply            apply a previously created patch"
+    @echo 
     @echo "Most targets (all except aoo_srcrelease and updatepack) accept suffixes"
     @echo "    add _<language> to build a target for one language only"
     @echo "        the default set of languages is alllangiso=$(alllangiso)"
@@ -81,9 +83,9 @@ help .PHONY :
     @echo "        the default set of package formats is archive and PKGFORMAT=$(PKGFORMAT)"
 
 
-LOCALPYFILES= \
-    $(BIN)$/uno.py \
-    $(BIN)$/unohelper.py \
+LOCALPYFILES=			\
+    $(BIN)$/uno.py		\
+    $(BIN)$/unohelper.py	\
     $(BIN)$/pythonloader.py \
     $(BIN)$/pythonscript.py \
     $(BIN)$/officehelper.py \
@@ -118,7 +120,7 @@ PKGFORMAT+=$(MAKETARGETS:e:s/.//)
 # Independent of PKGFORMAT, always build a default-language openoffice product
 # also in archive format, so that tests that require an OOo installation (like
 # smoketestoo_native) have one available:
-openoffice_$(defaultlangiso) : $$@.archive
+#openoffice_$(defaultlangiso) : $$@.archive
 
 .IF "$(VERBOSE)"=="TRUE"
 VERBOSESWITCH=-verbose
@@ -126,6 +128,20 @@ VERBOSESWITCH=-verbose
 VERBOSESWITCH=-quiet
 .ENDIF
 
+.IF "$(release:U)"=="T"
+RELEASE_SWITCH=-release
+$(foreach,i,$(alllangiso) openoffice_$i.msi) : prepare_release_build
+.ELSE
+RELEASE_SWITCH=
+.ENDIF
+
+prepare_release_build .PHONY:
+    @$(PERL) -w $(SOLARENV)$/bin$/release_prepare.pl 	\
+        --lst-file $(PRJ)$/util$/openoffice.lst 	\
+        --product-name Apache_OpenOffice		\
+        --output-path $(OUT) 				\
+        $(alllangiso)
+
 .IF "$(VERBOSE_INSTALLER)"=="TRUE"
 VERBOSESWITCH+=-log
 .ENDIF
@@ -166,12 +182,20 @@ MSIOFFICETEMPLATEDIR=$(MISC)$/openoffice$/msi_templates
 MSILANGPACKTEMPLATEDIR=$(MISC)$/ooolangpack$/msi_templates
 MSISDKOOTEMPLATEDIR=$(MISC)$/sdkoo$/msi_templates
 
-ADDDEPS=$(NOLOGOSPLASH) $(DEVNOLOGOSPLASH)
+ADDDEPS=adddeps
+adddeps .PHONY : $(NOLOGOSPLASH) $(DEVNOLOGOSPLASH)
 
 .IF "$(OS)" == "WNT"
-ADDDEPS+=msitemplates
+adddeps : msitemplates
 .ENDIF
 
+.IF "$(LOCALPYFILES)"!=""
+local_python_files .PHONY : $(LOCALPYFILES)
+adddeps : local_python_files
+updatepack : local_python_files
+.ENDIF			# "$(LOCALPYFILES)"!=""
+
+
 $(foreach,i,$(alllangiso) openoffice_$i) : $(ADDDEPS)
 openoffice_$(defaultlangiso).archive : $(ADDDEPS)
 
@@ -187,23 +211,19 @@ $(foreach,i,$(alllangiso) sdkoo_$i) : $(ADDDEPS)
 
 $(foreach,i,$(alllangiso) sdkoodev_$i) : $(ADDDEPS)
 
-.IF "$(MAKETARGETS)"!=""
-$(MAKETARGETS) : $(ADDDEPS)
-.ENDIF			# "$(MAKETARGETS)"!=""
-
 $(foreach,i,$(alllangiso) openoffice_$i) : $$@{$(PKGFORMAT:^".")}
-.IF "$(MAKETARGETS)"!=""
-.IF "$(MAKETARGETS:e)"=="" && "$(MAKETARGETS:s/_//)"!="$(MAKETARGETS)"
-$(MAKETARGETS) : $$@{$(PKGFORMAT:^".")}
-$(MAKETARGETS){$(PKGFORMAT:^".")} : $(ADDDEPS)
-.ENDIF			# "$(MAKETARGETS:e)"=="" && "$(MAKETARGETS:s/_//)"!="$(MAKETARGETS)"
-.ENDIF			# "$(MAKETARGETS)"!=""
+$(foreach,i,$(alllangiso) openofficewithjre_$i) : $$@{$(PKGFORMAT:^".")}
+$(foreach,i,$(alllangiso) openofficedev_$i) : $$@{$(PKGFORMAT:^".")}
+$(foreach,i,$(alllangiso) ooolanguagepack_$i) : $$@{$(PKGFORMAT:^".")}
+$(foreach,i,$(alllangiso) ooodevlanguagepack_$i) : $$@{$(PKGFORMAT:^".")}
+$(foreach,i,$(alllangiso) sdkoo_$i) : $$@{$(PKGFORMAT:^".")}
+$(foreach,i,$(alllangiso) sdkoodev_$i) : $$@{$(PKGFORMAT:^".")}
 
 
 # This macro makes calling the make_installer.pl script a bit easier.
 # Just add -p and -msitemplate switches.
 MAKE_INSTALLER_COMMAND=					\
-    @$(PERL) -w $(SOLARENV)$/bin$/make_installer.pl 	\
+    @$(PERL) -w $(SOLARENV)$/bin$/make_installer.pl \
         -f $(PRJ)$/util$/openoffice.lst 	\
         -l $(subst,$(@:s/_/ /:1)_, $(@:b)) 	\
         -u $(OUT) 				\
@@ -215,7 +235,7 @@ MAKE_INSTALLER_COMMAND=					\
 # This macro makes calling gen_update_info.pl a bit easier
 # Just add --product switches, and xml input file and redirect output.
 GEN_UPDATE_INFO_COMMAND=					\
-    @$(PERL) -w $(SOLARENV)$/bin$/gen_update_info.pl		\
+    @$(PERL) -w $(SOLARENV)$/bin$/gen_update_info.pl	\
         --buildid $(BUILD)				\
         --arch "$(RTL_ARCH)"				\
         --os "$(RTL_OS)"				\
@@ -223,9 +243,10 @@ GEN_UPDATE_INFO_COMMAND=					\
         --languages $(subst,$(@:s/_/ /:1)_, $(@:b))
 
 openoffice_%{$(PKGFORMAT:^".")} :
-    $(MAKE_INSTALLER_COMMAND) 		\
-        -p Apache_OpenOffice		\
-        -msitemplate $(MSIOFFICETEMPLATEDIR)
+    $(MAKE_INSTALLER_COMMAND) 			\
+        -p Apache_OpenOffice			\
+        -msitemplate $(MSIOFFICETEMPLATEDIR)	\
+        $(RELEASE_SWITCH)
     $(GEN_UPDATE_INFO_COMMAND)		\
         --product Apache_OpenOffice	\
         $(PRJ)$/util$/update.xml	\
@@ -240,11 +261,9 @@ openoffice_%{.archive} :
         $(PRJ)$/util$/update.xml	\
         > $(MISC)/$(@:b)_$(RTL_OS)_$(RTL_ARCH)$(@:e).update.xml
 
-$(foreach,i,$(alllangiso) openofficewithjre_$i) : $$@{$(PKGFORMAT:^".")}
 openofficewithjre_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND) -p Apache_OpenOffice_wJRE -msitemplate $(MSIOFFICETEMPLATEDIR)
 
-$(foreach,i,$(alllangiso) openofficedev_$i) : $$@{$(PKGFORMAT:^".")}
 openofficedev_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND)		\
         -p Apache_OpenOffice_Dev	\
@@ -254,54 +273,57 @@ openofficedev_%{$(PKGFORMAT:^".")} :
         $(PRJ)$/util$/update.xml 		\
         > $(MISC)/$(@:b)_$(RTL_OS)_$(RTL_ARCH)$(@:e).update.xml
 
-$(foreach,i,$(alllangiso) ooolanguagepack_$i) : $$@{$(PKGFORMAT:^".")}
 ooolanguagepack_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND)			\
         -p Apache_OpenOffice			\
         -msitemplate $(MSILANGPACKTEMPLATEDIR)	\
         -languagepack
 
-$(foreach,i,$(alllangiso) ooodevlanguagepack_$i) : $$@{$(PKGFORMAT:^".")}
 ooodevlanguagepack_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND) -p Apache_OpenOffice_Dev -msitemplate $(MSILANGPACKTEMPLATEDIR) -languagepack
 
-$(foreach,i,$(alllangiso) sdkoo_$i) : $$@{$(PKGFORMAT:^".")}
 sdkoo_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND) -p Apache_OpenOffice_SDK -msitemplate $(MSISDKOOTEMPLATEDIR) -dontstrip
 
-$(foreach,i,$(alllangiso) sdkoodev_$i) : $$@{$(PKGFORMAT:^".")}
 sdkoodev_%{$(PKGFORMAT:^".")} :
     $(MAKE_INSTALLER_COMMAND) -p Apache_OpenOffice_Dev_SDK -msitemplate $(MSISDKOOTEMPLATEDIR) -dontstrip
 
-.IF "$(MAKETARGETS)"!=""
-.IF "$(MAKETARGETS:e)"=="" && "$(MAKETARGETS:s/_//)"!="$(MAKETARGETS)"
-$(MAKETARGETS) : $$@{$(PKGFORMAT:^".")}
-$(MAKETARGETS){$(PKGFORMAT:^".")} : $(ADDDEPS)
-.ENDIF			# "$(MAKETARGETS:e)"=="" && "$(MAKETARGETS:s/_//)"!="$(MAKETARGETS)"
-.ENDIF			# "$(MAKETARGETS)"!=""
-
 .ELSE			# "$(alllangiso)"!=""
 openoffice:
     @echo cannot pack nothing...
 
 .ENDIF			# "$(alllangiso)"!=""
 
-.IF "$(LOCALPYFILES)"!=""
-$(foreach,i,$(alllangiso) openoffice_$i{$(PKGFORMAT:^".") .archive} openofficewithjre_$i{$(PKGFORMAT:^".")} openofficedev_$i{$(PKGFORMAT:^".")} sdkoo_$i{$(PKGFORMAT:^".")}) updatepack : $(LOCALPYFILES)
-.ENDIF			# "$(LOCALPYFILES)"!=""
-
 $(BIN)$/%.py : $(SOLARSHAREDBIN)$/pyuno$/%.py
-    @$(COPY) $< $@
+    $(COPY) $< $@
 
-#$(BIN)$/intro.zip : $(SOLARCOMMONPCKDIR)$/openoffice_nologo$/intro.zip
 $(BIN)$/intro.zip : $(SOLARCOMMONPCKDIR)$/intro.zip
     $(COPY) $< $@
 
-#$(BIN)$/dev$/intro.zip : $(SOLARCOMMONPCKDIR)$/openoffice_dev_nologo$/intro.zip
 $(BIN)$/dev$/intro.zip : $(SOLARCOMMONPCKDIR)$/openoffice_dev$/intro.zip
     @-$(MKDIR) $(@:d)
     $(COPY) $< $@
 
+
+.IF "$(OS)" == "WNT"
+patch_create .PHONY : $(PRJ)$/data
+    perl -I $(SOLARENV)$/bin/modules $(SOLARENV)$/bin$/patch_create.pl	\
+        --product-name Apache_OpenOffice				\
+        --output-path $(OUT)						\
+        --data-path $(PRJ)$/data					\
+        --lst-file $(PRJ)$/util$/openoffice.lst
+patch_apply .PHONY :
+    perl -I $(SOLARENV)$/bin/modules $(SOLARENV)$/bin$/patch_apply.pl \
+        ../wntmsci12.pro/Apache_OpenOffice/msp/v-4-0-1_v-4-1-0/en-US/openoffice.msp
+
+$(PRJ)$/data :
+    mkdir $@
+.ELSE
+patch .PHONY :
+    @echo "patches can only be created on Windows at the moment"
+.ENDIF
+
+
 msitemplates .PHONY: msi_template_files msi_langpack_template_files msi_sdk_template_files
 
 MSI_OFFICE_TEMPLATE_FILES=		\
@@ -460,3 +482,8 @@ $(MSISDKOOTEMPLATEDIR) $(MSISDKOOTEMPLATEDIR)$/Binary :
     -$(MKDIRHIER) $@
 $(MSISDKOOTEMPLATEDIR)/% : $(MSISDKOOTEMPLATESOURCE)$/%
     $(GNUCOPY) $< $@
+
+
+# Local Variables:
+# tab-width: 8
+# End:
diff --git a/solenv/bin/make_installer.pl b/solenv/bin/make_installer.pl
index 871e806..8aa7449 100644
--- a/solenv/bin/make_installer.pl
+++ b/solenv/bin/make_installer.pl
@@ -86,9 +86,11 @@ use installer::windows::upgrade;
 use installer::worker;
 use installer::xpdinstaller;
 use installer::ziplist;
-
+use installer::patch::InstallationSet;
+use installer::patch::Msi;
 use strict;
 
+
 sub GetSetupScriptLines ($$$)
 {
     my ($allsettingsarrayref, $allvariableshashref, $includepatharrayref) = @_;
@@ -210,9 +212,6 @@ sub MakeWindowsBuild ($$$$$$$$$$$$$$$$$$$$)
     my $newidtdir = $idtdirbase . $installer::globals::separator . "00";    # new files into language independent directory "00"
     installer::systemactions::create_directory($newidtdir);
 
-    my @allfilecomponents = ();
-    my @allregistrycomponents = ();
-
     # Collecting all files with flag "BINARYTABLE"
     my $binarytablefiles = installer::worker::collect_all_items_with_special_flag($filesinproductlanguageresolvedarrayref ,"BINARYTABLE");
 
@@ -234,27 +233,81 @@ sub MakeWindowsBuild ($$$$$$$$$$$$$$$$$$$$)
     # Collection all available directory trees
     installer::windows::directory::collectdirectorytrees($directoriesforepmarrayref);
 
-    $filesinproductlanguageresolvedarrayref = installer::windows::file::create_files_table(
+    $filesinproductlanguageresolvedarrayref = installer::windows::file::filter_files(
+        $filesinproductlanguageresolvedarrayref,
+        $allvariableshashref);
+    installer::windows::file::prepare_file_table_creation(
+        $filesinproductlanguageresolvedarrayref,
+        $directoriesforepmarrayref,
+        $allvariableshashref);
+    my $file_table_data = installer::windows::file::create_file_table_data(
         $filesinproductlanguageresolvedarrayref,
-        \@allfilecomponents,
-        $newidtdir,
         $allvariableshashref);
+    installer::windows::file::create_file_table($file_table_data, $newidtdir);
+    installer::windows::file::create_filehash_table($filesinproductlanguageresolvedarrayref, $newidtdir);
+    my @allfilecomponents = installer::windows::file::collect_components($filesinproductlanguageresolvedarrayref);
+
 
+    installer::windows::directory::prepare_directory_table_creation(
+        $directoriesforepmarrayref,
+        $allvariableshashref);
     installer::windows::directory::create_directory_table(
         $directoriesforepmarrayref,
         $newidtdir,
-        $allvariableshashref,
-        $loggingdir);
+        $allvariableshashref);
 
     # Attention: The table "Registry.idt" contains language specific strings -> parameter: $languagesarrayref !
-    installer::windows::registry::create_registry_table($registryitemsinproductlanguageresolvedarrayref, \@allregistrycomponents, $newidtdir, $languagesarrayref, $allvariableshashref);
+    my $registry_table_data = installer::windows::registry::prepare_registry_table(
+        $registryitemsinproductlanguageresolvedarrayref,
+        $languagesarrayref,
+        $allvariableshashref);
+    my @allregistrycomponents = installer::windows::registry::collect_registry_components($registry_table_data);
+
+    my $target_registry_component_translation = installer::windows::component::prepare_component_table_creation(
+        \@allfilecomponents,
+        \@allregistrycomponents,
+        $allvariableshashref);
+
+    @allregistrycomponents = installer::windows::component::apply_component_translation(
+        $target_registry_component_translation,
+        @allregistrycomponents);
+    installer::windows::registry::translate_component_names(
+        $target_registry_component_translation,
+        $registryitemsinproductlanguageresolvedarrayref,
+        $registry_table_data);
+
+    installer::windows::registry::create_registry_table_32(
+        $newidtdir,
+        $languagesarrayref,
+        $allvariableshashref,
+        $registry_table_data);
+    installer::windows::registry::create_registry_table_64(
+        $newidtdir,
+        $languagesarrayref,
+        $allvariableshashref,
+        $registry_table_data);
 
-    installer::windows::component::create_component_table($filesinproductlanguageresolvedarrayref, $registryitemsinproductlanguageresolvedarrayref, $directoriesforepmarrayref, \@allfilecomponents, \@allregistrycomponents, $newidtdir, $allvariableshashref);
+    my $component_table_data = installer::windows::component::create_component_table_data (
+        $filesinproductlanguageresolvedarrayref,
+        $registryitemsinproductlanguageresolvedarrayref,
+        $directoriesforepmarrayref,
+        \@allfilecomponents,
+        \@allregistrycomponents,
+        $allvariableshashref);
+    installer::windows::component::create_component_table(
+        $component_table_data,
+        $newidtdir);
 
     # Attention: The table "Feature.idt" contains language specific strings -> parameter: $languagesarrayref !
     installer::windows::feature::add_uniquekey($modulesinproductlanguageresolvedarrayref);
-    $modulesinproductlanguageresolvedarrayref = installer::windows::feature::sort_feature($modulesinproductlanguageresolvedarrayref);
-    installer::windows::feature::create_feature_table($modulesinproductlanguageresolvedarrayref, $newidtdir, $languagesarrayref, $allvariableshashref);
+    $modulesinproductlanguageresolvedarrayref = installer::windows::feature::sort_feature(
+        $modulesinproductlanguageresolvedarrayref);
+
+    installer::windows::feature::create_feature_table(
+        $modulesinproductlanguageresolvedarrayref,
+        $newidtdir,
+        $languagesarrayref,
+        $allvariableshashref);
 
     installer::windows::featurecomponent::create_featurecomponent_table(
         $filesinproductlanguageresolvedarrayref,
@@ -482,7 +535,11 @@ sub MakeWindowsBuild ($$$$$$$$$$$$$$$$$$$$)
 
             $installer::logger::Info->print( "... creating msi database (language $onelanguage) ... \n" );
 
-            installer::windows::msiglobal::set_uuid_into_component_table($languageidtdir, $allvariableshashref);    # setting new GUID for the components using the tool uuidgen.exe
+            # setting new GUID for the components using the tool uuidgen.exe
+#            installer::windows::msiglobal::set_uuid_into_component_table(
+#                $languageidtdir,
+#                $allvariableshashref);
+
             installer::windows::msiglobal::prepare_64bit_database($languageidtdir, $allvariableshashref);   # making last 64 bit changes
             installer::windows::msiglobal::create_msi_database($languageidtdir ,$msifilename);
 
@@ -1302,6 +1359,38 @@ foreach my $key (sort keys %$allvariableshashref)
 }
 
 
+# When we are building a release (-release option was given on the command line)
+# then we need additional information.
+if ($installer::globals::is_release)
+{
+    $installer::logger::Info->print("...  building a release, checking required values  ... \n");
+    $installer::globals::target_version = $allvariableshashref->{'PRODUCTVERSION'};
+    $installer::globals::source_version = $allvariableshashref->{'PREVIOUS_VERSION'};
+    if ( ! defined $installer::globals::source_version)
+    {
+        $installer::globals::source_version = installer::patch::ReleasesList::GetPreviousVersion(
+            $installer::globals::target_version);
+    }
+    if ( ! defined $installer::globals::source_version)
+    {
+        installer::exiter::exit_program(
+            "can not detect the previous version number.  Please add a 'PREVIOUS_VERSION' variable to openoffice.lst",
+            "make_installer.pl");
+    }
+
+    # Determine if we are building a new major release, ie if target_version is ?.0.0
+    $installer::globals::is_major_release
+        = installer::patch::Version::IsMajorVersion($installer::globals::target_version);
+
+    $installer::logger::Info->printf("    building version %s\n", $installer::globals::target_version);
+    $installer::logger::Info->printf("        which is %sa major version\n",
+        $installer::globals::is_major_release
+            ? ""
+            : "not ");
+    $installer::logger::Info->printf("    previous version is %s\n", $installer::globals::source_version);
+}
+
+
 ########################################################
 # Check if this is simple packaging mechanism
 ########################################################
@@ -1597,6 +1686,22 @@ for (;1;last)
         else { $installer::globals::makedownload = 0; }
     }
 
+    # Set up an MSI object for the source version.
+    if ($installer::globals::is_release
+        && $installer::globals::iswindowsbuild)
+    {
+        $installer::logger::Info->printf("preparing MSI object for source version %s\n",
+            $installer::globals::source_version);
+        my $source_version_string = join(
+            "",
+            installer::patch::Version::StringToNumberArray($installer::globals::source_version));
+        $installer::globals::source_msi = installer::patch::Msi->FindAndCreate(
+            $installer::globals::source_version,
+            0,
+            $$languagestringref,
+            $installer::globals::product);
+    }
+
     ############################################################
     # Beginning of language specific logging mechanism
     # Until now only global logging into default: logfile.txt
diff --git a/solenv/bin/modules/installer/globals.pm b/solenv/bin/modules/installer/globals.pm
index 6d43e32..603991a 100644
--- a/solenv/bin/modules/installer/globals.pm
+++ b/solenv/bin/modules/installer/globals.pm
@@ -155,8 +155,8 @@ BEGIN
     $fontsfolder = "FontsFolder";
     $fontsfoldername = "Fonts";
     $fontsdirparent = "";
-    $fontsdirname = "";
     $fontsdirhostname = "truetype";
+    $fontsdirname = $fontsdirhostname;
     $officefolder = "OfficeFolder";
     $officemenufolder = "OfficeMenuFolder";
     $startupfolder = "StartupFolder";
@@ -535,6 +535,13 @@ BEGIN
 
     # ToDo: Needs to be expanded for additional platforms
 
+    $is_release = 0;  # Is changed in parameter.pm when the -release option is given.
+    $source_version = undef;
+    $target_version = undef;
+    $source_msi = undef;
+
+    # Is set to 1 when target_version is a major version, ie ?.0.0
+    $is_major_release = 0;
 }
 
 1;
diff --git a/solenv/bin/modules/installer/logger.pm b/solenv/bin/modules/installer/logger.pm
index 3cac508..652bc17 100644
--- a/solenv/bin/modules/installer/logger.pm
+++ b/solenv/bin/modules/installer/logger.pm
@@ -93,10 +93,11 @@ our $Info = installer::logger->new("info",
 
 =head2 SetupSimpleLogging ($filename)
 
-    Setup logging so that $Global, $Lang and $Info all print to the console AND to the log file.
+    Setup logging so that $Global, $Lang and $Info all print to the console.
+    If $filename is given then logging also goes to that file.
 
 =cut
-sub SetupSimpleLogging ($)
+sub SetupSimpleLogging (;$)
 {
     my ($log_filename) = @_;
 
@@ -114,7 +115,10 @@ sub SetupSimpleLogging ($)
         'is_show_relative_time' => 1,
         'forward' => [$Info]
         );
-    $Info->set_filename($log_filename);
+    if (defined $log_filename)
+    {
+        $Info->set_filename($log_filename);
+    }
     $Info->{'is_print_to_console'} = 1;
     $installer::globals::quiet = 0;
     starttime();
diff --git a/solenv/bin/modules/installer/parameter.pm b/solenv/bin/modules/installer/parameter.pm
index 1cb5ba9..478362e 100644
--- a/solenv/bin/modules/installer/parameter.pm
+++ b/solenv/bin/modules/installer/parameter.pm
@@ -164,6 +164,10 @@ sub getparameter
             $path =~ s/^\Q$installer::globals::destdir\E//;
             $installer::globals::rootpath = $path;
         }
+        elsif ($param eq "-release")
+        {
+            $installer::globals::is_release = 1;
+        }
         else
         {
             installer::logger::print_error( "unknown parameter: $param" );
diff --git a/solenv/bin/modules/installer/patch/FileSequenceList.pm b/solenv/bin/modules/installer/patch/FileSequenceList.pm
index 6c607d8..80d0583 100644
--- a/solenv/bin/modules/installer/patch/FileSequenceList.pm
+++ b/solenv/bin/modules/installer/patch/FileSequenceList.pm
@@ -21,7 +21,6 @@
 
 package installer::patch::FileSequenceList;
 
-use XML::LibXML;
 use strict;
 
 =head1 NAME
@@ -50,22 +49,53 @@ sub new ($)
 
 
 
-sub SetFromFileList ($$)
+sub SetFromMap ($$)
 {
-    my ($self, $files) = @_;
+    my ($self, $map) = @_;
 
-    my %data = map {$_->{'uniquename'} => $_->{'sequencenumber'}} @$files;
-    $self->{'data'} = \%data;
+    $self->{'data'} = $map;
 }
 
 
 
 
-sub SetFromMap ($$)
+sub SetFromMsi ($$)
 {
-    my ($self, $map) = @_;
+    my ($self, $msi) = @_;
 
-    $self->{'data'} = $map;
+    my $file_table = $msi->GetTable("File");
+    my $file_map = $msi->GetFileMap();
+
+    my $file_column_index = $file_table->GetColumnIndex("File");
+    my $filename_column_index = $file_table->GetColumnIndex("FileName");
+    my $sequence_column_index = $file_table->GetColumnIndex("Sequence");
+
+    my %sequence_data = ();
+
+    printf("extracting columns %d and %d from %d rows\n",
+        $file_column_index,
+        $sequence_column_index,
+        $file_table->GetRowCount());
+
+    foreach my $row (@{$file_table->GetAllRows()})
+    {
+        my $unique_name = $row->GetValue($file_column_index);
+        my $filename = $row->GetValue($filename_column_index);
+        my ($long_filename,$short_filename) = installer::patch::Msi::SplitLongShortName($filename);
+        my $sequence = $row->GetValue($sequence_column_index);
+        my $directory_item = $file_map->{$unique_name}->{'directory'};
+        my $source_path = $directory_item->{'full_source_long_name'};
+        my $target_path = $directory_item->{'full_target_long_name'};
+        my $key = $source_path ne ""
+            ? $source_path."/".$long_filename
+            : $long_filename;
+        $sequence_data{$key} = {
+            'sequence' => $sequence,
+            'uniquename' => $unique_name,
+            'row' => $row
+        };
+    }
+    $self->{'data'} = \%sequence_data;
 }
 
 
@@ -81,78 +111,45 @@ sub GetFileCount ($)
 
 
 
-=head2 GetSequenceNumbers ($files)
-
-    $files is a hash that maps unique file names (File->File) to sequence
-    numbers (File->Sequence). The later is (expected to be) initially unset and
-    is set in this method.
-
-    For new files -- entries in the given $files that do not exist in the 'data'
-    member -- no sequence numbers are defined.
-
-    When there are removed files -- entries in the 'data' member that do not
-    exist in the given $files -- then a list of these files is returned.  In
-    that case the given $files remain unmodified.
-
-    The returned list is empty when everyting is OK.
-
-=cut
-sub GetSequenceNumbers ($$)
+sub get_removed_files ($@)
 {
-    my ($self, $files) = @_;
+    my ($self, $target_unique_names) = @_;
+
+    my %uniquename_to_row_map = map{$_->{'uniquename'} => $_->{'row'}} values %{$self->{'data'}};
 
     # Check if files have been removed.
     my @missing = ();
-    foreach my $name (keys %{$self->{'data'}})
+    foreach my $item (values %{$self->{'data'}})
     {
-        if ( ! defined $files->{$name})
+        my ($uniquename, $row) = ($item->{'uniquename'}, $item->{'row'});
+        if ( ! defined $target_unique_names->{$uniquename})
         {
-            push @missing, $name;
+            # $name is defined in source but not in target => it has been removed.
+            push @missing, $row;
         }
     }
-    if (scalar @missing > 0)
-    {
-        # Yes.  Return the names of the removed files.
-        return @missing;
-    }
-
-    # No files where removed.  Set the sequence numbers.
-    foreach my $name (keys %$files)
-    {
-        $files->{$name} = $self->{'data'}->{$name};
-    }
-    return ();
+    return @missing;
 }
 
 
 
 
-sub GetDifference ($$)
+sub get_sequence_and_unique_name($$)
 {
-    my ($self, $other) = @_;
+    my ($self, $source_path) = @_;
 
-    # Create maps for easy reference.
-    my (@files_in_both, @files_in_self, @files_in_other);
-    foreach my $name (keys %{$self->{'data'}})
+    my $sequence_and_unique_name = $self->{'data'}->{$source_path};
+    if ( ! defined $sequence_and_unique_name)
     {
-        if (defined $other->{'data'}->{$name})
-        {
-            push @files_in_both, $name;
-        }
-        else
-        {
-            push @files_in_self, $name;
-        }
+        $installer::logger::Lang->printf("can not find entry for source path '%s'\n", $source_path);
+        return (undef,undef);
     }
-    foreach my $name (keys %{$self->{'data'}})
+    else
     {
-        if ( ! defined $self->{'data'}->{$name})
-        {
-            push @files_in_other, $name;
-        }
+        return (
+            $sequence_and_unique_name->{'sequence'},
+            $sequence_and_unique_name->{'uniquename'});
     }
-
-    return (\@files_in_both, \@files_in_self, \@files_in_other);
 }
 
 
diff --git a/solenv/bin/modules/installer/patch/InstallationSet.pm b/solenv/bin/modules/installer/patch/InstallationSet.pm
index 67ff1fe..7d6647d 100644
--- a/solenv/bin/modules/installer/patch/InstallationSet.pm
+++ b/solenv/bin/modules/installer/patch/InstallationSet.pm
@@ -25,9 +25,25 @@ use installer::patch::Tools;
 use installer::patch::Version;
 use installer::logger;
 
+use strict;
 
+# TODO: Detect the location of 7z.exe
 my $Unpacker = "/c/Program\\ Files/7-Zip/7z.exe";
 
+
+
+# TODO: Is there a touch in a standard library?
+sub touch ($)
+{
+    my ($filename) = @_;
+
+    open my $out, ">", $filename;
+    close $out;
+}
+
+
+
+
 =head1 NAME
 
     package installer::patch::InstallationSet  -  Functions for handling installation sets
@@ -48,29 +64,25 @@ sub UnpackExe ($$)
 
     # Unpack to a temporary path and change its name to the destination path
     # only when the unpacking has completed successfully.
-    my $temporary_destination_path = $destination_path . ".tmp";
-    File::Path::make_path($temporary_destination_path);
+    File::Path::make_path($destination_path);
 
-    my $windows_filename = installer::patch::Tools::CygpathToWindows($filename);
-    my $windows_destination_path = installer::patch::Tools::CygpathToWindows($temporary_destination_path);
+    my $windows_filename = installer::patch::Tools::ToEscapedWindowsPath($filename);
+    my $windows_destination_path = installer::patch::Tools::ToEscapedWindowsPath($destination_path);
     my $command = join(" ",
         $Unpacker,
-        "x", "-o".$windows_destination_path,
+        "x",
+        "-y",
+        "-o".$windows_destination_path,
         $windows_filename);
     my $result = qx($command);
 
     # Check the existence of the .cab files.
-    my $cab_filename = File::Spec->catfile($temporary_destination_path, "openoffice1.cab");
+    my $cab_filename = File::Spec->catfile($destination_path, "openoffice1.cab");
     if ( ! -f $cab_filename)
     {
         installer::logger::PrintError("cab file '%s' was not extracted from installation set\n", $cab_filename);
         return 0;
     }
-    if (rename($temporary_destination_path, $destination_path) == 0)
-    {
-        installer::logger::PrintError("can not rename temporary extraction directory\n");
-        return 0;
-    }
     return 1;
 }
 
@@ -99,7 +111,7 @@ sub UnpackCab ($$$)
     # Extract the directory structure from the 'File' and 'Directory' tables in the given msi.
     $installer::logger::Info->printf("setting up directory tree\n");
     my $file_table = $msi->GetTable("File");
-    my $file_to_directory_map = $msi->GetFileToDirectoryMap();
+    my $file_map = $msi->GetFileMap();
 
     # Step 2
     # Unpack the .cab file to a temporary path.
@@ -122,19 +134,17 @@ sub UnpackCab ($$$)
     foreach my $file_row (@{$file_table->GetAllRows()})
     {
         my $unique_name = $file_row->GetValue('File');
-        my $directory_full_names = $file_to_directory_map->{$unique_name};
-        my ($source_full_name, $target_full_name) = @$directory_full_names;
+        my $directory_item = $file_map->{$unique_name}->{'directory'};
+        my $source_full_name = $directory_item->{'full_source_long_name'};
 
         my $flat_filename = File::Spec->catfile($temporary_destination_path, $unique_name);
         my $dir_path = File::Spec->catfile($destination_path, $source_full_name);
         my $dir_filename = File::Spec->catfile($dir_path, $unique_name);
 
-        printf("%d: making path %s and copying %s to %s\n",
-            $count,
-            $dir_path,
-            $unique_name,
-            $dir_filename);
-        File::Path::make_path($dir_path);
+        if ( ! -d $dir_path)
+        {
+            File::Path::make_path($dir_path);
+        }
         File::Copy::move($flat_filename, $dir_filename);
 
         ++$count;
@@ -166,16 +176,14 @@ sub UnpackCabFlat ($$$)
     # when another step fails.
 
     $installer::logger::Info->printf("unpacking cab file\n");
-    my $temporary_destination_path = $destination_path . ".tmp";
-    File::Path::make_path($temporary_destination_path);
-    my $windows_cab_filename = installer::patch::Tools::CygpathToWindows($cab_filename);
-    my $windows_destination_path = installer::patch::Tools::CygpathToWindows($temporary_destination_path);
+    File::Path::make_path($destination_path);
+    my $windows_cab_filename = installer::patch::Tools::ToEscapedWindowsPath($cab_filename);
+    my $windows_destination_path = installer::patch::Tools::ToEscapedWindowsPath($destination_path);
     my $command = join(" ",
         $Unpacker,
         "x", "-o".$windows_destination_path,
         $windows_cab_filename,
         "-y");
-    printf("running command '%s'\n", $command);
     open my $cmd, $command."|";
     my $extraction_count = 0;
     my $file_count = $file_table->GetRowCount();
@@ -190,61 +198,52 @@ sub UnpackCabFlat ($$$)
             $extraction_count*100/$file_count);
     }
     close $cmd;
-    printf("extraction done                               \n");
-
-    rename($temporary_destination_path, $destination_path)
-        || installer::logger::PrintError(
-            "can not rename the temporary directory '%s' to '%s'\n",
-            $temporary_destination_path,
-            $destination_path);
 }
 
 
 
 
-=head GetUnpackedMsiPath ($version, $language, $package_format, $product)
+=head GetUnpackedExePath ($version, $is_current_version, $language, $package_format, $product)
 
     Convenience function that returns where a downloadable installation set is extracted to.
 
 =cut
-sub GetUnpackedMsiPath ($$$$)
+sub GetUnpackedExePath ($$$$$)
 {
-    my ($version, $language, $package_format, $product) = @_;
+    my ($version, $is_current_version, $language, $package_format, $product) = @_;
 
-    return File::Spec->catfile(
-        GetUnpackedPath($version, $language, $package_format, $product),
-        "unpacked_msi");
+    my $path = GetUnpackedPath($version, $is_current_version, $language, $package_format, $product);
+    return File::Spec->catfile($path, "unpacked");
 }
 
 
 
 
-=head GetUnpackedCabPath ($version, $language, $package_format, $product)
+=head GetUnpackedCabPath ($version, $is_current_version, $language, $package_format, $product)
 
     Convenience function that returns where a cab file is extracted
     (with injected directory structure from the msi file) to.
 
 =cut
-sub GetUnpackedCabPath ($$$$)
+sub GetUnpackedCabPath ($$$$$)
 {
-    my ($version, $language, $package_format, $product) = @_;
+    my ($version, $is_current_version, $language, $package_format, $product) = @_;
 
-    return File::Spec->catfile(
-        GetUnpackedPath($version, $language, $package_format, $product),
-        "unpacked_cab");
+    my $path = GetUnpackedPath($version, $is_current_version, $language, $package_format, $product);
+    return File::Spec->catfile($path, "unpacked");
 }
 
 
 
 
-=head2 GetUnpackedPath($version, $language, $package_format, $product)
+=head2 GetUnpackedPath($version, $is_current_version, $language, $package_format, $product)
 
     Internal function for creating paths to where archives are unpacked.
 
 =cut
-sub GetUnpackedPath ($$$$)
+sub GetUnpackedPath ($$$$$)
 {
-    my ($version, $language, $package_format, $product) = @_;
+    my ($version, $is_current_version, $language, $package_format, $product) = @_;
 
     return File::Spec->catfile(
         $ENV{'SRC_ROOT'},
@@ -252,13 +251,41 @@ sub GetUnpackedPath ($$$$)
         $ENV{'INPATH'},
         $product,
         $package_format,
-        installer::patch::Version::ArrayToDirectoryName(installer::patch::Version::StringToNumberArray($version)),
+        installer::patch::Version::ArrayToDirectoryName(
+            installer::patch::Version::StringToNumberArray($version)),
         $language);
 }
 
 
 
 
+sub GetMsiFilename ($$)
+{
+    my ($path, $version) = @_;
+
+    my $no_dot_version = installer::patch::Version::ArrayToNoDotName(
+        installer::patch::Version::StringToNumberArray(
+            $version));
+    return File::Spec->catfile(
+        $path,
+        "openoffice" . $no_dot_version . ".msi");
+}
+
+
+
+
+sub GetCabFilename ($$)
+{
+    my ($path, $version) = @_;
+
+    return File::Spec->catfile(
+        $path,
+        "openoffice1.cab");
+}
+
+
+
+
 =head2 Download($language, $release_data, $filename)
 
     Download an installation set to $filename.  The URL for the
@@ -464,4 +491,282 @@ sub ProvideDownloadSet ($$$)
     return $ext_sources_filename;
 }
 
+
+
+
+sub ProvideUnpackedExe ($$$$$)
+{
+    my ($version, $is_current_version, $language, $package_format, $product_name) = @_;
+
+    # Check if the exe has already been unpacked.
+    my $unpacked_exe_path = installer::patch::InstallationSet::GetUnpackedExePath(
+        $version,
+        $is_current_version,
+        $language,
+        $package_format,
+        $product_name);
+    my $unpacked_exe_flag_filename = File::Spec->catfile($unpacked_exe_path, "__exe_is_unpacked");
+    my $exe_is_unpacked = -f $unpacked_exe_flag_filename;
+
+    if ($exe_is_unpacked)
+    {
+        # Yes, exe has already been unpacked.  There is nothing more to do.
+        $installer::logger::Info->printf("downloadable installation set has already been unpacked to\n");
+        $installer::logger::Info->printf("    %s\n", $unpacked_exe_path);
+        return 1;
+    }
+    elsif ($is_current_version)
+    {
+        # For the current version the exe is created from the unpacked
+        # content and both are expected to be already present.
+
+        # In order to have the .cab and its unpacked content in one
+        # directory and don't interfere with the creation of regular
+        # installation sets, we copy the unpacked .exe into a separate
+        # directory.
+
+        my $original_path = File::Spec->catfile(
+            $ENV{'SRC_ROOT'},
+            "instsetoo_native",
+            $ENV{'INPATH'},
+            $product_name,
+            $package_format,
+            "install",
+            $language);
+        $installer::logger::Info->printf("creating a copy\n");
+        $installer::logger::Info->printf("    of %s\n", $original_path);
+        $installer::logger::Info->printf("    at %s\n", $unpacked_exe_path);
+        File::Path::make_path($unpacked_exe_path) unless -d $unpacked_exe_path;
+    my ($file_count,$directory_count) = CopyRecursive($original_path, $unpacked_exe_path);
+    return 0 if ( ! defined $file_count);
+        $installer::logger::Info->printf("    copied %d files in %d directories\n",
+        $file_count,
+        $directory_count);
+
+        touch($unpacked_exe_flag_filename);
+
+        return 1;
+    }
+    else
+    {
+        # No, we have to unpack the exe.
+
+        # Provide the exe.
+        my $filename = installer::patch::InstallationSet::ProvideDownloadSet(
+            $version,
+            $language,
+            $package_format);
+
+        # Unpack it.
+        if (defined $filename)
+        {
+            if (installer::patch::InstallationSet::UnpackExe($filename, $unpacked_exe_path))
+            {
+                $installer::logger::Info->printf("downloadable installation set has been unpacked to\n");
+                $installer::logger::Info->printf("    %s\n", $unpacked_exe_path);
+
+                touch($unpacked_exe_flag_filename);
+
+                return 1;
+            }
+        }
+        else
+        {
+            installer::logger::PrintError("could not provide .exe installation set at '%s'\n", $filename);
+        }
+    }
+
+    return 0;
+}
+
+
+
+
+sub CopyRecursive ($$)
+{
+    my ($source_path, $destination_path) = @_;
+
+    return (undef,undef) unless -d $source_path;
+
+    my @todo = ([$source_path, $destination_path]);
+    my $file_count = 0;
+    my $directory_count = 0;
+    while (scalar @todo > 0)
+    {
+    my ($source,$destination) = @{shift @todo};
+
+    next if ! -d $source;
+    File::Path::make_path($destination);
+    ++$directory_count;
+
+    # Read list of files in the current source directory.
+    opendir( my $dir, $source);
+    my @files = readdir $dir;
+    closedir $dir;
+
+    # Copy all files and push all directories to @todo.
+    foreach my $file (@files)
+    {
+        next if $file =~ /^\.+$/;
+
+        my $source_file = File::Spec->catfile($source, $file);
+        my $destination_file = File::Spec->catfile($destination, $file);
+        if ( -f $source_file)
+        {
+        File::Copy::copy($source_file, $destination_file);
+        ++$file_count;
+        }
+        elsif ( -d $source_file)
+        {
+        push @todo, [$source_file, $destination_file];
+        }
+    }
+    }
+
+    return ($file_count, $directory_count);
+}
+
+
+
+
+sub CheckLocalCopy ($$$$)
+{
+    my ($version, $language, $package_format, $product_name) = @_;
+
+    # Compare creation times of the original .msi and its copy.
+
+    my $original_path = File::Spec->catfile(
+        $ENV{'SRC_ROOT'},
+        "instsetoo_native",
+        $ENV{'INPATH'},
+        $product_name,
+        $package_format,
+        "install",
+        $language);
+
+    my $copy_path = installer::patch::InstallationSet::GetUnpackedExePath(
+        $version,
+        1,
+        $language,
+        $package_format,
+        $product_name);
+
+    my $msi_basename = "openoffice"
+        . installer::patch::Version::ArrayToNoDotName(
+            installer::patch::Version::StringToNumberArray($version))
+        . ".msi";
+
+    my $original_msi_filename = File::Spec->catfile($original_path, $msi_basename);
+    my $copied_msi_filename = File::Spec->catfile($copy_path, $msi_basename);
+
+    my @original_msi_stats = stat($original_msi_filename);
+    my @copied_msi_stats = stat($copied_msi_filename);
+    my $original_msi_mtime = $original_msi_stats[9];
+    my $copied_msi_mtime = $copied_msi_stats[9];
+
+    if (defined $original_msi_mtime
+        && defined $copied_msi_mtime
+        && $original_msi_mtime > $copied_msi_mtime)
+    {
+        # The installation set is newer than its copy.
+        # Remove the copy.
+        $installer::logger::Info->printf(
+            "removing copy of installation set (version %s) because it is out of date\n",
+            $version);
+        File::Path::remove_tree($copy_path);
+    }
+}
+
+
+
+
+=head2 ProvideUnpackedCab
+
+    1a. Make sure that a downloadable installation set is present.
+    1b. or that a freshly built installation set (packed and unpacked is present)
+    2. Unpack the downloadable installation set
+    3. Unpack the .cab file.
+
+    The 'Provide' in the function name means that any step that has
+    already been made is not made again.
+
+=cut
+sub ProvideUnpackedCab ($$$$$)
+{
+    my ($version, $is_current_version, $language, $package_format, $product_name) = @_;
+
+    if ($is_current_version)
+    {
+        # For creating patches we maintain a copy of the unpacked .exe.  Make sure that that is updated when
+        # a new installation set has been built.
+        CheckLocalCopy($version, $language, $package_format, $product_name);
+    }
+
+    # Check if the cab file has already been unpacked.
+    my $unpacked_cab_path = installer::patch::InstallationSet::GetUnpackedCabPath(
+        $version,
+        $is_current_version,
+        $language,
+        $package_format,
+        $product_name);
+    my $unpacked_cab_flag_filename = File::Spec->catfile($unpacked_cab_path, "__cab_is_unpacked");
+    my $cab_is_unpacked = -f $unpacked_cab_flag_filename;
+
+    if ($cab_is_unpacked)
+    {
+        # Yes. Cab was already unpacked. There is nothing more to do.
+        $installer::logger::Info->printf("cab has already been unpacked to\n");
+        $installer::logger::Info->printf("    %s\n", $unpacked_cab_path);
+
+        return 1;
+    }
+    else
+    {
+        # Make sure that the exe is unpacked and the cab file exists.
+        ProvideUnpackedExe($version, $is_current_version, $language, $package_format, $product_name);
+
+        # Unpack the cab file.
+        my $unpacked_exe_path = installer::patch::InstallationSet::GetUnpackedExePath(
+                $version,
+                $is_current_version,
+                $language,
+                $package_format,
+                $product_name);
+        my $msi = new installer::patch::Msi(
+                installer::patch::InstallationSet::GetMsiFilename($unpacked_exe_path, $version),
+                $version,
+                $is_current_version,
+                $language,
+                $product_name);
+
+        my $cab_filename = installer::patch::InstallationSet::GetCabFilename(
+            $unpacked_exe_path,
+            $version);
+        if ( ! -f $cab_filename)
+        {
+             # Cab file does not exist.
+            installer::logger::PrintError(
+                "could not find .cab file at '%s'.  Extraction of .exe seems to have failed.\n",
+                $cab_filename);
+            return 0;
+        }
+
+        if (installer::patch::InstallationSet::UnpackCab(
+            $cab_filename,
+            $msi,
+            $unpacked_cab_path))
+        {
+            $installer::logger::Info->printf("unpacked cab file '%s'\n", $cab_filename);
+            $installer::logger::Info->printf("    to '%s'\n", $unpacked_cab_path);
+
+            touch($unpacked_cab_flag_filename);
+
+            return 1;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+}
 1;
diff --git a/solenv/bin/modules/installer/patch/Msi.pm b/solenv/bin/modules/installer/patch/Msi.pm
index c5c650a..5cefda8 100644
--- a/solenv/bin/modules/installer/patch/Msi.pm
+++ b/solenv/bin/modules/installer/patch/Msi.pm
@@ -23,6 +23,11 @@ package installer::patch::Msi;
 
 use installer::patch::MsiTable;
 use installer::patch::Tools;
+use installer::patch::InstallationSet;
+
+use File::Basename;
+use File::Copy;
+
 use strict;
 
 
@@ -32,9 +37,37 @@ use strict;
 
 =cut
 
+sub FindAndCreate($$$$$)
+{
+    my ($class, $version, $is_current_version, $language, $product_name) = @_;
+
+    my $condensed_version = $version;
+    $condensed_version =~ s/\.//g;
+
+    # When $version is the current version we have to search the msi at a different place.
+    my $path;
+    my $filename;
+    my $is_current = 0;
+    $path = installer::patch::InstallationSet::GetUnpackedExePath(
+        $version,
+        $is_current_version,
+        $language,
+        "msi",
+        $product_name);
+
+    # Find the msi in the path.ls .
+    $filename = File::Spec->catfile($path, "openoffice".$condensed_version.".msi");
+    $is_current = $is_current_version;
+
+    return $class->new($filename, $version, $is_current, $language, $product_name);
+}
+
+
 
 
-=head2 new($class, $version, $language, $product_name)
+
+
+=head2 new($class, $filename, $version, $is_current_version, $language, $product_name)
 
     Create a new object of the Msi class.  The values of $version, $language, and $product_name define
     where to look for the msi file.
@@ -42,47 +75,24 @@ use strict;
     If construction fails then IsValid() will return false.
 
 =cut
-sub new ($$$$)
+sub new ($$$$$$)
 {
-    my ($class, $version, $language, $product_name) = @_;
-
-    my $path = installer::patch::InstallationSet::GetUnpackedMsiPath(
-        $version,
-        $language,
-        "msi",
-        $product_name);
-
-    # Find the msi in the path.
-    my $filename = undef;
-    if ( -d $path)
-    {
-        my @msi_files = glob(File::Spec->catfile($path, "*.msi"));
-        if (scalar @msi_files != 1)
-        {
-            printf STDERR ("there are %d msi files in %s, should be 1", scalar @msi_files, $filename);
-            $filename = "";
-        }
-        else
-        {
-            $filename = $msi_files[0];
-        }
-    }
-    else
-    {
-        installer::logger::PrintError("can not access path '%s' to find msi\n", $path);
-        return undef;
-    }
+    my ($class, $filename, $version, $is_current_version, $language, $product_name) = @_;
 
     if ( ! -f $filename)
     {
-        installer::logger::PrintError("can not access MSI file at '%s'\n", $filename);
+        installer::logger::PrintError("can not find the .msi file for version %s and language %s at '%s'\n",
+            $version,
+            $language,
+            $filename);
         return undef;
     }
 
     my $self = {
         'filename' => $filename,
-        'path' => $path,
+        'path' => dirname($filename),
         'version' => $version,
+        'is_current_version' => $is_current_version,
         'language' => $language,
         'package_format' => "msi",
         'product_name' => $product_name,
@@ -107,6 +117,41 @@ sub IsValid ($)
 
 
 
+=head2 Commit($self)
+
+    Write all modified tables back into the databse.
+
+=cut
+sub Commit ($)
+{
+    my $self = shift;
+
+    my @tables_to_update = ();
+    foreach my $table (values %{$self->{'tables'}})
+    {
+        push @tables_to_update,$table if ($table->IsModified());
+    }
+
+    if (scalar @tables_to_update > 0)
+    {
+        $installer::logger::Info->printf("writing modified tables to database:\n");
+        foreach my $table (@tables_to_update)
+        {
+            $installer::logger::Info->printf("    %s\n", $table->GetName());
+            $self->PutTable($table);
+        }
+
+        foreach my $table (@tables_to_update)
+        {
+            $table->UpdateTimestamp();
+            $table->MarkAsUnmodified();
+        }
+    }
+}
+
+
+
+
 =head2 GetTable($seld, $table_name)
 
     Return an MsiTable object for $table_name.  Table objects are kept
@@ -129,8 +174,8 @@ sub GetTable ($$)
             my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name;
             my $command = join(" ",
                 "msidb.exe",
-                "-d", installer::patch::Tools::CygpathToWindows($self->{'filename'}),
-                "-f", installer::patch::Tools::CygpathToWindows($self->{'tmpdir'}),
+                "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
+                "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
                 "-e", $table_name);
             my $result = qx($command);
             print $result;
@@ -147,6 +192,51 @@ sub GetTable ($$)
 
 
 
+=head2 PutTable($self, $table)
+
+    Write the given table back to the databse.
+
+=cut
+sub PutTable ($$)
+{
+    my ($self, $table) = @_;
+
+    # Create text file from the current table content.
+    $table->WriteFile();
+
+    my $table_name = $table->GetName();
+
+    # Store table from text file into database.
+    my $table_filename = $table->{'filename'};
+
+    if (length($table_name) > 8)
+    {
+        # The file name of the table data must not be longer than 8 characters (not counting the extension).
+        # The name passed as argument to the -i option may be longer.
+        my $truncated_table_name = substr($table_name,0,8);
+        my $table_truncated_filename = File::Spec->catfile(
+            dirname($table_filename),
+            $truncated_table_name.".idt");
+        File::Copy::copy($table_filename, $table_truncated_filename) || die("can not create table file with short name");
+    }
+
+    my $command = join(" ",
+        "msidb.exe",
+        "-d", installer::patch::Tools::ToEscapedWindowsPath($self->{'filename'}),
+        "-f", installer::patch::Tools::ToEscapedWindowsPath($self->{'tmpdir'}),
+        "-i", $table_name);
+    my $result = system($command);
+
+    if ($result != 0)
+    {
+        installer::logger::PrintError("writing table '%s' back to database failed", $table_name);
+        # For error messages see http://msdn.microsoft.com/en-us/library/windows/desktop/aa372835%28v=vs.85%29.aspx
+    }
+}
+
+
+
+
 =head2 EnsureAYoungerThanB ($filename_a, $filename_b)
 
     Internal function (not a method) that compares to files according
@@ -226,52 +316,44 @@ sub SplitTargetSourceLongShortName ($)
 }
 
 
+=head2 GetDirectoryMap($self)
 
-
-=head2 GetFileToDirectoryMap ($)
-
-    Return a map (hash) that maps the unique name (column 'File' in
-    the 'File' table) to its directory names.  Each value is a
-    reference to an array of two elements: the source path and the
-    target path.
-
-    The map is kept alive for the lifetime of the Msi object.  All
-    calls but the first are cheap.
+    Return a map that maps directory unique names (column 'Directory' in table 'Directory')
+    to hashes that contains short and long source and target names.
 
 =cut
-sub GetFileToDirectoryMap ($)
+sub GetDirectoryMap ($)
 {
     my ($self) = @_;
 
-    if (defined $self->{'FileToDirectoryMap'})
+    if (defined $self->{'DirectoryMap'})
     {
-        return $self->{'FileToDirectoryMap'};
+        return $self->{'DirectoryMap'};
     }
 
-    my $file_table = $self->GetTable("File");
     my $directory_table = $self->GetTable("Directory");
-    my $component_table = $self->GetTable("Component");
-    $installer::logger::Info->printf("got access to tables File, Directory, Component\n");
-
     my %dir_map = ();
     foreach my $row (@{$directory_table->GetAllRows()})
     {
-        my ($target_name, undef, $source_name, undef)
+        my ($target_long_name, $target_short_name, $source_long_name, $source_short_name)
             = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir"));
-        $dir_map{$row->GetValue("Directory")} = {
+        my $unique_name = $row->GetValue("Directory");
+        $dir_map{$unique_name} =
+        {
+            'unique_name' => $unique_name,
             'parent' => $row->GetValue("Directory_Parent"),
-            'source_name' => $source_name,
-            'target_name' => $target_name};
+            'default_dir' => $row->GetValue("DefaultDir"),
+            'source_long_name' => $source_long_name,
+            'source_short_name' => $source_short_name,
+            'target_long_name' => $target_long_name,
+            'target_short_name' => $target_short_name
+        };
     }
 
     # Set up full names for all directories.
     my @todo = map {$_} (keys %dir_map);
-    my $process_count = 0;
-    my $push_count = 0;
     while (scalar @todo > 0)
     {
-        ++$process_count;
-
         my $key = shift @todo;
         my $item = $dir_map{$key};
         next if defined $item->{'full_source_name'};
@@ -279,17 +361,25 @@ sub GetFileToDirectoryMap ($)
         if ($item->{'parent'} eq "")
         {
             # Directory has no parent => full names are the same as the name.
-            $item->{'full_source_name'} = $item->{'source_name'};
-            $item->{'full_target_name'} = $item->{'target_name'};
+            $item->{'full_source_long_name'} = $item->{'source_long_name'};
+            $item->{'full_source_short_name'} = $item->{'source_short_name'};
+            $item->{'full_target_long_name'} = $item->{'target_long_name'};
+            $item->{'full_target_short_name'} = $item->{'target_short_name'};
         }
         else
         {
             my $parent = $dir_map{$item->{'parent'}};
-            if ( defined $parent->{'full_source_name'})
+            if ( defined $parent->{'full_source_long_name'})
             {
                 # Parent aleady has full names => we can create the full name of the current item.
-                $item->{'full_source_name'} = $parent->{'full_source_name'} . "/" . $item->{'source_name'};
-                $item->{'full_target_name'} = $parent->{'full_target_name'} . "/" . $item->{'target_name'};
+                $item->{'full_source_long_name'}
+                    = $parent->{'full_source_long_name'} . "/" . $item->{'source_long_name'};
+                $item->{'full_source_short_name'}
+                    = $parent->{'full_source_short_name'} . "/" . $item->{'source_short_name'};
+                $item->{'full_target_long_name'}
+                    = $parent->{'full_target_long_name'} . "/" . $item->{'target_long_name'};
+                $item->{'full_target_short_name'}
+                    = $parent->{'full_target_short_name'} . "/" . $item->{'target_short_name'};
             }
             else
             {
@@ -297,45 +387,78 @@ sub GetFileToDirectoryMap ($)
                 # Push both to the head of the list.
                 unshift @todo, $key;
                 unshift @todo, $item->{'parent'};
-
-                ++$push_count;
             }
         }
     }
 
-    foreach my $key (keys %dir_map)
+    # Postprocess the path names for cleanup.
+    foreach my $item (values %dir_map)
     {
-        $dir_map{$key}->{'full_source_name'} =~ s/\/(\.\/)+/\//g;
-        $dir_map{$key}->{'full_source_name'} =~ s/^SourceDir\///;
-        $dir_map{$key}->{'full_target_name'} =~ s/\/(\.\/)+/\//g;
-        $dir_map{$key}->{'full_target_name'} =~ s/^SourceDir\///;
+        foreach my $id (
+            'full_source_long_name',
+            'full_source_short_name',
+            'full_target_long_name',
+            'full_target_short_name')
+        {
+            $item->{$id} =~ s/\/(\.\/)+/\//g;
+            $item->{$id} =~ s/^SourceDir\///;
+            $item->{$id} =~ s/^\.$//;
+        }
     }
-    $installer::logger::Info->printf("for %d directories there where %d processing steps and %d pushes\n",
-        $directory_table->GetRowCount(),
-        $process_count,
-        $push_count);
+
+    $self->{'DirectoryMap'} = \%dir_map;
+    return $self->{'DirectoryMap'};
+}
+
+
+
+
+=head2 GetFileMap ($)
+
+    Return a map (hash) that maps the unique name (column 'File' in
+    the 'File' table) to data that is associated with that file, like
+    the directory or component.
+
+    The map is kept alive for the lifetime of the Msi object.  All
+    calls but the first are cheap.
+
+=cut
+sub GetFileMap ($)
+{
+    my ($self) = @_;
+
+    if (defined $self->{'FileMap'})
+    {
+        return $self->{'FileMap'};
+    }
+
+    my $file_table = $self->GetTable("File");
+    my $component_table = $self->GetTable("Component");
+    my $dir_map = $self->GetDirectoryMap();
 
     # Setup a map from component names to directory items.
-    my %component_to_directory_map = map {$_->GetValue('Component') => $_->GetValue('Directory_')} @{$component_table->GetAllRows()};
+    my %component_to_directory_map =
+        map
+        {$_->GetValue('Component') => $_->GetValue('Directory_')}
+        @{$component_table->GetAllRows()};
 
     # Finally, create the map from files to directories.
-    my $map = {};
+    my $file_map = {};
     my $file_component_index = $file_table->GetColumnIndex("Component_");
     my $file_file_index = $file_table->GetColumnIndex("File");
     foreach my $file_row (@{$file_table->GetAllRows()})
     {
         my $component_name = $file_row->GetValue($file_component_index);
         my $directory_name = $component_to_directory_map{$component_name};
-        my $dir_item = $dir_map{$directory_name};
         my $unique_name = $file_row->GetValue($file_file_index);
-        $map->{$unique_name} = [$dir_item->{'full_source_name'},$dir_item->{'full_target_name'}];
+        $file_map->{$unique_name} = {
+            'directory' => $dir_map->{$directory_name},
+            'component_name' => $component_name
+        };
     }
 
-    $installer::logger::Info->printf("got full paths for %d files\n",
-        $file_table->GetRowCount());
-
-    $self->{'FileToDirectoryMap'} = $map;
-    return $map;
+    $self->{'FileMap'} = $file_map;
+    return $file_map;
 }
 
 
diff --git a/solenv/bin/modules/installer/patch/MsiRow.pm b/solenv/bin/modules/installer/patch/MsiRow.pm
index 24a6fd2..ca414c6 100644
--- a/solenv/bin/modules/installer/patch/MsiRow.pm
+++ b/solenv/bin/modules/installer/patch/MsiRow.pm
@@ -99,6 +99,15 @@ sub SetValue ($$$)
 
 
 
+sub GetAllValues ($)
+{
+    my ($self) = @_;
+    return @{$self->{'values'}};
+}
+
+
+
+
 sub Format ($$)
 {
     my $self = shift;
diff --git a/solenv/bin/modules/installer/patch/MsiTable.pm b/solenv/bin/modules/installer/patch/MsiTable.pm
index a95b94a..0ff557c 100644
--- a/solenv/bin/modules/installer/patch/MsiTable.pm
+++ b/solenv/bin/modules/installer/patch/MsiTable.pm
@@ -44,11 +44,16 @@ sub new ($$$)
 
     my $self = {
         'name' => $table_name,
-        'is_valid' => 1
+        'filename' => $filename,
+        'columns' => undef,
+        'column_specs' => undef,
+        'codepage' => undef,
+        'is_valid' => 1,
+        'is_modified' => 0
     };
     bless($self, $class);
 
-    if ( -f $filename)
+    if (defined $filename &&  -f $filename)
     {
         $self->ReadFile($filename);
     }
@@ -58,6 +63,51 @@ sub new ($$$)
 
 
 
+sub SetColumnData ($@)
+{
+    my ($self, @data) = @_;
+
+    if (((scalar @data) % 2) != 0)
+    {
+        installer::logger::PrintError("column data has to have an even number of elements: (<column-name> <data-spec>)+)\n");
+        $self->{'is_valid'} = 0;
+        return;
+    }
+
+    $self->{'columns'} = [];
+    $self->{'column_specs'} = [];
+    while (scalar @data > 0)
+    {
+        my $name = shift @data;
+        my $spec = shift @data;
+        push @{$self->{'columns'}}, $name;
+        push @{$self->{'column_specs'}}, $spec;
+    }
+}
+
+
+
+
+sub SetIndexColumns ($@)
+{
+    my ($self, @index_columns) = @_;
+
+    $self->{'index_columns'} = [@index_columns];
+}
+
+
+
+
+sub SetCodepage ($$)
+{
+    my ($self, $codepage) = @_;
+
+    $self->{'codepage'} = $codepage;
+}
+
+
+
+
 sub IsValid ($)
 {
     my ($self) = @_;
@@ -106,17 +156,22 @@ sub ReadFile ($$)
     # Table name, index columns.
     my $line = Trim(<$in>);
     my @items = split(/\t/, $line);
-    if (scalar @items == 3)
+    my $item_count = scalar @items;
+    if ($item_count>=1 && $items[0] eq $self->{'name'})
+    {
+        # No codepage.
+    }
+    elsif ($item_count>=2 && $items[1] eq $self->{'name'})
     {
         $self->{'codepage'} = shift @items;
     }
-    my $table_name = shift @items;
-    if ($table_name ne $self->{'name'})
+    else
     {
-        printf STDERR ("reading wrong table data for table '%s' (got %s)\n", $self->{'name'}, $table_name);
+        printf STDERR ("reading wrong table data for table '%s' (got %s)\n", $self->{'name'}, $items[0]);
         $self->{'is_valid'} = 0;
         return;
     }
+    shift @items;
     $self->{'index_columns'} = [@items];
     $self->{'index_column_index'} = $self->GetColumnIndex($items[0]);
 
@@ -136,6 +191,58 @@ sub ReadFile ($$)
 
 
 
+
+=head WriteFile($self, $filename)
+
+    Write a text file containing the current table content.
+
+=cut
+sub WriteFile ($$)
+{
+    my ($self, $filename) = @_;
+
+    open my $out, ">".$self->{'filename'};
+
+    print $out join("\t", @{$self->{'columns'}})."\r\n";
+    print $out join("\t", @{$self->{'column_specs'}})."\r\n";
+    if (defined $self->{'codepage'})
+    {
+        print $out $self->{'codepage'} . "\t";
+    }
+    print $out $self->{'name'} . "\t";
+    print $out join("\t",@{$self->{'index_columns'}})."\r\n";
+
+    foreach my $row (@{$self->{'rows'}})
+    {
+        print $out $row->Format("\t")."\r\n";
+    }
+
+    close $out;
+}
+
+
+
+
+sub UpdateTimestamp ($)
+{
+    my $self = shift;
+
+    utime(undef,undef, $self->{'filename'});
+}
+
+
+
+
+sub GetName ($)
+{
+    my $self = shift;
+
+    return $self->{'name'};
+}
+
+
+
+
 =head2 GetColumnCount($self)
 
     Return the number of columns in the table.
@@ -193,6 +300,33 @@ sub GetColumnIndex ($$)
 
 
 
+=head2 GetRowIndex($self, $index_column_index, $index_column_value)
+
+    Return the index, starting at 0, of the (first) row that has value $index_column_value
+    in column with index $index_column_index.
+
+    Return -1 if now such row is found.
+
+=cut
+sub GetRowIndex ($$$)
+{
+    my ($self, $index_column_index, $index_column_value) = @_;
+
+    my $rows = $self->{'rows'};
+    for (my ($row_index,$row_count)=(0,scalar @$rows); $row_index<$row_count; ++$row_index)
+    {
+        my $row = $rows->[$row_index];
+        if ($row->GetValue($index_column_index) eq $index_column_value)
+        {
+            return $row_index;
+        }
+    }
+
+    return -1;
+}
+
+
+
 
 =head2 GetValue($self, $selector_column, $selector_column_value, $value_column)
 
@@ -270,5 +404,89 @@ sub GetAllRows ($)
 
 
 
+=head2 SetRow($self, {$key, $value}*)
+
+    Replace an existing row.  If no matching row is found then add the row.
+
+    The row is defined by a set of key/value pairs.  Their order is defined by the keys (column names)
+    and their indices as defined in $self->{'columns'}.
+
+    Rows are compared by their values of the index column.  By default this is the first element of
+    $self->{'index_columns'} but is overruled by the last key that starts with a '*'.
+
+=cut
+sub SetRow ($@)
+{
+    my $self = shift;
+    my @data = @_;
+
+    my @items = ();
+    my $index_column = $self->{'index_columns'}->[0];
+
+    # Key/Value has to have an even number of entries.
+    MsiTools::Die("invalid arguments given to MsiTable::SetRow()\n") if (scalar @data%2) != 0;
+
+    # Find column indices for column names.
+    while (scalar @data > 0)
+    {
+        my $column_name = shift @data;
+        if ($column_name =~ /^\*(.*)$/)
+        {
+            # Column name starts with a '*'.  Use it as index column.
+            $column_name = $1;
+            $index_column = $1;
+        }
+        my $value = shift @data;
+        my $column_index = $self->GetColumnIndex($column_name);
+        $items[$column_index] = $value;
+    }
+
+    my $index_column_index = $self->GetColumnIndex($index_column);
+    my $row_index = $self->GetRowIndex($index_column_index, $items[$index_column_index]);
+
+    if ($row_index < 0)
+    {
+        # Row does not yet exist.  Add it.
+        push @{$self->{'rows'}}, installer::patch::MsiRow->new($self, @items);
+    }
+    else
+    {
+        # Row does already exist.  Replace it.
+        $self->{'rows'}->[$row_index] = installer::patch::MsiRow->new($self, @items);
+    }
+
+    $self->MarkAsModified();
+}
+
+
+
+
+sub MarkAsModified ($)
+{
+    my $self = shift;
+
+    $self->{'is_modified'} = 1;
+}
+
+
+
+
+sub MarkAsUnmodified ($)
+{
+    my $self = shift;
+
+    $self->{'is_modified'} = 0;
+}
+
+
+
+
+sub IsModified ($)
+{
+    my $self = shift;
+
+    return $self->{'is_modified'};
+}
+
 
 1;
diff --git a/solenv/bin/modules/installer/patch/ReleasesList.pm b/solenv/bin/modules/installer/patch/ReleasesList.pm
index 320e864..e0648eb 100644
--- a/solenv/bin/modules/installer/patch/ReleasesList.pm
+++ b/solenv/bin/modules/installer/patch/ReleasesList.pm
@@ -21,8 +21,9 @@
 
 package installer::patch::ReleasesList;
 
-use XML::LibXML;
+use XML::Parser;
 use File::Spec;
+
 use strict;
 
 =head1 NAME
@@ -43,7 +44,8 @@ sub Instance()
 {
     if ( ! defined $Instance)
     {
-        $Instance = new installer::patch::ReleasesList();
+        $Instance = new installer::patch::ReleasesList(
+            File::Spec->catfile($ENV{'SRC_ROOT'}, "instsetoo_native", "data", "releases.xml"));
     }
     return $Instance;
 }
@@ -51,19 +53,23 @@ sub Instance()
 
 
 
-=head2 new($class)
+=head2 new($class, $filename)
 
     Internal constructor.  Don't call.
 
 =cut
-sub new ($)
+sub new ($$)
 {
-    my ($class) = @_;
+    my ($class, $filename) = @_;
 
-    my $self = {};
+    my $self = {
+        'releases' => []
+    };
     bless($self, $class);
 
-    $self->Read();
+
+    $self->Read($filename);
+
 
     return $self;
 }
@@ -87,14 +93,14 @@ sub GetFirstChild ($$)
     }
     else
     {
-        my @child_nodes = $node->getElementsByTagName($child_name);
-        if (scalar @child_nodes == 0)
+        my $value = $node->{$child_name};
+        if (ref($value) eq 'ARRAY')
         {
-            return undef;
+            return $value->[0];
         }
         else
         {
-            return $child_nodes[0];
+            return $value;
         }
     }
 }
@@ -107,17 +113,24 @@ sub GetFirstChild ($$)
     Internal function that returns the trimmed text content of a node.
 
 =cut
-sub GetText ($)
+sub GetText ($;$)
 {
-    my ($node) = @_;
+    my ($node, $default_text) = @_;
 
     if ( ! defined $node)
     {
-        return "";
+        if (defined $default_text)
+        {
+            return $default_text;
+        }
+        else
+        {
+            return "";
+        }
     }
     else
     {
-        my $text = $node->textContent();
+        my $text = $node->{'__text__'};
         $text =~ s/(^\s+|\s+$)//g;
         return $text;
     }
@@ -125,39 +138,165 @@ sub GetText ($)
 
 
 
+sub GetAttribute ($$)
+{
+    my ($node, $attribute_name) = @_;
+
+    my $attributes = $node->{'__attributes__'};
+    if ( ! defined $attributes)
+    {
+        return undef;
+    }
+    else
+    {
+        return $attributes->{$attribute_name};
+    }
+}
+
+
+
+
+sub PrintNode($$);
+sub ReadDomTree ($)
+{
+    my ($filename) = @_;
+
+    my $root = {};
+    my $data = {
+        'current_node' => $root,
+        'node_stack' => []
+    };
+    my $parser = new XML::Parser(
+        'Handlers' => {
+            'Start' => sub {HandleStartTag($data, @_)},
+            'End' => sub{HandleEndTag($data, @_)},
+            'Char' => sub{HandleText($data, @_)}
+        });
+    $parser->parsefile($filename);
+
+#    PrintNode("", $root);
+
+    return $root;
+}
+
 
-=head2 Read($self)
+
+
+sub PrintNode($$)
+{
+    my ($indentation, $node) = @_;
+
+    if (defined $node->{'__attributes__'})
+    {
+        while (my ($name,$attribute) = each(%{$node->{'__attributes__'}}))
+        {
+            printf("    %s%s -> %s\n", $indentation, $name, $attribute);
+        }
+    }
+
+    while (my ($key,$value) = each(%$node))
+    {
+        if ($key eq '__text__')
+        {
+            printf("%stext '%s'\n", $indentation, $value);
+        }
+        elsif ($key eq '__attributes__')
+        {
+            next;
+        }
+        elsif (ref($value) eq "ARRAY")
+        {
+            foreach my $item (@$value)
+            {
+                printf("%s%s {\n", $indentation, $key);
+                PrintNode($indentation."    ", $item);
+                printf("%s}\n", $indentation);
+            }
+        }
+        else
+        {
+            printf("%s%s {\n", $indentation, $key);
+            PrintNode($indentation."    ", $value);
+            printf("%s}\n", $indentation);
+        }
+    }
+}
+
+
+sub HandleStartTag ($$$@)
+{
+    my ($data, $expat, $element, @attributes) = @_;
+
+    # Create new node with attributes.
+    my $node = {'__attributes__' => {@attributes}};
+
+    # Append it to the list of $element objects.
+    my $current_node = $data->{'current_node'};
+    $current_node->{$element} = [] unless defined $current_node->{$element};
+    push @{$current_node->{$element}}, $node;
+
+    # Make the new node the current node.
+    push @{$data->{'node_stack'}}, $current_node;
+    $data->{'current_node'} = $node;
+}
+
+sub HandleEndTag ($$$)
+{
+    my ($data, $expat, $element) = @_;
+
+    # Restore the parent node as current node.
+    $data->{'current_node'} = pop @{$data->{'node_stack'}};
+}
+
+sub HandleText ($$$)
+{
+    my ($data, $expat, $text) = @_;
+    if ($text !~ /^\s*$/)
+    {
+        $data->{'current_node'}->{'__text__'} .= $text;
+    }
+}
+
+=head2 Read($self, $filename)
 
     Read the releases.xml file as doctree and parse its content.
 
 =cut
-sub Read ($)
+sub Read ($$)
 {
-    my ($self) = @_;
+    my ($self, $filename) = @_;
 
-    my $filename = File::Spec->catfile($ENV{'SRC_ROOT'}, "instsetoo_native", "data", "releases.xml");
-    my $parser = XML::LibXML->new();
-    my $document = $parser->parse_file($filename);
-    foreach my $release_node ($document->getElementsByTagName("release"))
+    my $document = ReadDomTree($filename);
+    foreach my $release_node (@{$document->{'releases'}->[0]->{'release'}})
     {
         my $version_node = GetFirstChild($release_node, "version");
-        my $version = GetText($version_node);
-        next if $version eq "";
+        my $version_major = GetText(GetFirstChild($version_node, "major"));
+        my $version_minor = GetText(GetFirstChild($version_node, "minor"), "0");
+        my $version_micro = GetText(GetFirstChild($version_node, "micro"), "0");
+        my $version = sprintf("%d.%d.%d", $version_major, $version_minor, $version_micro);
+        die "could not read version from releases.xml" if $version eq "";
 
-        foreach my $download_node (GetFirstChild($release_node, "download"))
-        {
-            my $package_node = GetFirstChild($download_node, "package-format");
-            my $package_format = GetText($package_node);
-            next if $package_format eq "";
+        push @{$self->{'releases'}}, $version;
 
-            my $download_data = ParseDownloadData($download_node);
-            if (defined $download_data)
+        my $download_node = GetFirstChild($release_node, "downloads");
+        my $package_format = GetText(GetFirstChild($download_node, "package-format"));
+        my $url_template = GetText(GetFirstChild($download_node, "url-template"));
+        my $upgrade_code = GetText(GetFirstChild($download_node, "upgrade-code"));
+        my $build_id = GetText(GetFirstChild($download_node, "build-id"));
+        die "could not read package format from releases.xml" if $package_format eq "";
+
+        $self->{$version}->{$package_format}->{'upgrade-code'} = $upgrade_code;
+        $self->{$version}->{$package_format}->{'build-id'} = $build_id;
+
+        foreach my $item_node (@{$download_node->{'item'}})
+        {
+            my ($language, $download_data) = ParseDownloadData($item_node, $url_template);
+            if (defined $download_data && defined $language)
             {
-                $self->{$version}->{$package_format} = $download_data;
+                $self->{$version}->{$package_format}->{$language} = $download_data;
             }
         }
     }
-
 }
 
 
@@ -168,43 +307,69 @@ sub Read ($)
     Parse the data for one set of download data (there is one per release and package format).
 
 =cut
-sub ParseDownloadData ($)
+sub ParseDownloadData ($$)
 {
-    my ($download_node) = @_;
+    my ($item_node, $url_template) = @_;
 
-    my $url_node = GetFirstChild($download_node, "url-template");
-    my $url_template = GetText($url_node);
-    if ($url_template eq "")
+    my $language = GetText(GetFirstChild($item_node, "language"));
+    my $checksum_node = GetFirstChild($item_node, "checksum");
+    if ( ! defined $checksum_node)
     {
-        print STDERR "releases data file corrupt (no URL template)\n";
+        print STDERR "releases data file corrupt (item has no 'checksum' node)\n";
         return undef;
     }
-
-    my $download_data = {};
-    foreach my $item_node (@{$download_node->getElementsByTagName("item")})
-    {
-        my $language = GetText(GetFirstChild($item_node, "language"));
-        my $checksum_node = GetFirstChild($item_node, "checksum");
-        if ( ! defined $checksum_node)
+    my $checksum_type = GetAttribute($checksum_node, "type");
+    my $checksum_value = GetText($checksum_node);
+    my $file_size = GetText(GetFirstChild($item_node, "size"));
+    my $product_code = GetText(GetFirstChild($item_node, "product-code"));
+
+    my $url = $url_template;
+    $url =~ s/\%L/$language/g;
+    return (
+        $language,
         {
-            print STDERR "releases data file corrupt (item has no 'checksum' node)\n";
-            return undef;
-        }
-        my $checksum_type = $checksum_node->getAttribute("type");
-        my $checksum_value = GetText($checksum_node);
-        my $file_size = GetText(GetFirstChild($item_node, "size"));
-
-        my $url = $url_template;
-                $url =~ s/\%L/$language/g;
-        $download_data->{$language} = {
             'URL' => $url,
             'checksum-type' => $checksum_type,
             'checksum-value' => $checksum_value,
-            'file-size' => $file_size
-        };
+            'file-size' => $file_size,
+            'product-code' => $product_code
+        });
+}
+
+
+
+
+=head2 GetPreviousVersion($version)
+
+    Look up $version in the sorted list of released versions.  Return
+    the previous element.  Whe $version is not found then return the
+    last element (under the assumption that $version will be the next
+    released version).
+
+=cut
+sub GetPreviousVersion ($)
+{
+    my ($current_version) = @_;
+
+    my $release_data = installer::patch::ReleasesList::Instance();
+    my $previous_version = undef;
+    foreach my $version (@{$release_data->{'releases'}})
+    {
+        if ($version eq $current_version)
+        {
+            return $previous_version;
+        }
+        else
+        {
+            $previous_version = $version;
+        }
     }
 
-    return $download_data;
+    return $previous_version;
 }
 
+
+
+
+
 1;
diff --git a/solenv/bin/modules/installer/patch/Tools.pm b/solenv/bin/modules/installer/patch/Tools.pm
index b29b559..06035a3 100644
--- a/solenv/bin/modules/installer/patch/Tools.pm
+++ b/solenv/bin/modules/installer/patch/Tools.pm
@@ -30,17 +30,32 @@ package installer::patch::Tools;
 
 
 
-=head2 CygpathToWindows ($path)
+=head2 ToEscapedWindowsPath ($path)
 
     Convert the given path with the 'cygpath' command into Windows format.  Quote backslashes.
 
 =cut
-sub CygpathToWindows($)
+sub ToEscapedWindowsPath($)
 {
     my ($path) = @_;
+
     my $windows_path = qx(cygpath -w "$path");
     $windows_path =~ s/(^\s+|\s+$)//g;
     $windows_path =~ s/\\/\\\\/g;
+
+    return $windows_path;
+}
+
+
+
+
+sub ToWindowsPath ($)
+{
+    my ($path) = @_;
+
+    my $windows_path = qx(cygpath -w "$path");
+    $windows_path =~ s/(^\s+|\s+$)//g;
+
     return $windows_path;
 }
 
diff --git a/solenv/bin/modules/installer/patch/Version.pm b/solenv/bin/modules/installer/patch/Version.pm
index 685df6d..4e50cb0 100644
--- a/solenv/bin/modules/installer/patch/Version.pm
+++ b/solenv/bin/modules/installer/patch/Version.pm
@@ -35,7 +35,7 @@ my $VersionPartCount = 3;
 
 
 
-=head StringToNumberArray($version_string)
+=head2 StringToNumberArray($version_string)
 
     Convert a version string (where the individual parts are separated by '.') into an array of three numbers.
     Missing numbers are filled with 0.
@@ -57,7 +57,7 @@ sub StringToNumberArray ($)
 
 
 
-=head ArrayToDirectoryName (@)
+=head2 ArrayToDirectoryName (@)
 
     Return a directory name (without any path) for the given array of version numbers.
 
@@ -69,6 +69,37 @@ sub ArrayToDirectoryName (@)
 
 
 
+=head2 ArrayToNoDotName (@)
+
+    This symply creates a version array (A,B,C) into a version string
+    "ABC" with no dots between major, minor and micro version number.
+
+=cut
+sub ArrayToNoDotName (@)
+{
+    return join("", @_);
+}
+
+
+
+
+=head2 IsMajorVersion ($version_string)
+
+    Return 1 if $version_string is a major version, ie. ?.0.0
+    Return 0 otherwise.
+
+=cut
+sub IsMajorVersion ($)
+{
+    my ($version_string) = @_;
+    my @version = installer::patch::Version::StringToNumberArray($version_string);
+    for (my $index=1; $index<$VersionPartCount; ++$index)
+    {
+        return 0 if $version[$index] ne "0";
+    }
+    return 1;
+}
+
 
 
 1;
diff --git a/solenv/bin/modules/installer/systemactions.pm b/solenv/bin/modules/installer/systemactions.pm
index b4396b4..a492f37 100644
--- a/solenv/bin/modules/installer/systemactions.pm
+++ b/solenv/bin/modules/installer/systemactions.pm
@@ -1710,36 +1710,48 @@ sub read_complete_directory
 # Version 2
 ##############################################################
 
-sub read_full_directory {
+sub read_full_directory ($$$)
+{
     my ( $currentdir, $pathstring, $collector ) = @_;
     my $item;
     my $fullname;
     local *DH;
 
-    unless (opendir(DH, $currentdir))
-    {
-        return;
-    }
-    while (defined ($item = readdir(DH)))
+    $installer::logger::Lang->printf("seaching files under '%s'\n", $currentdir);
+
+    my @directory_queue = [$currentdir, $pathstring];
+

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list