package de.tum.in.tumcampusapp.managers; import android.content.Context; import android.database.Cursor; import com.google.common.base.Optional; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.Date; import de.tum.in.tumcampusapp.auxiliary.Const; import de.tum.in.tumcampusapp.auxiliary.NetUtils; import de.tum.in.tumcampusapp.auxiliary.Utils; import de.tum.in.tumcampusapp.cards.FilmCard; import de.tum.in.tumcampusapp.cards.NewsCard; import de.tum.in.tumcampusapp.cards.generic.Card; import de.tum.in.tumcampusapp.models.tumcabe.News; /** * News Manager, handles database stuff, external imports */ public class NewsManager extends AbstractManager implements Card.ProvidesCard { private static final int TIME_TO_SYNC = 1800; // 1/2 hour private static final String NEWS_URL = "https://tumcabe.in.tum.de/Api/news/"; private static final String NEWS_SOURCES_URL = NEWS_URL + "sources"; private final Context mContext; /** * Constructor, open/create database, create table if necessary * * @param context Context */ public NewsManager(Context context) { super(context); mContext = context; // create news sources table db.execSQL("CREATE TABLE IF NOT EXISTS news_sources (id INTEGER PRIMARY KEY, icon VARCHAR, title VARCHAR)"); // create table if needed db.execSQL("CREATE TABLE IF NOT EXISTS news (id INTEGER PRIMARY KEY, src INTEGER, title TEXT, link VARCHAR, " + "image VARCHAR, date VARCHAR, created VARCHAR, dismissed INTEGER)"); } /** * Convert JSON object to News and download news image * * @param json see above * @return News * @throws JSONException if the json is invalid */ private static News getFromJson(JSONObject json) throws JSONException { String id = json.getString(Const.JSON_NEWS); String src = json.getString(Const.JSON_SRC); String title = json.getString(Const.JSON_TITLE); String link = json.getString(Const.JSON_LINK); String image = json.getString(Const.JSON_IMAGE); Date date = Utils.getISODateTime(json.getString(Const.JSON_DATE)); Date created = Utils.getISODateTime(json.getString(Const.JSON_CREATED)); return new News(id, title, link, src, image, date, created); } /** * Removes all old items (older than 3 months) */ void cleanupDb() { db.execSQL("DELETE FROM news WHERE date < date('now','-3 month')"); } /** * Download news from external interface (JSON) * * @param force True to force download over normal sync period, else false * @throws JSONException */ public void downloadFromExternal(boolean force) throws JSONException { SyncManager sync = new SyncManager(mContext); if (!force && !sync.needSync(this, TIME_TO_SYNC)) { return; } NetUtils net = new NetUtils(mContext); // Load all news sources Optional<JSONArray> jsonArray = net.downloadJsonArray(NEWS_SOURCES_URL, CacheManager.VALIDITY_ONE_MONTH, force); if (jsonArray.isPresent()) { JSONArray arr = jsonArray.get(); db.beginTransaction(); try { for (int i = 0; i < arr.length(); i++) { JSONObject obj = arr.getJSONObject(i); replaceIntoSourcesDb(obj); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } } // Load all news since the last sync jsonArray = net.downloadJsonArray(NEWS_URL + getLastId(), CacheManager.VALIDITY_ONE_DAY, force); // Delete all too old items cleanupDb(); if (!jsonArray.isPresent()) { return; } db.beginTransaction(); try { JSONArray arr = jsonArray.get(); for (int i = 0; i < arr.length(); i++) { JSONObject obj = arr.getJSONObject(i); replaceIntoDb(getFromJson(obj)); } db.setTransactionSuccessful(); } finally { db.endTransaction(); } sync.replaceIntoDb(this); } /** * Get all news from the database * * @return Database cursor (_id, src, title, description, link, image, date, created, icon, source) */ public Cursor getAllFromDb(Context context) { String selectedNewspread = Utils.getSetting(mContext, "news_newspread", "7"); StringBuilder and = new StringBuilder(); Cursor c = getNewsSources(); if (c.moveToFirst()) { do { int id = c.getInt(0); boolean show = Utils.getSettingBool(context, "news_source_" + id, id <= 7); if (!show) { continue; } if (!and.toString().isEmpty()) { and.append(" OR "); } and.append("s.id=\"").append(id).append('\"'); } while (c.moveToNext()); } c.close(); return db.rawQuery("SELECT n.id AS _id, n.src, n.title, " + "n.link, n.image, n.date, n.created, s.icon, s.title AS source, n.dismissed, " + "(julianday('now') - julianday(date)) AS diff " + "FROM news n, news_sources s " + "WHERE n.src=s.id " + (and.toString().isEmpty() ? "" : "AND (" + and.toString() + ") ") + "AND (s.id < 7 OR s.id > 13 OR s.id=?) " + "ORDER BY date DESC", new String[]{selectedNewspread}); } /** * Get the index of the newest item that is older than 'now' * * @return index of the newest item that is older than 'now' - 1 */ public int getTodayIndex() { String selectedNewspread = Utils.getSetting(mContext, "news_newspread", "7"); Cursor c = db.rawQuery("SELECT COUNT(*) FROM news WHERE date(date)>date() AND (src < 7 OR src > 13 OR src=?)", new String[]{selectedNewspread}); if (c.moveToFirst()) { int res = c.getInt(0); c.close(); return res == 0 ? 0 : res - 1; } c.close(); return 0; } private String getLastId() { String lastId = ""; Cursor c = db.rawQuery("SELECT id FROM news ORDER BY id DESC LIMIT 1", null); if (c.moveToFirst()) { lastId = c.getString(0); } c.close(); return lastId; } public Cursor getNewsSources() { String selectedNewspread = Utils.getSetting(mContext, "news_newspread", "7"); return db.rawQuery("SELECT id, icon, " + "CASE WHEN title LIKE 'newspread%' THEN \"Newspread\" ELSE title END " + "FROM news_sources WHERE id < 7 OR id > 13 OR id=?", new String[]{selectedNewspread}); } /** * Replace or Insert a event in the database * * @param n News object */ void replaceIntoDb(News n) { db.execSQL("REPLACE INTO news (id, src, title, link, image, date, " + "created, dismissed) VALUES (?, ?, ?, ?, ?, ?, ?, 0)", new String[]{n.id, n.src, n.title, n.link, n.image, Utils.getDateTimeString(n.date), Utils.getDateTimeString(n.created)}); } /** * Replace or Insert a news source in the database * * @param n News source object * @throws JSONException */ void replaceIntoSourcesDb(JSONObject n) throws JSONException { db.execSQL("REPLACE INTO news_sources (id, icon, title) VALUES (?, ?, ?)", new String[]{n.getString(Const.JSON_SOURCE), n.has(Const.JSON_ICON) ? n.getString(Const.JSON_ICON) : "", n.getString(Const.JSON_TITLE)}); } public void setDismissed(String id, int d) { db.execSQL("UPDATE news SET dismissed=? WHERE id=?", new String[]{String.valueOf(d), id}); } /** * Adds the newest news card * * @param context Context */ @Override public void onRequestCard(Context context) { StringBuilder and = new StringBuilder(); Cursor c = getNewsSources(); if (c.moveToFirst()) { do { int id = c.getInt(0); boolean show = Utils.getSettingBool(context, "card_news_source_" + id, true); if (!show) { continue; } if (!and.toString().isEmpty()) { and.append(" OR "); } and.append("s.id=\"").append(id).append('\"'); } while (c.moveToNext()); } c.close(); //boolean showImportant = Utils.getSettingBool(context, "card_news_alert", true); if (!and.toString().isEmpty()) { StringBuilder query = new StringBuilder("SELECT n.id AS _id, n.src, n.title, " + "n.link, n.image, n.date, n.created, s.icon, s.title AS source, n.dismissed, " + "ABS(julianday(date()) - julianday(n.date)) AS date_diff "); if (Utils.getSettingBool(context, "card_news_latest_only", true)) { // Limit to one entry per source query.append("FROM (news n JOIN ( " + "SELECT src, MIN(abs(julianday(date()) - julianday(date))) AS diff " + "FROM news WHERE src!=\"2\" OR (julianday(date()) - julianday(date))<0 " + "GROUP BY src) last ON (n.src = last.src " + "AND date_diff = last.diff) " + "), news_sources s "); } else { query.append("FROM news n, news_sources s "); } query.append("WHERE n.src = s.id AND ((").append(and).append(") ) ORDER BY date_diff ASC"); Cursor cur = db.rawQuery(query.toString(), null); int i = 0; if (cur.moveToFirst()) { do { NewsCard card; if (FilmCard.isNewsAFilm(cur, i)) { card = new FilmCard(context); } else { card = new NewsCard(context); } card.setNews(cur, i); card.apply(); i++; } while (cur.moveToNext()); } else { cur.close(); } } } }