/*
* File : ExportTaskDlg.java
* Created : 24-nov-2015 16:37
* By : fbusquets
*
* JClic - Authoring and playing system for educational activities
*
* Copyright (C) 2000 - 2005 Francesc Busquets & Departament
* d'Educacio de la Generalitat de Catalunya
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details (see the LICENSE file).
*/
package edu.xtec.jclic.project;
import edu.xtec.jclic.AuthorSettings;
import edu.xtec.util.BrowserLauncher;
import edu.xtec.util.Messages;
import edu.xtec.util.Options;
import edu.xtec.util.ResourceBridge;
import edu.xtec.util.StrUtils;
import edu.xtec.util.StreamIO;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.ListIterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import org.json.JSONObject;
/**
*
* @author Francesc Busquets (fbusquets@xtec.cat)
* @version 24.11.15
*/
public class ExportTaskDlg extends javax.swing.JPanel {
ResourceBridge rb;
Options options;
edu.xtec.util.SwingWorker sw;
File exportPath;
static String indexHtml = "<!DOCTYPE html>\n"
+ "<html>\n"
+ " <head>\n"
+ " <meta charset=\"UTF-8\">\n"
+ " <title>%TITLE%</title>\n"
+ " <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n"
+ " <meta name=\"mobile-web-app-capable\" content=\"yes\">\n"
+ " <meta name=\"application-name\" content=\"%TITLE%\">\n"
+ " <link rel=\"shortcut icon\" href=\"favicon.ico\">\n"
+ " <link rel=\"icon\" sizes=\"16x16\" href=\"favicon.ico\">\n"
+ " <link rel=\"icon\" sizes=\"72x72\" href=\"icon-72.png\">\n"
+ " <link rel=\"icon\" sizes=\"192x192\" href=\"icon-192.png\">\n"
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
+ " <script type=\"text/javascript\" src=\"%JSCODEBASE%\"></script>\n"
+ " </head>\n"
+ " <body style=\"margin:0\">\n"
+ " <div class =\"JClic\" data-project=\"%MAINFILE%\"></div>\n"
+ " </body>\n"
+ "</html>";
static String imsmanifest = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<!-- Generated by JClic - https://projectestac.github.io/jclic -->\n"
+ "<manifest identifier=\"%SCORMID%\" version=\"1.0\" xmlns=\"http://www.imsproject.org/xsd/imscp_rootv1p1p2\" "
+ "xmlns:adlcp=\"http://www.adlnet.org/xsd/adlcp_rootv1p2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ "xsi:schemaLocation= \"http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd\">\n"
+ " <metadata> \n"
+ " <schema>ADL SCORM</schema>\n"
+ " <schemaversion>1.2</schemaversion>\n"
+ " </metadata> \n"
+ " <organizations default=\"JClic\"> \n"
+ " <organization identifier=\"JClic\"> \n"
+ " <title>%SCORMTITLE%</title>\n"
+ " <item identifier=\"ITEM_%SCORMID%\" identifierref=\"RES_%SCORMID%\">\n"
+ " <title>%SCORMTITLE%</title>\n"
+ " </item>\n"
+ " </organization>\n"
+ " </organizations>\n"
+ " <resources>\n"
+ " <resource identifier=\"RES_%SCORMID%\" type=\"webcontent\" href=\"/index.html\" adlcp:scormtype=\"sco\"> \n"
+ "%FILETAGS% </resource>\n"
+ " </resources>\n"
+ "</manifest>";
/**
* Creates new ExportTaskDlg
*
* @param rb
*/
public ExportTaskDlg(ResourceBridge rb) {
this.rb = rb;
options = rb.getOptions();
initComponents();
}
PrintStream ps = new PrintStream(new ByteArrayOutputStream()) {
@Override
public synchronized void println(String s) {
logArea.append(s + "\n");
logArea.setCaretPosition(logArea.getText().length());
}
@Override
public synchronized void print(String s) {
logArea.append(s);
logArea.setCaretPosition(logArea.getText().length());
}
};
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
logArea = new javax.swing.JTextArea();
javax.swing.JPanel bottomPanel = new javax.swing.JPanel();
copyBtn = new javax.swing.JButton();
browserBtn = new javax.swing.JButton();
cancelBtn = new javax.swing.JButton();
setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10));
setLayout(new java.awt.BorderLayout(0, 10));
jScrollPane1.setPreferredSize(new java.awt.Dimension(600, 500));
logArea.setEditable(false);
logArea.setLineWrap(true);
logArea.setWrapStyleWord(true);
jScrollPane1.setViewportView(logArea);
add(jScrollPane1, java.awt.BorderLayout.CENTER);
bottomPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 10, 5));
copyBtn.setText(options.getMsg("export_project_copyLog"));
copyBtn.setEnabled(false);
copyBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
copyBtnActionPerformed(evt);
}
});
bottomPanel.add(copyBtn);
browserBtn.setIcon(new javax.swing.ImageIcon(getClass().getResource("/edu/xtec/resources/icons/html_doc.gif"))); // NOI18N
browserBtn.setText(options.getMsg("export_project_launchBrowser"));
browserBtn.setEnabled(false);
browserBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
browserBtnActionPerformed(evt);
}
});
bottomPanel.add(browserBtn);
cancelBtn.setText(options.getMsg("CANCEL"));
cancelBtn.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelBtnActionPerformed(evt);
}
});
bottomPanel.add(cancelBtn);
add(bottomPanel, java.awt.BorderLayout.SOUTH);
}// </editor-fold>//GEN-END:initComponents
private void copyBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyBtnActionPerformed
StringSelection stringSelection = new StringSelection(logArea.getText());
Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
clpbrd.setContents(stringSelection, null);
}//GEN-LAST:event_copyBtnActionPerformed
private void cancelBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelBtnActionPerformed
if (sw != null) {
ProjectFileUtils.interrupt = true;
} else {
JDialog myDlg = (JDialog) javax.swing.SwingUtilities.getAncestorOfClass(JDialog.class, this);
if (myDlg != null) {
myDlg.dispose();
}
}
}//GEN-LAST:event_cancelBtnActionPerformed
private void browserBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browserBtnActionPerformed
String url = exportPath + "/index.html";
BrowserLauncher.openURL(url);
ps.println("File " + url + " opened in web browser.");
}//GEN-LAST:event_browserBtnActionPerformed
public static void doTask(ResourceBridge rb, AuthorSettings settings, Component parent,
final String inputPath, final String outputPath, final String mainFileName,
final JClicProject project, final boolean copyAll, final String scormFile) {
final Messages msg = rb.getOptions().getMessages();
final ExportTaskDlg exportDlg = new ExportTaskDlg(rb);
final AuthorSettings set = settings;
JDialog dlg = new JDialog(JOptionPane.getFrameForComponent(parent), true);
dlg.setTitle(msg.get("export_project_exporting"));
dlg.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
dlg.getContentPane().setLayout(new BorderLayout(10, 10));
dlg.getContentPane().add(exportDlg, BorderLayout.CENTER);
dlg.pack();
dlg.setLocationRelativeTo(parent);
dlg.setLocation((parent.getWidth() - dlg.getWidth()) / 2, (parent.getHeight() - dlg.getHeight()) / 2);
exportDlg.sw = new edu.xtec.util.SwingWorker() {
@Override
public Object construct() {
try {
ArrayList<String> fileList = new ArrayList<String>();
fileList.add("imsmanifest.xml");
if (!copyAll) {
exportDlg.ps.println("Processing: " + inputPath);
ProjectFileUtils.processSingleFile(inputPath, outputPath, fileList, exportDlg.ps);
} else {
exportDlg.ps.println("Processing all projects in: " + inputPath);
ProjectFileUtils.processRootFolder(inputPath, outputPath, fileList, exportDlg.ps);
}
exportDlg.ps.println("Generating file " + outputPath + "/index.html");
FileOutputStream fos = new FileOutputStream(new File(outputPath, "index.html"));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"));
String s = indexHtml.replaceAll("%TITLE%", StrUtils.safeHtml(project.settings.title));
s = StrUtils.replace(s, "%MAINFILE%", mainFileName);
s = StrUtils.replace(s, "%JSCODEBASE%", set.jsCodeBase);
pw.print(s);
pw.flush();
pw.close();
fileList.add("index.html");
JSONObject json = project.settings.toJSON(msg);
json.put("mainFile", mainFileName);
exportDlg.exportPath = new File(outputPath);
String fn = "favicon.ico";
exportDlg.ps.println("Copying " + fn);
StreamIO.writeStreamTo(getClass().getResourceAsStream("/edu/xtec/resources/icons/" + fn),
new FileOutputStream(new File(outputPath, fn)));
fileList.add(fn);
fn = "icon-192.png";
exportDlg.ps.println("Copying " + fn);
StreamIO.writeStreamTo(getClass().getResourceAsStream("/edu/xtec/resources/icons/" + fn),
new FileOutputStream(new File(outputPath, fn)));
fileList.add(fn);
fn = "icon-72.png";
exportDlg.ps.println("Copying " + fn);
StreamIO.writeStreamTo(getClass().getResourceAsStream("/edu/xtec/resources/icons/" + fn),
new FileOutputStream(new File(outputPath, fn)));
fileList.add(fn);
exportDlg.ps.println("Copying project cover model");
StreamIO.writeStreamTo(getClass().getResourceAsStream("/edu/xtec/resources/icons/cover-base.jpg"),
new FileOutputStream(new File(outputPath, "project-cover.jpg")));
fileList.add("project-cover.jpg");
json.put("cover", "project-cover.jpg");
exportDlg.ps.println("Copying project thumbnail model");
StreamIO.writeStreamTo(getClass().getResourceAsStream("/edu/xtec/resources/icons/thumb-base.jpg"),
new FileOutputStream(new File(outputPath, "project-thumb.jpg")));
fileList.add("project-thumb.jpg");
json.put("thumbnail", "project-thumb.jpg");
exportDlg.ps.println("Generating project.json");
fileList.add("project.json");
// Order fileList and remove duplicate values
Collections.sort(fileList);
fileList = new ArrayList<String>(new LinkedHashSet<String>(fileList));
json.append("files", fileList);
fos = new FileOutputStream(new File(outputPath, "project.json"));
pw = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"));
pw.print(json.toString(2));
pw.flush();
pw.close();
exportDlg.ps.println("Generating imsmanifest.xml");
fos = new FileOutputStream(new File(outputPath, "imsmanifest.xml"));
pw = new PrintWriter(new OutputStreamWriter(fos, "UTF-8"));
s = imsmanifest.replaceAll("%SCORMTITLE%", StrUtils.safeHtml(project.settings.title));
s = StrUtils.replace(s, "%SCORMID%", "JClic-" + Integer.toHexString((int) (Math.random() * 0xEFFFFF + 0x100000)).toUpperCase());
StringBuilder sb = new StringBuilder();
ListIterator<String> it = fileList.listIterator();
while (it.hasNext()) {
sb.append(" <file href=\"").append(it.next()).append("\"/>\n");
}
s = StrUtils.replace(s, "%FILETAGS%", sb.toString());
pw.print(s);
pw.flush();
pw.close();
if (scormFile != null) {
final byte[] BUFFER = new byte[1024];
exportDlg.ps.println("Generating file \"" + scormFile + "\" with all content inside");
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(scormFile));
it = fileList.listIterator();
while (it.hasNext()) {
String file = it.next();
FileInputStream in = new FileInputStream(new File(outputPath, file));
ZipEntry entry = new ZipEntry(file);
out.putNextEntry(entry);
int i;
while ((i = in.read(BUFFER)) != -1) {
out.write(BUFFER, 0, i);
}
out.closeEntry();
}
out.close();
}
exportDlg.ps.println("\n" + msg.get("export_project_finished") + " " + outputPath);
exportDlg.ps.println("\n" + msg.get("export_project_notice"));
if (scormFile != null) {
exportDlg.ps.printf("\n" + msg.get("export_project_scorm_notice") + "\n\n", scormFile);
}
exportDlg.browserBtn.setEnabled(true);
} catch (InterruptedException iex) {
exportDlg.ps.println("\nWARNING: The process was interrupted! Contents of the output folder might be unsuitable.");
} catch (Exception ex) {
exportDlg.ps.println("\nERROR processing ZIP file: " + ex.getMessage());
}
return null;
}
@Override
public void finished() {
exportDlg.cancelBtn.setText(msg.get("OK"));
exportDlg.copyBtn.setEnabled(true);
exportDlg.sw = null;
}
};
exportDlg.sw.start();
dlg.setVisible(true);
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browserBtn;
private javax.swing.JButton cancelBtn;
private javax.swing.JButton copyBtn;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextArea logArea;
// End of variables declaration//GEN-END:variables
}