it.tidalwave.bluebill.mobile.news.DefaultNewsController Maven / Gradle / Ivy
/***********************************************************************************************************************
*
* blueBill Mobile - open source birdwatching
* ==========================================
*
* Copyright (C) 2009, 2010 by Tidalwave s.a.s. (http://www.tidalwave.it)
* http://bluebill.tidalwave.it/mobile/
*
***********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
***********************************************************************************************************************
*
* $Id: DefaultNewsController.java,v 70fb0b4b2a54 2010/07/04 21:14:16 fabrizio $
*
**********************************************************************************************************************/
package it.tidalwave.bluebill.mobile.news;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import it.tidalwave.util.Id;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.netbeans.util.Locator;
import it.tidalwave.mobile.io.FileSystem;
import it.tidalwave.mobile.io.IoUtils;
import it.tidalwave.mobile.io.MasterFileSystem;
import it.tidalwave.mobile.rss.Message;
import it.tidalwave.mobile.rss.RSSFeed;
import it.tidalwave.mobile.rss.RSSFeedReader;
import it.tidalwave.bluebill.mobile.network.NetworkingPreferences;
import it.tidalwave.mobile.io.StringsIO;
import static it.tidalwave.mobile.rss.Vocabulary.*;
/***********************************************************************************************************************
*
* @author Fabrizio Giudici
* @version $Id: $
*
**********************************************************************************************************************/
@ServiceProvider(service=NewsController.class)
public class DefaultNewsController implements NewsController
{
private static final String CLASS = DefaultNewsController.class.getName();
private static final Logger logger = Logger.getLogger(CLASS);
private static final String NEWS_FEED_URL = "http://bluebill.tidalwave.it/mobile/blog.rss";
private final static long ONE_DAY = 24 * 60 * 60 * 1000;
private final static long ONE_MINUTE = 60 * 1000;
protected enum Status
{
NEEDS_DOWNLOAD,
DOWNLOADING,
DOWNLOADED
}
@Nonnegative
protected long refreshPeriod = ONE_DAY;
@Nonnull
protected Status status = Status.NEEDS_DOWNLOAD;
@CheckForNull
protected Thread thread;
@Nonnull
protected final File cachedFile;
@Nonnull
protected final File readLogFile;
@CheckForNull
protected RSSFeed rssFeed;
@Nonnull
private final Provider masterFileSystem = Locator.createProviderFor(MasterFileSystem.class);
@Nonnull
private final Provider networkingPreferences = Locator.createProviderFor(NetworkingPreferences.class);
@CheckForNull
protected Thread thread1;
private final Set readMessages = new TreeSet();
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public DefaultNewsController()
throws IOException
{
final FileSystem internalFileSystem = masterFileSystem.get().getInternalFileSystem();
cachedFile = internalFileSystem.getFile("blog.rss");
readLogFile = internalFileSystem.getFile("blog-read-messages.txt");
loadReadMessagesLog();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void start()
{
logger.info("start()");
if (thread == null)
{
if (!networkingPreferences.get().isNetworkConnectionAllowed())
{
logger.info(">>>> not checking, internet connections are disabled");
}
else
{
thread = new Thread()
{
@Override
public void run()
{
try
{
updateCache();
}
catch (Exception e)
{
logger.warning("While running news feed check: %s", e);
logger.throwing(CLASS, "check()", e);
}
thread = null;
}
};
thread.start();
}
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Nonnull
public void loadNewsFeed (final @Nonnull NewsUI newsUI)
{
logger.info("loadNewsFeed(%s)", newsUI);
if (rssFeed != null)
{
newsUI.notifyFeedAvailable(rssFeed);
}
else
{
if (status == Status.NEEDS_DOWNLOAD)
{
try
{
probeCache();
}
catch (Exception e)
{
newsUI.notifyFeedUnavailable(e);
return;
}
}
if (status != Status.NEEDS_DOWNLOAD)
{
loadNewsFeedFromCache(newsUI);
}
// NOT_DOWNLOADED
else if (networkingPreferences.get().isNetworkConnectionAllowed())
{
downloadNewsAndLoadRssFeed(newsUI);
}
else
{
final String title = NbBundle.getMessage(DefaultNewsController.class, "confirmDownloadTitle");
final String message = NbBundle.getMessage(DefaultNewsController.class, "confirmDownloadMessage");
newsUI.getCommonUITasks().alertDialog(title, message, new Runnable()
{
public void run()
{
thread1 = new Thread()
{
@Override
public void run()
{
downloadNewsAndLoadRssFeed(newsUI);
thread1 = null;
}
};
thread1.start();
}
},
new Runnable()
{
public void run()
{
newsUI.close();
}
});
}
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void markRead (final @Nonnull Message message)
throws IOException
{
logger.info("markRead(%s)", message);
readMessages.add(message.getId());
storeReadMessagesLog();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void markAllRead (final @Nonnull RSSFeed rssFeed, final @Nonnull NewsUI newsUI)
throws IOException
{
logger.info("markAllRead(%s, %s)", rssFeed, newsUI);
try
{
for (final Message message : rssFeed.get(MESSAGES))
{
readMessages.add(message.getId());
}
storeReadMessagesLog();
final String message = NbBundle.getMessage(DefaultNewsController.class, "allMarkedAsRead");
newsUI.getCommonUITasks().showTemporaryMessage(message);
}
catch (NotFoundException e)
{
logger.warning("markAllRead(): %s", e);
logger.throwing(CLASS, "markAllRead()", e);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public boolean isRead (final @Nonnull Message message)
{
return readMessages.contains(message.getId());
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void downloadNewsAndLoadRssFeed (final @Nonnull NewsUI newsUI)
{
logger.fine("downloadNewsAndLoadRssFeed(%s)", newsUI);
try
{
downloadNews();
loadNewsFeedFromCache(newsUI);
}
catch (Exception e)
{
newsUI.notifyFeedUnavailable(e);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void loadNewsFeedFromCache (final @Nonnull NewsUI newsUI)
{
try
{
synchronized (this)
{
while (status != Status.DOWNLOADED)
{
wait();
}
}
loadFeed();
newsUI.notifyFeedAvailable(rssFeed);
}
catch (Exception e)
{
newsUI.notifyFeedUnavailable(e);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void updateCache()
throws Exception
{
logger.info("updateCache()");
probeCache();
if (status == Status.NEEDS_DOWNLOAD)
{
downloadNews();
loadFeed();
checkNotification();
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void loadFeed()
throws Exception, FileNotFoundException
{
InputStream is = null;
try
{
is = new FileInputStream(cachedFile);
final RSSFeedReader reader = new RSSFeedReader();
rssFeed = reader.readMessages(is);
}
finally
{
IoUtils.safeClose(is);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void probeCache()
throws Exception
{
logger.info("probeCache()");
logger.info(">>>> cached file is %s", cachedFile.getAbsolutePath());
if (!cachedFile.exists())
{
logger.info(">>>> cached file doesn't exist, needs download");
setStatus(Status.NEEDS_DOWNLOAD);
}
else
{
final long lastModified = cachedFile.lastModified();
final long now = getTimestamp();
final long delta = now - lastModified;
setStatus((delta >= refreshPeriod) ? Status.NEEDS_DOWNLOAD : Status.DOWNLOADED);
logger.info(">>>> now: %s, latest news download: %s, delta: %d min, status: %s",
new Date(now), new Date(lastModified), delta / ONE_MINUTE, status);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void downloadNews()
throws FileNotFoundException, IOException, MalformedURLException
{
logger.info("downloadNews()");
setStatus(Status.DOWNLOADING);
// TODO: be smart, check the last update time of the feed
final URL url = createUrl(NEWS_FEED_URL);
final InputStream is = url.openStream();
final OutputStream os = new FileOutputStream(cachedFile);
IoUtils.copy(is, os);
IoUtils.safeClose(is);
IoUtils.safeClose(os);
setStatus(Status.DOWNLOADED);
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void checkNotification()
{
logger.info("checkNotification()");
try
{
final List messageIds = new ArrayList();
for (final Message message : rssFeed.get(MESSAGES))
{
messageIds.add(message.getId());
}
messageIds.removeAll(readMessages);
logger.info(">>>> unread messages: %d", messageIds.size());
if (!messageIds.isEmpty())
{
postUnreadNewsNotification();
}
}
catch (NotFoundException e)
{
logger.warning("markAllRead(): %s", e);
logger.throwing(CLASS, "markAllRead()", e);
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected void postUnreadNewsNotification()
{
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected long getTimestamp()
{
return System.currentTimeMillis();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
@Nonnull
protected URL createUrl (final String string)
throws MalformedURLException
{
return new URL(string);
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
protected synchronized void setStatus (final @Nonnull Status status)
{
logger.info("setStatus(%s)", status);
this.status = status;
notifyAll();
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
private void loadReadMessagesLog()
throws IOException
{
if (readLogFile.exists())
{
readMessages.addAll(new StringsIO(readLogFile).load(new StringsIO.Processor()
{
@Nonnull
public Id processString (final @Nonnull String string)
{
return new Id(string);
}
}));
}
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
private void storeReadMessagesLog()
throws IOException
{
new StringsIO(readLogFile).store(readMessages);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy