package org.syncany.gui.tray;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.syncany.gui.util.I18n;
import com.google.common.collect.Sets;
/**
* In contrast to see {@link org.syncany.gui.tray.DefaultTrayIcon}, this tray icon uses the mavericks+ notification
* center to notify of changes. This is a more common way on OS X. The notifier's source code is available at:
* https://github.com/syncany/syncany-osx-notifier
*
* <p>Only supported by Mac OS X 10.9+</p>
*
* @author Christian Roth <christian.roth@port17.de>
*/
public class OSXTrayIcon extends DefaultTrayIcon {
private static final Logger logger = Logger.getLogger(OSXTrayIcon.class.getSimpleName());
private final static String TERMINAL_NOTIFIER_PACKAGE = "/org/syncany/gui/helper/osx-notifier.zip";
private final static String TERMINAL_NOTIFIER_BINARY = "/Syncany.app/Contents/MacOS/Syncany";
private File terminalNotifierExtractedBinary;
private boolean useFallbackNotificationSystem;
public OSXTrayIcon(Shell shell, TrayIconTheme theme) {
super(shell, theme);
extractHelperUtility();
testHelperUtility();
}
@Override
protected void displayNotification(final String subject, final String message) {
if (useFallbackNotificationSystem) {
super.displayNotification(subject, message);
return;
}
Display.getDefault().asyncExec(new Runnable() {
public void run() {
List<String> command = new ArrayList<>();
command.add(terminalNotifierExtractedBinary.getAbsolutePath());
command.add("-title");
command.add(subject);
command.add("-message");
command.add(message);
try {
Runtime.getRuntime().exec(command.toArray(new String[command.size()]));
}
catch (IOException e) {
throw new RuntimeException("Unable to notify using " + terminalNotifierExtractedBinary, e);
}
}
});
}
private void extractHelperUtility() {
try {
logger.log(Level.INFO, "Extracting required helper tools...");
// create a target for the app
// dont use deleteOnExit or FileUtils.forceDeleteOnExit due to http://stackoverflow.com/questions/617414/create-a-temporary-directory-in-java/1506777#1506777
final File terminalNotifierExtracted = Files.createTempDirectory("syncany-osx-notifier").toFile();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Extracting syncany-notifier to {0}", new Object[]{terminalNotifierExtracted});
}
// extract the compressed notifier
File temporaryZipFile = File.createTempFile("syncany-notifier", ".zip");
FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(TERMINAL_NOTIFIER_PACKAGE), temporaryZipFile);
extractZip(temporaryZipFile, terminalNotifierExtracted);
// make it executable
terminalNotifierExtractedBinary = new File(terminalNotifierExtracted, TERMINAL_NOTIFIER_BINARY);
Set<PosixFilePermission> perms = Sets.newHashSet(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(terminalNotifierExtractedBinary.toPath(), perms);
logger.log(Level.INFO, "Using {0} to deliver notifications", new Object[]{terminalNotifierExtractedBinary});
temporaryZipFile.delete();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
logger.log(Level.INFO, "Cleaning up notification helper...");
try {
FileUtils.deleteDirectory(terminalNotifierExtracted);
}
catch (IOException e) {
logger.log(Level.SEVERE, "Unable to clean up notification helper", e);
}
}
});
logger.log(Level.INFO, "Done Extracting helper tools");
}
catch (NullPointerException | IOException e) {
logger.log(Level.SEVERE, "Unable to extract required helpers", e);
}
}
private void testHelperUtility() {
useFallbackNotificationSystem = !terminalNotifierExtractedBinary.exists() || !terminalNotifierExtractedBinary.canExecute();
if (useFallbackNotificationSystem) {
logger.log(Level.INFO, "Unable to notify using the native helper utility ({0}), using generic swt fallback", new Object[]{terminalNotifierExtractedBinary});
displayNotification(I18n.getText("org.syncany.gui.tray.TrayIcon.notify.osx.helperFailedWarning.subject"),
I18n.getText("org.syncany.gui.tray.TrayIcon.notify.osx.helperFailedWarning.message"));
}
}
private void extractZip(File zipFilePath, File targetFolder) throws IOException {
ZipFile zipFile = new ZipFile(zipFilePath);
Enumeration<?> zipFileEntriesEnumeration = zipFile.entries();
while (zipFileEntriesEnumeration.hasMoreElements()) {
ZipEntry zipEntry = (ZipEntry) zipFileEntriesEnumeration.nextElement();
String zipEntryName = zipEntry.getName();
File targetFile = new File(targetFolder, zipEntryName);
if (zipEntryName.endsWith("/")) {
targetFile.mkdirs();
}
else {
File parent = targetFile.getParentFile();
if (parent != null) {
parent.mkdirs();
}
FileUtils.copyInputStreamToFile(zipFile.getInputStream(zipEntry), targetFile);
}
}
zipFile.close();
}
}