/* Copyright (C) 2005-2011 Fabio Riccardi */
package com.lightcrafts.app;
import static com.lightcrafts.app.Locale.LOCALE;
import com.lightcrafts.app.other.OtherApplication;
import com.lightcrafts.app.other.LightroomApplication;
import com.lightcrafts.app.other.UnknownApplication;
import com.lightcrafts.image.ImageInfo;
import com.lightcrafts.image.export.ImageExportOptions;
import com.lightcrafts.image.types.LZNImageType;
import com.lightcrafts.jai.JAIContext;
import com.lightcrafts.jai.opimage.CachedImage;
import com.lightcrafts.mediax.jai.PlanarImage;
import com.lightcrafts.model.Engine;
import com.lightcrafts.platform.Platform;
import com.lightcrafts.platform.ProgressDialog;
import com.lightcrafts.ui.browser.model.PreviewUpdater;
import com.lightcrafts.ui.editor.Document;
import com.lightcrafts.ui.export.SaveOptions;
import com.lightcrafts.utils.thread.ProgressThread;
import com.lightcrafts.utils.xml.XMLException;
import com.lightcrafts.utils.xml.XmlDocument;
import com.lightcrafts.utils.xml.XmlNode;
import java.awt.*;
import java.awt.image.RenderedImage;
import java.io.*;
/**
* Lengthy procedures that get run during save and export.
*/
public class DocumentWriter {
/**
* Save the given Document to a File, reporting progress during lengthy
* jobs using the given ProgressThread. The XMLException can only arise
* when surprises occur in the LZN structure as it is navigated for
* features that mangle XML like "copy original" and "multilayer TIFF".
*/
public static boolean save(
Document doc, ComboFrame frame, boolean saveDirectly,
ProgressThread progress
) throws IOException {
// Construct the LZN data that encode the Document's state:
XmlDocument xml = new XmlDocument(
Application.LznNamespace, "LightZoneTransform"
);
XmlNode root = xml.getRoot();
doc.save(root);
// Try/catch XML manipulation errors:
try {
// The next steps depend on the SaveOptions:
SaveOptions options = doc.getSaveOptions();
if (options.isLzn()) {
// Just write the XML to a file, with a thumbnail and a preview:
frame.pause();
saveLzn(doc, xml);
frame.resume();
}
else {
// We're performing some kind of Engine export:
Engine engine = doc.getEngine();
ImageExportOptions export =
SaveOptions.getExportOptions(options);
OtherApplication app = (OtherApplication)doc.getSource();
// Mangle LZN and add it to the export options as appropriate:
if (options.isSidecarJpeg() || options.isSidecarTiff()) {
if (app instanceof LightroomApplication) {
File file = LightroomApplication.getOriginalFile(
options.getFile()
);
mangleLznSidecarFile(xml,file);
}
addLznMetadata(export, xml);
}
else if (options.isMultilayerTiff()) {
mangleLznMultilayerTiff(xml);
addLznMetadata(export, xml);
}
String message = LOCALE.get("SavingMessage");
if (app != null && app != UnknownApplication.INSTANCE &&
saveDirectly) {
String name = app.getName();
message = LOCALE.get("SavingToMessage", name);
}
if (progress != null) {
export(engine, export, progress);
return true;
}
return exportWithDialog(engine, export, message, frame);
}
}
catch (XMLException e) {
throw new IOException(
"Internal error in XML mangling: " + e.getMessage()
);
}
return true;
}
/**
* Call DocumentWriter.export() under a progress dialog.
*/
static boolean exportWithDialog(
final Engine engine,
final ImageExportOptions options,
final String title,
final Frame parent
) throws IOException {
ProgressDialog dialog = Platform.getPlatform().getProgressDialog();
ProgressThread thread = new ProgressThread(dialog) {
public void run() {
try {
// Write the file:
export(engine, options, this);
}
catch (IOException e) {
// This exception should be unpacked in the error handling
// below, following dialog.showProgress().
throw new RuntimeException(e);
}
}
};
dialog.showProgress(parent, thread, title, 0, 10, true);
// Unpack any Throwable, in case it hides a checked exception:
Throwable error = dialog.getThrown();
if (error != null) {
if ( error instanceof IOException )
throw (IOException) error;
if ( error instanceof RuntimeException )
throw (RuntimeException) error;
throw new RuntimeException( error );
}
if (thread.isCanceled()) {
return false;
}
return true;
}
/**
* Write the XML to a file as-is, and add a thumbnail for the browser.
*/
static void saveLzn(Document doc, XmlDocument xmlDoc) throws IOException {
Engine engine = doc.getEngine();
// Fill up the LZN file:
SaveOptions options = doc.getSaveOptions();
File file = options.getFile();
OutputStream out = new FileOutputStream(file);
xmlDoc.write(out);
out.close();
// Add thumbnail data for the browser:
RenderedImage thumb = engine.getRendering(new Dimension(320, 320));
// divorce the preview from the document
thumb = new CachedImage((PlanarImage) thumb, JAIContext.fileCache);
ImageInfo info = ImageInfo.getInstanceFor(file);
LZNImageType.INSTANCE.putImage(info, thumb);
// Cache a high resolution preview:
int size = PreviewUpdater.PreviewSize;
RenderedImage preview = engine.getRendering(new Dimension(size, size));
// divorce the preview from the document
preview = new CachedImage((PlanarImage) preview, JAIContext.fileCache);
PreviewUpdater.cachePreviewForImage(file, preview);
}
/**
* Set the "auxilliary data" on the ImageExportOptions
* to an XMP structure derived from the contents of the XmlDocument while
* preserving any preexisting XMP in the given metadata.
*/
private static void addLznMetadata( ImageExportOptions export,
XmlDocument xml ) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
xml.write( out );
byte[] bytes = out.toByteArray();
export.setAuxData( bytes );
}
private static void mangleLznSidecarFile(XmlDocument xml, File file)
throws XMLException
{
// Tags cloned from com.lightcrafts.ui.editor.Document:
XmlNode root = xml.getRoot();
XmlNode imageNode = root.getChild("Image");
imageNode.setAttribute("path", file.getAbsolutePath());
imageNode.setAttribute("relativePath", file.getName());
}
private static void mangleLznMultilayerTiff(XmlDocument xml)
throws XMLException
{
// Tags cloned from com.lightcrafts.ui.editor.Document:
XmlNode root = xml.getRoot();
XmlNode imageNode = root.getChild("Image");
imageNode.setAttribute("path", "");
imageNode.setAttribute("relativePath", "");
imageNode.setAttribute("self", "true");
}
/**
* Call Engine.export() with some options and a progress object for
* feedback. This works by creating a temp file, streaming into that,
* removing the final destination file if it exists, and then renaming
* the temp file. If anything goes wrong, the temp file gets cleaned up.
*/
public static void export(
Engine engine, ImageExportOptions options, ProgressThread progress
) throws IOException {
File tempFile = null;
try {
// Set up the temp file where the export goes first:
File exportFile = options.getExportFile();
File exportDir = exportFile.getParentFile();
tempFile = File.createTempFile(
"LZExport", ".tmp", exportDir
);
options.setExportFile(tempFile);
// Write to the temp file:
engine.write(progress, options);
// Restore the final destination file:
options.setExportFile(exportFile);
// First unlink any file that is in the way:
if (exportFile.exists()) {
boolean deleteOK = exportFile.delete();
if (! deleteOK) {
// What side effects does closeAll() have on other threads?
ImageInfo.closeAll();
deleteOK = exportFile.delete();
}
if (! deleteOK) {
throw new IOException(
LOCALE.get("ExportDeleteError", exportFile.getPath())
);
}
}
// Then move the temp file to the final destination:
boolean renameOK = tempFile.renameTo(exportFile);
if (! renameOK) {
// What side effects does closeAll() have on other threads?
ImageInfo.closeAll();
renameOK = tempFile.renameTo(exportFile);
}
if (! renameOK) {
throw new IOException(
LOCALE.get(
"ExportRenameError",
tempFile.getPath(),
exportFile.getPath()
)
);
}
}
finally {
if (tempFile != null) {
tempFile.delete();
}
}
}
}