package com.cellasoft.univrapp.manager; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.cellasoft.univrapp.Application; import com.cellasoft.univrapp.BuildConfig; import com.cellasoft.univrapp.criteria.ItemCriteria; import com.cellasoft.univrapp.loader.*; import com.cellasoft.univrapp.model.Channel; import com.cellasoft.univrapp.model.Channel.Channels; import com.cellasoft.univrapp.model.Image; import com.cellasoft.univrapp.model.Image.Images; import com.cellasoft.univrapp.model.Item; import com.cellasoft.univrapp.model.Item.Items; import com.cellasoft.univrapp.model.Lecturer; import com.cellasoft.univrapp.model.Lecturer.Lecturers; import com.cellasoft.univrapp.provider.DatabaseHelper; import com.cellasoft.univrapp.provider.Provider; import com.cellasoft.univrapp.utils.ActiveList; import com.cellasoft.univrapp.utils.Lists; import com.cellasoft.univrapp.widget.ContactItemInterface; import java.lang.ref.WeakReference; import java.util.HashSet; import java.util.List; import java.util.Set; public class ContentManager { public static final ChannelLoader FULL_CHANNEL_LOADER = new FullChannelLoader(); public static final ChannelLoader LIGHTWEIGHT_CHANNEL_LOADER = new LightweightChannelLoader(); public static final ItemLoader FULL_ITEM_LOADER = new FullItemLoader(); public static final ItemLoader LIGHTWEIGHT_ITEM_LOADER = new LightweightItemLoader(); public static final LecturerLoader LIGHTWEIGHT_LECTURER_LOADER = new LightweightLecturerLoader(); public static final LecturerLoader FULL_LECTURER_LOADER = new FullLecturerLoader(); public static final int ITEM_TYPE_CHANNEL = 1; public static final int ITEM_TYPE_ITEM = 2; private static final SparseArray<WeakReference<Channel>> channelCache = new SparseArray<WeakReference<Channel>>(); private static Set<String> recentReadArticles = new HashSet<String>(); private static ContentResolver cr; static { cr = Application.getInstance().getContentResolver(); } public static Channel loadChannel(int id, ChannelLoader loader) { Channel channel = getChannelFromCache(id); if (channel != null) return channel; Cursor cursor = cr.query(Channels.CONTENT_URI, loader.getProjection(), Provider.WHERE_ID, new String[]{String.valueOf(id)}, null); if (cursor.moveToFirst()) { channel = loader.load(cursor); putChannelToCache(channel); } cursor.close(); return channel; } public static boolean saveLecturer(Lecturer lecturer) { ContentValues values = new ContentValues(); values.put(Lecturers.ID, lecturer.id); values.put(Lecturers.KEY, lecturer.key); values.put(Lecturers.DEST, lecturer.dest); values.put(Lecturers.THUMBNAIL, lecturer.thumbnail); values.put(Lecturers.NAME, lecturer.name); values.put(Lecturers.TELEPHONE, lecturer.telephone); values.put(Lecturers.EMAIL, lecturer.email); values.put(Lecturers.OFFICE, lecturer.office); values.put(Lecturers.DEPARTMENT, lecturer.department); values.put(Lecturers.SECTOR, lecturer.sector); if (!existLecturer(lecturer)) { cr.insert(Lecturers.CONTENT_URI, values); return true; } else { cr.update(Lecturers.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(lecturer.id)}); } return false; } public static void subscribe(Channel channel) { if (existChannel(channel)) saveChannel(channel); } public static boolean saveChannel(Channel channel) { ContentValues values = new ContentValues(); if (channel.id == 0) { values.put(Channels.LECTURER_ID, channel.lecturerId); values.put(Channels.TITLE, channel.title); values.put(Channels.URL, channel.url); values.put(Channels.DESCRIPTION, channel.description); values.put(Channels.STARRED, channel.starred); values.put(Channels.MUTE, channel.mute); values.put(Channels.UPDATE_TIME, channel.updateTime); values.put(Channels.IMAGE_URL, channel.imageUrl); Uri contentUri = cr.insert(Channels.CONTENT_URI, values); channel.id = (int) ContentUris.parseId(contentUri); } else { values.put(Channels.UPDATE_TIME, channel.updateTime); cr.update(Channels.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); } // invalidate cache Channel channelInCache = getChannelFromCache(channel.id); if (channelInCache != null) { channelCache.remove(channel.id); putChannelToCache(channel); } return true; } public static void deleteChannel(Channel channel) { cr.delete(Items.CONTENT_URI, Items.CHANNEL_ID + "=?", new String[]{String.valueOf(channel.id)}); cr.delete(Channels.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); channel.id = 0; if (isChannelInCache(channel.id)) { channelCache.remove(channel.id); } } public static int cleanChannel(Channel channel) { return cr.delete(Items.CONTENT_URI, Items.CHANNEL_ID + "=?", new String[]{String.valueOf(channel.id)}); } // ASC a->z 1->n // DESC z->a n->1 public static void cleanUp(Channel channel, int keepMaxItems) { // delete old items Cursor cursor = cr.query(Items.limitAndStartAt(1, keepMaxItems - 1), new String[]{Items.ID, Items.PUB_DATE, Items.UPDATE_TIME}, Items.CHANNEL_ID + "=?", new String[]{String.valueOf(channel.id)}, Items.UPDATE_TIME + " DESC, " + Items.PUB_DATE + " DESC, " + Items.ID + " ASC"); if (cursor.moveToNext()) { long id = cursor.getLong(0); long lastPubDate = cursor.getLong(1); long updateTime = cursor.getLong(2); cursor.close(); String selection = Items.CHANNEL_ID + "=? AND (" + Items.UPDATE_TIME + "<? OR (" + Items.UPDATE_TIME + "=? AND (" + Items.PUB_DATE + "<? OR (" + Items.PUB_DATE + " =? AND " + Items.ID + ">?))))"; int deletedItems = cr.delete( Items.CONTENT_URI, selection, new String[]{String.valueOf(channel.id), String.valueOf(updateTime), String.valueOf(updateTime), String.valueOf(lastPubDate), String.valueOf(lastPubDate), String.valueOf(id)}); if (BuildConfig.DEBUG) Log.d("DEBUG", "Number of deleted items: " + deletedItems); } else { if (BuildConfig.DEBUG) Log.d("DEBUG", "No item to be deleted"); cursor.close(); } } public static boolean saveItem(Item item) { ContentValues values = new ContentValues(); if (item.id == 0) { if (existItem(item)) return false; values.put(Items.TITLE, item.title); values.put(Items.DESCRIPTION, item.description); values.put(Items.PUB_DATE, item.pubDate.getTime()); values.put(Items.LINK, item.link); values.put(Items.READ, item.read); values.put(Items.CHANNEL_ID, item.channel.id); values.put(Items.UPDATE_TIME, item.updateTime); Uri contentUri = cr.insert(Items.CONTENT_URI, values); item.id = (int) ContentUris.parseId(contentUri); } else { values.put(Items.READ, item.read); cr.update(Items.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(item.id)}); } return true; } public static Item loadItem(int id, ItemLoader loader, ChannelLoader channelLoader) { Cursor cursor = cr.query(Items.CONTENT_URI, loader.getProjection(), Items.ID + "=?", new String[]{String.valueOf(id)}, null); Item item = null; if (cursor.moveToNext()) { item = loader.load(cursor); if (channelLoader != null) { item.channel = loadChannel(item.channel.id, channelLoader); } } cursor.close(); return item; } public static List<Item> loadItems(ItemCriteria criteria, ItemLoader loader, ChannelLoader channelLoader) { Cursor cursor = cr.query(criteria.getContentUri(), loader.getProjection(), criteria.getSelection(), criteria.getSelectionArgs(), criteria.getOrderBy()); List<Item> items = Lists.newArrayList(); items.clear(); while (cursor.moveToNext()) { Item item = loader.load(cursor); if (channelLoader != null) { item.channel = loadChannel(item.channel.id, channelLoader); } items.add(item); } cursor.close(); return items; } public static List<Channel> loadAllChannels(ChannelLoader loader) { Cursor cursor = cr.query(Channels.CONTENT_URI, loader.getProjection(), null, null, null); List<Channel> channels = Lists.newArrayList(); while (cursor.moveToNext()) { Channel channel = loader.load(cursor); putChannelToCache(channel); channels.add(channel); } cursor.close(); return channels; } public static void loadAllItemsOfChannel(Channel channel, ItemLoader loader) { Cursor cursor = cr.query(Items.CONTENT_URI, loader.getProjection(), Items.CHANNEL_ID + "=?", new String[]{String.valueOf(channel.id)}, Items.UPDATE_TIME + " DESC, " + Items.PUB_DATE + " DESC, " + Items.ID + " ASC"); ActiveList<Item> items = channel.getItems(); items.clear(); while (cursor.moveToNext()) { Item item = loader.load(cursor); item.channel = channel; items.add(item); } cursor.close(); } public static boolean existSubscription(int lecturerId) { Cursor cursor = cr.query(Channels.CONTENT_URI, new String[]{Channels.LECTURER_ID}, Channels.LECTURER_ID + "=?", new String[]{String.valueOf(lecturerId)}, null); boolean result = cursor.moveToFirst(); cursor.close(); return result; } public static List<ContactItemInterface> loadAllLecturers( LecturerLoader loader) { Cursor cursor = cr.query(Lecturers.CONTENT_URI, loader.getProjection(), null, null, null); List<ContactItemInterface> lecturers = Lists.newArrayList(); while (cursor.moveToNext()) { Lecturer lecturer = loader.load(cursor); lecturers.add(lecturer); } cursor.close(); return lecturers; } public static Lecturer loadLecturer(int id, LecturerLoader loader) { Cursor cursor = cr.query(Lecturers.CONTENT_URI, loader.getProjection(), Provider.WHERE_ID, new String[]{String.valueOf(id)}, null); Lecturer lecturer = null; if (cursor.moveToFirst()) { lecturer = loader.load(cursor); } cursor.close(); return lecturer; } public static List<Image> loadAllQueuedImages() { return loadImages(Image.IMAGE_STATUS_QUEUED); } public static Image loadImage(String url) { Cursor cursor = cr.query(Images.CONTENT_URI, new String[]{Images.ID, Images.URL, Images.STATUS}, Images.URL + "=?", new String[]{url}, null); Image image = null; if (cursor.moveToFirst()) { image = new Image(cursor.getInt(0), cursor.getString(1), (byte) cursor.getInt(2)); } cursor.close(); return image; } public static List<Image> loadImages(int status) { Cursor cursor = cr.query(Images.CONTENT_URI, new String[]{Images.ID, Images.URL, Images.STATUS, Images.RETRIES}, Images.STATUS + "=?", new String[]{String.valueOf(status)}, Images.UPDATE_TIME + " DESC, " + Images.RETRIES + " ASC, " + Images.ID); List<Image> images = Lists.newArrayList(); while (cursor.moveToNext()) { Image image = new Image(cursor.getInt(0), cursor.getString(1), (byte) cursor.getInt(2)); image.retries = (byte) cursor.getInt(3); images.add(image); } cursor.close(); return images; } public static boolean saveImage(Image image) { ContentValues values = new ContentValues(); if (image.id == 0) { if (existImage(image)) return false; values.put(Images.URL, image.url); values.put(Images.STATUS, image.status); values.put(Images.UPDATE_TIME, image.updateTime); values.put(Images.RETRIES, image.retries); Uri contentUri = cr.insert(Images.CONTENT_URI, values); image.id = (int) ContentUris.parseId(contentUri); } else { values.put(Images.STATUS, image.status); values.put(Images.RETRIES, image.retries); cr.update(Images.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(image.id)}); } return true; } public static List<Integer> loadOldestImageIds(int keepMaxItems) { Cursor cursor = cr.query(Images.CONTENT_URI, new String[]{Images.COUNT}, null, null, null); int totalImages = 0; if (cursor.moveToNext()) { totalImages = cursor.getInt(0); } cursor.close(); List<Integer> images = Lists.newArrayList(); if (totalImages - keepMaxItems > 0) { cursor = cr.query(Images.limit(totalImages - keepMaxItems), new String[]{Images.ID}, null, null, Images.ID + " ASC"); while (cursor.moveToNext()) { images.add(cursor.getInt(0)); } cursor.close(); } return images; } public static void deleteImage(Image image) { cr.delete(Images.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(image.id)}); } public static void deleteImage(int imageId) { cr.delete(Images.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(imageId)}); } public static void deleteItem(Item item) { cr.delete(Items.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(item.id)}); } public static void deleteLecturer(Lecturer lecturer) { cr.delete(Lecturers.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(lecturer.id)}); } public static void deleteAllLecturers() { Cursor cursor = cr.query(Lecturers.CONTENT_URI, new String[]{Lecturers.ID}, null, null, null); while (cursor.moveToNext()) { int id = cursor.getInt(0); cr.delete(Lecturers.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(id)}); } cursor.close(); } public static void deleteAllImages() { Cursor cursor = cr.query(Images.CONTENT_URI, new String[]{Images.ID}, null, null, null); while (cursor.moveToNext()) { int id = cursor.getInt(0); cr.delete(Images.CONTENT_URI, Provider.WHERE_ID, new String[]{String.valueOf(id)}); } cursor.close(); } public static boolean existItem(Item item) { return existItem(item.link); } public static boolean existItem(String link) { Cursor cursor = cr.query(Items.CONTENT_URI, new String[]{Items.ID}, Items.LINK + "=?", new String[]{link}, null); boolean result = cursor.moveToFirst(); cursor.close(); return result; } public static boolean existChannel(Channel channel) { Cursor cursor = cr.query(Channels.CONTENT_URI, new String[]{Channels.ID}, Channels.URL + "=?", new String[]{channel.url}, null); boolean result = cursor.moveToFirst(); cursor.close(); return result; } public static boolean existLecturer(Lecturer lecturer) { Cursor cursor = cr.query(Lecturers.CONTENT_URI, new String[]{Lecturers.ID}, Lecturers.ID + "=?", new String[]{String.valueOf(lecturer.id)}, null); boolean result = cursor.moveToFirst(); cursor.close(); return result; } public static boolean existImage(Image image) { Cursor cursor = cr.query(Images.CONTENT_URI, new String[]{Images.ID}, Images.URL + "=?", new String[]{image.url}, null); boolean result = cursor.moveToFirst(); cursor.close(); return result; } public static void clearReadArticles() { recentReadArticles.clear(); } public static boolean isItemRead(int itemId) { return recentReadArticles.contains(String.valueOf(itemId)); } public static void markItemAsRead(Item item) { saveItemReadState(item, Item.READ); } public static void markAllItemsOfChannelAsRead(Channel channel) { Cursor cursor = cr.query(Items.CONTENT_URI, new String[]{Items.ID,}, Items.CHANNEL_ID + "=? AND " + Items.READ + "=0", new String[]{String.valueOf(channel.id)}, null); Item item = new Item(); while (cursor.moveToNext()) { item.id = cursor.getInt(0); item.read = Item.UNREAD; markItemAsRead(item); } cursor.close(); for (Item channelItem : channel.getItems()) { channelItem.read = Item.READ; } } public static void saveItemReadState(Item item, int readState) { if (item.isRead()) return; item.read = Item.READ; ContentValues values = new ContentValues(); values.put(Items.READ, readState); cr.update(Items.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(item.id)}); String key = String.valueOf(item.id); if (!recentReadArticles.contains(key)) { recentReadArticles.add(key); } } public static void markChannelToStarred(Channel channel) { if (channel.starred) return; channel.starred = true; ContentValues values = new ContentValues(); if (channel.id != 0) { values.put(Channels.STARRED, 1); cr.update(Channels.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); } } public static void unmarkChannelToStarred(Channel channel) { if (!channel.starred) return; channel.starred = false; ContentValues values = new ContentValues(); if (channel.id != 0) { values.put(Channels.STARRED, 0); cr.update(Channels.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); } } public static void markChannelToMute(Channel channel) { if (channel.mute) return; channel.mute = true; ContentValues values = new ContentValues(); if (channel.id != 0) { values.put(Channels.MUTE, 1); cr.update(Channels.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); } } public static void unmarkChannelToMute(Channel channel) { if (!channel.mute) return; channel.mute = false; ContentValues values = new ContentValues(); if (channel.id != 0) { values.put(Channels.MUTE, 0); cr.update(Channels.CONTENT_URI, values, Provider.WHERE_ID, new String[]{String.valueOf(channel.id)}); } } /** * @param cr * @return a map of ChannelId <-> Unread count */ public static SparseIntArray countUnreadItemsForEachChannel() { Cursor cursor = cr.query( Items.countUnreadEachChannel(), new String[]{Items.CHANNEL_ID, Items.UNREAD_COUNT}, Items.READ + "=? OR " + Items.READ + "=?", new String[]{String.valueOf(Item.UNREAD), String.valueOf(Item.KEPT_UNREAD)}, null); SparseIntArray unreadCounts = new SparseIntArray(cursor.getCount()); while (cursor.moveToNext()) { unreadCounts.put(cursor.getInt(0), cursor.getInt(1)); } cursor.close(); return unreadCounts; } public static int countUnreadItems() { Cursor cursor = cr.query( Items.countUnread(), new String[]{Items.UNREAD_COUNT}, Items.READ + "=? OR " + Items.READ + "=?", new String[]{String.valueOf(Item.UNREAD), String.valueOf(Item.KEPT_UNREAD)}, null); int unreadCounts = 0; if (cursor.moveToNext()) { unreadCounts = cursor.getInt(0); } cursor.close(); return unreadCounts; } private static boolean isChannelInCache(int channelId) { return channelCache.indexOfKey(channelId) >= 0; } private static void putChannelToCache(Channel channel) { if (!isChannelInCache(channel.id)) { channelCache.put(channel.id, new WeakReference<Channel>(channel)); } } private static Channel getChannelFromCache(int id) { if (isChannelInCache(id)) { WeakReference<Channel> channelRef = channelCache.get(id); return channelRef.get(); } return null; } public static void clearDatabase() { Application.getInstance().deleteDatabase(DatabaseHelper.DATABASE_NAME); } public static void unsubscribe(Channel channel) { deleteChannel(channel); } }