package com.intellij.lang.javascript.flex.actions.airpackage;
import com.intellij.flex.model.bc.BuildConfigurationNature;
import com.intellij.flex.model.bc.TargetPlatform;
import com.intellij.ide.actions.ShowFilePathAction;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.flex.FlexModuleType;
import com.intellij.lang.javascript.flex.actions.ExternalTask;
import com.intellij.lang.javascript.flex.build.FlexResourceBuildTargetScopeProvider;
import com.intellij.lang.javascript.flex.projectStructure.model.*;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.CompileStatusNotification;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Consumer;
import com.intellij.util.PathUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.io.File;
import java.util.*;
import static com.intellij.lang.javascript.flex.actions.airpackage.AirPackageProjectParameters.DesktopPackageType;
public class AirPackageAction extends DumbAwareAction {
public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.balloonGroup("AIR Packaging");
public void update(final AnActionEvent e) {
final Project project = e.getProject();
boolean flexModulePresent = false;
boolean airAppPresent = false;
if (project != null) {
final FlexModuleType flexModuleType = FlexModuleType.getInstance();
MODULES_LOOP:
for (Module module : ModuleManager.getInstance(project).getModules()) {
if (ModuleType.get(module) == flexModuleType) {
flexModulePresent = true;
for (FlexBuildConfiguration bc : FlexBuildConfigurationManager.getInstance(module).getBuildConfigurations()) {
final BuildConfigurationNature nature = bc.getNature();
if (nature.isApp() && !nature.isWebPlatform()) {
airAppPresent = true;
break MODULES_LOOP;
}
}
}
}
}
e.getPresentation().setVisible(flexModulePresent);
e.getPresentation().setEnabled(airAppPresent &&
!CompilerManager.getInstance(project).isCompilationActive() &&
!AirPackageProjectParameters.getInstance(project).isPackagingInProgress());
}
public void actionPerformed(final AnActionEvent e) {
final Project project = e.getProject();
if (project == null) return;
final AirPackageDialog dialog = new AirPackageDialog(project);
if (!dialog.showAndGet()) {
return;
}
final Collection<Pair<Module, FlexBuildConfiguration>> modulesAndBCs = dialog.getSelectedBCs();
final Set<Module> modules = new THashSet<>();
for (Pair<Module, FlexBuildConfiguration> bc : modulesAndBCs) {
modules.add(bc.first);
}
final CompilerManager compilerManager = CompilerManager.getInstance(project);
final CompileScope compileScope = compilerManager.createModulesCompileScope(modules.toArray(new Module[modules.size()]), false);
FlexResourceBuildTargetScopeProvider.setBCsToCompileForPackaging(compileScope, modulesAndBCs);
compilerManager.make(compileScope, new CompileStatusNotification() {
public void finished(final boolean aborted, final int errors, final int warnings, final CompileContext compileContext) {
if (!aborted && errors == 0) {
createPackages(project, modulesAndBCs, dialog.getPasswords());
}
}
});
}
private static void createPackages(final Project project,
final Collection<Pair<Module, FlexBuildConfiguration>> modulesAndBCs,
final PasswordStore passwords) {
final Collection<Pair<ExternalTask, String>> tasksAndPackagePaths = new ArrayList<>();
final AirPackageProjectParameters params = AirPackageProjectParameters.getInstance(project);
for (Pair<Module, FlexBuildConfiguration> moduleAndBC : modulesAndBCs) {
final FlexBuildConfiguration bc = moduleAndBC.second;
final String outputFolder = PathUtil.getParentPath(bc.getActualOutputFilePath());
if (bc.getTargetPlatform() == TargetPlatform.Desktop) {
final DesktopPackageType packageType = params.desktopPackageType;
final ExternalTask task = AirPackageUtil.createAirDesktopTask(moduleAndBC.first, bc, packageType, passwords);
final String packagePath = outputFolder + "/" +
bc.getAirDesktopPackagingOptions().getPackageFileName() + packageType.getFileExtension();
tasksAndPackagePaths.add(Pair.create(task, packagePath));
}
else {
if (bc.getAndroidPackagingOptions().isEnabled()) {
final AndroidPackagingOptions packagingOptions = bc.getAndroidPackagingOptions();
final ExternalTask task = AirPackageUtil.createAndroidPackageTask(moduleAndBC.first, bc, params.androidPackageType,
params.apkCaptiveRuntime, params.apkDebugListenPort, passwords);
final String packagePath = outputFolder + "/" + packagingOptions.getPackageFileName() + ".apk";
tasksAndPackagePaths.add(Pair.create(task, packagePath));
}
if (bc.getIosPackagingOptions().isEnabled()) {
final IosPackagingOptions packagingOptions = bc.getIosPackagingOptions();
final ExternalTask task = AirPackageUtil
.createIOSPackageTask(moduleAndBC.first, bc, params.iosPackageType, params.iosFastPackaging,
bc.getIosPackagingOptions().getSigningOptions().getIOSSdkPath(), 0, passwords);
final String packagePath = outputFolder + "/" + packagingOptions.getPackageFileName() + ".ipa";
tasksAndPackagePaths.add(Pair.create(task, packagePath));
}
}
}
createPackages(project, tasksAndPackagePaths);
}
private static void createPackages(final Project project, final Collection<Pair<ExternalTask, String>> tasksAndPackagePaths) {
final Iterator<Pair<ExternalTask, String>> iterator = tasksAndPackagePaths.iterator();
final Pair<ExternalTask, String> taskAndPackagePath = iterator.next();
final ExternalTask task = taskAndPackagePath.first;
final String packagePath = taskAndPackagePath.second;
final Consumer<List<String>> onSuccessRunnable =
createSuccessConsumer(project, iterator, packagePath, new THashMap<>());
ExternalTask
.runInBackground(task, FlexBundle.message("packaging.air.application", PathUtil.getFileName(packagePath)),
onSuccessRunnable, createFailureConsumer(project, packagePath, task));
}
private static Consumer<List<String>> createSuccessConsumer(final Project project,
final Iterator<Pair<ExternalTask, String>> iterator,
final String createdPackagePath,
final Map<String, List<String>> packagePathsToWarnings) {
return messages -> {
packagePathsToWarnings.put(createdPackagePath, messages);
if (iterator.hasNext()) {
final Pair<ExternalTask, String> taskAndPackagePath = iterator.next();
final ExternalTask task = taskAndPackagePath.first;
final String packagePath = taskAndPackagePath.second;
final Consumer<List<String>> onSuccessRunnable = createSuccessConsumer(project, iterator, packagePath, packagePathsToWarnings);
ExternalTask
.runInBackground(task, FlexBundle.message("packaging.air.application", PathUtil.getFileName(packagePath)),
onSuccessRunnable, createFailureConsumer(project, packagePath, task));
}
else {
final StringBuilder hrefs = new StringBuilder();
for (Map.Entry<String, List<String>> entry : packagePathsToWarnings.entrySet()) {
final String packagePath = entry.getKey();
final List<String> warnings = entry.getValue();
if (hrefs.length() > 0) {
hrefs.append("<br>");
}
hrefs.append("<a href='").append(packagePath).append("'>").append(PathUtil.getFileName(packagePath)).append("</a>");
if (!warnings.isEmpty()) {
hrefs.append("<br>");
for (String warning : warnings) {
hrefs.append(warning).append("<br>");
}
}
}
final String message = FlexBundle.message("air.application.created", packagePathsToWarnings.size(), hrefs);
final NotificationListener listener = new NotificationListener() {
public void hyperlinkUpdate(@NotNull final Notification notification, @NotNull final HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
notification.expire();
final String packagePath = event.getDescription();
ShowFilePathAction.openFile(new File(packagePath));
}
}
};
NOTIFICATION_GROUP.createNotification("", message, NotificationType.INFORMATION, listener).notify(project);
}
};
}
private static Consumer<List<String>> createFailureConsumer(final Project project, final String packagePath, final ExternalTask task) {
return messages -> {
final String reason = StringUtil.join(messages, "<br>");
final NotificationListener listener = new NotificationListener.Adapter() {
@Override
protected void hyperlinkActivated(@NotNull Notification notification, @NotNull HyperlinkEvent e) {
if ("full.error.message".equals(e.getDescription())) {
Messages.showIdeaMessageDialog(project, reason, "Error Message",
new String[]{Messages.OK_BUTTON}, 0, null, null);
}
if ("adt.command.line".equals(e.getDescription())) {
Messages.showIdeaMessageDialog(project, task.getCommandLine(), "ADT Command Line",
new String[]{Messages.OK_BUTTON}, 0, null, null);
}
}
};
final String message;
if (reason.length() > 500) {
message = FlexBundle
.message("failed.to.create.air.package.truncated", PathUtil.getFileName(packagePath), reason.substring(0, 500) + "...");
}
else {
message = FlexBundle.message("failed.to.create.air.package", PathUtil.getFileName(packagePath), reason);
}
NOTIFICATION_GROUP.createNotification("", message, NotificationType.ERROR, listener).notify(project);
};
}
@Nullable
public static PasswordStore getPasswords(final Project project,
final Collection<? extends AirPackagingOptions> allPackagingOptions) {
final Collection<AirSigningOptions> signingOptionsWithUnknownPasswords = new ArrayList<>();
for (AirPackagingOptions packagingOptions : allPackagingOptions) {
final AirSigningOptions signingOptions = packagingOptions.getSigningOptions();
final boolean tempCertificate = !(packagingOptions instanceof IosPackagingOptions) && signingOptions.isUseTempCertificate();
if (!tempCertificate && !PasswordStore.isPasswordKnown(project, signingOptions)) {
signingOptionsWithUnknownPasswords.add(signingOptions);
}
}
if (!signingOptionsWithUnknownPasswords.isEmpty()) {
final KeystorePasswordDialog dialog = new KeystorePasswordDialog(project, signingOptionsWithUnknownPasswords);
return dialog.showAndGet() ? dialog.getPasswords() : null;
}
return PasswordStore.getInstance(project);
}
}