1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.oracle.tools.packager.windows; 27 28 import com.oracle.tools.packager.*; 29 import com.sun.javafx.tools.packager.bundlers.BundleParams; 30 31 import java.io.*; 32 import java.text.MessageFormat; 33 import java.util.*; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 import static com.oracle.tools.packager.StandardBundlerParam.SECONDARY_LAUNCHERS; 38 import static com.oracle.tools.packager.StandardBundlerParam.SERVICE_HINT; 39 import static com.oracle.tools.packager.StandardBundlerParam.VERBOSE; 40 import static com.oracle.tools.packager.windows.WindowsBundlerParam.*; 41 42 public class WinExeBundler extends AbstractBundler { 43 44 private static final ResourceBundle I18N = 45 ResourceBundle.getBundle(WinExeBundler.class.getName()); 46 47 public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER = new WindowsBundlerParam<>( 48 I18N.getString("param.app-bundler.name"), 49 I18N.getString("param.app-bundler.description"), 50 "win.app.bundler", 51 WinAppBundler.class, 52 params -> new WinAppBundler(), 53 null); 54 55 public static final BundlerParamInfo<WinServiceBundler> SERVICE_BUNDLER = new WindowsBundlerParam<>( 56 I18N.getString("param.service-bundler.name"), 57 I18N.getString("param.service-bundler.description"), 58 "win.service.bundler", 59 WinServiceBundler.class, 60 params -> new WinServiceBundler(), 61 null); 62 63 public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>( 64 I18N.getString("param.config-root.name"), 65 I18N.getString("param.config-root.description"), 66 "configRoot", 67 File.class, params -> { 68 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows"); 69 imagesRoot.mkdirs(); 70 return imagesRoot; 71 }, 72 (s, p) -> null); 73 74 //default for .exe is user level installation 75 // only do system wide if explicitly requested 76 public static final StandardBundlerParam<Boolean> EXE_SYSTEM_WIDE = 77 new StandardBundlerParam<>( 78 I18N.getString("param.system-wide.name"), 79 I18N.getString("param.system-wide.description"), 80 "win.exe." + BundleParams.PARAM_SYSTEM_WIDE, 81 Boolean.class, 82 params -> params.containsKey(SYSTEM_WIDE.getID()) 83 ? SYSTEM_WIDE.fetchFrom(params) 84 : false, // EXEs default to user local install 85 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) // valueOf(null) is false, and we actually do want null 86 ); 87 88 public static final BundlerParamInfo<File> EXE_IMAGE_DIR = new WindowsBundlerParam<>( 89 I18N.getString("param.image-dir.name"), 90 I18N.getString("param.image-dir.description"), 91 "win.exe.imageDir", 92 File.class, 93 params -> { 94 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 95 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 96 return new File(imagesRoot, "win-exe.image"); 97 }, 98 (s, p) -> null); 99 100 private final static String DEFAULT_EXE_PROJECT_TEMPLATE = "template.iss"; 101 private static final String TOOL_INNO_SETUP_COMPILER = "iscc.exe"; 102 103 public static final BundlerParamInfo<String> TOOL_INNO_SETUP_COMPILER_EXECUTABLE = new WindowsBundlerParam<>( 104 I18N.getString("param.iscc-path.name"), 105 I18N.getString("param.iscc-path.description"), 106 "win.exe.iscc.exe", 107 String.class, 108 params -> { 109 for (String dirString : (System.getenv("PATH") + ";C:\\Program Files (x86)\\Inno Setup 5;C:\\Program Files\\Inno Setup 5").split(";")) { 110 File f = new File(dirString.replace("\"", ""), TOOL_INNO_SETUP_COMPILER); 111 if (f.isFile()) { 112 return f.toString(); 113 } 114 } 115 return null; 116 }, 117 null); 118 119 public WinExeBundler() { 120 super(); 121 baseResourceLoader = WinResources.class; 122 } 123 124 @Override 125 public String getName() { 126 return I18N.getString("bundler.name"); 127 } 128 129 @Override 130 public String getDescription() { 131 return I18N.getString("bundler.description"); 132 } 133 134 @Override 135 public String getID() { 136 return "exe"; 137 } 138 139 @Override 140 public String getBundleType() { 141 return "INSTALLER"; 142 } 143 144 @Override 145 public Collection<BundlerParamInfo<?>> getBundleParameters() { 146 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 147 results.addAll(WinAppBundler.getAppBundleParameters()); 148 results.addAll(getExeBundleParameters()); 149 return results; 150 } 151 152 public static Collection<BundlerParamInfo<?>> getExeBundleParameters() { 153 return Arrays.asList( 154 DESCRIPTION, 155 COPYRIGHT, 156 LICENSE_FILE, 157 MENU_GROUP, 158 MENU_HINT, 159 // RUN_AT_STARTUP, 160 SHORTCUT_HINT, 161 // SERVICE_HINT, 162 // START_ON_INSTALL, 163 // STOP_ON_UNINSTALL, 164 SYSTEM_WIDE, 165 TITLE, 166 VENDOR 167 ); 168 } 169 170 @Override 171 public File execute(Map<String, ? super Object> params, File outputParentDir) { 172 return bundle(params, outputParentDir); 173 } 174 175 static class VersionExtractor extends PrintStream { 176 double version = 0f; 177 178 public VersionExtractor() { 179 super(new ByteArrayOutputStream()); 180 } 181 182 double getVersion() { 183 if (version == 0f) { 184 String content = new String(((ByteArrayOutputStream) out).toByteArray()); 185 Pattern pattern = Pattern.compile("Inno Setup (\\d+.?\\d*)"); 186 Matcher matcher = pattern.matcher(content); 187 if (matcher.find()) { 188 String v = matcher.group(1); 189 version = new Double(v); 190 } 191 } 192 return version; 193 } 194 } 195 196 private static double findToolVersion(String toolName) { 197 try { 198 if (toolName == null || "".equals(toolName)) return 0f; 199 200 ProcessBuilder pb = new ProcessBuilder( 201 toolName, 202 "/?"); 203 VersionExtractor ve = new VersionExtractor(); 204 IOUtils.exec(pb, Log.isDebug(), true, ve); //not interested in the output 205 double version = ve.getVersion(); 206 Log.verbose(MessageFormat.format(I18N.getString("message.tool-version"), toolName, version)); 207 return version; 208 } catch (Exception e) { 209 if (Log.isDebug()) { 210 e.printStackTrace(); 211 } 212 return 0f; 213 } 214 } 215 216 @Override 217 public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 218 try { 219 if (p == null) throw new ConfigException(I18N.getString("error.parameters-null"), I18N.getString("error.parameters-null.advice")); 220 221 //run basic validation to ensure requirements are met 222 //we are not interested in return code, only possible exception 223 APP_BUNDLER.fetchFrom(p).validate(p); 224 225 // make sure some key values don't have newlines 226 for (BundlerParamInfo<String> pi : Arrays.asList( 227 APP_NAME, 228 COPYRIGHT, 229 DESCRIPTION, 230 MENU_GROUP, 231 TITLE, 232 VENDOR, 233 VERSION) 234 ) { 235 String v = pi.fetchFrom(p); 236 if (v.contains("\n") | v.contains("\r")) { 237 throw new ConfigException("Parmeter '" + pi.getID() + "' cannot contain a newline.", 238 "Change the value of '" + pi.getID() + " so that it does not contain any newlines"); 239 } 240 } 241 242 //exe bundlers trim the copyright to 100 characters, tell them this will happen 243 if (COPYRIGHT.fetchFrom(p).length() > 100) { 244 throw new ConfigException( 245 I18N.getString("error.copyright-is-too-long"), 246 I18N.getString("error.copyright-is-too-long.advice")); 247 } 248 249 // validate license file, if used, exists in the proper place 250 if (p.containsKey(LICENSE_FILE.getID())) { 251 RelativeFileSet appResources = APP_RESOURCES.fetchFrom(p); 252 for (String license : LICENSE_FILE.fetchFrom(p)) { 253 if (!appResources.contains(license)) { 254 throw new ConfigException( 255 I18N.getString("error.license-missing"), 256 MessageFormat.format(I18N.getString("error.license-missing.advice"), 257 license, appResources.getBaseDirectory().toString())); 258 } 259 } 260 } 261 262 263 if (SERVICE_HINT.fetchFrom(p)) { 264 SERVICE_BUNDLER.fetchFrom(p).validate(p); 265 } 266 267 double innoVersion = findToolVersion(TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p)); 268 269 //Inno Setup 5+ is required 270 double minVersion = 5.0f; 271 272 if (innoVersion < minVersion) { 273 Log.info(MessageFormat.format(I18N.getString("message.tool-wrong-version"), TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion)); 274 throw new ConfigException( 275 I18N.getString("error.iscc-not-found"), 276 I18N.getString("error.iscc-not-found.advice")); 277 } 278 279 return true; 280 } catch (RuntimeException re) { 281 if (re.getCause() instanceof ConfigException) { 282 throw (ConfigException) re.getCause(); 283 } else { 284 throw new ConfigException(re); 285 } 286 } 287 } 288 289 private boolean prepareProto(Map<String, ? super Object> params) throws IOException { 290 File imageDir = EXE_IMAGE_DIR.fetchFrom(params); 291 File appOutputDir = APP_BUNDLER.fetchFrom(params).doBundle(params, imageDir, true); 292 if (appOutputDir == null) { 293 return false; 294 } 295 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 296 if (licenseFiles != null) { 297 RelativeFileSet appRoot = APP_RESOURCES.fetchFrom(params); 298 //need to copy license file to the root of win-app.image 299 for (String s : licenseFiles) { 300 File lfile = new File(appRoot.getBaseDirectory(), s); 301 IOUtils.copyFile(lfile, new File(imageDir, lfile.getName())); 302 } 303 } 304 305 for (Map<String, ? super Object> fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) { 306 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 307 if (icon != null && icon.exists()) { 308 IOUtils.copyFile(icon, new File(appOutputDir, icon.getName())); 309 } 310 } 311 312 if (SERVICE_HINT.fetchFrom(params)) { 313 // copies the service launcher to the app root folder 314 appOutputDir = SERVICE_BUNDLER.fetchFrom(params).doBundle(params, appOutputDir, true); 315 if (appOutputDir == null) { 316 return false; 317 } 318 } 319 return true; 320 } 321 322 public File bundle(Map<String, ? super Object> p, File outputDirectory) { 323 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 324 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 325 } 326 if (!outputDirectory.canWrite()) { 327 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 328 } 329 330 // validate we have valid tools before continuing 331 String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p); 332 if (iscc == null || !new File(iscc).isFile()) { 333 Log.info(I18N.getString("error.iscc-not-found")); 334 Log.info(MessageFormat.format(I18N.getString("message.iscc-file-string"), iscc)); 335 return null; 336 } 337 338 File imageDir = EXE_IMAGE_DIR.fetchFrom(p); 339 try { 340 imageDir.mkdirs(); 341 342 boolean menuShortcut = MENU_HINT.fetchFrom(p); 343 boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); 344 if (!menuShortcut && !desktopShortcut) { 345 //both can not be false - user will not find the app 346 Log.verbose(I18N.getString("message.one-shortcut-required")); 347 p.put(MENU_HINT.getID(), true); 348 } 349 350 if (prepareProto(p) && prepareProjectConfig(p)) { 351 File configScript = getConfig_Script(p); 352 if (configScript.exists()) { 353 Log.info(MessageFormat.format(I18N.getString("message.running-wsh-script"), configScript.getAbsolutePath())); 354 IOUtils.run("wscript", configScript, VERBOSE.fetchFrom(p)); 355 } 356 return buildEXE(p, outputDirectory); 357 } 358 return null; 359 } catch (IOException ex) { 360 ex.printStackTrace(); 361 return null; 362 } finally { 363 try { 364 if (VERBOSE.fetchFrom(p)) { 365 saveConfigFiles(p); 366 } 367 if (imageDir != null && !Log.isDebug()) { 368 IOUtils.deleteRecursive(imageDir); 369 } else if (imageDir != null) { 370 Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath())); 371 } 372 } catch (FileNotFoundException ex) { 373 //noinspection ReturnInsideFinallyBlock 374 return null; 375 } 376 } 377 } 378 379 //name of post-image script 380 private File getConfig_Script(Map<String, ? super Object> params) { 381 return new File(EXE_IMAGE_DIR.fetchFrom(params), APP_NAME.fetchFrom(params) + "-post-image.wsf"); 382 } 383 384 protected void saveConfigFiles(Map<String, ? super Object> params) { 385 try { 386 File configRoot = CONFIG_ROOT.fetchFrom(params); 387 if (getConfig_ExeProjectFile(params).exists()) { 388 IOUtils.copyFile(getConfig_ExeProjectFile(params), 389 new File(configRoot, getConfig_ExeProjectFile(params).getName())); 390 } 391 if (getConfig_Script(params).exists()) { 392 IOUtils.copyFile(getConfig_Script(params), 393 new File(configRoot, getConfig_Script(params).getName())); 394 } 395 if (getConfig_SmallInnoSetupIcon(params).exists()) { 396 IOUtils.copyFile(getConfig_SmallInnoSetupIcon(params), 397 new File(configRoot, getConfig_SmallInnoSetupIcon(params).getName())); 398 } 399 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 400 } catch (IOException ioe) { 401 ioe.printStackTrace(); 402 } 403 } 404 405 private String getAppIdentifier(Map<String, ? super Object> params) { 406 String nm = IDENTIFIER.fetchFrom(params); 407 408 //limitation of innosetup 409 if (nm.length() > 126) 410 nm = nm.substring(0, 126); 411 412 return nm; 413 } 414 415 416 private String getLicenseFile(Map<String, ? super Object> params) { 417 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 418 if (licenseFiles == null || licenseFiles.isEmpty()) { 419 return ""; 420 } else { 421 return licenseFiles.get(0); 422 } 423 } 424 425 void validateValueAndPut(Map<String, String> data, String key, BundlerParamInfo<String> param, Map<String, ? super Object> params) throws IOException { 426 String value = param.fetchFrom(params); 427 if (value.contains("\r") || value.contains("\n")) { 428 throw new IOException("Configuration Parameter " + param.getID() + " cannot contain multiple lines of text"); 429 } 430 data.put(key, innosetupEscape(value)); 431 } 432 433 private String innosetupEscape(String value) { 434 if (value.contains("\"") || !value.trim().equals(value)) { 435 value = "\"" + value.replace("\"", "\"\"") + "\""; 436 } 437 return value; 438 } 439 440 boolean prepareMainProjectFile(Map<String, ? super Object> params) throws IOException { 441 Map<String, String> data = new HashMap<>(); 442 data.put("PRODUCT_APP_IDENTIFIER", innosetupEscape(getAppIdentifier(params))); 443 444 validateValueAndPut(data, "APPLICATION_NAME", APP_NAME, params); 445 446 validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params); 447 validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params); // TODO make our own version paraminfo? 448 validateValueAndPut(data, "INSTALLER_FILE_NAME", INSTALLER_FILE_NAME, params); 449 450 data.put("APPLICATION_LAUNCHER_FILENAME", innosetupEscape(WinAppBundler.getLauncherName(params))); 451 452 data.put("APPLICATION_DESKTOP_SHORTCUT", SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 453 data.put("APPLICATION_MENU_SHORTCUT", MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 454 validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params); 455 validateValueAndPut(data, "APPLICATION_COMMENTS", TITLE, params); // TODO this seems strange, at least in name 456 validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params); 457 458 data.put("APPLICATION_LICENSE_FILE", innosetupEscape(getLicenseFile(params))); 459 460 Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params); 461 462 if (isSystemWide) { 463 data.put("APPLICATION_INSTALL_ROOT", "{pf}"); 464 data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); 465 } else { 466 data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); 467 data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); 468 } 469 470 if (BIT_ARCH_64.fetchFrom(params)) { 471 data.put("ARCHITECTURE_BIT_MODE", "x64"); 472 } else { 473 data.put("ARCHITECTURE_BIT_MODE", ""); 474 } 475 476 if (SERVICE_HINT.fetchFrom(params)) { 477 data.put("RUN_FILENAME", innosetupEscape(WinServiceBundler.getAppSvcName(params))); 478 } else { 479 validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params); 480 } 481 validateValueAndPut(data, "APPLICATION_DESCRIPTION", DESCRIPTION, params); 482 data.put("APPLICATION_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 483 data.put("APPLICATION_NOT_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnFalse" : "returnTrue"); 484 data.put("START_ON_INSTALL", START_ON_INSTALL.fetchFrom(params) ? "-startOnInstall" : ""); 485 data.put("STOP_ON_UNINSTALL", STOP_ON_UNINSTALL.fetchFrom(params) ? "-stopOnUninstall" : ""); 486 data.put("RUN_AT_STARTUP", RUN_AT_STARTUP.fetchFrom(params) ? "-runAtStartup" : ""); 487 488 StringBuilder secondaryLaunchersCfg = new StringBuilder(); 489 for (Map<String, ? super Object> launcher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 490 String application_name = APP_NAME.fetchFrom(launcher); 491 if (MENU_HINT.fetchFrom(launcher)) { 492 //Name: "{group}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 493 secondaryLaunchersCfg.append("Name: \"{group}\\"); 494 secondaryLaunchersCfg.append(application_name); 495 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 496 secondaryLaunchersCfg.append(application_name); 497 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 498 secondaryLaunchersCfg.append(application_name); 499 secondaryLaunchersCfg.append(".ico\"\r\n"); 500 } 501 if (SHORTCUT_HINT.fetchFrom(launcher)) { 502 //Name: "{commondesktop}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 503 secondaryLaunchersCfg.append("Name: \"{commondesktop}\\"); 504 secondaryLaunchersCfg.append(application_name); 505 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 506 secondaryLaunchersCfg.append(application_name); 507 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 508 secondaryLaunchersCfg.append(application_name); 509 secondaryLaunchersCfg.append(".ico\"\r\n"); 510 } 511 } 512 data.put("SECONDARY_LAUNCHERS", secondaryLaunchersCfg.toString()); 513 514 StringBuilder registryEntries = new StringBuilder(); 515 String regName = APP_REGISTRY_NAME.fetchFrom(params); 516 List<Map<String, ? super Object>> fetchFrom = FILE_ASSOCIATIONS.fetchFrom(params); 517 for (int i = 0; i < fetchFrom.size(); i++) { 518 Map<String, ? super Object> fileAssociation = fetchFrom.get(i); 519 String description = FA_DESCRIPTION.fetchFrom(fileAssociation); 520 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 521 522 List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); 523 String entryName = regName + "File"; 524 if (i > 0) { 525 entryName += "." + i; 526 } 527 528 if (extensions == null) { 529 Log.info(I18N.getString("message.creating-association-with-null-extension")); 530 } else { 531 for (String ext : extensions) { 532 if (isSystemWide) { 533 // "Root: HKCR; Subkey: \".myp\"; ValueType: string; ValueName: \"\"; ValueData: \"MyProgramFile\"; Flags: uninsdeletevalue" 534 registryEntries.append("Root: HKCR; Subkey: \".") 535 .append(ext) 536 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 537 .append(entryName) 538 .append("\"; Flags: uninsdeletevalue\r\n"); 539 } else { 540 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\.") 541 .append(ext) 542 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 543 .append(entryName) 544 .append("\"; Flags: uninsdeletevalue\r\n"); 545 } 546 } 547 } 548 549 if (extensions != null && !extensions.isEmpty()) { 550 String ext = extensions.get(0); 551 List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation); 552 for (String mime : mimeTypes) { 553 if (isSystemWide) { 554 // "Root: HKCR; Subkey: HKCR\\Mime\\Database\\Content Type\\application/chaos; ValueType: string; ValueName: Extension; ValueData: .chaos; Flags: uninsdeletevalue" 555 registryEntries.append("Root: HKCR; Subkey: \"Mime\\Database\\Content Type\\") 556 .append(mime) 557 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 558 .append(ext) 559 .append("\"; Flags: uninsdeletevalue\r\n"); 560 } else { 561 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\Mime\\Database\\Content Type\\") 562 .append(mime) 563 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 564 .append(ext) 565 .append("\"; Flags: uninsdeletevalue\r\n"); 566 } 567 } 568 } 569 570 if (isSystemWide) { 571 //"Root: HKCR; Subkey: \"MyProgramFile\"; ValueType: string; ValueName: \"\"; ValueData: \"My Program File\"; Flags: uninsdeletekey" 572 registryEntries.append("Root: HKCR; Subkey: \"") 573 .append(entryName) 574 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 575 .append(description) 576 .append("\"; Flags: uninsdeletekey\r\n"); 577 } else { 578 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 579 .append(entryName) 580 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 581 .append(description) 582 .append("\"; Flags: uninsdeletekey\r\n"); 583 584 } 585 586 if (icon != null && icon.exists()) { 587 if (isSystemWide) { 588 // "Root: HKCR; Subkey: \"MyProgramFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\MYPROG.EXE,0\"\n" + 589 registryEntries.append("Root: HKCR; Subkey: \"") 590 .append(entryName) 591 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 592 .append(icon.getName()) 593 .append("\"\r\n"); 594 } else { 595 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 596 .append(entryName) 597 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 598 .append(icon.getName()) 599 .append("\"\r\n"); 600 } 601 } 602 603 if (isSystemWide) { 604 //"Root: HKCR; Subkey: \"MyProgramFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" 605 registryEntries.append("Root: HKCR; Subkey: \"") 606 .append(entryName) 607 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 608 .append(APP_NAME.fetchFrom(params)) 609 .append("\"\" \"\"%1\"\"\"\r\n"); 610 } else { 611 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 612 .append(entryName) 613 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 614 .append(APP_NAME.fetchFrom(params)) 615 .append("\"\" \"\"%1\"\"\"\r\n"); 616 } 617 } 618 if (registryEntries.length() > 0) { 619 data.put("FILE_ASSOCIATIONS", "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + registryEntries.toString()); 620 } else { 621 data.put("FILE_ASSOCIATIONS", ""); 622 } 623 624 Writer w = new BufferedWriter(new FileWriter(getConfig_ExeProjectFile(params))); 625 String content = preprocessTextResource( 626 WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ExeProjectFile(params).getName(), 627 I18N.getString("resource.inno-setup-project-file"), DEFAULT_EXE_PROJECT_TEMPLATE, data, 628 VERBOSE.fetchFrom(params), 629 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 630 w.write(content); 631 w.close(); 632 return true; 633 } 634 635 private final static String DEFAULT_INNO_SETUP_ICON = "icon_inno_setup.bmp"; 636 637 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 638 prepareMainProjectFile(params); 639 640 //prepare installer icon 641 File iconTarget = getConfig_SmallInnoSetupIcon(params); 642 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + iconTarget.getName(), 643 I18N.getString("resource.setup-icon"), 644 DEFAULT_INNO_SETUP_ICON, 645 iconTarget, 646 VERBOSE.fetchFrom(params), 647 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 648 649 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_Script(params).getName(), 650 I18N.getString("resource.post-install-script"), 651 (String) null, 652 getConfig_Script(params), 653 VERBOSE.fetchFrom(params), 654 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 655 return true; 656 } 657 658 private File getConfig_SmallInnoSetupIcon(Map<String, ? super Object> params) { 659 return new File(EXE_IMAGE_DIR.fetchFrom(params), 660 APP_NAME.fetchFrom(params) + "-setup-icon.bmp"); 661 } 662 663 private File getConfig_ExeProjectFile(Map<String, ? super Object> params) { 664 return new File(EXE_IMAGE_DIR.fetchFrom(params), 665 APP_NAME.fetchFrom(params) + ".iss"); 666 } 667 668 669 private File buildEXE(Map<String, ? super Object> params, File outdir) throws IOException { 670 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outdir.getAbsolutePath())); 671 672 outdir.mkdirs(); 673 674 //run candle 675 ProcessBuilder pb = new ProcessBuilder( 676 TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params), 677 "/o"+outdir.getAbsolutePath(), 678 getConfig_ExeProjectFile(params).getAbsolutePath()); 679 pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params)); 680 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 681 682 Log.info(MessageFormat.format(I18N.getString("message.output-location"), outdir.getAbsolutePath())); 683 684 // presume the result is the ".exe" file with the newest modified time 685 // not the best solution, but it is the most reliable 686 File result = null; 687 long lastModified = 0; 688 File[] list = outdir.listFiles(); 689 if (list != null) { 690 for (File f : list) { 691 if (f.getName().endsWith(".exe") && f.lastModified() > lastModified) { 692 result = f; 693 lastModified = f.lastModified(); 694 } 695 } 696 } 697 698 return result; 699 } 700 }