1 /* 2 * Copyright (c) 2012, 2015, 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 INSTALLDIR_CHOOSER 168 ); 169 } 170 171 @Override 172 public File execute(Map<String, ? super Object> params, File outputParentDir) { 173 return bundle(params, outputParentDir); 174 } 175 176 static class VersionExtractor extends PrintStream { 177 double version = 0f; 178 179 public VersionExtractor() { 180 super(new ByteArrayOutputStream()); 181 } 182 183 double getVersion() { 184 if (version == 0f) { 185 String content = new String(((ByteArrayOutputStream) out).toByteArray()); 186 Pattern pattern = Pattern.compile("Inno Setup (\\d+.?\\d*)"); 187 Matcher matcher = pattern.matcher(content); 188 if (matcher.find()) { 189 String v = matcher.group(1); 190 version = new Double(v); 191 } 192 } 193 return version; 194 } 195 } 196 197 private static double findToolVersion(String toolName) { 198 try { 199 if (toolName == null || "".equals(toolName)) return 0f; 200 201 ProcessBuilder pb = new ProcessBuilder( 202 toolName, 203 "/?"); 204 VersionExtractor ve = new VersionExtractor(); 205 IOUtils.exec(pb, Log.isDebug(), true, ve); //not interested in the output 206 double version = ve.getVersion(); 207 Log.verbose(MessageFormat.format(I18N.getString("message.tool-version"), toolName, version)); 208 return version; 209 } catch (Exception e) { 210 if (Log.isDebug()) { 211 e.printStackTrace(); 212 } 213 return 0f; 214 } 215 } 216 217 @Override 218 public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 219 try { 220 if (p == null) throw new ConfigException(I18N.getString("error.parameters-null"), I18N.getString("error.parameters-null.advice")); 221 222 //run basic validation to ensure requirements are met 223 //we are not interested in return code, only possible exception 224 APP_BUNDLER.fetchFrom(p).validate(p); 225 226 // make sure some key values don't have newlines 227 for (BundlerParamInfo<String> pi : Arrays.asList( 228 APP_NAME, 229 COPYRIGHT, 230 DESCRIPTION, 231 MENU_GROUP, 232 TITLE, 233 VENDOR, 234 VERSION) 235 ) { 236 String v = pi.fetchFrom(p); 237 if (v.contains("\n") | v.contains("\r")) { 238 throw new ConfigException("Parmeter '" + pi.getID() + "' cannot contain a newline.", 239 "Change the value of '" + pi.getID() + " so that it does not contain any newlines"); 240 } 241 } 242 243 //exe bundlers trim the copyright to 100 characters, tell them this will happen 244 if (COPYRIGHT.fetchFrom(p).length() > 100) { 245 throw new ConfigException( 246 I18N.getString("error.copyright-is-too-long"), 247 I18N.getString("error.copyright-is-too-long.advice")); 248 } 249 250 // validate license file, if used, exists in the proper place 251 if (p.containsKey(LICENSE_FILE.getID())) { 252 List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(p); 253 for (String license : LICENSE_FILE.fetchFrom(p)) { 254 boolean found = false; 255 for (RelativeFileSet appResources : appResourcesList) { 256 found = found || appResources.contains(license); 257 } 258 if (!found) { 259 throw new ConfigException( 260 I18N.getString("error.license-missing"), 261 MessageFormat.format(I18N.getString("error.license-missing.advice"), 262 license)); 263 } 264 } 265 } 266 267 if (SERVICE_HINT.fetchFrom(p)) { 268 SERVICE_BUNDLER.fetchFrom(p).validate(p); 269 } 270 271 double innoVersion = findToolVersion(TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p)); 272 273 //Inno Setup 5+ is required 274 double minVersion = 5.0f; 275 276 if (innoVersion < minVersion) { 277 Log.info(MessageFormat.format(I18N.getString("message.tool-wrong-version"), TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion)); 278 throw new ConfigException( 279 I18N.getString("error.iscc-not-found"), 280 I18N.getString("error.iscc-not-found.advice")); 281 } 282 283 return true; 284 } catch (RuntimeException re) { 285 if (re.getCause() instanceof ConfigException) { 286 throw (ConfigException) re.getCause(); 287 } else { 288 throw new ConfigException(re); 289 } 290 } 291 } 292 293 private boolean prepareProto(Map<String, ? super Object> params) throws IOException { 294 File imageDir = EXE_IMAGE_DIR.fetchFrom(params); 295 File appOutputDir = APP_BUNDLER.fetchFrom(params).doBundle(params, imageDir, true); 296 if (appOutputDir == null) { 297 return false; 298 } 299 300 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 301 if (licenseFiles != null) { 302 //need to copy license file to the root of win.app.image 303 outerLoop: 304 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 305 for (String s : licenseFiles) { 306 if (rfs.contains(s)) { 307 File lfile = new File(rfs.getBaseDirectory(), s); 308 IOUtils.copyFile(lfile, new File(imageDir, lfile.getName())); 309 break outerLoop; 310 } 311 } 312 } 313 } 314 315 for (Map<String, ? super Object> fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) { 316 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 317 if (icon != null && icon.exists()) { 318 IOUtils.copyFile(icon, new File(appOutputDir, icon.getName())); 319 } 320 } 321 322 if (SERVICE_HINT.fetchFrom(params)) { 323 // copies the service launcher to the app root folder 324 appOutputDir = SERVICE_BUNDLER.fetchFrom(params).doBundle(params, appOutputDir, true); 325 if (appOutputDir == null) { 326 return false; 327 } 328 } 329 return true; 330 } 331 332 public File bundle(Map<String, ? super Object> p, File outputDirectory) { 333 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 334 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 335 } 336 if (!outputDirectory.canWrite()) { 337 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 338 } 339 340 // validate we have valid tools before continuing 341 String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p); 342 if (iscc == null || !new File(iscc).isFile()) { 343 Log.info(I18N.getString("error.iscc-not-found")); 344 Log.info(MessageFormat.format(I18N.getString("message.iscc-file-string"), iscc)); 345 return null; 346 } 347 348 File imageDir = EXE_IMAGE_DIR.fetchFrom(p); 349 try { 350 imageDir.mkdirs(); 351 352 boolean menuShortcut = MENU_HINT.fetchFrom(p); 353 boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); 354 if (!menuShortcut && !desktopShortcut) { 355 //both can not be false - user will not find the app 356 Log.verbose(I18N.getString("message.one-shortcut-required")); 357 p.put(MENU_HINT.getID(), true); 358 } 359 360 if (prepareProto(p) && prepareProjectConfig(p)) { 361 File configScript = getConfig_Script(p); 362 if (configScript.exists()) { 363 Log.info(MessageFormat.format(I18N.getString("message.running-wsh-script"), configScript.getAbsolutePath())); 364 IOUtils.run("wscript", configScript, VERBOSE.fetchFrom(p)); 365 } 366 return buildEXE(p, outputDirectory); 367 } 368 return null; 369 } catch (IOException ex) { 370 ex.printStackTrace(); 371 return null; 372 } finally { 373 try { 374 if (VERBOSE.fetchFrom(p)) { 375 saveConfigFiles(p); 376 } 377 if (imageDir != null && !Log.isDebug()) { 378 IOUtils.deleteRecursive(imageDir); 379 } else if (imageDir != null) { 380 Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath())); 381 } 382 } catch (FileNotFoundException ex) { 383 //noinspection ReturnInsideFinallyBlock 384 return null; 385 } 386 } 387 } 388 389 //name of post-image script 390 private File getConfig_Script(Map<String, ? super Object> params) { 391 return new File(EXE_IMAGE_DIR.fetchFrom(params), APP_NAME.fetchFrom(params) + "-post-image.wsf"); 392 } 393 394 protected void saveConfigFiles(Map<String, ? super Object> params) { 395 try { 396 File configRoot = CONFIG_ROOT.fetchFrom(params); 397 if (getConfig_ExeProjectFile(params).exists()) { 398 IOUtils.copyFile(getConfig_ExeProjectFile(params), 399 new File(configRoot, getConfig_ExeProjectFile(params).getName())); 400 } 401 if (getConfig_Script(params).exists()) { 402 IOUtils.copyFile(getConfig_Script(params), 403 new File(configRoot, getConfig_Script(params).getName())); 404 } 405 if (getConfig_SmallInnoSetupIcon(params).exists()) { 406 IOUtils.copyFile(getConfig_SmallInnoSetupIcon(params), 407 new File(configRoot, getConfig_SmallInnoSetupIcon(params).getName())); 408 } 409 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 410 } catch (IOException ioe) { 411 ioe.printStackTrace(); 412 } 413 } 414 415 private String getAppIdentifier(Map<String, ? super Object> params) { 416 String nm = IDENTIFIER.fetchFrom(params); 417 418 //limitation of innosetup 419 if (nm.length() > 126) 420 nm = nm.substring(0, 126); 421 422 return nm; 423 } 424 425 426 private String getLicenseFile(Map<String, ? super Object> params) { 427 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 428 if (licenseFiles == null || licenseFiles.isEmpty()) { 429 return ""; 430 } else { 431 return licenseFiles.get(0); 432 } 433 } 434 435 void validateValueAndPut(Map<String, String> data, String key, BundlerParamInfo<String> param, Map<String, ? super Object> params) throws IOException { 436 String value = param.fetchFrom(params); 437 if (value.contains("\r") || value.contains("\n")) { 438 throw new IOException("Configuration Parameter " + param.getID() + " cannot contain multiple lines of text"); 439 } 440 data.put(key, innosetupEscape(value)); 441 } 442 443 private String innosetupEscape(String value) { 444 if (value.contains("\"") || !value.trim().equals(value)) { 445 value = "\"" + value.replace("\"", "\"\"") + "\""; 446 } 447 return value; 448 } 449 450 boolean prepareMainProjectFile(Map<String, ? super Object> params) throws IOException { 451 Map<String, String> data = new HashMap<>(); 452 data.put("PRODUCT_APP_IDENTIFIER", innosetupEscape(getAppIdentifier(params))); 453 454 validateValueAndPut(data, "APPLICATION_NAME", APP_NAME, params); 455 456 validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params); 457 validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params); // TODO make our own version paraminfo? 458 validateValueAndPut(data, "INSTALLER_FILE_NAME", INSTALLER_FILE_NAME, params); 459 460 data.put("APPLICATION_LAUNCHER_FILENAME", innosetupEscape(WinAppBundler.getLauncherName(params))); 461 462 data.put("APPLICATION_DESKTOP_SHORTCUT", SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 463 data.put("APPLICATION_MENU_SHORTCUT", MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 464 validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params); 465 validateValueAndPut(data, "APPLICATION_COMMENTS", TITLE, params); // TODO this seems strange, at least in name 466 validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params); 467 468 data.put("APPLICATION_LICENSE_FILE", innosetupEscape(getLicenseFile(params))); 469 data.put("DISABLE_DIR_PAGE", INSTALLDIR_CHOOSER.fetchFrom(params) ? "No" : "Yes"); 470 471 Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params); 472 473 if (isSystemWide) { 474 data.put("APPLICATION_INSTALL_ROOT", "{pf}"); 475 data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); 476 } else { 477 data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); 478 data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); 479 } 480 481 if (BIT_ARCH_64.fetchFrom(params)) { 482 data.put("ARCHITECTURE_BIT_MODE", "x64"); 483 } else { 484 data.put("ARCHITECTURE_BIT_MODE", ""); 485 } 486 487 if (SERVICE_HINT.fetchFrom(params)) { 488 data.put("RUN_FILENAME", innosetupEscape(WinServiceBundler.getAppSvcName(params))); 489 } else { 490 validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params); 491 } 492 validateValueAndPut(data, "APPLICATION_DESCRIPTION", DESCRIPTION, params); 493 data.put("APPLICATION_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 494 data.put("APPLICATION_NOT_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnFalse" : "returnTrue"); 495 data.put("APPLICATION_APP_CDS", (UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params)) 496 ? "returnTrue" 497 : "returnFalse"); 498 data.put("START_ON_INSTALL", START_ON_INSTALL.fetchFrom(params) ? "-startOnInstall" : ""); 499 data.put("STOP_ON_UNINSTALL", STOP_ON_UNINSTALL.fetchFrom(params) ? "-stopOnUninstall" : ""); 500 data.put("RUN_AT_STARTUP", RUN_AT_STARTUP.fetchFrom(params) ? "-runAtStartup" : ""); 501 502 StringBuilder secondaryLaunchersCfg = new StringBuilder(); 503 for (Map<String, ? super Object> launcher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 504 String application_name = APP_NAME.fetchFrom(launcher); 505 if (MENU_HINT.fetchFrom(launcher)) { 506 //Name: "{group}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 507 secondaryLaunchersCfg.append("Name: \"{group}\\"); 508 secondaryLaunchersCfg.append(application_name); 509 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 510 secondaryLaunchersCfg.append(application_name); 511 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 512 secondaryLaunchersCfg.append(application_name); 513 secondaryLaunchersCfg.append(".ico\"\r\n"); 514 } 515 if (SHORTCUT_HINT.fetchFrom(launcher)) { 516 //Name: "{commondesktop}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 517 secondaryLaunchersCfg.append("Name: \"{commondesktop}\\"); 518 secondaryLaunchersCfg.append(application_name); 519 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 520 secondaryLaunchersCfg.append(application_name); 521 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 522 secondaryLaunchersCfg.append(application_name); 523 secondaryLaunchersCfg.append(".ico\"\r\n"); 524 } 525 } 526 data.put("SECONDARY_LAUNCHERS", secondaryLaunchersCfg.toString()); 527 528 StringBuilder registryEntries = new StringBuilder(); 529 String regName = APP_REGISTRY_NAME.fetchFrom(params); 530 List<Map<String, ? super Object>> fetchFrom = FILE_ASSOCIATIONS.fetchFrom(params); 531 for (int i = 0; i < fetchFrom.size(); i++) { 532 Map<String, ? super Object> fileAssociation = fetchFrom.get(i); 533 String description = FA_DESCRIPTION.fetchFrom(fileAssociation); 534 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 535 536 List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); 537 String entryName = regName + "File"; 538 if (i > 0) { 539 entryName += "." + i; 540 } 541 542 if (extensions == null) { 543 Log.info(I18N.getString("message.creating-association-with-null-extension")); 544 } else { 545 for (String ext : extensions) { 546 if (isSystemWide) { 547 // "Root: HKCR; Subkey: \".myp\"; ValueType: string; ValueName: \"\"; ValueData: \"MyProgramFile\"; Flags: uninsdeletevalue" 548 registryEntries.append("Root: HKCR; Subkey: \".") 549 .append(ext) 550 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 551 .append(entryName) 552 .append("\"; Flags: uninsdeletevalue\r\n"); 553 } else { 554 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\.") 555 .append(ext) 556 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 557 .append(entryName) 558 .append("\"; Flags: uninsdeletevalue\r\n"); 559 } 560 } 561 } 562 563 if (extensions != null && !extensions.isEmpty()) { 564 String ext = extensions.get(0); 565 List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation); 566 for (String mime : mimeTypes) { 567 if (isSystemWide) { 568 // "Root: HKCR; Subkey: HKCR\\Mime\\Database\\Content Type\\application/chaos; ValueType: string; ValueName: Extension; ValueData: .chaos; Flags: uninsdeletevalue" 569 registryEntries.append("Root: HKCR; Subkey: \"Mime\\Database\\Content Type\\") 570 .append(mime) 571 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 572 .append(ext) 573 .append("\"; Flags: uninsdeletevalue\r\n"); 574 } else { 575 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\Mime\\Database\\Content Type\\") 576 .append(mime) 577 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 578 .append(ext) 579 .append("\"; Flags: uninsdeletevalue\r\n"); 580 } 581 } 582 } 583 584 if (isSystemWide) { 585 //"Root: HKCR; Subkey: \"MyProgramFile\"; ValueType: string; ValueName: \"\"; ValueData: \"My Program File\"; Flags: uninsdeletekey" 586 registryEntries.append("Root: HKCR; Subkey: \"") 587 .append(entryName) 588 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 589 .append(description) 590 .append("\"; Flags: uninsdeletekey\r\n"); 591 } else { 592 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 593 .append(entryName) 594 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 595 .append(description) 596 .append("\"; Flags: uninsdeletekey\r\n"); 597 598 } 599 600 if (icon != null && icon.exists()) { 601 if (isSystemWide) { 602 // "Root: HKCR; Subkey: \"MyProgramFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\MYPROG.EXE,0\"\n" + 603 registryEntries.append("Root: HKCR; Subkey: \"") 604 .append(entryName) 605 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 606 .append(icon.getName()) 607 .append("\"\r\n"); 608 } else { 609 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 610 .append(entryName) 611 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 612 .append(icon.getName()) 613 .append("\"\r\n"); 614 } 615 } 616 617 if (isSystemWide) { 618 //"Root: HKCR; Subkey: \"MyProgramFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" 619 registryEntries.append("Root: HKCR; Subkey: \"") 620 .append(entryName) 621 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 622 .append(APP_NAME.fetchFrom(params)) 623 .append("\"\" \"\"%1\"\"\"\r\n"); 624 } else { 625 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 626 .append(entryName) 627 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 628 .append(APP_NAME.fetchFrom(params)) 629 .append("\"\" \"\"%1\"\"\"\r\n"); 630 } 631 } 632 if (registryEntries.length() > 0) { 633 data.put("FILE_ASSOCIATIONS", "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + registryEntries.toString()); 634 } else { 635 data.put("FILE_ASSOCIATIONS", ""); 636 } 637 638 Writer w = new BufferedWriter(new FileWriter(getConfig_ExeProjectFile(params))); 639 String content = preprocessTextResource( 640 WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ExeProjectFile(params).getName(), 641 I18N.getString("resource.inno-setup-project-file"), DEFAULT_EXE_PROJECT_TEMPLATE, data, 642 VERBOSE.fetchFrom(params), 643 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 644 w.write(content); 645 w.close(); 646 return true; 647 } 648 649 private final static String DEFAULT_INNO_SETUP_ICON = "icon_inno_setup.bmp"; 650 651 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 652 prepareMainProjectFile(params); 653 654 //prepare installer icon 655 File iconTarget = getConfig_SmallInnoSetupIcon(params); 656 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + iconTarget.getName(), 657 I18N.getString("resource.setup-icon"), 658 DEFAULT_INNO_SETUP_ICON, 659 iconTarget, 660 VERBOSE.fetchFrom(params), 661 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 662 663 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_Script(params).getName(), 664 I18N.getString("resource.post-install-script"), 665 (String) null, 666 getConfig_Script(params), 667 VERBOSE.fetchFrom(params), 668 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 669 return true; 670 } 671 672 private File getConfig_SmallInnoSetupIcon(Map<String, ? super Object> params) { 673 return new File(EXE_IMAGE_DIR.fetchFrom(params), 674 APP_NAME.fetchFrom(params) + "-setup-icon.bmp"); 675 } 676 677 private File getConfig_ExeProjectFile(Map<String, ? super Object> params) { 678 return new File(EXE_IMAGE_DIR.fetchFrom(params), 679 APP_NAME.fetchFrom(params) + ".iss"); 680 } 681 682 683 private File buildEXE(Map<String, ? super Object> params, File outdir) throws IOException { 684 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outdir.getAbsolutePath())); 685 686 outdir.mkdirs(); 687 688 //run candle 689 ProcessBuilder pb = new ProcessBuilder( 690 TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params), 691 "/o"+outdir.getAbsolutePath(), 692 getConfig_ExeProjectFile(params).getAbsolutePath()); 693 pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params)); 694 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 695 696 Log.info(MessageFormat.format(I18N.getString("message.output-location"), outdir.getAbsolutePath())); 697 698 // presume the result is the ".exe" file with the newest modified time 699 // not the best solution, but it is the most reliable 700 File result = null; 701 long lastModified = 0; 702 File[] list = outdir.listFiles(); 703 if (list != null) { 704 for (File f : list) { 705 if (f.getName().endsWith(".exe") && f.lastModified() > lastModified) { 706 result = f; 707 lastModified = f.lastModified(); 708 } 709 } 710 } 711 712 return result; 713 } 714 }