package org.xmind.ui.internal.editor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.ErrorSupportProvider;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.AbstractHyperlink;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.statushandlers.AbstractStatusAreaProvider;
import org.eclipse.ui.statushandlers.StatusAdapter;
import org.xmind.core.Core;
import org.xmind.core.CoreException;
import org.xmind.core.IDeserializer;
import org.xmind.core.IFileEntry;
import org.xmind.core.IMeta;
import org.xmind.core.IRevision;
import org.xmind.core.IRevisionManager;
import org.xmind.core.IRevisionRepository;
import org.xmind.core.ISerializer;
import org.xmind.core.ISheet;
import org.xmind.core.ITopic;
import org.xmind.core.IWorkbook;
import org.xmind.core.event.ICoreEventSource2;
import org.xmind.core.io.DirectoryInputSource;
import org.xmind.core.io.DirectoryOutputTarget;
import org.xmind.core.io.IStorage;
import org.xmind.core.util.CloneHandler;
import org.xmind.core.util.FileUtils;
import org.xmind.core.util.ProgressReporter;
import org.xmind.ui.blackbox.BlackBox;
import org.xmind.ui.blackbox.IBlackBoxMap;
import org.xmind.ui.internal.MindMapMessages;
import org.xmind.ui.internal.MindMapUIPlugin;
import org.xmind.ui.internal.dialogs.BlackBoxDialog;
import org.xmind.ui.internal.handlers.OpenBlackBoxDialogHandler;
import org.xmind.ui.internal.protocols.FilePathParser;
import org.xmind.ui.internal.utils.CommandUtils;
import org.xmind.ui.mindmap.IWorkbookRef;
import org.xmind.ui.mindmap.MindMapImageExporter;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.prefs.PrefConstants;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.util.ImageFormat;
import org.xmind.ui.util.Logger;
/**
* @author Frank Shaka
* @since 3.6.50
*/
public class LocalFileWorkbookRef extends AbstractWorkbookRef {
private static boolean DEBUG_BACKUP = MindMapUIPlugin
.isDebugging(MindMapUIPlugin.OPTION_LOCAL_FILE_BACKUP);
private static class LocalFileBackup {
private final File file;
private final File tempFile;
/**
*
*/
public LocalFileBackup(File file) {
this.file = file;
this.tempFile = new File(Core.getWorkspace().getTempFile(
"saving/" + UUID.randomUUID() + MindMapUI.FILE_EXT_XMIND)); //$NON-NLS-1$
}
public void makeBackup() {
if (!file.exists() || !file.canRead()) {
tempFile.delete();
return;
}
if (DEBUG_BACKUP) {
System.out.println("Making local file backup for: " //$NON-NLS-1$
+ file.getAbsolutePath());
System.out.println(" to: " + tempFile.getAbsolutePath()); //$NON-NLS-1$
}
try {
FileUtils.transfer(file, tempFile);
} catch (IOException e) {
MindMapUIPlugin.getDefault().getLog()
.log(new Status(IStatus.WARNING,
MindMapUIPlugin.PLUGIN_ID,
"Failed to make backup of local file: " //$NON-NLS-1$
+ file.getAbsolutePath(),
e));
}
}
public void restoreBackup() {
if (!tempFile.exists() || !tempFile.canRead()) {
return;
}
if (DEBUG_BACKUP) {
System.out.println("Restoring local file backup for: " //$NON-NLS-1$
+ file.getAbsolutePath());
System.out.println(" from: " + tempFile.getAbsolutePath()); //$NON-NLS-1$
}
try {
FileUtils.transfer(tempFile, file);
} catch (IOException e) {
MindMapUIPlugin.getDefault().getLog()
.log(new Status(IStatus.WARNING,
MindMapUIPlugin.PLUGIN_ID,
"Failed to restore backup of local file: " //$NON-NLS-1$
+ file.getAbsolutePath(),
e));
}
}
public void deleteBackup() {
if (DEBUG_BACKUP) {
System.out.println("Deleting local file backup for: " //$NON-NLS-1$
+ file.getAbsolutePath());
System.out.println(" at: " + tempFile.getAbsolutePath()); //$NON-NLS-1$
}
tempFile.delete();
}
}
private class LocalFileErrorSupportProvider
extends AbstractStatusAreaProvider {
/*
* (non-Javadoc)
* @see org.eclipse.ui.statushandlers.AbstractStatusAreaProvider#
* createSupportArea(org.eclipse.swt.widgets.Composite,
* org.eclipse.ui.statushandlers.StatusAdapter)
*/
@Override
public Control createSupportArea(Composite parent,
StatusAdapter statusAdapter) {
IBlackBoxMap[] blackBoxMaps = BlackBox.getMaps();
if (!MindMapUIPlugin.getDefault().getPreferenceStore()
.getBoolean(PrefConstants.AUTO_BACKUP_ENABLE)
|| (blackBoxMaps == null || blackBoxMaps.length == 0))
return null;
Composite hyperParent = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
hyperParent.setLayout(layout);
hyperParent.setBackground(parent.getBackground());
Label preLink = new Label(hyperParent, SWT.NONE);
preLink.setText(
MindMapMessages.LoadWorkbookJob_errorDialog_Pre_message);
preLink.setLayoutData(
new GridData(SWT.FILL, SWT.CENTER, false, false));
preLink.setBackground(parent.getBackground());
Hyperlink hyperlink = new Hyperlink(hyperParent, SWT.NONE);
hyperlink.setBackground(parent.getBackground());
hyperlink.setUnderlined(true);
hyperlink.setText(
MindMapMessages.LoadWorkbookJob_errorDialog_GoToBackup_message);
hyperlink.setLayoutData(
new GridData(SWT.FILL, SWT.CENTER, true, false));
hyperlink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
showBlackBoxView();
}
});
hyperlink.setFont(
FontUtils.getBoldRelative(JFaceResources.DEFAULT_FONT, 0));
/* Prevent focus box from being painted: */
try {
Field fPaintFocus = AbstractHyperlink.class
.getDeclaredField("paintFocus"); //$NON-NLS-1$
fPaintFocus.setAccessible(true);
fPaintFocus.set(hyperlink, false);
} catch (Throwable e) {
// ignore
}
return hyperParent;
}
private void showBlackBoxView() {
final File damagedFile = getFile();
if (PlatformUI.isWorkbenchRunning()) {
final IWorkbenchWindow window = PlatformUI.getWorkbench()
.getActiveWorkbenchWindow();
if (window != null) {
final IWorkbenchPage page = window.getActivePage();
if (page != null) {
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
CommandUtils.executeCommand(
"org.xmind.ui.dialog.openBlackBoxDialog", //$NON-NLS-1$
window);
//set damaged file.
Object data = Display.getCurrent()
.getActiveShell().getData(
OpenBlackBoxDialogHandler.BLACK_BOX_DIALOG_DATA_KEY);
if (data instanceof BlackBoxDialog) {
BlackBoxDialog dialog = (BlackBoxDialog) data;
dialog.setDamagedFile(damagedFile);
}
}
});
}
}
}
}
}
private final LocalFileBackup backup;
private long timestamp;
private final ErrorSupportProvider errorSupportProvider;
protected LocalFileWorkbookRef(URI uri, IMemento memento) {
super(uri, memento);
Assert.isNotNull(uri);
Assert.isLegal(FilePathParser.URI_SCHEME.equals(uri.getScheme()),
"Invalid file URI: " + uri.toString()); //$NON-NLS-1$
this.backup = new LocalFileBackup(new File(uri));
this.errorSupportProvider = new LocalFileErrorSupportProvider();
}
/*
* (non-Javadoc)
* @see
* org.xmind.ui.internal.editor.AbstractWorkbookRef#getAdapter(java.lang.
* Class)
*/
@Override
public <T> T getAdapter(Class<T> adapter) {
if (ErrorSupportProvider.class.equals(adapter)) {
return adapter.cast(errorSupportProvider);
}
return super.getAdapter(adapter);
}
/*
* (non-Javadoc)
* @see org.xmind.ui.internal.editor.AbstractWorkbookRef#getSaveWizardId()
*/
@Override
public String getSaveWizardId() {
return LocalFileSaveWizard.ID;
}
private File getFile() {
return new File(getURI());
}
@Override
public String getName() {
String name = getFile().getName();
if (name != null) {
int dotIndex = name.lastIndexOf('.');
if (dotIndex < 0)
return name;
return name.substring(0, dotIndex);
}
return super.getName();
}
@Override
public String getDescription() {
return getFile().getAbsolutePath();
}
@Override
public int hashCode() {
return getFile().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null || !(obj instanceof LocalFileWorkbookRef))
return false;
LocalFileWorkbookRef that = (LocalFileWorkbookRef) obj;
File thisFile = this.getFile();
File thatFile = that.getFile();
return thisFile == thatFile
|| (thisFile != null && thisFile.equals(thatFile));
}
@Override
public String toString() {
return getFile().toString();
}
@Override
public boolean canSave() {
return true;
}
@Override
public boolean canImportFrom(IWorkbookRef source) {
return true;
}
/*
* (non-Javadoc)
* @see org.xmind.gef.ui.editor.Editable#getModificationTime()
*/
@Override
public long getModificationTime() {
return getFile().lastModified();
}
@Override
protected IWorkbook doLoadWorkbookFromURI(IProgressMonitor monitor, URI uri)
throws InterruptedException, InvocationTargetException {
File file = new File(uri);
try {
IDeserializer deserializer = Core.getWorkbookBuilder()
.newDeserializer();
deserializer.setEntryStreamNormalizer(getEncryptionHandler());
deserializer.setWorkbookStorage(getTempStorage());
InputStream stream = null;
try {
if (file.isDirectory()) {
deserializer.setInputSource(new DirectoryInputSource(file));
} else {
stream = new FileInputStream(file);
deserializer.setInputStream(stream);
}
ProgressReporter reporter = new ProgressReporter(monitor);
deserializer.deserializeManifest(reporter);
String passwordHint = deserializer.getManifest()
.getPasswordHint();
getEncryptable().setPasswordHint(passwordHint);
deserializer.deserialize(reporter);
} finally {
if (stream != null) {
stream.close();
}
}
return deserializer.getWorkbook();
} catch (IOException e) {
throw new InvocationTargetException(e);
} catch (CoreException e) {
if (e.getType() == Core.ERROR_CANCELLATION)
throw new InterruptedException();
if (e.getType() == Core.ERROR_WRONG_PASSWORD) {
if (getEncryptable() != null) {
getEncryptable().reset();
}
}
throw new InvocationTargetException(e);
}
}
@Override
protected void doSaveWorkbookToURI(IProgressMonitor monitor,
IWorkbook workbook, URI uri)
throws InterruptedException, InvocationTargetException {
doSaveWorkbookToURIFromSource(monitor, workbook, uri, null, null);
}
protected void doSaveWorkbookToURIFromSource(IProgressMonitor monitor,
IWorkbook workbook, URI uri, IWorkbookRef source,
IWorkbook sourceWorkbook)
throws InterruptedException, InvocationTargetException {
final Set<String> encryptionIgnoredEntries = new HashSet<String>();
boolean previewSaved = false;
if (source != null) {
try {
InputStream previewData = source.getPreviewImageData(
sourceWorkbook.getPrimarySheet().getId(), null);
if (previewData != null) {
try {
IFileEntry previewEntry = workbook.getManifest()
.createFileEntry(MindMapImageExporter
.toThumbnailArchivePath(
ImageFormat.PNG));
previewEntry.decreaseReference();
previewEntry.increaseReference();
OutputStream output = previewEntry.openOutputStream();
try {
FileUtils.transfer(previewData, output, false);
} finally {
output.close();
}
encryptionIgnoredEntries.add(previewEntry.getPath());
} finally {
previewData.close();
}
previewSaved = true;
}
} catch (IOException e) {
Logger.log(e, "Failed to import preview image"); //$NON-NLS-1$
}
}
if (!previewSaved) {
savePreviewImage(workbook, encryptionIgnoredEntries);
}
//save revisions, and then trim them.
try {
saveRevisions(workbook);
} catch (CoreException e) {
throw new InvocationTargetException(e);
}
File file = new File(uri);
ISerializer serializer = Core.getWorkbookBuilder().newSerializer();
serializer.setWorkbook(workbook);
serializer.setEntryStreamNormalizer(getEncryptionHandler());
serializer.setEncryptionIgnoredEntries(encryptionIgnoredEntries
.toArray(new String[encryptionIgnoredEntries.size()]));
/// set password hint to manifest
String passwordHint = getEncryptable().getPasswordHint();
if (passwordHint == null && source != null) {
IEncryptable encryptable = source.getAdapter(IEncryptable.class);
if (encryptable != null) {
passwordHint = encryptable.getPasswordHint();
}
}
if (passwordHint != null)
workbook.getManifest().setPasswordHint(passwordHint);
try {
OutputStream stream = null;
try {
if (file.isDirectory()) {
backup.deleteBackup();
serializer.setOutputTarget(new DirectoryOutputTarget(file));
} else {
backup.makeBackup();
stream = new FileOutputStream(file);
serializer.setOutputStream(stream);
}
serializer.serialize(new ProgressReporter(monitor));
backup.deleteBackup();
} finally {
if (stream != null) {
stream.close();
}
}
} catch (IOException e) {
backup.restoreBackup();
throw new InvocationTargetException(e);
} catch (CoreException e) {
backup.restoreBackup();
if (e.getType() == Core.ERROR_CANCELLATION)
throw new InterruptedException();
if (e.getType() == Core.ERROR_WRONG_PASSWORD) {
if (getEncryptable() != null) {
getEncryptable().reset();
}
}
throw new InvocationTargetException(e);
}
}
/**
* @param workbook
* @param encryptionIgnoredEntries
* @throws InvocationTargetException
*/
private void savePreviewImage(IWorkbook workbook,
final Set<String> encryptionIgnoredEntries)
throws InvocationTargetException {
IMindMapPreviewGenerator previewGenerator = findPreviewGenerator();
if (previewGenerator != null) {
ImageFormat previewFormat = ImageFormat.PNG;
IFileEntry entry = workbook.getManifest().createFileEntry(
MindMapImageExporter.toThumbnailArchivePath(previewFormat),
previewFormat.getMediaType());
entry.decreaseReference();
entry.increaseReference();
final Properties previewProperties;
try {
OutputStream previewOutput = entry.openOutputStream();
try {
previewProperties = previewGenerator.generateMindMapPreview(
this, workbook.getPrimarySheet(), previewOutput,
null);
} finally {
previewOutput.close();
}
} catch (IOException e) {
throw new InvocationTargetException(e);
}
workbook.getMeta().setValue(IMeta.ORIGIN_X, previewProperties
.getProperty(IMindMapPreviewGenerator.PREVIEW_ORIGIN_X));
workbook.getMeta().setValue(IMeta.ORIGIN_Y, previewProperties
.getProperty(IMindMapPreviewGenerator.PREVIEW_ORIGIN_Y));
workbook.getMeta().setValue(IMeta.BACKGROUND_COLOR,
previewProperties.getProperty(
IMindMapPreviewGenerator.PREVIEW_BACKGROUND));
encryptionIgnoredEntries.add(entry.getPath());
}
}
private void saveRevisions(IWorkbook workbook) throws CoreException {
if (!isContentDirty(workbook) || !shouldSaveNewRevisions(workbook))
return;
IRevisionRepository repo = workbook.getRevisionRepository();
for (ISheet sheet : workbook.getSheets()) {
IRevisionManager manager = repo.getRevisionManager(sheet.getId(),
IRevision.SHEET);
IRevision latestRevision = manager.getLatestRevision();
if (latestRevision == null || sheet.getModifiedTime() == 0 || sheet
.getModifiedTime() > latestRevision.getTimestamp()) {
try {
manager.addRevision(sheet);
} catch (Throwable e) {
throw new CoreException(Core.ERROR_INVALID_ARGUMENT,
"Invalid content for revisions."); //$NON-NLS-1$
}
}
}
}
private boolean isContentDirty(IWorkbook workbook) {
if (workbook == null)
return false;
if (getCommandStack() != null && getCommandStack().isDirty())
return true;
return workbook instanceof ICoreEventSource2
&& ((ICoreEventSource2) workbook)
.hasOnceListeners(Core.WorkbookPreSaveOnce);
}
private boolean shouldSaveNewRevisions(IWorkbook workbook) {
String value = workbook.getMeta()
.getValue(IMeta.CONFIG_AUTO_REVISION_GENERATION);
return value == null || IMeta.V_YES.equalsIgnoreCase(value);
}
@Override
protected void doImportFrom(IProgressMonitor monitor, IWorkbookRef source)
throws InterruptedException, InvocationTargetException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
IWorkbook sourceWorkbook = source.getWorkbook();
Assert.isTrue(sourceWorkbook != null);
URI targetURI = getURI();
Assert.isTrue(targetURI != null);
IStorage storage = getTempStorage();
IEncryptable sourceEncryptable = source.getAdapter(IEncryptable.class);
if (sourceEncryptable != null && sourceEncryptable.hasPassword()
&& sourceEncryptable instanceof WorkbookRefEncryptable) {
// getEncryptable()
// .setEncryptor(((WorkbookRefEncryptable) sourceEncryptable)
// .getEncryptor());
getEncryptable().setPassword(sourceEncryptable.getPassword());
getEncryptable()
.setPasswordHint(sourceEncryptable.getPasswordHint());
}
doClearTempStorageBeforeImport(subMonitor.newChild(5), storage);
try {
IWorkbook workbook = doCloneWorkbookForImport(
subMonitor.newChild(40), sourceWorkbook, source.getURI(),
storage);
doSaveWorkbookToURIFromSource(subMonitor.newChild(50), workbook,
targetURI, source, sourceWorkbook);
} finally {
subMonitor.setWorkRemaining(5);
doClearTempStorageAfterImport(subMonitor.newChild(5), storage);
}
}
protected void doClearTempStorageBeforeImport(IProgressMonitor monitor,
IStorage storage)
throws InterruptedException, InvocationTargetException {
storage.clear();
}
protected void doClearTempStorageAfterImport(IProgressMonitor monitor,
IStorage storage) {
storage.clear();
}
protected IWorkbook doCloneWorkbookForImport(IProgressMonitor monitor,
IWorkbook sourceWorkbook, URI sourceURI, IStorage storage)
throws InterruptedException, InvocationTargetException {
try {
IWorkbook workbook = Core.getWorkbookBuilder()
.createWorkbook(storage);
ISerializer serializer = Core.getWorkbookBuilder().newSerializer();
serializer.setWorkbook(workbook);
serializer.setWorkbookStorageAsOutputTarget();
serializer.setEntryStreamNormalizer(getEncryptionHandler());
serializer.serialize(null);
new CloneHandler().withWorkbooks(sourceWorkbook, workbook)
.copyWorkbookContents();
String oldFilePath;
if (sourceURI != null && FilePathParser.URI_SCHEME
.equals(sourceURI.getScheme())) {
oldFilePath = new File(sourceURI).getAbsolutePath();
} else {
oldFilePath = null;
}
updateAllRelativeFileHyperlinks(workbook, oldFilePath,
getFile().getAbsolutePath());
return workbook;
} catch (IOException e) {
throw new InvocationTargetException(e);
} catch (CoreException e) {
if (e.getType() == Core.ERROR_CANCELLATION)
throw new InterruptedException();
throw new InvocationTargetException(e);
}
}
private void updateAllRelativeFileHyperlinks(IWorkbook workbook,
String oldFilePath, String newFilePath) {
String oldBase;
if (oldFilePath == null) {
oldBase = FilePathParser.ABSTRACT_FILE_BASE;
} else {
oldBase = new File(oldFilePath).getParent();
}
String newBase = new File(newFilePath).getParent();
if (oldBase.equals(newBase))
return;
List<ISheet> sheets = workbook.getSheets();
for (ISheet sheet : sheets) {
updateRelativeFileHyperlinks(sheet.getRootTopic(), oldBase,
newBase);
}
}
private void updateRelativeFileHyperlinks(ITopic topic, String oldBase,
String newBase) {
String hyperlink = topic.getHyperlink();
if (FilePathParser.isFileURI(hyperlink)) {
String path = FilePathParser.toPath(hyperlink);
if (FilePathParser.isPathRelative(path)) {
String absolutePath = FilePathParser.toAbsolutePath(oldBase,
path);
hyperlink = FilePathParser.toRelativePath(newBase,
absolutePath);
topic.setHyperlink(FilePathParser.toURI(hyperlink, true));
}
}
List<ITopic> topics = topic.getAllChildren();
if (topics != null) {
for (ITopic temptopic : topics) {
updateRelativeFileHyperlinks(temptopic, oldBase, newBase);
}
}
}
@Override
public boolean activateNotifier() {
File file = getFile();
if (!file.exists()) {
fileRemoved(MindMapMessages.LocalFileWorkbookRef_removeDialog_title,
MindMapMessages.LocalFileWorkbookRef_removeDialog_message,
new String[] {
MindMapMessages.LocalFileWorkbookRef_removeDialog_saveAs_button,
MindMapMessages.LocalFileWorkbookRef_removeDialog_delete_button },
false);
return true;
} else if (file.lastModified() != timestamp) {
fileChanged(MindMapMessages.LocalFileWorkbookRef_changeDialog_title,
MindMapMessages.LocalFileWorkbookRef_changeDialog_message,
new String[] {
MindMapMessages.LocalFileWorkbookRef_changeDialog_update_button,
MindMapMessages.LocalFileWorkbookRef_changeDialog_cancel_button });
return true;
}
return false;
}
@Override
public void open(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
timestamp = getFile().lastModified();
super.open(monitor);
}
@Override
public void save(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
super.save(monitor);
timestamp = getFile().lastModified();
}
}