mirror of
https://github.com/moparisthebest/rcrdit
synced 2024-12-21 23:08:57 -05:00
Add List<StartStop> to Programs and generic scheduling algorithm, doesn't work perfectly yet and is not enabled
This commit is contained in:
parent
24bde789b8
commit
a79fc8b3c2
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,6 +2,8 @@
|
|||||||
*.iml
|
*.iml
|
||||||
target/
|
target/
|
||||||
out.xml
|
out.xml
|
||||||
rcrdit.ics
|
*.ics
|
||||||
xmltv.xml
|
xmltv.xml
|
||||||
rcrdit.cfg.xml
|
rcrdit.cfg.xml
|
||||||
|
*.txt
|
||||||
|
*.diff
|
@ -24,6 +24,9 @@ import com.moparisthebest.jdbc.QueryMapper;
|
|||||||
import com.moparisthebest.rcrdit.autorec.AutoRec;
|
import com.moparisthebest.rcrdit.autorec.AutoRec;
|
||||||
import com.moparisthebest.rcrdit.autorec.Profile;
|
import com.moparisthebest.rcrdit.autorec.Profile;
|
||||||
import com.moparisthebest.rcrdit.autorec.ProgramAutoRec;
|
import com.moparisthebest.rcrdit.autorec.ProgramAutoRec;
|
||||||
|
import com.moparisthebest.rcrdit.scheduler.PartialScheduler;
|
||||||
|
import com.moparisthebest.rcrdit.scheduler.Scheduler;
|
||||||
|
import com.moparisthebest.rcrdit.scheduler.StartStop;
|
||||||
import com.moparisthebest.rcrdit.tuner.DummyTuner;
|
import com.moparisthebest.rcrdit.tuner.DummyTuner;
|
||||||
import com.moparisthebest.rcrdit.tuner.HDHomerun;
|
import com.moparisthebest.rcrdit.tuner.HDHomerun;
|
||||||
import com.moparisthebest.rcrdit.tuner.Tuner;
|
import com.moparisthebest.rcrdit.tuner.Tuner;
|
||||||
@ -62,15 +65,12 @@ import java.math.BigInteger;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.sql.Connection;
|
import java.sql.*;
|
||||||
import java.sql.Date;
|
import java.time.*;
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import com.moparisthebest.rcrdit.requestbeans.GetScheduleRequest;
|
import com.moparisthebest.rcrdit.requestbeans.GetScheduleRequest;
|
||||||
import com.moparisthebest.rcrdit.requestbeans.NewRecordingRequest;
|
import com.moparisthebest.rcrdit.requestbeans.NewRecordingRequest;
|
||||||
@ -84,6 +84,9 @@ import org.glassfish.jersey.jackson.JacksonFeature;
|
|||||||
@Path("")
|
@Path("")
|
||||||
public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
||||||
|
|
||||||
|
// for testing, should be null normally
|
||||||
|
private static final LocalDateTime fakeTime = null;//LocalDateTime.of(2017, 3, 11, 0, 0);
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RcrdIt.class);
|
private static final Logger log = LoggerFactory.getLogger(RcrdIt.class);
|
||||||
|
|
||||||
// config items
|
// config items
|
||||||
@ -99,6 +102,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
private final List<AutoRec> autoRecs = new ArrayList<>();
|
private final List<AutoRec> autoRecs = new ArrayList<>();
|
||||||
private Tv schedule;
|
private Tv schedule;
|
||||||
|
|
||||||
|
private final Scheduler scheduler = new PartialScheduler();
|
||||||
|
|
||||||
private final String serverUri;
|
private final String serverUri;
|
||||||
|
|
||||||
|
|
||||||
@ -147,6 +152,12 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
System.out.print("\"" + name + "\", ");
|
System.out.print("\"" + name + "\", ");
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void addMeeting(final Calendar calendar, final VTimeZone tz,
|
||||||
|
final Instant start, final Instant stop,
|
||||||
|
final Program prog, final MessageDigest md, final boolean skipped) {
|
||||||
|
addMeeting(calendar, tz, start, stop, new ProgramAutoRec(prog, prog.getAutoRec()), md, skipped);
|
||||||
|
}
|
||||||
|
|
||||||
private static void addMeeting(final Calendar calendar, final VTimeZone tz,
|
private static void addMeeting(final Calendar calendar, final VTimeZone tz,
|
||||||
final Instant start, final Instant stop,
|
final Instant start, final Instant stop,
|
||||||
@ -174,6 +185,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
description += "\nPriority: " + prog.getAutoRec().getPriority();
|
description += "\nPriority: " + prog.getAutoRec().getPriority();
|
||||||
|
|
||||||
meeting.getProperties().add(new Description(description));
|
meeting.getProperties().add(new Description(description));
|
||||||
|
if(fakeTime != null)
|
||||||
|
meeting.getProperties().add(new DtStamp(new DateTime(1489294362645L)));
|
||||||
|
|
||||||
// add timezone info..
|
// add timezone info..
|
||||||
meeting.getProperties().add(tz.getTimeZoneId());
|
meeting.getProperties().add(tz.getTimeZoneId());
|
||||||
@ -209,12 +222,13 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
calendar.getComponents().add(meeting);
|
calendar.getComponents().add(meeting);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void reCalculateSchedule() {
|
private synchronized void reCalculateScheduleOrig() {
|
||||||
log.debug("import starting.");
|
log.debug("import starting.");
|
||||||
// cancel all pending timers, clear array, purge timer
|
// cancel all pending timers, clear array, purge timer
|
||||||
startTimers.forEach(TimerTask::cancel);
|
startTimers.forEach(TimerTask::cancel);
|
||||||
startTimers.clear();
|
startTimers.clear();
|
||||||
timer.purge();
|
timer.purge();
|
||||||
|
schedule.getPrograms().forEach(Program::clear);
|
||||||
|
|
||||||
if (schedule.getPrograms().isEmpty() || autoRecs.isEmpty()) {
|
if (schedule.getPrograms().isEmpty() || autoRecs.isEmpty()) {
|
||||||
log.debug("nothing to import.");
|
log.debug("nothing to import.");
|
||||||
@ -239,6 +253,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
|
|
||||||
// start now, get all matching programs scheduled for that minute, determine which to record, schedule timers, start any now? make sure current ones don't get reset
|
// start now, get all matching programs scheduled for that minute, determine which to record, schedule timers, start any now? make sure current ones don't get reset
|
||||||
Instant cursor = Instant.now().truncatedTo(ChronoUnit.MINUTES);
|
Instant cursor = Instant.now().truncatedTo(ChronoUnit.MINUTES);
|
||||||
|
if(fakeTime != null)
|
||||||
|
cursor = fakeTime.toInstant(ZoneOffset.systemDefault().getRules().getOffset(fakeTime)).truncatedTo(ChronoUnit.MINUTES);
|
||||||
//final Instant end = cursor.plus(1, ChronoUnit.DAYS);
|
//final Instant end = cursor.plus(1, ChronoUnit.DAYS);
|
||||||
final Instant end = schedule.getLastEndTime();
|
final Instant end = schedule.getLastEndTime();
|
||||||
final Map<ProgramAutoRec, Instant> currentRecs = new LinkedHashMap<>(tuners.numTuners()); // todo: needs to be sorted?
|
final Map<ProgramAutoRec, Instant> currentRecs = new LinkedHashMap<>(tuners.numTuners()); // todo: needs to be sorted?
|
||||||
@ -251,7 +267,7 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
// this program is on
|
// this program is on
|
||||||
for (final AutoRec autoRec : autoRecs) {
|
for (final AutoRec autoRec : autoRecs) {
|
||||||
if (autoRec.matches(program)) {
|
if (autoRec.matches(program)) {
|
||||||
programAutoRecs.add(new ProgramAutoRec(program, autoRec));
|
programAutoRecs.add(new ProgramAutoRec(program.setAutoRec(autoRec), autoRec));
|
||||||
break; // only match highest priority autorec, ie first since they are sorted
|
break; // only match highest priority autorec, ie first since they are sorted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,15 +292,15 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
// free tuner, just add
|
// free tuner, just add
|
||||||
if (currentRecs.size() < tuners.numTuners()) {
|
if (currentRecs.size() < tuners.numTuners()) {
|
||||||
currentRecs.put(programAutoRec, cursor);
|
currentRecs.put(programAutoRec, cursor);
|
||||||
|
programAutoRec.getProgram().addStartStop(new StartStop(cursor, true));
|
||||||
// check if we are starting one late, from skipped
|
// check if we are starting one late, from skipped
|
||||||
final Instant start = skippedRecs.remove(programAutoRec);
|
final Instant start = skippedRecs.remove(programAutoRec);
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
addMeeting(calendar, tz, start, cursor, programAutoRec, md, true);
|
addMeeting(calendar, tz, start, cursor, programAutoRec, md, true);
|
||||||
}
|
}
|
||||||
//System.out.println("scheduling: "+programAutoRec);
|
//System.out.println("scheduling: "+programAutoRec);
|
||||||
final RecordingTask rt = new RecordingTask(programAutoRec);
|
final RecordingTask rt = new RecordingTask(programAutoRec, cursor);
|
||||||
startTimers.add(rt);
|
startTimers.add(rt);
|
||||||
timer.schedule(rt, Date.from(cursor));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// from Tuners.recordNow
|
// from Tuners.recordNow
|
||||||
@ -302,9 +318,11 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
//System.out.println("replacing: "+ recToReplace + " with scheduling: "+programAutoRec);
|
//System.out.println("replacing: "+ recToReplace + " with scheduling: "+programAutoRec);
|
||||||
final RecordingTask rt = new RecordingTask(recToReplace, programAutoRec);
|
final RecordingTask rt = new RecordingTask(recToReplace, programAutoRec, cursor);
|
||||||
startTimers.add(rt);
|
startTimers.add(rt);
|
||||||
timer.schedule(rt, Date.from(cursor));
|
|
||||||
|
recToReplace.getProgram().addStartStop(new StartStop(cursor, false));
|
||||||
|
programAutoRec.getProgram().addStartStop(new StartStop(cursor, true, recToReplace.getProgram()));
|
||||||
|
|
||||||
// remove/replace it
|
// remove/replace it
|
||||||
final Instant start = currentRecs.remove(recToReplace);
|
final Instant start = currentRecs.remove(recToReplace);
|
||||||
@ -323,8 +341,163 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
cursor = cursor.plus(1, ChronoUnit.MINUTES);
|
cursor = cursor.plus(1, ChronoUnit.MINUTES);
|
||||||
}
|
}
|
||||||
// increment by minute up until end scheduling all timers until then, somehow store this info to show/email etc?
|
// increment by minute up until end scheduling all timers until then, somehow store this info to show/email etc?
|
||||||
|
|
||||||
log.debug("import done.\n\n------\n\n");
|
log.debug("import done.\n\n------\n\n");
|
||||||
|
|
||||||
|
if(fakeTime != null) {
|
||||||
|
try (FileOutputStream fout = new FileOutputStream("startTimers.orig.txt")) {
|
||||||
|
for (TimerTask tt : startTimers) {
|
||||||
|
fout.write(tt.toString().getBytes(UTF_8));
|
||||||
|
fout.write('\n');
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore e.printStackTrace();
|
||||||
|
}
|
||||||
|
try (FileOutputStream fout = new FileOutputStream("programs.orig.txt")) {
|
||||||
|
for (Program prog : schedule.getPrograms()) {
|
||||||
|
fout.write(prog.fullString().getBytes(UTF_8));
|
||||||
|
fout.write('\n');
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!calendar.getComponents().isEmpty())
|
||||||
|
try (FileOutputStream fout = new FileOutputStream(fakeTime != null ? "rcrdit.orig.ics" : "rcrdit.ics")) {
|
||||||
|
final CalendarOutputter outputter = new CalendarOutputter();
|
||||||
|
outputter.output(calendar, fout);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void reCalculateSchedule() {
|
||||||
|
if(fakeTime != null)
|
||||||
|
for(String file : new String[]{"rcrdit.ics", "startTimers.txt", "programs.txt", "rcrdit.orig.ics", "startTimers.orig.txt", "programs.orig.txt"})
|
||||||
|
new File(file).delete();
|
||||||
|
reCalculateScheduleOrig();
|
||||||
|
if(fakeTime != null)
|
||||||
|
reCalculateScheduleNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void reCalculateScheduleNew() {
|
||||||
|
log.debug("import starting.");
|
||||||
|
// cancel all pending timers, clear array, purge timer
|
||||||
|
startTimers.forEach(TimerTask::cancel);
|
||||||
|
startTimers.clear();
|
||||||
|
timer.purge();
|
||||||
|
schedule.getPrograms().forEach(Program::clear);
|
||||||
|
|
||||||
|
if (schedule.getPrograms().isEmpty() || autoRecs.isEmpty()) {
|
||||||
|
log.debug("nothing to import.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler.schedulePrograms(autoRecs, schedule.getPrograms(), tuners.numTuners(), schedule.getLastEndTime(),
|
||||||
|
RcrdIt.fakeTime != null ?
|
||||||
|
RcrdIt.fakeTime.toInstant(ZoneOffset.systemDefault().getRules().getOffset(RcrdIt.fakeTime)).truncatedTo(ChronoUnit.MINUTES) :
|
||||||
|
Instant.now().truncatedTo(ChronoUnit.MINUTES)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
for(final Program prog : schedule.getPrograms())
|
||||||
|
if(prog.getAutoRec() != null && prog.getStartStops().size() > 2)
|
||||||
|
System.out.println(prog);
|
||||||
|
if(true)return;
|
||||||
|
*/
|
||||||
|
final MessageDigest md;
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("no sha-256?", e);
|
||||||
|
}
|
||||||
|
// Create a TimeZone
|
||||||
|
final VTimeZone tz = TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone(ZoneId.systemDefault().getId()).getVTimeZone();
|
||||||
|
final Calendar calendar = new Calendar();
|
||||||
|
calendar.getProperties().add(new ProdId("-//rcrdit//rcrdit//EN"));
|
||||||
|
calendar.getProperties().add(Version.VERSION_2_0);
|
||||||
|
calendar.getProperties().add(CalScale.GREGORIAN);
|
||||||
|
|
||||||
|
// set up startTimers and rcrdit.ics
|
||||||
|
//for(final Program program : schedule.getPrograms()) {
|
||||||
|
//for(int x = 0; x < schedule.getPrograms().size(); ++x) {
|
||||||
|
for(int x = schedule.getPrograms().size() - 1; x >= 0; --x) {
|
||||||
|
final Program program = schedule.getPrograms().get(x);
|
||||||
|
if(program.getAutoRec() != null) {
|
||||||
|
// wanted to record
|
||||||
|
final List<StartStop> startStops = program.getStartStops();
|
||||||
|
if(startStops.isEmpty()) {
|
||||||
|
// whole thing skipped...
|
||||||
|
addMeeting(calendar, tz, program.getStart(), program.getStop(), program, md, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if((startStops.size() % 2) != 0) { // todo: this is really bad, wtf
|
||||||
|
System.out.println(program.fullString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int index = -1;
|
||||||
|
final StartStop first = startStops.get(++index);
|
||||||
|
final StartStop second = startStops.get(++index);
|
||||||
|
if(startStops.size() == 2 && first.getInstant().equals(program.getStart()) && second.getInstant().equals(program.getStop())) {
|
||||||
|
// whole thing recorded...
|
||||||
|
addMeeting(calendar, tz, program.getStart(), program.getStop(), program, md, false);
|
||||||
|
final RecordingTask rt = new RecordingTask(program, program.getStart());
|
||||||
|
startTimers.add(rt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(first.isStart()) {
|
||||||
|
if(first.getInstant().equals(program.getStart())) {
|
||||||
|
// not started at start time, skipped first part of program
|
||||||
|
addMeeting(calendar, tz, program.getStart(), first.getInstant(), program, md, true);
|
||||||
|
}
|
||||||
|
Program toStop = null;
|
||||||
|
Instant start = null, stop = null, lastStop = null;
|
||||||
|
for(final StartStop ss : program.getStartStops()) {
|
||||||
|
if(start == null && ss.isStart()) {
|
||||||
|
start = ss.getInstant();
|
||||||
|
toStop = ss.getToStop();
|
||||||
|
} else if(stop == null && !ss.isStart()) {
|
||||||
|
stop = ss.getInstant();
|
||||||
|
} else {
|
||||||
|
// both set
|
||||||
|
if(lastStop != null) { // todo: check if lastStop and start are the same, but shouldn't happen?
|
||||||
|
addMeeting(calendar, tz, lastStop, start, program, md, true);
|
||||||
|
}
|
||||||
|
addMeeting(calendar, tz, start, stop, program, md, false);
|
||||||
|
|
||||||
|
final RecordingTask rt = new RecordingTask(toStop, program, start);
|
||||||
|
startTimers.add(rt);
|
||||||
|
|
||||||
|
lastStop = stop;
|
||||||
|
start = stop = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("holy shit should never happen, bad scheduler?");
|
||||||
|
throw new RuntimeException("holy shit should never happen, bad scheduler?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("new import done.\n\n------\n\n");
|
||||||
|
|
||||||
|
try (FileOutputStream fout = new FileOutputStream("startTimers.txt")) {
|
||||||
|
for(TimerTask tt : startTimers) {
|
||||||
|
fout.write(tt.toString().getBytes(UTF_8));
|
||||||
|
fout.write('\n');
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore e.printStackTrace();
|
||||||
|
}
|
||||||
|
try (FileOutputStream fout = new FileOutputStream("programs.txt")) {
|
||||||
|
for(Program prog : schedule.getPrograms()) {
|
||||||
|
fout.write(prog.fullString().getBytes(UTF_8));
|
||||||
|
fout.write('\n');
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore e.printStackTrace();
|
||||||
|
}
|
||||||
if (!calendar.getComponents().isEmpty())
|
if (!calendar.getComponents().isEmpty())
|
||||||
try (FileOutputStream fout = new FileOutputStream("rcrdit.ics")) {
|
try (FileOutputStream fout = new FileOutputStream("rcrdit.ics")) {
|
||||||
final CalendarOutputter outputter = new CalendarOutputter();
|
final CalendarOutputter outputter = new CalendarOutputter();
|
||||||
@ -345,6 +518,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
//if(prog.getStop().isBefore(finalCursor)) {
|
//if(prog.getStop().isBefore(finalCursor)) {
|
||||||
final Instant start = entry.getValue();
|
final Instant start = entry.getValue();
|
||||||
addMeeting(calendar, tz, start, prog.getStop(), par, md, skipped);
|
addMeeting(calendar, tz, start, prog.getStop(), par, md, skipped);
|
||||||
|
if(!skipped)
|
||||||
|
prog.addStartStop(new StartStop(prog.getStop(), false));
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -395,7 +570,7 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
schedule = Tv.readSchedule(xmltvPaths, allChannels);
|
schedule = Tv.readSchedule(xmltvPaths, allChannels, RcrdIt.fakeTime != null ? RcrdIt.fakeTime.withMinute(0) : LocalDateTime.now().withMinute(0));
|
||||||
//System.out.println(schedule);
|
//System.out.println(schedule);
|
||||||
// todo: only re-calcuate if schedule changed
|
// todo: only re-calcuate if schedule changed
|
||||||
reCalculateSchedule();
|
reCalculateSchedule();
|
||||||
@ -407,14 +582,26 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
|
|
||||||
private class RecordingTask extends TimerTask {
|
private class RecordingTask extends TimerTask {
|
||||||
private final ProgramAutoRec stop, start;
|
private final ProgramAutoRec stop, start;
|
||||||
|
private final Instant runAt;
|
||||||
|
|
||||||
RecordingTask(final ProgramAutoRec stop, final ProgramAutoRec start) {
|
RecordingTask(final ProgramAutoRec stop, final ProgramAutoRec start, final Instant runAt) {
|
||||||
this.stop = stop;
|
this.stop = stop;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
|
this.runAt = runAt;
|
||||||
|
if(fakeTime != null)
|
||||||
|
timer.schedule(this, Date.from(runAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordingTask(final ProgramAutoRec start) {
|
RecordingTask(final ProgramAutoRec start, final Instant runAt) {
|
||||||
this(null, start);
|
this(null, start, runAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordingTask(final Program stop, final Program start, final Instant runAt) {
|
||||||
|
this(stop == null ? null : new ProgramAutoRec(stop, stop.getAutoRec()), start == null ? null : new ProgramAutoRec(start, start.getAutoRec()), runAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordingTask(final Program start, final Instant runAt) {
|
||||||
|
this(null, start, runAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -429,6 +616,15 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecordingTask{" +
|
||||||
|
"stop=" + stop +
|
||||||
|
", start=" + start +
|
||||||
|
", runAt=" + runAt +
|
||||||
|
"} ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -553,6 +749,7 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
//System.out.println(System.currentTimeMillis()); if(true) return;
|
||||||
final File cfg;
|
final File cfg;
|
||||||
if (args.length < 1 || !((cfg = new File(args[0])).exists())) {
|
if (args.length < 1 || !((cfg = new File(args[0])).exists())) {
|
||||||
System.err.println("Usage: java -jar rcrdit.jar /path/to/rcrdit.cfg.xml");
|
System.err.println("Usage: java -jar rcrdit.jar /path/to/rcrdit.cfg.xml");
|
||||||
|
@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* rcrdit records TV programs from TV tuners
|
||||||
|
* Copyright (C) 2017 Travis Burtrum
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.moparisthebest.rcrdit.scheduler;
|
||||||
|
|
||||||
|
import com.moparisthebest.rcrdit.autorec.AutoRec;
|
||||||
|
import com.moparisthebest.rcrdit.xmltv.Program;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mopar on 3/14/17.
|
||||||
|
*/
|
||||||
|
public class PartialScheduler implements Scheduler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PartialScheduler.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void schedulePrograms(final List<AutoRec> autoRecs, final List<Program> programs, final int numTuners, final Instant end, final Instant startAt) {
|
||||||
|
if (programs.isEmpty() || autoRecs.isEmpty()) {
|
||||||
|
log.debug("nothing to import.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// start now, get all matching programs scheduled for that minute, determine which to record, schedule timers, start any now? make sure current ones don't get reset
|
||||||
|
Instant cursor = startAt;
|
||||||
|
//final Instant end = cursor.plus(1, ChronoUnit.DAYS);
|
||||||
|
final Map<Program, Instant> currentRecs = new LinkedHashMap<>(numTuners); // todo: needs to be sorted?
|
||||||
|
final List<Program> programAutoRecs = new ArrayList<>();
|
||||||
|
log.debug("end: {}", end);
|
||||||
|
Program lastProgram = null, lastNonTimeProgram = null;
|
||||||
|
while (!cursor.isAfter(end)) {
|
||||||
|
programAutoRecs.clear();
|
||||||
|
for (final Program program : programs) {
|
||||||
|
lastNonTimeProgram = program;
|
||||||
|
if (!cursor.isBefore(program.getStart()) && cursor.isBefore(program.getStop())) {
|
||||||
|
lastProgram = program;
|
||||||
|
// this program is on
|
||||||
|
for (final AutoRec autoRec : autoRecs) {
|
||||||
|
if (autoRec.matches(program)) {
|
||||||
|
programAutoRecs.add(program.setAutoRec(autoRec));
|
||||||
|
break; // only match highest priority autorec, ie first since they are sorted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// schedule top 2 (numTuners), record stuff about rest, schedule only if not already recording ouch?
|
||||||
|
if (!programAutoRecs.isEmpty()) {
|
||||||
|
programAutoRecs.sort(Program::compareTo);
|
||||||
|
// start and stop are same minute and second
|
||||||
|
final Instant finalCursor = cursor;
|
||||||
|
|
||||||
|
// it already ended, and stopped itself, just remove it, for both currentRecs and skippedRecs
|
||||||
|
//recs.keySet().removeIf(c -> c.getProgram().getStop().isBefore(finalCursor));
|
||||||
|
for (final Iterator<Map.Entry<Program, Instant>> it = currentRecs.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
final Map.Entry<Program, Instant> entry = it.next();
|
||||||
|
final Program prog = entry.getKey();
|
||||||
|
if (!prog.getStop().isAfter(finalCursor)) {
|
||||||
|
//if(prog.getStop().isBefore(finalCursor)) {
|
||||||
|
final Instant start = entry.getValue();
|
||||||
|
prog.addStartStop(new StartStop(prog.getStop(), false));
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// look at highest programAutoRecs up to the number of tuners
|
||||||
|
for (int x = 0; x < Math.min(programAutoRecs.size(), numTuners); ++x) {
|
||||||
|
final Program programAutoRec = programAutoRecs.get(x);
|
||||||
|
// if we are already recording it, move on
|
||||||
|
if (currentRecs.containsKey(programAutoRec))
|
||||||
|
continue;
|
||||||
|
// free tuner, just add
|
||||||
|
if (currentRecs.size() < numTuners) {
|
||||||
|
currentRecs.put(programAutoRec, cursor);
|
||||||
|
programAutoRec.addStartStop(new StartStop(cursor, true));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// from Tuners.recordNow
|
||||||
|
final Program recToReplace = currentRecs.keySet().stream().filter(r -> r != null && programAutoRec.getChannelName().equals(r.getChannelName())).findFirst().orElseGet(
|
||||||
|
// look for un-used tuners
|
||||||
|
() -> currentRecs.keySet().stream().filter(Objects::isNull).findFirst().orElseGet(
|
||||||
|
// look for current recordings ending within 70 seconds
|
||||||
|
() -> {
|
||||||
|
final Instant oneMinuteInFuture = finalCursor.plusSeconds(70);
|
||||||
|
return currentRecs.keySet().stream().filter(r -> r == null || r.getStop().isBefore(oneMinuteInFuture)).findFirst().orElseGet(
|
||||||
|
// find lowest priority
|
||||||
|
() -> currentRecs.keySet().stream().min(Comparator.comparingInt(cr -> cr.getAutoRec().getPriority())).orElse(null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
//System.out.println("replacing: "+ recToReplace + " with scheduling: "+programAutoRec);
|
||||||
|
if(programAutoRec.equals(recToReplace)) { // todo: remove this cause should never happen due to currentRecs.containsKey(programAutoRec) above, just testing though...
|
||||||
|
System.out.println("wtf...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
recToReplace.addStartStop(new StartStop(cursor, false));
|
||||||
|
programAutoRec.addStartStop(new StartStop(cursor, true, recToReplace));
|
||||||
|
// remove/replace it
|
||||||
|
final Instant start = currentRecs.remove(recToReplace);
|
||||||
|
currentRecs.put(programAutoRec, cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.out.println(cursor);
|
||||||
|
//System.out.println(programAutoRecs);
|
||||||
|
}
|
||||||
|
cursor = cursor.plus(1, ChronoUnit.MINUTES);
|
||||||
|
}
|
||||||
|
log.debug("cursor: {}", cursor);
|
||||||
|
log.debug("lastProgram: {}", lastProgram);
|
||||||
|
log.debug("lastNonTimeProgram: {}", lastNonTimeProgram);
|
||||||
|
// increment by minute up until end scheduling all timers until then, somehow store this info to show/email etc?
|
||||||
|
|
||||||
|
log.debug("import done.\n\n------\n\n");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* rcrdit records TV programs from TV tuners
|
||||||
|
* Copyright (C) 2017 Travis Burtrum
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.moparisthebest.rcrdit.scheduler;
|
||||||
|
|
||||||
|
import com.moparisthebest.rcrdit.autorec.AutoRec;
|
||||||
|
import com.moparisthebest.rcrdit.xmltv.Program;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mopar on 3/14/17.
|
||||||
|
*/
|
||||||
|
public interface Scheduler {
|
||||||
|
void schedulePrograms(final List<AutoRec> autoRecs, final List<Program> programs, final int numTuners, final Instant end, final Instant startAt);
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* rcrdit records TV programs from TV tuners
|
||||||
|
* Copyright (C) 2017 Travis Burtrum
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program 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 Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.moparisthebest.rcrdit.scheduler;
|
||||||
|
|
||||||
|
import com.moparisthebest.rcrdit.xmltv.Program;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mopar on 3/14/17.
|
||||||
|
*/
|
||||||
|
public class StartStop {
|
||||||
|
private final Instant instant;
|
||||||
|
private final boolean start;
|
||||||
|
private final Program toStop;
|
||||||
|
|
||||||
|
public StartStop(final Instant instant, final boolean start, final Program toStop) {
|
||||||
|
this.instant = instant;
|
||||||
|
this.start = start;
|
||||||
|
this.toStop = toStop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StartStop(final Instant instant, final boolean start) {
|
||||||
|
this(instant, start, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getInstant() {
|
||||||
|
return instant;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Program getToStop() {
|
||||||
|
return toStop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof StartStop)) return false;
|
||||||
|
final StartStop startStop = (StartStop) o;
|
||||||
|
return start == startStop.start &&
|
||||||
|
Objects.equals(instant, startStop.instant) &&
|
||||||
|
Objects.equals(toStop, startStop.toStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(instant, start, toStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "StartStop{" +
|
||||||
|
"instant=" + instant +
|
||||||
|
", start=" + start +
|
||||||
|
", toStop=" + toStop +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,12 @@
|
|||||||
|
|
||||||
package com.moparisthebest.rcrdit.xmltv;
|
package com.moparisthebest.rcrdit.xmltv;
|
||||||
|
|
||||||
|
import com.moparisthebest.rcrdit.autorec.AutoRec;
|
||||||
|
import com.moparisthebest.rcrdit.scheduler.StartStop;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +32,7 @@ import java.util.Objects;
|
|||||||
//@XmlRootElement(name="programme")
|
//@XmlRootElement(name="programme")
|
||||||
//@JsonIgnoreProperties(value={"previously-shown", "lang", "", "system", "date"})
|
//@JsonIgnoreProperties(value={"previously-shown", "lang", "", "system", "date"})
|
||||||
//@JsonIgnoreProperties(ignoreUnknown = true)
|
//@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class Program {
|
public class Program implements Comparable<Program> {
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -60,6 +65,9 @@ public class Program {
|
|||||||
private final String channelId, channelName, title, subTitle, desc, episodeNum, date, category;
|
private final String channelId, channelName, title, subTitle, desc, episodeNum, date, category;
|
||||||
private final boolean previouslyShown;
|
private final boolean previouslyShown;
|
||||||
|
|
||||||
|
private final List<StartStop> startStops = new ArrayList<>();
|
||||||
|
private AutoRec autoRec;
|
||||||
|
|
||||||
public Program(final Instant start, final Instant stop, final String channelId, final String channelName, final String title, final String subTitle, final String desc, final String episodeNum, final String date, final String category, final boolean previouslyShown) {
|
public Program(final Instant start, final Instant stop, final String channelId, final String channelName, final String title, final String subTitle, final String desc, final String episodeNum, final String date, final String category, final boolean previouslyShown) {
|
||||||
this.start = start;
|
this.start = start;
|
||||||
this.stop = stop;
|
this.stop = stop;
|
||||||
@ -118,6 +126,39 @@ public class Program {
|
|||||||
return previouslyShown;
|
return previouslyShown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AutoRec getAutoRec() {
|
||||||
|
return autoRec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set autoRec if this.autoRec is null, or autoRec.priority > this.autoRec.priority
|
||||||
|
* @param autoRec to set, must be non-null
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Program setAutoRec(final AutoRec autoRec) {
|
||||||
|
if(this.autoRec == null || this.autoRec.getPriority() < autoRec.getPriority())
|
||||||
|
this.autoRec = autoRec;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addStartStop(final StartStop startStop) {
|
||||||
|
startStops.add(startStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<StartStop> getStartStops() {
|
||||||
|
return startStops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
startStops.clear();
|
||||||
|
this.autoRec = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(final Program o) {
|
||||||
|
return this.autoRec.compareTo(o.autoRec);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@ -155,6 +196,26 @@ public class Program {
|
|||||||
", date='" + date + '\'' +
|
", date='" + date + '\'' +
|
||||||
", category='" + category + '\'' +
|
", category='" + category + '\'' +
|
||||||
", previouslyShown=" + previouslyShown +
|
", previouslyShown=" + previouslyShown +
|
||||||
|
//", autoRec=" + autoRec +
|
||||||
|
//", startStops=" + startStops +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fullString() {
|
||||||
|
return "Program{" +
|
||||||
|
"start=" + start +
|
||||||
|
", stop=" + stop +
|
||||||
|
", channelId='" + channelId + '\'' +
|
||||||
|
", channelName='" + channelName + '\'' +
|
||||||
|
", title='" + title + '\'' +
|
||||||
|
", subTitle='" + subTitle + '\'' +
|
||||||
|
", desc='" + desc + '\'' +
|
||||||
|
", episodeNum='" + episodeNum + '\'' +
|
||||||
|
", date='" + date + '\'' +
|
||||||
|
", category='" + category + '\'' +
|
||||||
|
", previouslyShown=" + previouslyShown +
|
||||||
|
", autoRec=" + autoRec +
|
||||||
|
", startStops=" + startStops +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,7 @@ package com.moparisthebest.rcrdit.xmltv;
|
|||||||
import com.moparisthebest.sxf4j.impl.AbstractXmlElement;
|
import com.moparisthebest.sxf4j.impl.AbstractXmlElement;
|
||||||
import com.moparisthebest.sxf4j.impl.XmlElement;
|
import com.moparisthebest.sxf4j.impl.XmlElement;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.*;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
import java.time.format.ResolverStyle;
|
import java.time.format.ResolverStyle;
|
||||||
@ -78,7 +75,7 @@ public class Tv {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Tv readSchedule(final List<String> resources, final Set<String> allChannels) throws Exception {
|
public static Tv readSchedule(final List<String> resources, final Set<String> allChannels, final LocalDateTime topOfHour) throws Exception {
|
||||||
//try /*(InputStream is = new FileInputStream(resource))*/ {
|
//try /*(InputStream is = new FileInputStream(resource))*/ {
|
||||||
/*
|
/*
|
||||||
ObjectMapper mapper = new XmlMapper(new XmlFactory()); // create once, reuse
|
ObjectMapper mapper = new XmlMapper(new XmlFactory()); // create once, reuse
|
||||||
@ -128,7 +125,6 @@ public class Tv {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final LocalDateTime topOfHour = LocalDateTime.now().withMinute(0);
|
|
||||||
final Instant now = topOfHour.toInstant(ZoneOffset.systemDefault().getRules().getOffset(topOfHour)).truncatedTo(ChronoUnit.MINUTES);
|
final Instant now = topOfHour.toInstant(ZoneOffset.systemDefault().getRules().getOffset(topOfHour)).truncatedTo(ChronoUnit.MINUTES);
|
||||||
//final Instant now = Instant.now()..truncatedTo(ChronoUnit.MINUTES);
|
//final Instant now = Instant.now()..truncatedTo(ChronoUnit.MINUTES);
|
||||||
for (final XmlElement prog : tv.getChildren("programme")) {
|
for (final XmlElement prog : tv.getChildren("programme")) {
|
||||||
|
Loading…
Reference in New Issue
Block a user