/*
* Copyright (C) 2014 Michell Bak
*
* 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 com.miz.identification;
import android.content.Context;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.miz.abstractclasses.TvShowApiService;
import com.miz.apis.thetvdb.Episode;
import com.miz.apis.thetvdb.TvShow;
import com.miz.db.DbAdapterTvShowEpisodes;
import com.miz.db.DbAdapterTvShows;
import com.miz.functions.MizLib;
import com.miz.functions.TvShowLibraryUpdateCallback;
import com.miz.mizuu.MizuuApplication;
import com.miz.utils.FileUtils;
import com.miz.utils.LocalBroadcastUtils;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.miz.functions.PreferenceKeys.LANGUAGE_PREFERENCE;
public class TvShowIdentification {
private final Picasso mPicasso;
private final TvShowLibraryUpdateCallback mCallback;
private final Context mContext;
private ArrayList<ShowStructure> mShowStructures = new ArrayList<ShowStructure>();
private Multimap<String, Integer> mShowFolderNameMap = LinkedListMultimap.create();
private SparseBooleanArray mImdbMap = new SparseBooleanArray();
private String mShowId = null, mLocale = null;
private int mSeason = -1, mEpisode = -1;
private boolean mCancel = false;
public TvShowIdentification(Context context, TvShowLibraryUpdateCallback callback, ArrayList<ShowStructure> files) {
mContext = context;
mCallback = callback;
mShowStructures = new ArrayList<ShowStructure>(files);
mPicasso = MizuuApplication.getPicasso(mContext);
// Get the language preference
getLanguagePreference();
}
private void getLanguagePreference() {
mLocale = PreferenceManager.getDefaultSharedPreferences(mContext).getString(LANGUAGE_PREFERENCE, "en");
}
/**
* Use this to disable TV show searching
* and attempt identification based on the
* provided TV show ID.
* @param showId
*/
public void setShowId(String showId) {
mShowId = showId;
}
public boolean overrideShowId() {
return null != getShowId();
}
public String getShowId() {
return mShowId;
}
/**
* Use this to override the season
* and episode number of a given file.
* @param season
* @param episode
*/
public void setSeasonAndEpisode(int season, int episode) {
mSeason = season;
mEpisode = episode;
}
public int getSeason() {
return mSeason;
}
public int getEpisode() {
return mEpisode;
}
public boolean overrideSeasonAndEpisode() {
return getSeason() >= 0 && getEpisode() >= 0;
}
/**
* Accepts two-letter ISO 639-1 language codes, i.e. "en".
* @param language
*/
public void setLanguage(String language) {
mLocale = language;
}
public void cancel() {
mCancel = true;
}
public void start() {
// Go through all files
for (int i = 0; i < mShowStructures.size(); i++) {
if (mCancel)
return;
ShowStructure ss = mShowStructures.get(i);
mShowFolderNameMap.put(ss.getDecryptedShowFolderName(), i);
mImdbMap.put(i, ss.hasImdbId());
}
TvShowApiService service = MizuuApplication.getTvShowService(mContext);
for (String showFolderName : mShowFolderNameMap.keySet()) {
if (mCancel)
return;
TvShow show = null;
List<TvShow> results = new ArrayList<TvShow>();
if (!overrideShowId()) {
// Get the first ShowStructure element
ShowStructure ss = mShowStructures.get(mShowFolderNameMap.get(showFolderName).iterator().next());
// Check if there's an IMDb ID and attempt to search based on it
if (ss.hasImdbId())
results = service.searchByImdbId(ss.getImdbId(), null);
// If there's no results, attempt to search based on the show folder name and year
if (results.size() == 0) {
int year = mShowStructures.get(mShowFolderNameMap.get(showFolderName).iterator().next()).getReleaseYear();
if (year >= 0)
results = service.search(showFolderName + " " + year, null);
}
// If there's still no results, search based on the show folder name only
if (results.size() == 0)
results = service.search(showFolderName, null);
} else
show = service.get(getShowId(), mLocale);
// Check if the show folder name results in any matches
// - if it does, use that to identify all files
if (results.size() > 0 || overrideShowId()) {
// Get the TV show and create it in the database
if (!overrideShowId())
show = service.get(results.get(0).getId(), mLocale);
createShow(show);
int episodeCount = 0;
for (Integer value : mShowFolderNameMap.get(showFolderName)) {
if (mCancel)
continue;
ShowStructure ss = mShowStructures.get(value);
for (com.miz.identification.Episode ep : ss.getEpisodes()) {
if (mCancel)
continue;
downloadEpisode(show, ep.getSeason(), ep.getEpisode(), ss.getFilepath());
episodeCount++;
}
}
showAddedShowNotification(show, episodeCount);
} else {
// else go through each file and identify based on the filename
for (Integer value : mShowFolderNameMap.get(showFolderName)) {
if (mCancel)
continue;
ShowStructure ss = mShowStructures.get(value);
show = null;
results = new ArrayList<TvShow>();
// Check if there's an IMDb ID and attempt to search based on it
if (ss.hasImdbId())
results = service.searchByImdbId(ss.getImdbId(), null);
// If there's no results, attempt to search based on the show folder name and year
if (results.size() == 0) {
int year = ss.getReleaseYear();
if (year >= 0)
results = service.search(ss.getDecryptedFilename() + " " + year, null);
}
// If there's still no results, search based on the show folder name only
if (results.size() == 0)
results = service.search(ss.getDecryptedFilename(), null);
if (results.size() == 0) {
show = new TvShow();
show.setId(DbAdapterTvShows.UNIDENTIFIED_ID);
} else {
show = service.get(results.get(0).getId(), mLocale);
}
createShow(show);
for (com.miz.identification.Episode ep : ss.getEpisodes()) {
if (mCancel)
continue;
downloadEpisode(show, ep.getSeason(), ep.getEpisode(), ss.getFilepath());
}
showAddedShowNotification(show, ss.getEpisodes().size());
}
}
}
}
private void showAddedShowNotification(TvShow show, int episodeCount) {
if (show == null)
return;
File coverFile = FileUtils.getTvShowThumb(mContext, show.getId());
File backdropFile = FileUtils.getTvShowBackdrop(mContext, show.getId());
if (!backdropFile.exists())
backdropFile = coverFile;
try {
mCallback.onTvShowAdded(show.getId(), show.getTitle(), mPicasso.load(coverFile).resize(getNotificationImageSizeSmall(), (int) (getNotificationImageSizeSmall() * 1.5)).get(),
mPicasso.load(backdropFile).skipMemoryCache().resize(getNotificationImageWidth(), (getNotificationImageWidth() / 16) * 9).get(), episodeCount);
} catch (IOException e) {
mCallback.onTvShowAdded(show.getId(), show.getTitle(), null, null, episodeCount);
}
LocalBroadcastUtils.updateTvShowLibrary(mContext);
LocalBroadcastUtils.updateTvShowSeasonsOverview(mContext);
}
private void createShow(TvShow thisShow) {
boolean downloadCovers = true;
// Check if the show already exists before downloading the show info
if (!TextUtils.isEmpty(thisShow.getId()) && !thisShow.getId().equals(DbAdapterTvShows.UNIDENTIFIED_ID)) {
DbAdapterTvShows db = MizuuApplication.getTvDbAdapter();
Cursor cursor = db.getShow(thisShow.getId());
if (cursor.getCount() > 0) {
downloadCovers = false;
}
}
if (downloadCovers) {
if (!TextUtils.isEmpty(thisShow.getId()) && !thisShow.getId().equals(DbAdapterTvShows.UNIDENTIFIED_ID)) {
String thumb_filepath = FileUtils.getTvShowThumb(mContext, thisShow.getId()).getAbsolutePath();
String backdrop_filepath = FileUtils.getTvShowBackdrop(mContext, thisShow.getId()).getAbsolutePath();
// Download the cover file and try again if it fails
if (!TextUtils.isEmpty(thisShow.getCoverUrl()))
if (!MizLib.downloadFile(thisShow.getCoverUrl(), thumb_filepath))
MizLib.downloadFile(thisShow.getCoverUrl(), thumb_filepath);
MizLib.resizeBitmapFileToCoverSize(mContext, thumb_filepath);
// Download the backdrop image file and try again if it fails
if (!TextUtils.isEmpty(thisShow.getBackdropUrl()))
if (!MizLib.downloadFile(thisShow.getBackdropUrl(), backdrop_filepath))
MizLib.downloadFile(thisShow.getBackdropUrl(), backdrop_filepath);
DbAdapterTvShows dbHelper = MizuuApplication.getTvDbAdapter();
dbHelper.createShow(thisShow.getId(), thisShow.getTitle(), thisShow.getDescription(), thisShow.getActors(), thisShow.getGenres(),
thisShow.getRating(), thisShow.getCertification(), thisShow.getRuntime(), thisShow.getFirstAired(), "0");
}
}
}
private void downloadEpisode(TvShow thisShow, int season, int episode, String filepath) {
Episode thisEpisode = new Episode();
if (overrideSeasonAndEpisode()) {
season = getSeason();
episode = getEpisode();
}
ArrayList<Episode> episodes = thisShow.getEpisodes();
int count = episodes.size();
for (int i = 0; i < count; i++) {
if (episodes.get(i).getSeason() == season && episodes.get(i).getEpisode() == episode) {
thisEpisode = episodes.get(i);
break;
}
}
if (thisEpisode.getEpisode() == -1) {
thisEpisode.setEpisode(episode);
thisEpisode.setSeason(season);
}
// Download the episode screenshot file and try again if it fails
if (!TextUtils.isEmpty(thisEpisode.getScreenshotUrl())) {
String screenshotFile = FileUtils.getTvShowEpisode(mContext, thisShow.getId(), season, episode).getAbsolutePath();
if (!MizLib.downloadFile(thisEpisode.getScreenshotUrl(), screenshotFile))
MizLib.downloadFile(thisEpisode.getScreenshotUrl(), screenshotFile);
}
// Download season cover if it hasn't already been downloaded
if (thisShow.hasSeason(thisEpisode.getSeason())) {
File seasonFile = FileUtils.getTvShowSeason(mContext, thisShow.getId(), season);
if (!seasonFile.exists()) {
if (!MizLib.downloadFile(thisShow.getSeason(thisEpisode.getSeason()).getCoverPath(), seasonFile.getAbsolutePath()))
MizLib.downloadFile(thisShow.getSeason(thisEpisode.getSeason()).getCoverPath(), seasonFile.getAbsolutePath());
}
}
addToDatabase(thisShow, thisEpisode, filepath);
}
private void addToDatabase(TvShow thisShow, Episode ep, String filepath) {
DbAdapterTvShowEpisodes dbHelper = MizuuApplication.getTvEpisodeDbAdapter();
if (thisShow.getId().equals(DbAdapterTvShows.UNIDENTIFIED_ID)) {
// If it's an unidentified file, we shouldn't create a episode entry in the database
MizuuApplication.getTvShowEpisodeMappingsDbAdapter().createFilepathMapping(filepath,
thisShow.getId(), MizLib.addIndexZero(ep.getSeason()), MizLib.addIndexZero(ep.getEpisode()));
} else {
dbHelper.createEpisode(filepath, MizLib.addIndexZero(ep.getSeason()),
MizLib.addIndexZero(ep.getEpisode()), thisShow.getId(), ep.getTitle(),
ep.getDescription(), ep.getAirdate(), ep.getRating(), ep.getDirector(),
ep.getWriter(), ep.getGueststars(), "0", "0");
}
updateNotification(thisShow, ep, filepath);
}
private void updateNotification(TvShow thisShow, Episode ep, String filepath) {
File coverFile = FileUtils.getTvShowThumb(mContext, thisShow.getId());
File backdropFile = FileUtils.getTvShowEpisode(mContext, thisShow.getId(), MizLib.addIndexZero(ep.getSeason()), MizLib.addIndexZero(ep.getEpisode()));
if (!backdropFile.exists())
backdropFile = coverFile;
try {
mCallback.onEpisodeAdded(thisShow.getId(), thisShow.getId().equals(DbAdapterTvShows.UNIDENTIFIED_ID) ? filepath : thisShow.getTitle() + " S" + MizLib.addIndexZero(ep.getSeason()) + "E" + MizLib.addIndexZero(ep.getEpisode()),
mPicasso.load(coverFile).resize(getNotificationImageSizeSmall(), (int) (getNotificationImageSizeSmall() * 1.5)).get(),
mPicasso.load(backdropFile).skipMemoryCache().get());
} catch (IOException e) {
mCallback.onEpisodeAdded(thisShow.getId(), thisShow.getId().equals(DbAdapterTvShows.UNIDENTIFIED_ID) ? filepath : thisShow.getTitle() + " S" + MizLib.addIndexZero(ep.getSeason()) + "E" + MizLib.addIndexZero(ep.getEpisode()),
null, null);
}
}
// These variables don't need to be re-initialized
private int widgetWidth = 0, smallSize = 0;
private int getNotificationImageWidth() {
if (widgetWidth == 0)
widgetWidth = MizLib.getLargeNotificationWidth(mContext);
return widgetWidth;
}
private int getNotificationImageSizeSmall() {
if (smallSize == 0)
smallSize = MizLib.getThumbnailNotificationSize(mContext);
return smallSize;
}
}