/*
This file is part of JFLICKS.
JFLICKS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
JFLICKS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JFLICKS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jflicks.tv.scheduler.system;
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.jflicks.db.DbWorker;
import org.jflicks.nms.NMS;
import org.jflicks.nms.NMSConstants;
import org.jflicks.tv.Airing;
import org.jflicks.tv.Channel;
import org.jflicks.tv.Recording;
import org.jflicks.tv.RecordingRule;
import org.jflicks.tv.Show;
import org.jflicks.tv.ShowAiring;
import org.jflicks.tv.Task;
import org.jflicks.tv.postproc.PostProc;
import org.jflicks.tv.postproc.worker.Worker;
import org.jflicks.tv.scheduler.BaseScheduler;
import org.jflicks.tv.scheduler.RecordedShow;
import org.jflicks.util.LogUtil;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.osgi.Db4oService;
import com.db4o.query.Predicate;
/**
* Class that can control recording schedules.
*
* @author Doug Barnum
* @version 1.0
*/
public class SystemScheduler extends BaseScheduler implements DbWorker {
private ObjectContainer objectContainer;
private ObjectContainer cacheObjectContainer;
private ObjectContainer recordedObjectContainer;
private ObjectContainer recordingObjectContainer;
private Db4oService db4oService;
/**
* Simple default constructor.
*/
public SystemScheduler() {
setTitle("SystemScheduler");
}
/**
* We use the Db4oService to persist the recording settings.
*
* @return A Db4oService instance.
*/
public Db4oService getDb4oService() {
return (db4oService);
}
/**
* We use the Db4oService to persist the recording settings.
*
* @param s A Db4oService instance.
*/
public void setDb4oService(Db4oService s) {
db4oService = s;
}
private synchronized ObjectContainer getObjectContainer() {
if (objectContainer == null) {
Db4oService s = getDb4oService();
if (s != null) {
com.db4o.config.Configuration config = s.newConfiguration();
config.objectClass(RecordingRule.class).cascadeOnActivate(true);
config.objectClass(RecordingRule.class).cascadeOnUpdate(true);
config.objectClass(ShowAiring.class).cascadeOnActivate(true);
config.objectClass(ShowAiring.class).cascadeOnUpdate(true);
config.objectClass(Show.class).cascadeOnActivate(true);
config.objectClass(Show.class).cascadeOnUpdate(true);
config.objectClass(Airing.class).cascadeOnActivate(true);
config.objectClass(Airing.class).cascadeOnUpdate(true);
objectContainer = s.openFile(config, "db/sched.dat");
}
}
return (objectContainer);
}
private synchronized ObjectContainer getCacheObjectContainer() {
if (cacheObjectContainer == null) {
Db4oService s = getDb4oService();
if (s != null) {
com.db4o.config.Configuration config = s.newConfiguration();
config.objectClass(ShowAiring.class).cascadeOnUpdate(true);
cacheObjectContainer = s.openFile(config, "db/sacache.dat");
}
}
return (objectContainer);
}
private synchronized ObjectContainer getRecordedObjectContainer() {
if (recordedObjectContainer == null) {
Db4oService s = getDb4oService();
if (s != null) {
com.db4o.config.Configuration config = s.newConfiguration();
config.objectClass(
RecordedShow.class).objectField("showId").indexed(true);
recordedObjectContainer = s.openFile(config, "db/recorded.dat");
}
}
return (recordedObjectContainer);
}
private synchronized ObjectContainer getRecordingObjectContainer() {
if (recordingObjectContainer == null) {
Db4oService s = getDb4oService();
if (s != null) {
com.db4o.config.Configuration config = s.newConfiguration();
config.objectClass(Recording.class).cascadeOnUpdate(true);
recordingObjectContainer =
s.openFile(config, "db/recordings.dat");
}
}
return (recordingObjectContainer);
}
/**
* Close up all resources.
*/
public void close() {
if (objectContainer != null) {
boolean result = objectContainer.close();
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: closed " + result);
objectContainer = null;
} else {
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: Tried to close "
+ "but objectContainer null.");
}
if (cacheObjectContainer != null) {
boolean result = cacheObjectContainer.close();
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: (cache) closed " + result);
cacheObjectContainer = null;
} else {
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: Tried to close "
+ "but cacheObjectContainer null.");
}
if (recordedObjectContainer != null) {
boolean result = recordedObjectContainer.close();
LogUtil.log(LogUtil.DEBUG, "SystemScheduler (recorded): closed " + result);
recordedObjectContainer = null;
} else {
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: Tried to close "
+ "but recordedObjectContainer null.");
}
if (recordingObjectContainer != null) {
boolean result = recordingObjectContainer.close();
LogUtil.log(LogUtil.DEBUG, "SystemScheduler (recording): closed " + result);
recordingObjectContainer = null;
} else {
LogUtil.log(LogUtil.DEBUG, "SystemScheduler: Tried to close "
+ "but recordingObjectContainer null.");
}
}
/**
* {@inheritDoc}
*/
public RecordingRule[] getRecordingRules() {
RecordingRule[] result = null;
ObjectContainer oc = getObjectContainer();
if (oc != null) {
ObjectSet<RecordingRule> os =
oc.queryByExample(RecordingRule.class);
if (os != null) {
result = os.toArray(new RecordingRule[os.size()]);
if (isUpdateRecordingRules()) {
if (result != null) {
for (int i = 0; i < result.length; i++) {
addRecordingRule(result[i]);
}
}
os = oc.queryByExample(RecordingRule.class);
if (os != null) {
result = os.toArray(new RecordingRule[os.size()]);
}
}
// We have to make sure all of out RecordingRule instances
// in out array is set to sortByName.
for (int i = 0; i < result.length; i++) {
result[i].setSortBy(RecordingRule.SORT_BY_NAME);
}
Arrays.sort(result);
// At this point we should reconcile the current
// workers with the Task definitions of the RecordingRule
// instances. If something needs to change it should be
// written out to disk. This *could* be bad for users if
// using workers or more often they add them. This will
// dynamically find them without them having to fix it
// manually.
if (result != null) {
for (int i = 0; i < result.length; i++) {
reconcileRecordingRule(result[i]);
}
}
}
}
return (result);
}
private int contains(Task[] array, Task t) {
int result = -1;
if ((array != null) && (t != null)) {
String title = t.getTitle();
if (title != null) {
for (int i = 0; i < array.length; i++) {
if (title.equals(array[i].getTitle())) {
result = i;
break;
}
}
}
}
return (result);
}
private boolean hasTask(Worker[] array, Task t) {
boolean result = false;
if ((array != null) && (t != null)) {
String title = t.getTitle();
if (title != null) {
for (int i = 0; i < array.length; i++) {
if (title.equals(array[i].getTitle())) {
result = true;
break;
}
}
}
}
return (result);
}
private boolean hasWorker(Task[] array, Worker w) {
boolean result = false;
if ((array != null) && (w != null)) {
String title = w.getTitle();
if (title != null) {
for (int i = 0; i < array.length; i++) {
if (title.equals(array[i].getTitle())) {
result = true;
break;
}
}
}
}
return (result);
}
private void reconcileRecordingRule(RecordingRule rr) {
NMS n = getNMS();
if ((n != null) && (rr != null)) {
PostProc pp = n.getPostProc();
if (pp != null) {
Worker[] warray = pp.getWorkers();
Task[] tarray = rr.getTasks();
if ((warray != null) && (tarray != null)) {
ArrayList<Worker> wmissing = new ArrayList<Worker>();
for (int i = 0; i < warray.length; i++) {
if (!hasWorker(tarray, warray[i])) {
wmissing.add(warray[i]);
}
}
ArrayList<Task> tmissing = new ArrayList<Task>();
for (int i = 0; i < tarray.length; i++) {
if (!hasTask(warray, tarray[i])) {
tmissing.add(tarray[i]);
}
}
// At this point we have lists that have the
// "mis-matches". If either has anything in it
// then we need to create a new Task array and
// make it coincide with the Worker array.
if ((wmissing.size() > 0) || (tmissing.size() > 0)) {
// A fresh set of Tasks at their default settings.
// However we don't want to lose a setting the user
// has changed so we want to pick and choose from
// the old array and new array. Only use new array
// if it is new to us.
ArrayList<Task> tasklist = new ArrayList<Task>();
Task[] fresh = n.getTasks();
if ((fresh != null) && (fresh.length > 0)) {
for (int i = 0; i < fresh.length; i++) {
int index = contains(tarray, fresh[i]);
if (index != -1) {
tasklist.add(tarray[index]);
} else {
tasklist.add(fresh[i]);
}
}
}
// Update the proper tasks.
Task[] tasks = null;
if (tasklist.size() > 0) {
tasks = tasklist.toArray(new Task[tasklist.size()]);
}
if (tasks != null) {
Arrays.sort(tasks, new TaskByDescription());
}
LogUtil.log(LogUtil.INFO, "We need to update the RecordingRule since "
+ "the tasks have changed.");
rr.setTasks(tasks);
addRecordingRule(rr);
}
}
}
}
}
private RecordingRule getRecordingRuleById(String id) {
RecordingRule result = null;
ObjectContainer oc = getObjectContainer();
if (oc != null) {
final String sid = id;
List<RecordingRule> rules =
oc.query(new Predicate<RecordingRule>() {
public boolean match(RecordingRule rr) {
return (sid.equals(rr.getId()));
}
});
if ((rules != null) && (rules.size() > 0)) {
result = rules.get(0);
}
}
return (result);
}
/**
* {@inheritDoc}
*/
public void addRecordingRule(RecordingRule rr) {
ObjectContainer oc = getObjectContainer();
if ((rr != null) && (oc != null)) {
// First remove this rule if it already exists.
removeRecordingRule(rr);
oc.store(new RecordingRule(rr));
oc.commit();
addCacheFromRecordingRule(rr);
}
}
/**
* {@inheritDoc}
*/
public void removeRecordingRule(RecordingRule rr) {
ObjectContainer oc = getObjectContainer();
if ((rr != null) && (oc != null)) {
final String name = rr.getName();
List<RecordingRule> rules =
oc.query(new Predicate<RecordingRule>() {
public boolean match(RecordingRule rr) {
return (name.equals(rr.getName()));
}
});
if (rules != null) {
LogUtil.log(LogUtil.DEBUG, "Found " + rules.size() + " rules");
for (int i = 0; i < rules.size(); i++) {
RecordingRule trule = rules.get(i);
if (trule.getId().equals(rr.getId())) {
LogUtil.log(LogUtil.DEBUG, "Removing " + trule.getId());
oc.delete(trule);
}
}
}
}
}
/**
* {@inheritDoc}
*/
public Recording[] getRecordings() {
Recording[] result = null;
ObjectContainer oc = getRecordingObjectContainer();
if (oc != null) {
ObjectSet<Recording> os = oc.queryByExample(Recording.class);
if (os != null) {
result = os.toArray(new Recording[os.size()]);
if (isUpdateRecordings()) {
if (result != null) {
for (int i = 0; i < result.length; i++) {
addRecording(result[i]);
}
}
os = oc.queryByExample(Recording.class);
if (os != null) {
result = os.toArray(new Recording[os.size()]);
}
}
// We sometimes get an array that has at least one null
// entry. Seems like a bug in ObjectSet or perhaps we
// are doing something stupid. Either way lets make sure
// none are null.
result = zap(result);
Arrays.sort(result);
}
}
return (result);
}
/**
* {@inheritDoc}
*/
public void updateRecording(Recording r) {
ObjectContainer oc = getRecordingObjectContainer();
NMS n = getNMS();
if ((r != null) && (n != null) && (oc != null)) {
Recording old = n.getRecordingById(r.getId());
// Make sure the old one still exists...it may have gotten
// deleted.
if (old != null) {
removeRecording(r);
// Before we continue, lets make sure the recording is on disk.
// There is a chance that the user has deleted it underneath us
// and we are putting a bogus object. This is less likely but
// still a good check.
String path = r.getPath();
String hlspath = path.substring(0, path.lastIndexOf("."));
hlspath = hlspath + ".m3u8";
File hlsdisk = new File(hlspath);
File ondisk = new File(path);
if ((ondisk.exists()) || (hlsdisk.exists())) {
oc.store(new Recording(r));
oc.commit();
// Tell clients via the NMS.
n.sendMessage(NMSConstants.MESSAGE_RECORDING_UPDATE
+ " : " + r.getId());
}
}
}
}
/**
* {@inheritDoc}
*/
public void indexRecording(String s, Recording r) {
if ((s != null) && (r != null)) {
RecordingRule rr = getRecordingRuleById(r.getRecordingRuleId());
NMS n = getNMS();
if ((n != null) && (rr != null)) {
PostProc pp = n.getPostProc();
if (pp != null) {
pp.addProcessing(s, r);
pp.addProcessing(rr, r, true);
// Now that we have finished kill the rule if it was a once.
if (rr.isOnceType()) {
removeRecordingRule(rr);
}
}
}
}
}
/**
* {@inheritDoc}
*/
public void addRecording(Recording r) {
ObjectContainer oc = getRecordingObjectContainer();
if ((r != null) && (oc != null)) {
removeRecording(r);
oc.store(new Recording(r));
oc.commit();
// We have added a recording so it's time to queue up any
// post processing needed to be done.
RecordingRule rr = getRecordingRuleById(r.getRecordingRuleId());
NMS n = getNMS();
if ((rr != null) && (n != null)) {
PostProc pp = n.getPostProc();
if (pp != null) {
pp.addProcessing(rr, r, false);
}
}
}
}
/**
* {@inheritDoc}
*/
public void removeRecording(Recording r) {
ObjectContainer oc = getRecordingObjectContainer();
if ((r != null) && (oc != null)) {
final String id = r.getId();
List<Recording> recs = oc.query(new Predicate<Recording>() {
public boolean match(Recording r) {
return (id.equals(r.getId()));
}
});
if (recs != null) {
// We will delete them all but we should have only found 1.
for (int i = 0; i < recs.size(); i++) {
oc.delete(recs.get(i));
}
}
}
}
/**
* {@inheritDoc}
*/
public boolean isAlreadyRecorded(String showId) {
boolean result = false;
ObjectContainer oc = getRecordedObjectContainer();
if ((showId != null) && (oc != null)) {
final String sid = showId;
List<RecordedShow> shows =
oc.query(new Predicate<RecordedShow>() {
public boolean match(RecordedShow rs) {
return (sid.equals(rs.getShowId()));
}
});
if (shows != null) {
result = (shows.size() > 0);
}
}
return (result);
}
/**
* {@inheritDoc}
*/
public void addRecordedShow(RecordedShow rs) {
ObjectContainer oc = getRecordedObjectContainer();
if ((rs != null) && (oc != null)) {
removeRecordedShow(rs);
oc.store(rs);
oc.commit();
}
}
/**
* {@inheritDoc}
*/
public void removeRecordedShow(RecordedShow rs) {
ObjectContainer oc = getRecordedObjectContainer();
if ((rs != null) && (oc != null)) {
final String sid = rs.getShowId();
List<RecordedShow> shows =
oc.query(new Predicate<RecordedShow>() {
public boolean match(RecordedShow rs) {
return (sid.equals(rs.getShowId()));
}
});
if (shows != null) {
// We will delete them all but we should have only found 1.
for (int i = 0; i < shows.size(); i++) {
oc.delete(shows.get(i));
}
}
}
}
private void purge(ObjectContainer db, Class c) {
if ((db != null) && (c != null)) {
ObjectSet result = db.queryByExample(c);
while (result.hasNext()) {
db.delete(result.next());
}
}
}
private void addShow(Show s) {
ObjectContainer oc = getCacheObjectContainer();
if ((s != null) && (oc != null)) {
oc.store(s);
oc.commit();
}
}
private void removeShow(Show s) {
ObjectContainer oc = getCacheObjectContainer();
if ((s != null) && (oc != null)) {
final String id = s.getId();
List<Show> shows = oc.query(new Predicate<Show>() {
public boolean match(Show s) {
return (id.equals(s.getId()));
}
});
if (shows != null) {
// We will delete them all but we should have only found 1.
for (int i = 0; i < shows.size(); i++) {
oc.delete(shows.get(i));
}
}
}
}
private void addAiring(Airing a) {
ObjectContainer oc = getCacheObjectContainer();
if ((a != null) && (oc != null)) {
oc.store(a);
oc.commit();
}
}
private void removeAiring(Airing a) {
ObjectContainer oc = getCacheObjectContainer();
if ((a != null) && (oc != null)) {
final int cid = a.getChannelId();
List<Airing> airings = oc.query(new Predicate<Airing>() {
public boolean match(Airing a) {
return (cid == a.getChannelId());
}
});
if (airings != null) {
// We will delete them all but we should have only found 1.
for (int i = 0; i < airings.size(); i++) {
oc.delete(airings.get(i));
}
}
}
}
private void addCacheFromRecordingRule(RecordingRule rr) {
NMS n = getNMS();
if ((rr != null) && (n != null)) {
if (rr.isOnceType()) {
ShowAiring sa = rr.getShowAiring();
if (sa != null) {
Show s = sa.getShow();
Airing a = sa.getAiring();
if ((s != null) && (a != null)) {
addShow(s);
addAiring(a);
} else {
if (s == null) {
LogUtil.log(LogUtil.DEBUG, "A ONCE recording doesnt have a Show");
}
if (a == null) {
LogUtil.log(LogUtil.DEBUG, "A ONCE recording doesnt have a Airing");
}
}
} else {
LogUtil.log(LogUtil.DEBUG, "A ONCE recording does not have a ShowAiring");
}
} else {
Channel c =
n.getChannelById(rr.getChannelId(), rr.getListingId());
String seriesId = rr.getSeriesId();
ShowAiring[] array =
n.getShowAiringsByChannelAndSeriesId(c, seriesId);
if (array != null) {
for (int i = 0; i < array.length; i++) {
addShow(array[i].getShow());
addAiring(array[i].getAiring());
}
}
}
}
}
private Airing[] getAiringsByChannel(Channel c) {
Airing[] result = null;
ObjectContainer oc = getCacheObjectContainer();
if ((oc != null) && (c != null)) {
final int id = c.getId();
List<Airing> airings = oc.query(new Predicate<Airing>() {
public boolean match(Airing a) {
return (id == a.getChannelId());
}
});
if ((airings != null) && (airings.size() > 0)) {
ArrayList<Airing> list = new ArrayList<Airing>();
for (int i = 0; i < airings.size(); i++) {
Airing a = airings.get(i);
if (!list.contains(a)) {
list.add(a);
}
}
if (list.size() > 0) {
result = list.toArray(new Airing[list.size()]);
}
}
}
return (result);
}
private Show getShowByAiring(Airing a) {
Show result = null;
ObjectContainer oc = getCacheObjectContainer();
if ((oc != null) && (a != null)) {
final String id = a.getShowId();
List<Show> shows = oc.query(new Predicate<Show>() {
public boolean match(Show show) {
return (id.equals(show.getId()));
}
});
if ((shows != null) && (shows.size() > 0)) {
result = shows.get(0);
}
}
return (result);
}
/**
* {@inheritDoc}
*/
public void rebuildCache() {
ObjectContainer oc = getCacheObjectContainer();
if (oc != null) {
// First need to purge the cache.
purge(oc, Show.class);
purge(oc, Airing.class);
RecordingRule[] rules = getRecordingRules();
if (rules != null) {
// For each rule, get from the guide data and add to cache
for (int i = 0; i < rules.length; i++) {
addCacheFromRecordingRule(rules[i]);
}
}
}
}
/**
* {@inheritDoc}
*/
public ShowAiring[] getShowAiringsByChannelAndSeriesId(Channel c,
String seriesId) {
ShowAiring[] result = null;
ObjectContainer oc = getCacheObjectContainer();
if ((c != null) && (seriesId != null) && (oc != null)) {
Airing[] airings = getAiringsByChannel(c);
if (airings != null) {
ArrayList<ShowAiring> list = new ArrayList<ShowAiring>();
for (int i = 0; i < airings.length; i++) {
Show show = getShowByAiring(airings[i]);
if (show != null) {
if (seriesId.equals(show.getSeriesId())) {
// We have one.
ShowAiring sa = new ShowAiring(show, airings[i]);
list.add(sa);
}
}
}
if (list.size() > 0) {
result = list.toArray(new ShowAiring[list.size()]);
}
}
}
return (result);
}
private Recording[] zap(Recording[] array) {
Recording[] result = array;
if ((array != null) && (array.length > 0)) {
ArrayList<Recording> l = new ArrayList<Recording>();
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
l.add(array[i]);
}
}
if (l.size() > 0) {
result = l.toArray(new Recording[l.size()]);
}
}
return (result);
}
private boolean isUpdateRecordingRules() {
File f = new File("updateRecordingRules");
return (f.exists());
}
private boolean isUpdateRecordings() {
File f = new File("updateRecordings");
return (f.exists());
}
static class TaskByDescription implements Comparator<Task>, Serializable {
public int compare(Task t0, Task t1) {
String desc0 = t0.getDescription();
if (desc0 == null) {
desc0 = "";
}
String desc1 = t1.getDescription();
if (desc1 == null) {
desc1 = "";
}
return (desc0.compareTo(desc1));
}
}
}