/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2011 Dmitry Barashev
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 3
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.sourceforge.ganttproject.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.GPVersion;
import net.sourceforge.ganttproject.gui.NotificationChannel;
import net.sourceforge.ganttproject.gui.NotificationItem;
import net.sourceforge.ganttproject.gui.NotificationManager;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.language.GanttLanguage;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import biz.ganttproject.core.option.ChangeValueEvent;
import biz.ganttproject.core.option.ChangeValueListener;
import biz.ganttproject.core.option.DateOption;
import biz.ganttproject.core.option.DefaultBooleanOption;
import biz.ganttproject.core.option.DefaultDateOption;
import biz.ganttproject.core.option.DefaultEnumerationOption;
import biz.ganttproject.core.option.GPOptionGroup;
import biz.ganttproject.core.time.impl.GPTimeUnitStack;
/**
* Checks GanttProject RSS news feeds once per day
*
* @author dbarashev (Dmitry Barashev)
*/
public class RssFeedChecker {
private static enum CheckOption {
YES, NO, UNDEFINED
}
private final UIFacade myUiFacade;
private final DefaultEnumerationOption<CheckOption> myCheckRssOption = new DefaultEnumerationOption<CheckOption>(
"check", CheckOption.values()) {
@Override
protected String objectToString(CheckOption obj) {
return obj.toString();
}
@Override
protected CheckOption stringToObject(String value) {
return CheckOption.valueOf(value);
}
};
private final DefaultBooleanOption myBooleanCheckRssOption = new DefaultBooleanOption("rss.checkUpdates");
private final DateOption myLastCheckOption = new DefaultDateOption("lastCheck", null);
private final GPOptionGroup myOptionGroup = new GPOptionGroup("updateRss",
myCheckRssOption, myLastCheckOption);
private final GPOptionGroup myUiOptionGroup = new GPOptionGroup("rss", myBooleanCheckRssOption);
private GPTimeUnitStack myTimeUnitStack;
private static final String RSS_URL = "http://www.ganttproject.biz/my/feed";
protected static final int MAX_ATTEMPTS = 10;
private final RssParser parser = new RssParser();
private final NotificationItem myRssProposalNotification = new NotificationItem("",
GanttLanguage.getInstance().formatText("updateRss.question.template",
GanttLanguage.getInstance().getText("updateRss.question.0"),
GanttLanguage.getInstance().getText("updateRss.question.1"),
GanttLanguage.getInstance().getText("updateRss.question.2")),
NotificationManager.DEFAULT_HYPERLINK_LISTENER);
private String myOptionsVersion;
public RssFeedChecker(GPTimeUnitStack timeUnitStack, UIFacade uiFacade) {
myCheckRssOption.setValue(CheckOption.UNDEFINED.toString());
myUiFacade = uiFacade;
myTimeUnitStack = timeUnitStack;
myBooleanCheckRssOption.setValue(CheckOption.YES.equals(myCheckRssOption.getSelectedValue()));
myBooleanCheckRssOption.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
if (event.getTriggerID() != RssFeedChecker.this) {
if (myBooleanCheckRssOption.isChecked()) {
myCheckRssOption.setValue(CheckOption.YES.name(), RssFeedChecker.this);
} else {
myCheckRssOption.setValue(CheckOption.NO.name(), RssFeedChecker.this);
}
}
}
});
myCheckRssOption.addChangeValueListener(new ChangeValueListener() {
@Override
public void changeValue(ChangeValueEvent event) {
if (event.getID() != RssFeedChecker.this && CheckOption.UNDEFINED != myCheckRssOption.getSelectedValue()) {
myBooleanCheckRssOption.setValue(CheckOption.YES == myCheckRssOption.getSelectedValue(), RssFeedChecker.this);
}
}
});
}
private NotificationManager getNotificationManager() {
return myUiFacade.getNotificationManager();
}
public GPOptionGroup getOptions() {
return myOptionGroup;
}
public GPOptionGroup getUiOptions() {
return myUiOptionGroup;
}
public void run() {
Runnable command = null;
CheckOption checkOption = CheckOption.valueOf(myCheckRssOption.getValue());
if (CheckOption.NO == checkOption) {
if (myOptionsVersion == null) {
// We used opt-in before GP 2.7; now we use opt-out, and we suggest to
// subscribe once again to those who previously chosen not to.
checkOption = CheckOption.UNDEFINED;
myCheckRssOption.setSelectedValue(checkOption);
markLastCheck();
} else {
NotificationChannel.RSS.setDefaultNotification(myRssProposalNotification);
}
return;
}
Date lastCheck = myLastCheckOption.getValue();
if (lastCheck == null) {
// It is the first time we run, just mark it. We want to suggest
// subscribing to updates only to
// those who runs GP at least twice.
markLastCheck();
} else if (wasToday(lastCheck)) {
// It is not the first run of GP but it was last run today and RSS
// proposal has not been shown yet.
// Add it to RSS button but don't promote it, wait until tomorrow.
if (CheckOption.UNDEFINED == checkOption) {
NotificationChannel.RSS.setDefaultNotification(myRssProposalNotification);
}
} else {
// So it is not the first time and even not the first day we start GP.
// If no decision about subscribing, let's proactively suggest it,
// otherwise
// run check RSS.
if (CheckOption.UNDEFINED == checkOption) {
command = createRssProposalCommand();
} else {
command = createRssReadCommand();
}
}
if (command == null) {
return;
}
new Thread(command).start();
}
private Runnable createRssReadCommand() {
return new Runnable() {
@Override
public void run() {
GPLogger.log("Starting RSS check...");
HttpClient httpClient = new DefaultHttpClient();
String url = RSS_URL;
try {
for (int i = 0; i < MAX_ATTEMPTS; i++) {
HttpGet getRssUrl = new HttpGet(url);
getRssUrl.addHeader("User-Agent", "GanttProject " + GPVersion.CURRENT);
HttpResponse result = httpClient.execute(getRssUrl);
switch (result.getStatusLine().getStatusCode()) {
case HttpStatus.SC_OK:
processResponse(result.getEntity().getContent());
return;
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
httpClient.getConnectionManager().shutdown();
GPLogger.log("RSS check finished");
}
}
private void processResponse(InputStream responseStream) {
RssFeed feed = parser.parse(responseStream, myLastCheckOption.getValue());
List<NotificationItem> items = new ArrayList<NotificationItem>();
for (RssFeed.Item item : feed.getItems()) {
items.add(new NotificationItem(item.title, item.body, NotificationManager.DEFAULT_HYPERLINK_LISTENER));
}
Collections.reverse(items);
if (!items.isEmpty()) {
getNotificationManager().addNotifications(NotificationChannel.RSS, items);
}
markLastCheck();
}
};
}
private Runnable createRssProposalCommand() {
return new Runnable() {
@Override
public void run() {
onYes();
getNotificationManager().addNotifications(NotificationChannel.RSS,
Collections.singletonList(myRssProposalNotification));
}
};
}
private boolean wasToday(Date date) {
return myTimeUnitStack.createDuration(GPTimeUnitStack.DAY, date, GPTimeUnitStack.DAY.adjustLeft(new Date())).getLength() == 0;
}
private void onYes() {
myCheckRssOption.setValue(CheckOption.YES.toString());
}
private void markLastCheck() {
myLastCheckOption.setValue(new Date());
}
public void setOptionsVersion(String version) {
myOptionsVersion = version;
}
}