/* * Copyright (C) 2015 Simon Vig Therkildsen * * 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. */ package net.simonvt.cathode.provider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import java.util.List; import net.simonvt.cathode.CathodeApp; import net.simonvt.cathode.api.entity.Show; import net.simonvt.cathode.api.util.TimeUtils; import net.simonvt.cathode.database.DatabaseUtils; import net.simonvt.cathode.provider.DatabaseContract.EpisodeColumns; import net.simonvt.cathode.provider.DatabaseContract.ShowColumns; import net.simonvt.cathode.provider.ProviderSchematic.Episodes; import net.simonvt.cathode.provider.ProviderSchematic.Shows; import net.simonvt.cathode.settings.FirstAiredOffsetPreference; import net.simonvt.cathode.util.TextUtils; import net.simonvt.schematic.Cursors; public final class ShowDatabaseHelper { private static volatile ShowDatabaseHelper instance; public static ShowDatabaseHelper getInstance(Context context) { if (instance == null) { synchronized (ShowDatabaseHelper.class) { if (instance == null) { instance = new ShowDatabaseHelper(context.getApplicationContext()); } } } return instance; } private static final Object LOCK_ID = new Object(); private Context context; private ContentResolver resolver; private ShowDatabaseHelper(Context context) { this.context = context; resolver = context.getContentResolver(); CathodeApp.inject(context, this); } public long getId(long traktId) { synchronized (LOCK_ID) { Cursor c = resolver.query(Shows.SHOWS, new String[] { ShowColumns.ID, }, ShowColumns.TRAKT_ID + "=?", new String[] { String.valueOf(traktId), }, null); long id = -1L; if (c.moveToFirst()) { id = Cursors.getLong(c, ShowColumns.ID); } c.close(); return id; } } public long getIdFromTmdb(int tmdbId) { synchronized (LOCK_ID) { Cursor c = resolver.query(Shows.SHOWS, new String[] { ShowColumns.ID, }, ShowColumns.TMDB_ID + "=?", new String[] { String.valueOf(tmdbId), }, null); long id = -1L; if (c.moveToFirst()) { id = Cursors.getLong(c, ShowColumns.ID); } c.close(); return id; } } public boolean exists(long traktId) { return getId(traktId) != -1L; } public long getTraktId(long showId) { Cursor c = resolver.query(Shows.withId(showId), new String[] { ShowColumns.TRAKT_ID, }, null, null, null); long traktId = -1L; if (c.moveToFirst()) { traktId = Cursors.getLong(c, ShowColumns.TRAKT_ID); } c.close(); return traktId; } public int getTmdbId(long showId) { Cursor c = resolver.query(Shows.withId(showId), new String[] { ShowColumns.TMDB_ID, }, null, null, null); int tmdbId = -1; if (c.moveToFirst()) { tmdbId = Cursors.getInt(c, ShowColumns.TMDB_ID); } c.close(); return tmdbId; } public static final class IdResult { public long showId; public boolean didCreate; public IdResult(long showId, boolean didCreate) { this.showId = showId; this.didCreate = didCreate; } } public IdResult getIdOrCreate(long traktId) { synchronized (LOCK_ID) { long id = getId(traktId); if (id == -1L) { id = create(traktId); return new IdResult(id, true); } else { return new IdResult(id, false); } } } private long create(long traktId) { ContentValues cv = new ContentValues(); cv.put(ShowColumns.TRAKT_ID, traktId); cv.put(ShowColumns.NEEDS_SYNC, true); return Shows.getShowId(resolver.insert(Shows.SHOWS, cv)); } public long fullUpdate(Show show) { IdResult result = getIdOrCreate(show.getIds().getTrakt()); final long id = result.showId; ContentValues cv = getShowCVs(show); cv.put(ShowColumns.NEEDS_SYNC, false); cv.put(ShowColumns.LAST_SYNC, System.currentTimeMillis()); resolver.update(Shows.withId(id), cv, null, null); if (show.getGenres() != null) { insertShowGenres(id, show.getGenres()); } return id; } /** * Creates the show if it does not exist. */ public long partialUpdate(Show show) { IdResult result = getIdOrCreate(show.getIds().getTrakt()); final long id = result.showId; ContentValues cv = getShowCVs(show); resolver.update(Shows.withId(id), cv, null, null); if (show.getGenres() != null) { insertShowGenres(id, show.getGenres()); } return id; } public long getNextEpisodeId(long showId) { int lastWatchedSeason = -1; int lastWatchedEpisode = -1; Cursor lastWatchedCursor = resolver.query(Episodes.fromShow(showId), new String[] { EpisodeColumns.ID, EpisodeColumns.SEASON, EpisodeColumns.EPISODE, }, EpisodeColumns.WATCHED + "=1", null, EpisodeColumns.SEASON + " DESC, " + EpisodeColumns.EPISODE + " DESC LIMIT 1"); if (lastWatchedCursor.moveToFirst()) { lastWatchedSeason = Cursors.getInt(lastWatchedCursor, EpisodeColumns.SEASON); lastWatchedEpisode = Cursors.getInt(lastWatchedCursor, EpisodeColumns.EPISODE); } lastWatchedCursor.close(); Cursor nextEpisode = resolver.query(Episodes.fromShow(showId), new String[] { EpisodeColumns.ID, }, EpisodeColumns.SEASON + ">0 AND (" + EpisodeColumns.SEASON + ">? OR (" + EpisodeColumns.SEASON + "=? AND " + EpisodeColumns.EPISODE + ">?)) AND " + EpisodeColumns.FIRST_AIRED + " NOT NULL", new String[] { String.valueOf(lastWatchedSeason), String.valueOf(lastWatchedSeason), String.valueOf(lastWatchedEpisode), }, EpisodeColumns.SEASON + " ASC, " + EpisodeColumns.EPISODE + " ASC LIMIT 1"); long nextEpisodeId = -1L; if (nextEpisode.moveToFirst()) { nextEpisodeId = Cursors.getLong(nextEpisode, EpisodeColumns.ID); } nextEpisode.close(); return nextEpisodeId; } public boolean needsSync(long showId) { Cursor show = null; try { show = resolver.query(Shows.withId(showId), new String[] { ShowColumns.NEEDS_SYNC, }, null, null, null); if (show.moveToFirst()) { return Cursors.getBoolean(show, ShowColumns.NEEDS_SYNC); } return false; } finally { if (show != null) { show.close(); } } } public boolean isUpdated(long traktId, String lastUpdatedIso) { long lastUpdated = TimeUtils.getMillis(lastUpdatedIso); Cursor show = null; try { show = resolver.query(Shows.SHOWS, new String[] { ShowColumns.LAST_UPDATED, }, ShowColumns.TRAKT_ID + "=?", new String[] { String.valueOf(traktId), }, null); if (show.moveToFirst()) { return lastUpdated > Cursors.getLong(show, ShowColumns.LAST_UPDATED); } return false; } finally { if (show != null) show.close(); } } public boolean shouldUpdate(long traktId) { Cursor show = null; try { show = resolver.query(Shows.SHOWS, new String[] { ShowColumns.WATCHED_COUNT, ShowColumns.IN_COLLECTION_COUNT, ShowColumns.IN_WATCHLIST_COUNT, ShowColumns.IN_COLLECTION_COUNT, ShowColumns.IN_WATCHLIST, }, ShowColumns.TRAKT_ID + "=?", new String[] { String.valueOf(traktId), }, null); if (show.moveToFirst()) { final int watchedCount = Cursors.getInt(show, ShowColumns.WATCHED_COUNT); final int collectedCount = Cursors.getInt(show, ShowColumns.IN_COLLECTION_COUNT); final int watchlistCount = Cursors.getInt(show, ShowColumns.IN_WATCHLIST_COUNT); final boolean inWatchlist = Cursors.getBoolean(show, ShowColumns.IN_WATCHLIST); if (watchedCount > 0 || collectedCount > 0 || watchlistCount > 0 || inWatchlist) { return true; } } return false; } finally { if (show != null) show.close(); } } public void insertShowGenres(long showId, List<String> genres) { resolver.delete(ProviderSchematic.ShowGenres.fromShow(showId), null, null); for (String genre : genres) { ContentValues cv = new ContentValues(); cv.put(DatabaseContract.ShowGenreColumns.SHOW_ID, showId); cv.put(DatabaseContract.ShowGenreColumns.GENRE, TextUtils.upperCaseFirstLetter(genre)); resolver.insert(ProviderSchematic.ShowGenres.fromShow(showId), cv); } } public void setWatched(long showId, boolean watched) { ContentValues cv = new ContentValues(); cv.put(EpisodeColumns.WATCHED, watched); final long firstAiredOffset = FirstAiredOffsetPreference.getInstance().getOffsetMillis(); final long millis = System.currentTimeMillis() - firstAiredOffset; resolver.update(Episodes.fromShow(showId), cv, EpisodeColumns.FIRST_AIRED + "<?", new String[] { String.valueOf(millis), }); } public void setIsInWatchlist(long showId, boolean inWatchlist) { setIsInWatchlist(showId, inWatchlist, 0); } public void setIsInWatchlist(long showId, boolean inWatchlist, long listedAt) { ContentValues cv = new ContentValues(); cv.put(ShowColumns.IN_WATCHLIST, inWatchlist); cv.put(ShowColumns.LISTED_AT, listedAt); resolver.update(Shows.withId(showId), cv, null, null); } public void setIsInCollection(long traktId, boolean inCollection) { final long showId = getId(traktId); ContentValues cv = new ContentValues(); cv.put(EpisodeColumns.IN_COLLECTION, inCollection); final long firstAiredOffset = FirstAiredOffsetPreference.getInstance().getOffsetMillis(); final long millis = System.currentTimeMillis() - firstAiredOffset; resolver.update(Episodes.fromShow(showId), cv, EpisodeColumns.FIRST_AIRED + "<?", new String[] { String.valueOf(millis), }); } private static ContentValues getShowCVs(Show show) { ContentValues cv = new ContentValues(); cv.put(ShowColumns.TITLE, show.getTitle()); cv.put(ShowColumns.TITLE_NO_ARTICLE, DatabaseUtils.removeLeadingArticle(show.getTitle())); if (show.getYear() != null) cv.put(ShowColumns.YEAR, show.getYear()); if (show.getCountry() != null) cv.put(ShowColumns.COUNTRY, show.getCountry()); if (show.getOverview() != null) { cv.put(ShowColumns.OVERVIEW, show.getOverview()); } if (show.getRuntime() != null) cv.put(ShowColumns.RUNTIME, show.getRuntime()); if (show.getNetwork() != null) cv.put(ShowColumns.NETWORK, show.getNetwork()); if (show.getAirs() != null) { cv.put(ShowColumns.AIR_DAY, show.getAirs().getDay()); cv.put(ShowColumns.AIR_TIME, show.getAirs().getTime()); cv.put(ShowColumns.AIR_TIMEZONE, show.getAirs().getTimezone()); } if (show.getCertification() != null) { cv.put(ShowColumns.CERTIFICATION, show.getCertification()); } if (show.getTrailer() != null) cv.put(ShowColumns.TRAILER, show.getTrailer()); if (show.getHomepage() != null) { cv.put(ShowColumns.HOMEPAGE, show.getHomepage()); } if (show.getStatus() != null) { cv.put(ShowColumns.STATUS, show.getStatus().toString()); } cv.put(ShowColumns.TRAKT_ID, show.getIds().getTrakt()); cv.put(ShowColumns.SLUG, show.getIds().getSlug()); cv.put(ShowColumns.IMDB_ID, show.getIds().getImdb()); cv.put(ShowColumns.TVDB_ID, show.getIds().getTvdb()); cv.put(ShowColumns.TMDB_ID, show.getIds().getTmdb()); cv.put(ShowColumns.TVRAGE_ID, show.getIds().getTvrage()); if (show.getUpdatedAt() != null) { cv.put(ShowColumns.LAST_UPDATED, show.getUpdatedAt().getTimeInMillis()); } if (show.getRating() != null) { cv.put(ShowColumns.RATING, show.getRating()); } if (show.getVotes() != null) { cv.put(ShowColumns.VOTES, show.getVotes()); } return cv; } }