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.linux; 27 28 import com.oracle.tools.packager.*; 29 import com.sun.javafx.tools.packager.bundlers.BundleParams; 30 31 import javax.imageio.ImageIO; 32 import java.awt.image.BufferedImage; 33 import java.io.*; 34 import java.nio.file.Files; 35 import java.nio.file.attribute.PosixFilePermission; 36 import java.nio.file.attribute.PosixFilePermissions; 37 import java.text.MessageFormat; 38 import java.util.*; 39 import java.util.logging.Level; 40 import java.util.logging.Logger; 41 import java.util.regex.Pattern; 42 43 import static com.oracle.tools.packager.StandardBundlerParam.*; 44 import static com.oracle.tools.packager.linux.LinuxAppBundler.ICON_PNG; 45 46 public class LinuxDebBundler extends AbstractBundler { 47 48 private static final ResourceBundle I18N = 49 ResourceBundle.getBundle(LinuxDebBundler.class.getName()); 50 51 public static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER = new StandardBundlerParam<>( 52 I18N.getString("param.app-bundler.name"), 53 I18N.getString("param.app-bundler.description"), 54 "linux.app.bundler", 55 LinuxAppBundler.class, 56 params -> new LinuxAppBundler(), 57 (s, p) -> null); 58 59 // Debian rules for package naming are used here 60 // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source 61 // 62 // Package names must consist only of lower case letters (a-z), digits (0-9), 63 // plus (+) and minus (-) signs, and periods (.). 64 // They must be at least two characters long and must start with an alphanumeric character. 65 // 66 private static final Pattern DEB_BUNDLE_NAME_PATTERN = 67 Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+"); 68 69 public static final BundlerParamInfo<String> BUNDLE_NAME = new StandardBundlerParam<> ( 70 I18N.getString("param.bundle-name.name"), 71 I18N.getString("param.bundle-name.description"), 72 "linux.bundleName", 73 String.class, 74 params -> { 75 String nm = APP_NAME.fetchFrom(params); 76 77 if (nm == null) return null; 78 79 // make sure to lower case and spaces/underscores become dashes 80 nm = nm.toLowerCase().replaceAll("[ _]", "-"); 81 return nm; 82 }, 83 (s, p) -> { 84 if (!DEB_BUNDLE_NAME_PATTERN.matcher(s).matches()) { 85 throw new IllegalArgumentException( 86 new ConfigException( 87 MessageFormat.format(I18N.getString("error.invalid-value-for-package-name"), s), 88 I18N.getString("error.invalid-value-for-package-name.advice"))); 89 } 90 91 return s; 92 }); 93 94 public static final BundlerParamInfo<String> FULL_PACKAGE_NAME = new StandardBundlerParam<> ( 95 I18N.getString("param.full-package-name.name"), 96 I18N.getString("param.full-package-name.description"), 97 "linux.deb.fullPackageName", 98 String.class, 99 params -> BUNDLE_NAME.fetchFrom(params) + "-" + VERSION.fetchFrom(params), 100 (s, p) -> s); 101 102 public static final BundlerParamInfo<File> CONFIG_ROOT = new StandardBundlerParam<>( 103 I18N.getString("param.config-root.name"), 104 I18N.getString("param.config-root.description"), 105 "configRoot", 106 File.class, 107 params -> new File(BUILD_ROOT.fetchFrom(params), "linux"), 108 (s, p) -> new File(s)); 109 110 public static final BundlerParamInfo<File> DEB_IMAGE_DIR = new StandardBundlerParam<>( 111 I18N.getString("param.image-dir.name"), 112 I18N.getString("param.image-dir.description"), 113 "linux.deb.imageDir", 114 File.class, 115 params -> { 116 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 117 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 118 return new File(new File(imagesRoot, "linux-deb.image"), FULL_PACKAGE_NAME.fetchFrom(params)); 119 }, 120 (s, p) -> new File(s)); 121 122 public static final BundlerParamInfo<File> APP_IMAGE_ROOT = new StandardBundlerParam<>( 123 I18N.getString("param.app-image-root.name"), 124 I18N.getString("param.app-image-root.description"), 125 "linux.deb.imageRoot", 126 File.class, 127 params -> { 128 File imageDir = DEB_IMAGE_DIR.fetchFrom(params); 129 return new File(imageDir, "opt"); 130 }, 131 (s, p) -> new File(s)); 132 133 public static final BundlerParamInfo<File> CONFIG_DIR = new StandardBundlerParam<>( 134 I18N.getString("param.config-dir.name"), 135 I18N.getString("param.config-dir.description"), 136 "linux.deb.configDir", 137 File.class, 138 params -> new File(DEB_IMAGE_DIR.fetchFrom(params), "DEBIAN"), 139 (s, p) -> new File(s)); 140 141 public static final BundlerParamInfo<String> EMAIL = new StandardBundlerParam<> ( 142 I18N.getString("param.maintainer-email.name"), 143 I18N.getString("param.maintainer-email.description"), 144 BundleParams.PARAM_EMAIL, 145 String.class, 146 params -> "Unknown", 147 (s, p) -> s); 148 149 public static final BundlerParamInfo<String> MAINTAINER = new StandardBundlerParam<> ( 150 I18N.getString("param.maintainer-name.name"), 151 I18N.getString("param.maintainer-name.description"), 152 "linux.deb.maintainer", 153 String.class, 154 params -> VENDOR.fetchFrom(params) + " <" + EMAIL.fetchFrom(params) + ">", 155 (s, p) -> s); 156 157 public static final BundlerParamInfo<String> LICENSE_TEXT = new StandardBundlerParam<> ( 158 I18N.getString("param.license-text.name"), 159 I18N.getString("param.license-text.description"), 160 "linux.deb.licenseText", 161 String.class, 162 params -> { 163 try { 164 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 165 166 //need to copy license file to the root of linux-app.image 167 if (licenseFiles.size() > 0) { 168 String licFileStr = licenseFiles.get(0); 169 170 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 171 if (rfs.contains(licFileStr)) { 172 return new String(IOUtils.readFully(new File(rfs.getBaseDirectory(), licFileStr))); 173 } 174 } 175 } 176 } catch (Exception e) { 177 if (Log.isDebug()) { 178 e.printStackTrace(); 179 } 180 } 181 return LICENSE_TYPE.fetchFrom(params); 182 }, 183 (s, p) -> s); 184 185 public static final BundlerParamInfo<String> XDG_FILE_PREFIX = new StandardBundlerParam<> ( 186 I18N.getString("param.xdg-prefix.name"), 187 I18N.getString("param.xdg-prefix.description"), 188 "linux.xdg-prefix", 189 String.class, 190 params -> { 191 try { 192 String vendor; 193 if (params.containsKey(VENDOR.getID())) { 194 vendor = VENDOR.fetchFrom(params); 195 } else { 196 vendor = "javapackager"; 197 } 198 String appName = APP_FS_NAME.fetchFrom(params); 199 200 return (vendor + "-" + appName).replaceAll("\\s", ""); 201 } catch (Exception e) { 202 if (Log.isDebug()) { 203 e.printStackTrace(); 204 } 205 } 206 return "unknown-MimeInfo.xml"; 207 }, 208 (s, p) -> s); 209 210 private final static String DEFAULT_ICON = "javalogo_white_32.png"; 211 private final static String DEFAULT_CONTROL_TEMPLATE = "template.control"; 212 private final static String DEFAULT_PRERM_TEMPLATE = "template.prerm"; 213 private final static String DEFAULT_PREINSTALL_TEMPLATE = "template.preinst"; 214 private final static String DEFAULT_POSTRM_TEMPLATE = "template.postrm"; 215 private final static String DEFAULT_POSTINSTALL_TEMPLATE = "template.postinst"; 216 private final static String DEFAULT_COPYRIGHT_TEMPLATE = "template.copyright"; 217 private final static String DEFAULT_DESKTOP_FILE_TEMPLATE = "template.desktop"; 218 private final static String DEFAULT_INIT_SCRIPT_TEMPLATE = "template.deb.init.script"; 219 220 public final static String TOOL_DPKG = "dpkg-deb"; 221 222 public LinuxDebBundler() { 223 super(); 224 baseResourceLoader = LinuxResources.class; 225 } 226 227 public static boolean testTool(String toolName, String minVersion) { 228 try { 229 ProcessBuilder pb = new ProcessBuilder( 230 toolName, 231 "--version"); 232 IOUtils.exec(pb, Log.isDebug(), true); //FIXME not interested in the output 233 } catch (Exception e) { 234 Log.verbose(MessageFormat.format(I18N.getString("message.test-for-tool"), toolName, e.getMessage())); 235 return false; 236 } 237 return true; 238 } 239 240 @Override 241 public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 242 try { 243 if (p == null) throw new ConfigException( 244 I18N.getString("error.parameters-null"), 245 I18N.getString("error.parameters-null.advice")); 246 247 //run basic validation to ensure requirements are met 248 //we are not interested in return code, only possible exception 249 APP_BUNDLER.fetchFrom(p).doValidate(p); 250 251 //NOTE: Can we validate that the required tools are available before we start? 252 if (!testTool(TOOL_DPKG, "1")){ 253 throw new ConfigException( 254 MessageFormat.format(I18N.getString("error.tool-not-found"), TOOL_DPKG), 255 I18N.getString("error.tool-not-found.advice")); 256 } 257 258 259 // validate license file, if used, exists in the proper place 260 if (p.containsKey(LICENSE_FILE.getID())) { 261 List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(p); 262 for (String license : LICENSE_FILE.fetchFrom(p)) { 263 boolean found = false; 264 for (RelativeFileSet appResources : appResourcesList) { 265 found = found || appResources.contains(license); 266 } 267 if (!found) { 268 throw new ConfigException( 269 I18N.getString("error.license-missing"), 270 MessageFormat.format(I18N.getString("error.license-missing.advice"), 271 license)); 272 } 273 } 274 } else { 275 Log.info(I18N.getString("message.debs-like-licenses")); 276 } 277 278 boolean serviceHint = p.containsKey(SERVICE_HINT.getID()) && SERVICE_HINT.fetchFrom(p); 279 280 // for services, the app launcher must be less than 16 characters or init.d complains 281 if (serviceHint && BUNDLE_NAME.fetchFrom(p).length() > 16) { 282 throw new ConfigException( 283 MessageFormat.format(I18N.getString("error.launcher-name-too-long"), BUNDLE_NAME.fetchFrom(p)), 284 MessageFormat.format(I18N.getString("error.launcher-name-too-long.advice"), BUNDLE_NAME.getID())); 285 } 286 287 //treat default null as "system wide install" 288 boolean systemWide = SYSTEM_WIDE.fetchFrom(p) == null || SYSTEM_WIDE.fetchFrom(p); 289 290 if (serviceHint && !systemWide) { 291 throw new ConfigException( 292 I18N.getString("error.no-support-for-peruser-daemons"), 293 I18N.getString("error.no-support-for-peruser-daemons.advice")); 294 } 295 296 // only one mime type per association, at least one file extention 297 List<Map<String, ? super Object>> associations = FILE_ASSOCIATIONS.fetchFrom(p); 298 if (associations != null) { 299 for (int i = 0; i < associations.size(); i++) { 300 Map<String, ? super Object> assoc = associations.get(i); 301 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 302 if (mimes == null || mimes.isEmpty()) { 303 throw new ConfigException( 304 MessageFormat.format(I18N.getString("error.no-content-types-for-file-association"), i), 305 I18N.getString("error.no-content-types-for-file-association.advice")); 306 } else if (mimes.size() > 1) { 307 throw new ConfigException( 308 MessageFormat.format(I18N.getString("error.too-many-content-types-for-file-association"), i), 309 I18N.getString("error.too-many-content-types-for-file-association.advice")); 310 } 311 } 312 } 313 314 // bundle name has some restrictions 315 // the string converter will throw an exception if invalid 316 BUNDLE_NAME.getStringConverter().apply(BUNDLE_NAME.fetchFrom(p), p); 317 318 return true; 319 } catch (RuntimeException re) { 320 if (re.getCause() instanceof ConfigException) { 321 throw (ConfigException) re.getCause(); 322 } else { 323 throw new ConfigException(re); 324 } 325 } 326 } 327 328 private boolean prepareProto(Map<String, ? super Object> p) { 329 File appImageRoot = APP_IMAGE_ROOT.fetchFrom(p); 330 File appDir = APP_BUNDLER.fetchFrom(p).doBundle(p, appImageRoot, true); 331 return appDir != null; 332 } 333 334 //@Override 335 public File bundle(Map<String, ? super Object> p, File outdir) { 336 if (!outdir.isDirectory() && !outdir.mkdirs()) { 337 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outdir.getAbsolutePath())); 338 } 339 if (!outdir.canWrite()) { 340 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outdir.getAbsolutePath())); 341 } 342 343 //we want to create following structure 344 // <package-name> 345 // DEBIAN 346 // control (file with main package details) 347 // menu (request to create menu) 348 // ... other control files if needed .... 349 // opt 350 // AppFolder (this is where app image goes) 351 // launcher executable 352 // app 353 // runtime 354 355 File imageDir = DEB_IMAGE_DIR.fetchFrom(p); 356 File configDir = CONFIG_DIR.fetchFrom(p); 357 358 try { 359 360 imageDir.mkdirs(); 361 configDir.mkdirs(); 362 if (prepareProto(p) && prepareProjectConfig(p)) { 363 return buildDeb(p, outdir); 364 } 365 return null; 366 } catch (IOException ex) { 367 ex.printStackTrace(); 368 return null; 369 } finally { 370 try { 371 if (VERBOSE.fetchFrom(p)) { 372 saveConfigFiles(p); 373 } 374 if (imageDir != null && !Log.isDebug()) { 375 IOUtils.deleteRecursive(imageDir); 376 } else if (imageDir != null) { 377 Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath())); 378 } 379 } catch (FileNotFoundException ex) { 380 //noinspection ReturnInsideFinallyBlock 381 return null; 382 } 383 } 384 } 385 386 /* 387 * set permissions with a string like "rwxr-xr-x" 388 * 389 * This cannot be directly backport to 22u which is unfortunately built with 1.6 390 */ 391 private void setPermissions(File file, String permissions) { 392 Set<PosixFilePermission> filePermissions = PosixFilePermissions.fromString(permissions); 393 try { 394 if (file.exists()) { 395 Files.setPosixFilePermissions(file.toPath(), filePermissions); 396 } 397 } catch (IOException ex) { 398 Logger.getLogger(LinuxDebBundler.class.getName()).log(Level.SEVERE, null, ex); 399 } 400 401 } 402 403 protected void saveConfigFiles(Map<String, ? super Object> params) { 404 try { 405 File configRoot = CONFIG_ROOT.fetchFrom(params); 406 File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom(params), params); 407 408 if (getConfig_ControlFile(params).exists()) { 409 IOUtils.copyFile(getConfig_ControlFile(params), 410 new File(configRoot, getConfig_ControlFile(params).getName())); 411 } 412 if (getConfig_CopyrightFile(params).exists()) { 413 IOUtils.copyFile(getConfig_CopyrightFile(params), 414 new File(configRoot, getConfig_CopyrightFile(params).getName())); 415 } 416 if (getConfig_PreinstallFile(params).exists()) { 417 IOUtils.copyFile(getConfig_PreinstallFile(params), 418 new File(configRoot, getConfig_PreinstallFile(params).getName())); 419 } 420 if (getConfig_PrermFile(params).exists()) { 421 IOUtils.copyFile(getConfig_PrermFile(params), 422 new File(configRoot, getConfig_PrermFile(params).getName())); 423 } 424 if (getConfig_PostinstallFile(params).exists()) { 425 IOUtils.copyFile(getConfig_PostinstallFile(params), 426 new File(configRoot, getConfig_PostinstallFile(params).getName())); 427 } 428 if (getConfig_PostrmFile(params).exists()) { 429 IOUtils.copyFile(getConfig_PostrmFile(params), 430 new File(configRoot, getConfig_PostrmFile(params).getName())); 431 } 432 if (getConfig_DesktopShortcutFile(rootDir, params).exists()) { 433 IOUtils.copyFile(getConfig_DesktopShortcutFile(rootDir, params), 434 new File(configRoot, getConfig_DesktopShortcutFile(rootDir, params).getName())); 435 } 436 for (Map<String, ? super Object> secondaryLauncher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 437 if (getConfig_DesktopShortcutFile(rootDir, secondaryLauncher).exists()) { 438 IOUtils.copyFile(getConfig_DesktopShortcutFile(rootDir, secondaryLauncher), 439 new File(configRoot, getConfig_DesktopShortcutFile(rootDir, secondaryLauncher).getName())); 440 } 441 } 442 if (getConfig_IconFile(rootDir, params).exists()) { 443 IOUtils.copyFile(getConfig_IconFile(rootDir, params), 444 new File(configRoot, getConfig_IconFile(rootDir, params).getName())); 445 } 446 if (SERVICE_HINT.fetchFrom(params)) { 447 if (getConfig_InitScriptFile(params).exists()) { 448 IOUtils.copyFile(getConfig_InitScriptFile(params), 449 new File(configRoot, getConfig_InitScriptFile(params).getName())); 450 } 451 } 452 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 453 } catch (IOException ioe) { 454 ioe.printStackTrace(); 455 } 456 } 457 458 private String getArch() { 459 String arch = System.getProperty("os.arch"); 460 if ("i386".equals(arch)) 461 return "i386"; 462 else 463 return "amd64"; 464 } 465 466 private long getInstalledSizeKB(Map<String, ? super Object> params) { 467 return getInstalledSizeKB(APP_IMAGE_ROOT.fetchFrom(params)) >> 10; 468 } 469 470 private long getInstalledSizeKB(File dir) { 471 long count = 0; 472 File[] children = dir.listFiles(); 473 if (children != null) { 474 for (File file : children) { 475 if (file.isFile()) { 476 count += file.length(); 477 } 478 else if (file.isDirectory()) { 479 count += getInstalledSizeKB(file); 480 } 481 } 482 } 483 return count; 484 } 485 486 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 487 Map<String, String> data = createReplacementData(params); 488 File rootDir = LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom(params), params); 489 490 //prepare installer icon 491 File iconTarget = getConfig_IconFile(rootDir, params); 492 File icon = ICON_PNG.fetchFrom(params); 493 if (icon == null || !icon.exists()) { 494 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 495 I18N.getString("resource.menu-icon"), 496 DEFAULT_ICON, 497 iconTarget, 498 VERBOSE.fetchFrom(params), 499 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 500 } else { 501 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 502 I18N.getString("resource.menu-icon"), 503 icon, 504 iconTarget, 505 VERBOSE.fetchFrom(params), 506 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 507 } 508 509 StringBuilder installScripts = new StringBuilder(); 510 StringBuilder removeScripts = new StringBuilder(); 511 for (Map<String, ? super Object> secondaryLauncher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 512 Map<String, String> secondaryLauncherData = createReplacementData(secondaryLauncher); 513 secondaryLauncherData.put("APPLICATION_FS_NAME", data.get("APPLICATION_FS_NAME")); 514 secondaryLauncherData.put("DESKTOP_MIMES", ""); 515 516 //prepare desktop shortcut 517 Writer w = new BufferedWriter(new FileWriter(getConfig_DesktopShortcutFile(rootDir, secondaryLauncher))); 518 String content = preprocessTextResource( 519 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_DesktopShortcutFile(rootDir, secondaryLauncher).getName(), 520 I18N.getString("resource.menu-shortcut-descriptor"), 521 DEFAULT_DESKTOP_FILE_TEMPLATE, 522 secondaryLauncherData, 523 VERBOSE.fetchFrom(params), 524 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 525 w.write(content); 526 w.close(); 527 528 //prepare installer icon 529 iconTarget = getConfig_IconFile(rootDir, secondaryLauncher); 530 icon = ICON_PNG.fetchFrom(secondaryLauncher); 531 if (icon == null || !icon.exists()) { 532 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 533 I18N.getString("resource.menu-icon"), 534 DEFAULT_ICON, 535 iconTarget, 536 VERBOSE.fetchFrom(params), 537 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 538 } else { 539 fetchResource(LinuxAppBundler.LINUX_BUNDLER_PREFIX + iconTarget.getName(), 540 I18N.getString("resource.menu-icon"), 541 icon, 542 iconTarget, 543 VERBOSE.fetchFrom(params), 544 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 545 } 546 547 //postinst copying of desktop icon 548 installScripts.append(" xdg-desktop-menu install --novendor /opt/"); 549 installScripts.append(data.get("APPLICATION_FS_NAME")); 550 installScripts.append("/"); 551 installScripts.append(secondaryLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 552 installScripts.append(".desktop\n"); 553 554 //postrm cleanup of desktop icon 555 removeScripts.append(" xdg-desktop-menu uninstall --novendor /opt/"); 556 removeScripts.append(data.get("APPLICATION_FS_NAME")); 557 removeScripts.append("/"); 558 removeScripts.append(secondaryLauncherData.get("APPLICATION_LAUNCHER_FILENAME")); 559 removeScripts.append(".desktop\n"); 560 } 561 data.put("SECONDARY_LAUNCHERS_INSTALL", installScripts.toString()); 562 data.put("SECONDARY_LAUNCHERS_REMOVE", removeScripts.toString()); 563 564 StringBuilder cdsScript = new StringBuilder(); 565 if (UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params)) { 566 cdsScript.append("/opt/"); 567 cdsScript.append(data.get("APPLICATION_FS_NAME")); 568 cdsScript.append("/"); 569 cdsScript.append(data.get("APPLICATION_LAUNCHER_FILENAME")); 570 cdsScript.append(" -Xappcds:generatecache\n"); 571 } 572 573 data.put("APP_CDS_CACHE", cdsScript.toString()); 574 575 List<Map<String, ? super Object>> associations = FILE_ASSOCIATIONS.fetchFrom(params); 576 data.put("FILE_ASSOCIATION_INSTALL", ""); 577 data.put("FILE_ASSOCIATION_REMOVE", ""); 578 data.put("DESKTOP_MIMES", ""); 579 if (associations != null) { 580 String mimeInfoFile = XDG_FILE_PREFIX.fetchFrom(params) + "-MimeInfo.xml"; 581 StringBuilder mimeInfo = new StringBuilder("<?xml version=\"1.0\"?>\n<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>\n"); 582 StringBuilder registrations = new StringBuilder(); 583 StringBuilder deregistrations = new StringBuilder(); 584 StringBuilder desktopMimes = new StringBuilder("MimeType="); 585 boolean addedEntry = false; 586 587 for (Map<String, ? super Object> assoc : associations) { 588 // <mime-type type="application/x-vnd.awesome"> 589 // <comment>Awesome document</comment> 590 // <glob pattern="*.awesome"/> 591 // <glob pattern="*.awe"/> 592 // </mime-type> 593 594 if (assoc == null) { 595 continue; 596 } 597 598 String description = FA_DESCRIPTION.fetchFrom(assoc); 599 File faIcon = FA_ICON.fetchFrom(assoc); //TODO FA_ICON_PNG 600 List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc); 601 if (extensions == null) { 602 Log.info(I18N.getString("message.creating-association-with-null-extension")); 603 } 604 605 List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc); 606 if (mimes == null || mimes.isEmpty()) { 607 continue; 608 } 609 String thisMime = mimes.get(0); 610 String dashMime = thisMime.replace('/', '-'); 611 612 mimeInfo.append(" <mime-type type='") 613 .append(thisMime) 614 .append("'>\n"); 615 if (description != null && !description.isEmpty()) { 616 mimeInfo.append(" <comment>") 617 .append(description) 618 .append("</comment>\n"); 619 } 620 621 if (extensions != null) { 622 for (String ext : extensions) { 623 mimeInfo.append(" <glob pattern='*.") 624 .append(ext) 625 .append("'/>\n"); 626 } 627 } 628 629 mimeInfo.append(" </mime-type>\n"); 630 if (!addedEntry) { 631 registrations.append(" xdg-mime install /opt/") 632 .append(data.get("APPLICATION_FS_NAME")) 633 .append("/") 634 .append(mimeInfoFile) 635 .append("\n"); 636 637 deregistrations.append(" xdg-mime uninstall /opt/") 638 .append(data.get("APPLICATION_FS_NAME")) 639 .append("/") 640 .append(mimeInfoFile) 641 .append("\n"); 642 addedEntry = true; 643 } else { 644 desktopMimes.append(";"); 645 } 646 desktopMimes.append(thisMime); 647 648 if (faIcon != null && faIcon.exists()) { 649 int size = getSquareSizeOfImage(faIcon); 650 651 if (size > 0) { 652 File target = new File(rootDir, APP_FS_NAME.fetchFrom(params) + "_fa_" + faIcon.getName()); 653 IOUtils.copyFile(faIcon, target); 654 655 //xdg-icon-resource install --context mimetypes --size 64 awesomeapp_fa_1.png application-x.vnd-awesome 656 registrations.append(" xdg-icon-resource install --context mimetypes --size ") 657 .append(size) 658 .append(" /opt/") 659 .append(data.get("APPLICATION_FS_NAME")) 660 .append("/") 661 .append(target.getName()) 662 .append(" ") 663 .append(dashMime) 664 .append("\n"); 665 666 //xdg-icon-resource uninstall --context mimetypes --size 64 awesomeapp_fa_1.png application-x.vnd-awesome 667 deregistrations.append(" xdg-icon-resource uninstall --context mimetypes --size ") 668 .append(size) 669 .append(" /opt/") 670 .append(data.get("APPLICATION_FS_NAME")) 671 .append("/") 672 .append(target.getName()) 673 .append(" ") 674 .append(dashMime) 675 .append("\n"); 676 } 677 } 678 } 679 mimeInfo.append("</mime-info>"); 680 681 if (addedEntry) { 682 Writer w = new BufferedWriter(new FileWriter(new File(rootDir, mimeInfoFile))); 683 w.write(mimeInfo.toString()); 684 w.close(); 685 data.put("FILE_ASSOCIATION_INSTALL", registrations.toString()); 686 data.put("FILE_ASSOCIATION_REMOVE", deregistrations.toString()); 687 data.put("DESKTOP_MIMES", desktopMimes.toString()); 688 } 689 } 690 691 //prepare desktop shortcut 692 Writer w = new BufferedWriter(new FileWriter(getConfig_DesktopShortcutFile(rootDir, params))); 693 String content = preprocessTextResource( 694 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_DesktopShortcutFile(rootDir, params).getName(), 695 I18N.getString("resource.menu-shortcut-descriptor"), 696 DEFAULT_DESKTOP_FILE_TEMPLATE, 697 data, 698 VERBOSE.fetchFrom(params), 699 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 700 w.write(content); 701 w.close(); 702 703 //prepare control file 704 w = new BufferedWriter(new FileWriter(getConfig_ControlFile(params))); 705 content = preprocessTextResource( 706 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_ControlFile(params).getName(), 707 I18N.getString("resource.deb-control-file"), 708 DEFAULT_CONTROL_TEMPLATE, 709 data, 710 VERBOSE.fetchFrom(params), 711 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 712 w.write(content); 713 w.close(); 714 715 w = new BufferedWriter(new FileWriter(getConfig_PreinstallFile(params))); 716 content = preprocessTextResource( 717 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PreinstallFile(params).getName(), 718 I18N.getString("resource.deb-preinstall-script"), 719 DEFAULT_PREINSTALL_TEMPLATE, 720 data, 721 VERBOSE.fetchFrom(params), 722 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 723 w.write(content); 724 w.close(); 725 setPermissions(getConfig_PreinstallFile(params), "rwxr-xr-x"); 726 727 w = new BufferedWriter(new FileWriter(getConfig_PrermFile(params))); 728 content = preprocessTextResource( 729 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PrermFile(params).getName(), 730 I18N.getString("resource.deb-prerm-script"), 731 DEFAULT_PRERM_TEMPLATE, 732 data, 733 VERBOSE.fetchFrom(params), 734 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 735 w.write(content); 736 w.close(); 737 setPermissions(getConfig_PrermFile(params), "rwxr-xr-x"); 738 739 w = new BufferedWriter(new FileWriter(getConfig_PostinstallFile(params))); 740 content = preprocessTextResource( 741 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostinstallFile(params).getName(), 742 I18N.getString("resource.deb-postinstall-script"), 743 DEFAULT_POSTINSTALL_TEMPLATE, 744 data, 745 VERBOSE.fetchFrom(params), 746 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 747 w.write(content); 748 w.close(); 749 setPermissions(getConfig_PostinstallFile(params), "rwxr-xr-x"); 750 751 w = new BufferedWriter(new FileWriter(getConfig_PostrmFile(params))); 752 content = preprocessTextResource( 753 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_PostrmFile(params).getName(), 754 I18N.getString("resource.deb-postrm-script"), 755 DEFAULT_POSTRM_TEMPLATE, 756 data, 757 VERBOSE.fetchFrom(params), 758 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 759 w.write(content); 760 w.close(); 761 setPermissions(getConfig_PostrmFile(params), "rwxr-xr-x"); 762 763 w = new BufferedWriter(new FileWriter(getConfig_CopyrightFile(params))); 764 content = preprocessTextResource( 765 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_CopyrightFile(params).getName(), 766 I18N.getString("resource.deb-copyright-file"), 767 DEFAULT_COPYRIGHT_TEMPLATE, 768 data, 769 VERBOSE.fetchFrom(params), 770 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 771 w.write(content); 772 w.close(); 773 774 if (SERVICE_HINT.fetchFrom(params)) { 775 //prepare init script 776 w = new BufferedWriter(new FileWriter(getConfig_InitScriptFile(params))); 777 content = preprocessTextResource( 778 LinuxAppBundler.LINUX_BUNDLER_PREFIX + getConfig_InitScriptFile(params).getName(), 779 I18N.getString("resource.deb-init-script"), 780 DEFAULT_INIT_SCRIPT_TEMPLATE, 781 data, 782 VERBOSE.fetchFrom(params), 783 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 784 w.write(content); 785 w.close(); 786 setPermissions(getConfig_InitScriptFile(params), "rwxr-xr-x"); 787 } 788 789 return true; 790 } 791 792 private Map<String, String> createReplacementData(Map<String, ? super Object> params) { 793 Map<String, String> data = new HashMap<>(); 794 795 data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params)); 796 data.put("APPLICATION_FS_NAME", APP_FS_NAME.fetchFrom(params)); 797 data.put("APPLICATION_PACKAGE", BUNDLE_NAME.fetchFrom(params)); 798 data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params)); 799 data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params)); 800 data.put("APPLICATION_VERSION", VERSION.fetchFrom(params)); 801 data.put("APPLICATION_LAUNCHER_FILENAME", APP_FS_NAME.fetchFrom(params)); 802 data.put("XDG_PREFIX", XDG_FILE_PREFIX.fetchFrom(params)); 803 data.put("DEPLOY_BUNDLE_CATEGORY", CATEGORY.fetchFrom(params)); 804 data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params)); 805 data.put("APPLICATION_SUMMARY", TITLE.fetchFrom(params)); 806 data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params)); 807 data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params)); 808 data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params)); 809 data.put("APPLICATION_ARCH", getArch()); 810 data.put("APPLICATION_INSTALLED_SIZE", Long.toString(getInstalledSizeKB(params))); 811 data.put("SERVICE_HINT", String.valueOf(SERVICE_HINT.fetchFrom(params))); 812 data.put("START_ON_INSTALL", String.valueOf(START_ON_INSTALL.fetchFrom(params))); 813 data.put("STOP_ON_UNINSTALL", String.valueOf(STOP_ON_UNINSTALL.fetchFrom(params))); 814 data.put("RUN_AT_STARTUP", String.valueOf(RUN_AT_STARTUP.fetchFrom(params))); 815 return data; 816 } 817 818 private File getConfig_DesktopShortcutFile(File rootDir, Map<String, ? super Object> params) { 819 return new File(rootDir, 820 APP_FS_NAME.fetchFrom(params) + ".desktop"); 821 } 822 823 private File getConfig_IconFile(File rootDir, Map<String, ? super Object> params) { 824 return new File(rootDir, 825 APP_FS_NAME.fetchFrom(params) + ".png"); 826 } 827 828 private File getConfig_InitScriptFile(Map<String, ? super Object> params) { 829 return new File(LinuxAppBundler.getRootDir(APP_IMAGE_ROOT.fetchFrom(params), params), 830 BUNDLE_NAME.fetchFrom(params) + ".init"); 831 } 832 833 private File getConfig_ControlFile(Map<String, ? super Object> params) { 834 return new File(CONFIG_DIR.fetchFrom(params), "control"); 835 } 836 837 private File getConfig_PreinstallFile(Map<String, ? super Object> params) { 838 return new File(CONFIG_DIR.fetchFrom(params), "preinst"); 839 } 840 841 private File getConfig_PrermFile(Map<String, ? super Object> params) { 842 return new File(CONFIG_DIR.fetchFrom(params), "prerm"); 843 } 844 845 private File getConfig_PostinstallFile(Map<String, ? super Object> params) { 846 return new File(CONFIG_DIR.fetchFrom(params), "postinst"); 847 } 848 849 private File getConfig_PostrmFile(Map<String, ? super Object> params) { 850 return new File(CONFIG_DIR.fetchFrom(params), "postrm"); 851 } 852 853 private File getConfig_CopyrightFile(Map<String, ? super Object> params) { 854 return new File(CONFIG_DIR.fetchFrom(params), "copyright"); 855 } 856 857 private File buildDeb(Map<String, ? super Object> params, File outdir) throws IOException { 858 File outFile = new File(outdir, FULL_PACKAGE_NAME.fetchFrom(params)+".deb"); 859 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outFile.getAbsolutePath())); 860 861 outFile.getParentFile().mkdirs(); 862 863 //run dpkg 864 ProcessBuilder pb = new ProcessBuilder( 865 "fakeroot", TOOL_DPKG, "-b", FULL_PACKAGE_NAME.fetchFrom(params), 866 outFile.getAbsolutePath()); 867 pb = pb.directory(DEB_IMAGE_DIR.fetchFrom(params).getParentFile()); 868 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 869 870 Log.info(MessageFormat.format(I18N.getString("message.output-to-location"), outFile.getAbsolutePath())); 871 872 return outFile; 873 } 874 875 @Override 876 public String getName() { 877 return I18N.getString("bundler.name"); 878 } 879 880 @Override 881 public String getDescription() { 882 return I18N.getString("bundler.description"); 883 } 884 885 @Override 886 public String getID() { 887 return "deb"; 888 } 889 890 @Override 891 public String getBundleType() { 892 return "INSTALLER"; 893 } 894 895 @Override 896 public Collection<BundlerParamInfo<?>> getBundleParameters() { 897 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 898 results.addAll(LinuxAppBundler.getAppBundleParameters()); 899 results.addAll(getDebBundleParameters()); 900 return results; 901 } 902 903 public static Collection<BundlerParamInfo<?>> getDebBundleParameters() { 904 return Arrays.asList( 905 BUNDLE_NAME, 906 COPYRIGHT, 907 CATEGORY, 908 DESCRIPTION, 909 EMAIL, 910 ICON_PNG, 911 LICENSE_FILE, 912 LICENSE_TYPE, 913 TITLE, 914 VENDOR 915 ); 916 } 917 918 @Override 919 public File execute(Map<String, ? super Object> params, File outputParentDir) { 920 return bundle(params, outputParentDir); 921 } 922 923 public int getSquareSizeOfImage(File f) { 924 try { 925 BufferedImage bi = ImageIO.read(f); 926 if (bi.getWidth() == bi.getHeight()) { 927 return bi.getWidth(); 928 } else { 929 return 0; 930 } 931 } catch (Exception e) { 932 e.printStackTrace(); 933 return 0; 934 } 935 } 936 }