/* 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.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedReader; import java.io.FileReader; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.HashMap; import org.jflicks.job.AbstractJob; import org.jflicks.job.JobManager; import org.jflicks.nms.NMS; import org.jflicks.nms.NMSConstants; import org.jflicks.tv.Recording; import org.jflicks.tv.recorder.Recorder; import org.jflicks.tv.scheduler.PendingRecord; import org.jflicks.tv.scheduler.RecordedShow; import org.jflicks.util.LogUtil; /** * This job will run and queue recording jobs. * * @author Doug Barnum * @version 1.0 */ public class SystemSchedulerJob extends AbstractJob implements PropertyChangeListener { private SystemScheduler systemScheduler; private HashMap<Recorder, Recording> recordingHashMap; /** * This job supports the SystemScheduler plugin. * * @param s A given SystemScheduler instance. */ public SystemSchedulerJob(SystemScheduler s) { setSystemScheduler(s); setRecordingHashMap(new HashMap<Recorder, Recording>()); setSleepTime(15000); } private SystemScheduler getSystemScheduler() { return (systemScheduler); } private void setSystemScheduler(SystemScheduler s) { systemScheduler = s; } private HashMap<Recorder, Recording> getRecordingHashMap() { return (recordingHashMap); } private void setRecordingHashMap(HashMap<Recorder, Recording> m) { recordingHashMap = m; } private void addRecording(Recorder r, Recording rec) { HashMap<Recorder, Recording> m = getRecordingHashMap(); if ((m != null) && (r != null) && (rec != null)) { m.put(r, rec); } } private void notifyClients(String message) { SystemScheduler ss = getSystemScheduler(); if (ss != null) { NMS n = ss.getNMS(); if (n != null) { n.sendMessage(message); } } } private Recording getRecording(Recorder r) { Recording result = null; HashMap<Recorder, Recording> m = getRecordingHashMap(); if ((m != null) && (r != null)) { result = m.get(r); } return (result); } private void removeRecording(Recorder r) { HashMap<Recorder, Recording> m = getRecordingHashMap(); if ((m != null) && (r != null)) { m.remove(r); } } /** * {@inheritDoc} */ public void start() { setTerminate(false); } /** * {@inheritDoc} */ public void run() { // Run the request first after a little sleep... JobManager.sleep(getSleepTime()); SystemScheduler ss = getSystemScheduler(); if (ss != null) { ss.requestRescheduling(); } while (!isTerminate()) { // Get the recordings due to start in the next 60 seconds. PendingRecord[] array = ss.getReadyPendingRecords(); if (array != null) { // We have recordings to launch. boolean done = false; int index = 0; int retries = 0; while (!done) { long now = System.currentTimeMillis(); if (array[index].getStart() < now) { // Ok, time to launch the recording... Recorder recorder = array[index].getRecorder(); if (!recorder.isRecording()) { // Reassign a file name because disk space may // have changed at this point and we should make // sure we have space. File justintime = ss.createFile(array[index]); array[index].setFile(justintime); Recording crec = array[index].getRecording(); crec.setPath(justintime.getPath()); addRecording(recorder, crec); recorder.addPropertyChangeListener("Recording", this); LogUtil.log(LogUtil.INFO, "recording on :" + array[index].getChannel() + " " + new Date(now)); long dur = array[index].getDuration(); dur -= ((now - array[index].getStart()) / 1000); LogUtil.log(LogUtil.INFO, "adjust length: " + dur); crec.setRealStart(now); recorder.startRecording(array[index].getChannel(), dur, array[index].getFile(), false); index++; if (index == array.length) { done = true; } notifyClients(NMSConstants.MESSAGE_RECORDING_ADDED); } else if (retries < 15) { LogUtil.log(LogUtil.INFO, "hmm, recorder busy will retry"); JobManager.sleep(1000); retries++; if (recorder.isRecordingLiveTV()) { recorder.stopRecording(); } } else { LogUtil.log(LogUtil.INFO, "Recorder stayed busy. Give up"); index++; if (index == array.length) { done = true; } } } else { // Sleep 5 seconds. JobManager.sleep(5000); } } } JobManager.sleep(getSleepTime()); // Check to see if we have any imports to work on. checkImports(); } } /** * {@inheritDoc} */ public void stop() { setTerminate(true); } /** * We listen for when a Recorder has finished so we can update the * Recording instance to reflect that it is no longer recording. * * @param event A given PropertyChangeEvent object. */ public void propertyChange(PropertyChangeEvent event) { Boolean bobj = (Boolean) event.getNewValue(); if ((bobj != null) && (!bobj.booleanValue())) { // A Recorder finished!! LogUtil.log(LogUtil.INFO, "Recorder: " + event.getSource() + " finished"); Recorder r = (Recorder) event.getSource(); SystemScheduler ss = getSystemScheduler(); if ((r != null) && (ss != null)) { Recording rec = getRecording(r); if (rec != null) { rec.setCurrentlyRecording(false); long now = System.currentTimeMillis(); rec.setDuration((now - rec.getRealStart()) / 1000L); LogUtil.log(LogUtil.INFO, "Setting true duration: " + rec.getDuration()); ss.updateRecording(rec); removeRecording(r); r.removePropertyChangeListener("Recording", this); // At this point we COULD have a bad recording - that // it really didn't record properly. In a perfect // world this would not happen but in reality it // actually does from time to time. We want to // remove it and allow for re-recording. To do that // we use the NMS. if (isBad(rec)) { LogUtil.log(LogUtil.INFO, rec.getTitle() + " " + rec.getPath() + " was bad!!!"); NMS nms = ss.getNMS(); if (nms != null) { LogUtil.log(LogUtil.INFO, "Removing and" + " rescheduling " + rec); nms.removeRecording(rec, true); } } else { // Now we want to do any indexing of the recording // if so configured. String iname = r.getIndexerName(); LogUtil.log(LogUtil.INFO, "Indexing recording: " + iname); if (iname != null) { ss.indexRecording(iname, rec); } } } } } } private boolean isBad(Recording r) { boolean result = false; if (r != null) { String path = r.getPath(); File f = new File(path); if (!f.exists()) { result = true; } else { if (f.length() == 0L) { result = true; } } if (result) { // OK something wrong with the TS file. Check for an // HLS file in case this is how it's being recorded. path = path.substring(0, path.lastIndexOf(".")); path = path + ".m3u8"; f = new File(path); result = !f.exists(); } } return (result); } private void checkImports() { SystemScheduler ss = getSystemScheduler(); if (ss != null) { File home = new File("."); File importdir = new File(home, "import"); if ((importdir.exists()) && (importdir.isDirectory())) { File recorded = new File(importdir, "recorded.txt"); if ((recorded.exists()) && (recorded.isFile())) { LogUtil.log(LogUtil.DEBUG, "SystemScheduler: importing recorded"); try { // We expect a programid one per line. BufferedReader br = new BufferedReader(new FileReader(recorded)); String line = null; int count = 0; while ((line = br.readLine()) != null) { RecordedShow rs = new RecordedShow(line); ss.addRecordedShow(rs); count++; } br.close(); if (!recorded.delete()) { LogUtil.log(LogUtil.WARNING, "checkImports: delete failed"); } LogUtil.log(LogUtil.INFO, "SystemScheduler: imported " + count + " shows from recorded.txt"); } catch (IOException ex) { LogUtil.log(LogUtil.WARNING, "checkImports: " + ex.getMessage()); } } } } } }