package org.jetbrains.appcode.reveal; import com.intellij.execution.ExecutionException; import com.intellij.execution.util.ExecUtil; import com.intellij.internal.statistic.UsageTrigger; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Version; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.mac.foundation.NSWorkspace; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.cidr.AppleScript; import com.jetbrains.cidr.xcode.frameworks.ApplePlatform; import com.jetbrains.cidr.xcode.frameworks.AppleSdk; import com.jetbrains.cidr.xcode.plist.Plist; import com.jetbrains.cidr.xcode.plist.PlistDriver; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.ide.script.IdeScriptException; import java.io.File; import java.util.List; public class Reveal { public static final Logger LOG = Logger.getInstance("#" + Reveal.class.getPackage().getName()); private static final List<String> APPLICATION_BUNDLE_IDENTIFIERS = ContainerUtil.newArrayList("com.ittybittyapps.Reveal2", "com.ittybittyapps.Reveal"); @Nullable public static File getDefaultRevealApplicationBundle() { for (String identifier: APPLICATION_BUNDLE_IDENTIFIERS) { String path = NSWorkspace.absolutePathForAppBundleWithIdentifier(identifier); if (path != null) { File file = new File(path); if (file.exists()) { return file; } } } return null; } @Nullable private static File getRevealInspectionScript(@NotNull File bundle) { File result = new File(bundle, "/Contents/Resources/InspectApplication.scpt"); return result.exists() ? result : null; } @Contract("_, null -> null") public static File getRevealLib(@NotNull File bundle, @Nullable AppleSdk sdk) { if (sdk == null) return null; ApplePlatform platform = sdk.getPlatform(); String libraryPath = "/Contents/SharedSupport/"; if (platform.isIOS()) { libraryPath += "iOS-Libraries/"; } else if (platform.isTv()) { libraryPath += "tvOS-Libraries/"; } if (isCompatibleWithRevealTwoOrHigher(bundle)) { libraryPath += "RevealServer.framework/RevealServer"; } else if (platform.isTv()) { libraryPath += "libReveal-tvOS.dylib"; } else { libraryPath += "libReveal.dylib"; } File result = new File(bundle, libraryPath); return result.exists() ? result : null; } public static boolean isCompatible(@NotNull File bundle) { Version version = getRevealVersion(bundle); return version != null && version.isOrGreaterThan(2299); } public static boolean isCompatibleWithRevealOnePointSixOrHigher(@NotNull File bundle) { Version version = getRevealVersion(bundle); return version != null && version.isOrGreaterThan(5589); } public static boolean isCompatibleWithRevealTwoOrHigher(@NotNull File bundle) { Version version = getRevealVersion(bundle); return version != null && version.isOrGreaterThan(8378); } @Nullable public static Version getRevealVersion(@NotNull File bundle) { Plist plist = PlistDriver.readAnyFormatSafe(new File(bundle, "Contents/Info.plist")); if (plist == null) return null; String version = plist.getString("CFBundleVersion"); if (version == null) return null; List<String> parts = StringUtil.split(version, "."); if (parts.isEmpty()) return null; return new Version(StringUtil.parseInt(parts.get(0), 0), parts.size() > 1 ? StringUtil.parseInt(parts.get(1), 0) : 0, parts.size() > 2 ? StringUtil.parseInt(parts.get(2), 0) : 0); } public static void refreshReveal(@NotNull File revealBundle, @NotNull String bundleID, @Nullable String deviceName) throws ExecutionException { UsageTrigger.trigger("appcode.reveal.showInReveal"); if (isCompatibleWithRevealOnePointSixOrHigher(revealBundle)) { refreshRevealPostOnePointSix(revealBundle, bundleID, deviceName); } else { refreshRevealPreOnePointSix(bundleID, deviceName); } } private static void refreshRevealPostOnePointSix(@NotNull File revealBundle, @NotNull String bundleID, @Nullable String deviceName) throws ExecutionException { // Reveal 1.6 and later bundle the refresh script with the application - execute it using osascript File inspectionScript = getRevealInspectionScript(revealBundle); if (inspectionScript == null) { throw new ExecutionException("Cannot refresh Reveal. Inspection script could not be found."); } try { List<String> args = ContainerUtil.newArrayList(ExecUtil.getOsascriptPath(), inspectionScript.toString(), bundleID); ContainerUtil.addIfNotNull(args, deviceName); ProcessBuilder pb = new ProcessBuilder(args); Process p = pb.start(); p.waitFor(); } catch (Exception e) { throw new ExecutionException("Cannot refresh Reveal: " + e.getMessage(), e); } } private static void refreshRevealPreOnePointSix(@NotNull String bundleID, @Nullable String deviceName) throws ExecutionException { // Pre Reveal 1.6, the refresh script was not bundled with the application String script = "activate\n" + "repeat with doc in documents\n" + " refresh doc " + " application bundle identifier \"" + StringUtil.escapeQuotes(bundleID) + "\""; if (deviceName != null) { script += " device name \"" + StringUtil.escapeQuotes(deviceName) + "\""; } script += " when available\n" + "end repeat\n" + "activate\n"; try { AppleScript.tell("Reveal", script, true ); } catch (IdeScriptException e) { LOG.info("Reveal script failed:\n" + script); throw new ExecutionException("Cannot refresh Reveal: " + e.getMessage(), e); } } }