/*
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.live;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.jflicks.configure.BaseConfig;
import org.jflicks.configure.Configuration;
import org.jflicks.configure.NameValue;
import org.jflicks.job.JobManager;
import org.jflicks.nms.NMS;
import org.jflicks.nms.NMSConstants;
import org.jflicks.tv.Channel;
import org.jflicks.tv.LiveTV;
import org.jflicks.tv.recorder.Recorder;
import org.jflicks.tv.scheduler.RecorderInformation;
import org.jflicks.tv.scheduler.Scheduler;
import org.jflicks.util.StartsWithFilter;
import org.jflicks.util.LogUtil;
import org.jflicks.util.Util;
/**
* This class is a base implementation of the Live interface.
*
* @author Doug Barnum
* @version 1.0
*/
public abstract class BaseLive extends BaseConfig implements Live {
private String title;
private NMS nms;
private ArrayList<Session> sessionList;
/**
* Simple empty constructor.
*/
public BaseLive() {
setSessionList(new ArrayList<Session>());
}
/**
* {@inheritDoc}
*/
public String getTitle() {
return (title);
}
/**
* Convenience method to set this property.
*
* @param s The given title value.
*/
public void setTitle(String s) {
title = s;
}
/**
* {@inheritDoc}
*/
public NMS getNMS() {
return (nms);
}
/**
* {@inheritDoc}
*/
public void setNMS(NMS n) {
nms = n;
}
private ArrayList<Session> getSessionList() {
return (sessionList);
}
private void setSessionList(ArrayList<Session> l) {
sessionList = l;
}
private void addSession(Session s) {
ArrayList<Session> l = getSessionList();
if ((s != null) && (l != null)) {
l.add(s);
}
}
private void removeSession(Session s) {
ArrayList<Session> l = getSessionList();
if ((s != null) && (l != null)) {
l.remove(s);
}
}
private Session findSession(LiveTV liveTV) {
Session result = null;
ArrayList<Session> l = getSessionList();
if ((liveTV != null) && (l != null)) {
String id = liveTV.getId();
if (id != null) {
for (int i = 0; i < l.size(); i++) {
Session tmp = l.get(i);
if (tmp != null) {
LiveTV ltmp = tmp.getLiveTV();
if (id.equals(ltmp.getId())) {
result = tmp;
break;
}
}
}
}
}
return (result);
}
/**
* {@inheritDoc}
*/
public LiveTV openSession() {
return (openSession(null));
}
/**
* {@inheritDoc}
*/
public LiveTV openSession(String channelNumber) {
LiveTV result = new LiveTV();
result.setMessageType(LiveTV.MESSAGE_TYPE_NONE);
NMS n = getNMS();
Recorder[] recs = getRecorders();
if ((n != null) && (recs != null)) {
String[] names = getListingNames();
if (names != null) {
ArrayList<RecorderInformation> ris =
new ArrayList<RecorderInformation>();
for (int i = 0; i < names.length; i++) {
Recorder tmp = findRecorder(recs, names[i]);
if (tmp != null) {
RecorderInformation ri = new RecorderInformation();
ri.setRecorder(tmp);
ri.setChannels(n.getChannelsByListingName(names[i]));
ris.add(ri);
}
}
if (ris.size() > 0) {
RecorderInformation[] riArray =
ris.toArray(new RecorderInformation[ris.size()]);
Session session = new Session(result, riArray);
addSession(session);
result.setChannels(computeAllChannels(session));
Channel c = computeStartChannel(session, channelNumber);
changeChannel(result, c);
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Available Recorders!");
}
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Guide data!");
}
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Recorders!");
}
return (result);
}
/**
* {@inheritDoc}
*/
public LiveTV openSession(String host, int port) {
return (openSession(host, port, null));
}
/**
* {@inheritDoc}
*/
public LiveTV openSession(String host, int port, String channelNumber) {
LiveTV result = new LiveTV(host, port);
result.setMessageType(LiveTV.MESSAGE_TYPE_NONE);
NMS n = getNMS();
Recorder[] recs = getRecorders();
if ((n != null) && (recs != null)) {
String[] names = getListingNames();
if (names != null) {
ArrayList<RecorderInformation> ris =
new ArrayList<RecorderInformation>();
for (int i = 0; i < names.length; i++) {
Recorder tmp = findRecorder(recs, names[i]);
if (tmp != null) {
RecorderInformation ri = new RecorderInformation();
ri.setRecorder(tmp);
ri.setChannels(n.getChannelsByListingName(names[i]));
ris.add(ri);
}
}
if (ris.size() > 0) {
RecorderInformation[] riArray =
ris.toArray(new RecorderInformation[ris.size()]);
Session session = new Session(result, riArray);
addSession(session);
result.setChannels(computeAllChannels(session));
Channel c = computeStartChannel(session, channelNumber);
changeChannel(result, c);
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Available Recorders!");
}
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Guide data!");
}
} else {
result.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
result.setMessage("No Recorders!");
}
return (result);
}
/**
* {@inheritDoc}
*/
public LiveTV changeChannel(LiveTV l, Channel c) {
LiveTV result = null;
if ((l != null) && (c != null)) {
result = changeChannelRecording(l, c);
}
return (result);
}
private LiveTV changeChannelRecording(LiveTV l, Channel c) {
if ((l != null) && (c != null)) {
Session s = findSession(l);
if (s != null) {
// We tear down the old to start a new channel. We don't
// care about quick tuning as we should never change
// channels on a recording.
Recorder old = s.getCurrentRecorder();
if (old != null) {
LogUtil.log(LogUtil.DEBUG, "stop recording");
old.stopRecording();
cleanup(l);
}
Recorder r = computeRecorder(s, c);
LogUtil.log(LogUtil.DEBUG, "channel: " + c);
LogUtil.log(LogUtil.DEBUG, "recorder: " + r);
if ((r != null) && (!r.isRecording())) {
File output = computeFile(s, c, r.getExtension());
if (output != null) {
// If we are using the same recorder, let's sleep
// a short time so things can settle down.
if (r.equals(old)) {
LogUtil.log(LogUtil.DEBUG, "waiting a bit...");
JobManager.sleep(500);
}
LogUtil.log(LogUtil.DEBUG, "recording to file <" + output + ">");
s.setCurrentRecorder(r);
r.startRecording(c, 60 * 60 * 4, output, true);
String hls = output.getPath();
hls = hls.substring(0, hls.lastIndexOf("."));
hls = hls + ".m3u8";
l.setPath(hls);
l.setCurrentChannel(c);
// We are going to block here until we
// have a valid files. Things take time
// to spin up.
int minseg = getConfiguredMinimumSegmentCount();
int loopmax = minseg * 10;
File m3u8 = new File(hls);
int loops = 0;
boolean done = false;
while (!done) {
loops++;
done = count(l) >= minseg;
if (!done) {
done = loops > loopmax;
}
try {
Thread.sleep(1000);
} catch (Exception ex) {
}
}
} else {
l.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
l.setMessage("No File!");
LogUtil.log(LogUtil.DEBUG, "No File!");
}
}
} else {
l.setMessageType(LiveTV.MESSAGE_TYPE_ERROR);
l.setMessage("No Available Recorders!");
LogUtil.log(LogUtil.DEBUG, "No Available Recorders!");
}
}
return (l);
}
private int count(LiveTV l) {
int result = 0;
File file = new File(l.getPath());
File parent = file.getParentFile();
String fname = file.getName();
if ((parent != null) && (fname != null)) {
// This really should get everything.
fname = fname.substring(0, fname.lastIndexOf("."));
File[] array =
parent.listFiles(new StartsWithFilter(fname));
if (array != null) {
result = array.length;
}
}
return (result);
}
private void cleanup(LiveTV l) {
if (l != null) {
File file = new File(l.getPath());
File parent = file.getParentFile();
String fname = file.getName();
if ((parent != null) && (fname != null)) {
// This really should get everything.
fname = fname.substring(0, fname.lastIndexOf("."));
File[] array =
parent.listFiles(new StartsWithFilter(fname));
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (!array[i].delete()) {
LogUtil.log(LogUtil.WARNING, array[i].getPath() + " del fail");
}
}
}
}
}
}
/**
* {@inheritDoc}
*/
public void closeSession(LiveTV l) {
if (l != null) {
Session s = findSession(l);
if (s != null) {
Recorder r = s.getCurrentRecorder();
if (r != null) {
r.stopRecording();
cleanup(l);
LogUtil.log(LogUtil.DEBUG, "closeSession: stopRecording");
}
}
}
}
private String getConfiguredLiveDirectory() {
String result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue nv = c.findNameValueByName(NMSConstants.LIVE_DIRECTORY);
if (nv != null) {
result = nv.getValue();
}
}
return (result);
}
private String getConfiguredStartChannel() {
String result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue nv =
c.findNameValueByName(NMSConstants.LIVE_START_CHANNEL);
if (nv != null) {
result = nv.getValue();
}
}
return (result);
}
private int getConfiguredMinimumSegmentCount() {
int result = 6;
Configuration c = getConfiguration();
if (c != null) {
NameValue nv =
c.findNameValueByName(NMSConstants.MINIMUM_SEGMENT_COUNT);
if (nv != null) {
result = Util.str2int(nv.getValue(), result);
}
}
return (result);
}
private Recorder[] getRecorders() {
Recorder[] result = null;
NMS n = getNMS();
if (n != null) {
Scheduler s = n.getScheduler();
if (s != null) {
Recorder[] array = s.getConfiguredRecorders();
if ((array != null) && (array.length > 0)) {
ArrayList<Recorder> rlist = new ArrayList<Recorder>();
for (int i = 0; i < array.length; i++) {
if (array[i].isHlsMode()) {
rlist.add(array[i]);
}
}
if (rlist.size() > 0) {
result = rlist.toArray(new Recorder[rlist.size()]);
}
}
}
}
return (result);
}
private String[] getListingNames() {
String[] result = null;
NMS n = getNMS();
if (n != null) {
Scheduler s = n.getScheduler();
if (s != null) {
result = s.getConfiguredListingNames();
}
}
return (result);
}
private Recorder findRecorder(Recorder[] array, String name) {
Recorder result = null;
if ((array != null) && (name != null)) {
NMS n = getNMS();
if (n != null) {
Scheduler s = n.getScheduler();
if (s != null) {
for (int i = 0; i < array.length; i++) {
if (!array[i].isRecording()) {
String tmp = s.getListingNameByRecorder(array[i]);
LogUtil.log(LogUtil.DEBUG, "findRecorder: found <" + tmp + ">");
if ((tmp != null) && (tmp.equals(name))) {
// Found one. We won't break because we want
// to use the least likely tuner to be used
// for a recording which would be later in
// the list.
result = array[i];
}
}
}
}
}
}
return (result);
}
private Channel findChannel(Channel[] array, String cnumber) {
Channel result = null;
if ((array != null) && (cnumber != null)) {
for (int i = 0; i < array.length; i++) {
if (cnumber.equals(array[i].getNumber())) {
result = array[i];
break;
}
}
}
return (result);
}
private Channel computeStartChannel(Session s, String startc) {
Channel result = null;
if (s != null) {
RecorderInformation[] array = s.getRecorderInformations();
if ((array != null) && (array.length > 0)) {
if (startc == null) {
startc = getConfiguredStartChannel();
}
LogUtil.log(LogUtil.DEBUG, "start channel: " + startc);
if (startc != null) {
for (int i = 0; i < array.length; i++) {
result = findChannel(array[i].getChannels(), startc);
if (result != null) {
break;
}
}
}
if (result == null) {
// We didn't find the right Channel so let's just
// set it to something so we don't crap out.
for (int i = 0; i < array.length; i++) {
Channel[] chans = array[i].getChannels();
if ((chans != null) && (chans.length > 0)) {
result = chans[0];
break;
}
}
}
}
}
return (result);
}
private Channel[] computeAllChannels(Session s) {
Channel[] result = null;
RecorderInformation[] array = s.getRecorderInformations();
if (array != null) {
LogUtil.log(LogUtil.DEBUG, "channel count: " + array.length);
ArrayList<Channel> clist = new ArrayList<Channel>();
for (int i = 0; i < array.length; i++) {
Channel[] tmp = array[i].getChannels();
if (tmp != null) {
LogUtil.log(LogUtil.DEBUG, "computeAllChannels: adding "
+ tmp.length + " channels");
Collections.addAll(clist, tmp);
}
}
if (clist.size() > 0) {
result = clist.toArray(new Channel[clist.size()]);
Arrays.sort(result);
}
}
return (result);
}
private Recorder computeRecorder(Session s, Channel c) {
Recorder result = null;
if ((s != null) && (c != null)) {
RecorderInformation[] array = s.getRecorderInformations();
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (array[i].supports(c)) {
result = array[i].getRecorder();
break;
}
}
}
}
return (result);
}
private File computeFile(Session s, Channel c, String ext) {
File result = null;
String sdir = getConfiguredLiveDirectory();
if ((s != null) && (sdir != null) && (c != null)) {
File dir = new File(sdir);
if ((dir.exists()) && (dir.isDirectory())) {
LiveTV l = s.getLiveTV();
if (l != null) {
String id = l.getId() + "-" + System.currentTimeMillis();
result = new File(dir, "live-" + id + "-" + c.getNumber()
+ "." + ext);
}
}
}
return (result);
}
}