/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package net.xmind.workbench.internal.notification;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import net.xmind.signin.IDataStore;
import net.xmind.signin.ILicenseInfo;
import net.xmind.signin.ILicenseListener;
import net.xmind.signin.XMindNet;
import net.xmind.signin.internal.XMindNetRequest;
import net.xmind.workbench.internal.Messages;
import net.xmind.workbench.internal.XMindNetWorkbench;
import org.apache.commons.httpclient.HttpStatus;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IStartup;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchListener;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
import org.xmind.core.Core;
import org.xmind.ui.dialogs.SimpleInfoPopupDialog;
public class SiteEventNotificationService implements IStartup,
IWorkbenchListener, ILicenseListener {
private class CheckSiteEventJob extends Job {
private XMindNetRequest request = null;
private boolean startup;
public CheckSiteEventJob(boolean startup) {
super("Check Site Events"); //$NON-NLS-1$
setSystem(true);
setPriority(DECORATE);
this.startup = startup;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
request = new XMindNetRequest();
request.uri("/_api/events"); //$NON-NLS-1$
request.addParameter("version", "3.3.0"); //$NON-NLS-1$ //$NON-NLS-2$
request.addParameter("os", Platform.getOS()); //$NON-NLS-1$
request.addParameter("arch", Platform.getOSArch()); //$NON-NLS-1$
request.addParameter("nl", Platform.getNL()); //$NON-NLS-1$
request.addParameter("distrib", getDistributionId()); //$NON-NLS-1$
request.addParameter("account_type", getAccountType()); //$NON-NLS-1$
request.get();
if (monitor.isCanceled() || request.isAborted())
return Status.CANCEL_STATUS;
int code = request.getCode();
IDataStore data = request.getData();
if (code == HttpStatus.SC_OK) {
if (data != null) {
return handleSiteEvents(monitor, data);
} else {
XMindNetWorkbench.log(request.getException(),
"Failed to parse response of site events."); //$NON-NLS-1$
}
} else if (code == XMindNetRequest.ERROR) {
XMindNetWorkbench.log(request.getException(),
"Failed to connect to xmind.net for site events."); //$NON-NLS-1$
} else {
XMindNetWorkbench.log(request.getException(),
"Failed to retrieve site events: " + code); //$NON-NLS-1$
}
return Status.CANCEL_STATUS;
}
protected IStatus handleSiteEvents(IProgressMonitor monitor,
IDataStore data) {
List<ISiteEvent> siteEvents = new ArrayList<ISiteEvent>();
for (IDataStore siteData : data.getChildren("events")) { //$NON-NLS-1$
siteEvents.add(new DataStoreSiteEvent(siteData));
}
// for test:
// JSONObject js = new JSONObject();
// js.put("id", "8");
// js.put("prompt", "always");
// js.put("title", "XMind official group on Biggerplate");
// js.put("url", "http://blog.xmind.net/en/2012/02/biggerplate/");
// siteEvents.add(new JSONSiteEvent(js));
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
SiteEventStore localStore = getLocalEventStore();
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
List<ISiteEvent> newEvents = localStore.calcNewEvents(siteEvents,
startup);
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
if (!newEvents.isEmpty()) {
showNotifications(newEvents);
}
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
setLocalEventStore(new SiteEventStore(siteEvents));
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
try {
saveLocalEventStore();
} catch (IOException e) {
XMindNetWorkbench.log(e, "Failed to save site events."); //$NON-NLS-1$
}
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
return Status.OK_STATUS;
}
@Override
protected void canceling() {
if (request != null) {
request.abort();
}
super.canceling();
}
}
private static class URLAction extends Action {
private String url;
private boolean openExternal;
public URLAction(String text, String url, boolean openExternal) {
super(
text == null ? Messages.SiteEventNotificationService_View_text
: text);
this.url = url;
this.openExternal = openExternal;
}
@Override
public void run() {
XMindNet.gotoURL(openExternal, url);
}
}
private IWorkbench workbench;
private CheckSiteEventJob job;
private SiteEventStore localEventStore;
private String licenseType = "free"; //$NON-NLS-1$
private boolean startup = true;
private IPreferenceStore prefStore;
public SiteEventNotificationService() {
prefStore = new ScopedPreferenceStore(InstanceScope.INSTANCE,
"org.xmind.cathy"); //$NON-NLS-1$
}
private SiteEventStore getLocalEventStore() {
if (localEventStore == null) {
File file = getLocalFile();
if (file.isFile()) {
try {
localEventStore = new SiteEventStore(new InputStreamReader(
new FileInputStream(file), "UTF-8")); //$NON-NLS-1$
} catch (IOException e) {
localEventStore = new SiteEventStore();
}
} else {
localEventStore = new SiteEventStore();
}
}
return localEventStore;
}
private void setLocalEventStore(SiteEventStore store) {
this.localEventStore = store;
}
private void saveLocalEventStore() throws IOException {
if (localEventStore == null)
return;
File file = getLocalFile();
if (!file.exists()) {
file.getParentFile().mkdirs();
}
localEventStore.save(new OutputStreamWriter(new FileOutputStream(file),
"UTF-8")); //$NON-NLS-1$
}
private static File getLocalFile() {
return new File(Core.getWorkspace().getAbsolutePath("site-events.xml")); //$NON-NLS-1$
}
private void checkEvent() {
if (isNotificationAllowed()) {
if (job != null && job.getResult() == null)
return;
job = new CheckSiteEventJob(startup);
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
if (job == event.getJob()) {
job = null;
}
}
});
job.schedule();
startup = false;
} else {
stop();
}
}
private boolean isNotificationAllowed() {
return prefStore.getBoolean("checkUpdatesOnStartup"); //$NON-NLS-1$
}
private void showNotifications(final List<ISiteEvent> events) {
if (workbench == null)
return;
if (canShowNotifications()) {
doShowNotifications(events);
} else {
Thread thread = new Thread(new Runnable() {
public void run() {
while (!canShowNotifications()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
return;
}
}
doShowNotifications(events);
}
});
thread.setPriority(Thread.MIN_PRIORITY);
thread.setDaemon(true);
thread.setName("WaitToShowNotifications"); //$NON-NLS-1$
thread.start();
}
}
private boolean canShowNotifications() {
if (workbench == null)
return false;
final IWorkbench wb = workbench;
final Display display = wb.getDisplay();
final boolean[] active = new boolean[1];
display.syncExec(new Runnable() {
public void run() {
IWorkbenchWindow window = wb.getActiveWorkbenchWindow();
active[0] = window != null
&& display.getActiveShell() == window.getShell();
}
});
return active[0];
}
private void doShowNotifications(final List<ISiteEvent> events) {
if (workbench == null)
return;
final IWorkbench wb = workbench;
final Display display = wb.getDisplay();
if (display == null || display.isDisposed())
return;
display.asyncExec(new Runnable() {
public void run() {
IWorkbenchWindow window = wb.getActiveWorkbenchWindow();
if (window == null)
return;
final Shell shell = window.getShell();
if (shell == null || shell.isDisposed())
return;
for (ISiteEvent event : events) {
SimpleInfoPopupDialog dialog = new SimpleInfoPopupDialog(
shell,
null,
event.getTitle(),
0,
event.getMoreUrl() == null ? null
: new URLAction(
Messages.SiteEventNotificationService_More_text,
event.getMoreUrl(), event
.isOpenExternal()),
new URLAction(event.getActionText(), event
.getEventUrl(), event.isOpenExternal()));
dialog.setDuration(10000);
dialog.setGroupId("org.xmind.notifications"); //$NON-NLS-1$
dialog.popUp(shell);
}
}
});
}
private void stop() {
if (job != null) {
Thread thread = job.getThread();
job.cancel();
if (thread != null) {
thread.interrupt();
}
job = null;
}
}
public void earlyStartup() {
this.workbench = PlatformUI.getWorkbench();
workbench.addWorkbenchListener(this);
int type = XMindNet.getLicenseInfo().getType();
if (type == ILicenseInfo.VERIFYING) {
XMindNet.addLicenseListener(this);
} else {
parseLicenseType(type);
checkEvent();
}
}
protected void parseLicenseType(int type) {
if ((type & ILicenseInfo.VALID_PRO_LICENSE) != 0) {
licenseType = "pro_license"; //$NON-NLS-1$
} else if ((type & ILicenseInfo.VALID_PLUS_LICENSE) != 0) {
licenseType = "plus_license"; //$NON-NLS-1$
} else if ((type & ILicenseInfo.VALID_PRO_SUBSCRIPTION) != 0) {
licenseType = "pro"; //$NON-NLS-1$
} else {
licenseType = "free"; //$NON-NLS-1$
}
}
public void postShutdown(IWorkbench workbench) {
stop();
if (this.workbench == null)
return;
this.workbench.removeWorkbenchListener(this);
this.workbench = null;
XMindNet.removeLicenseListener(this);
}
public boolean preShutdown(IWorkbench workbench, boolean forced) {
stop();
return true;
}
private static final String getDistributionId() {
String distribId = System
.getProperty("org.xmind.product.distribution.id"); //$NON-NLS-1$
if (distribId == null || "".equals(distribId)) { //$NON-NLS-1$
distribId = "cathy_portable"; //$NON-NLS-1$
}
return distribId;
}
private String getAccountType() {
return licenseType;
}
public void licenseVerified(ILicenseInfo info) {
XMindNet.removeLicenseListener(this);
parseLicenseType(info.getType());
checkEvent();
}
// public void authorized(IAccountInfo accountInfo) {
// accountType = accountInfo.hasValidSubscription() ? "pro" : "free"; //$NON-NLS-1$ //$NON-NLS-2$
// XMindNet.removeAuthorizationListener(this);
// checkEvent();
// }
//
// public void unauthorized(IStatus result, IAccountInfo accountInfo) {
// accountType = "free"; //$NON-NLS-1$
// XMindNet.removeAuthorizationListener(this);
// checkEvent();
// }
}