diff --git a/.gitignore b/.gitignore index cbcf08e..26734c6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.iml target/ out.xml -rcrdit.ics +*.ics xmltv.xml rcrdit.cfg.xml +*.txt +*.diff \ No newline at end of file diff --git a/src/main/java/com/moparisthebest/rcrdit/RcrdIt.java b/src/main/java/com/moparisthebest/rcrdit/RcrdIt.java index 0882f1f..4c12b90 100644 --- a/src/main/java/com/moparisthebest/rcrdit/RcrdIt.java +++ b/src/main/java/com/moparisthebest/rcrdit/RcrdIt.java @@ -24,6 +24,9 @@ import com.moparisthebest.jdbc.QueryMapper; import com.moparisthebest.rcrdit.autorec.AutoRec; import com.moparisthebest.rcrdit.autorec.Profile; 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.HDHomerun; import com.moparisthebest.rcrdit.tuner.Tuner; @@ -62,15 +65,12 @@ import java.math.BigInteger; import java.net.URI; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.sql.Connection; -import java.sql.Date; -import java.sql.DriverManager; -import java.sql.SQLException; +import java.sql.*; +import java.time.*; import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.Date; import java.util.stream.Collectors; import com.moparisthebest.rcrdit.requestbeans.GetScheduleRequest; import com.moparisthebest.rcrdit.requestbeans.NewRecordingRequest; @@ -84,6 +84,9 @@ import org.glassfish.jersey.jackson.JacksonFeature; @Path("") 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); // config items @@ -99,6 +102,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { private final List autoRecs = new ArrayList<>(); private Tv schedule; + private final Scheduler scheduler = new PartialScheduler(); + private final String serverUri; @@ -147,6 +152,12 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { 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, final Instant start, final Instant stop, @@ -174,6 +185,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { description += "\nPriority: " + prog.getAutoRec().getPriority(); meeting.getProperties().add(new Description(description)); + if(fakeTime != null) + meeting.getProperties().add(new DtStamp(new DateTime(1489294362645L))); // add timezone info.. meeting.getProperties().add(tz.getTimeZoneId()); @@ -209,12 +222,13 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { calendar.getComponents().add(meeting); } - private synchronized void reCalculateSchedule() { + private synchronized void reCalculateScheduleOrig() { 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."); @@ -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 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 = schedule.getLastEndTime(); final Map 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 for (final AutoRec autoRec : autoRecs) { 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 } } @@ -276,15 +292,15 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { // free tuner, just add if (currentRecs.size() < tuners.numTuners()) { currentRecs.put(programAutoRec, cursor); + programAutoRec.getProgram().addStartStop(new StartStop(cursor, true)); // check if we are starting one late, from skipped final Instant start = skippedRecs.remove(programAutoRec); if (start != null) { addMeeting(calendar, tz, start, cursor, programAutoRec, md, true); } //System.out.println("scheduling: "+programAutoRec); - final RecordingTask rt = new RecordingTask(programAutoRec); + final RecordingTask rt = new RecordingTask(programAutoRec, cursor); startTimers.add(rt); - timer.schedule(rt, Date.from(cursor)); continue; } // from Tuners.recordNow @@ -302,9 +318,11 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { ) ); //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); - 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 final Instant start = currentRecs.remove(recToReplace); @@ -323,8 +341,163 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { 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? - + 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 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()) try (FileOutputStream fout = new FileOutputStream("rcrdit.ics")) { final CalendarOutputter outputter = new CalendarOutputter(); @@ -345,6 +518,8 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { //if(prog.getStop().isBefore(finalCursor)) { final Instant start = entry.getValue(); addMeeting(calendar, tz, start, prog.getStop(), par, md, skipped); + if(!skipped) + prog.addStartStop(new StartStop(prog.getStop(), false)); it.remove(); } } @@ -395,7 +570,7 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { @Override public void run() { 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); // todo: only re-calcuate if schedule changed reCalculateSchedule(); @@ -407,14 +582,26 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { private class RecordingTask extends TimerTask { 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.start = start; + this.runAt = runAt; + if(fakeTime != null) + timer.schedule(this, Date.from(runAt)); } - RecordingTask(final ProgramAutoRec start) { - this(null, start); + RecordingTask(final ProgramAutoRec start, final Instant runAt) { + 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 @@ -429,6 +616,15 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { e.printStackTrace(); } } + + @Override + public String toString() { + return "RecordingTask{" + + "stop=" + stop + + ", start=" + start + + ", runAt=" + runAt + + "} "; + } } @Override @@ -553,6 +749,7 @@ public class RcrdIt extends ResourceConfig implements AutoCloseable { } public static void main(String[] args) throws Exception { + //System.out.println(System.currentTimeMillis()); if(true) return; final File cfg; if (args.length < 1 || !((cfg = new File(args[0])).exists())) { System.err.println("Usage: java -jar rcrdit.jar /path/to/rcrdit.cfg.xml"); diff --git a/src/main/java/com/moparisthebest/rcrdit/scheduler/PartialScheduler.java b/src/main/java/com/moparisthebest/rcrdit/scheduler/PartialScheduler.java new file mode 100644 index 0000000..8d85c59 --- /dev/null +++ b/src/main/java/com/moparisthebest/rcrdit/scheduler/PartialScheduler.java @@ -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 . + */ + +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 autoRecs, final List 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 currentRecs = new LinkedHashMap<>(numTuners); // todo: needs to be sorted? + final List 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> it = currentRecs.entrySet().iterator(); it.hasNext(); ) { + final Map.Entry 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"); + } +} diff --git a/src/main/java/com/moparisthebest/rcrdit/scheduler/Scheduler.java b/src/main/java/com/moparisthebest/rcrdit/scheduler/Scheduler.java new file mode 100644 index 0000000..daf0467 --- /dev/null +++ b/src/main/java/com/moparisthebest/rcrdit/scheduler/Scheduler.java @@ -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 . + */ + +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 autoRecs, final List programs, final int numTuners, final Instant end, final Instant startAt); +} diff --git a/src/main/java/com/moparisthebest/rcrdit/scheduler/StartStop.java b/src/main/java/com/moparisthebest/rcrdit/scheduler/StartStop.java new file mode 100644 index 0000000..4db5179 --- /dev/null +++ b/src/main/java/com/moparisthebest/rcrdit/scheduler/StartStop.java @@ -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 . + */ + +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 + + '}'; + } +} diff --git a/src/main/java/com/moparisthebest/rcrdit/xmltv/Program.java b/src/main/java/com/moparisthebest/rcrdit/xmltv/Program.java index a60bf7f..84d70f6 100644 --- a/src/main/java/com/moparisthebest/rcrdit/xmltv/Program.java +++ b/src/main/java/com/moparisthebest/rcrdit/xmltv/Program.java @@ -18,7 +18,12 @@ package com.moparisthebest.rcrdit.xmltv; +import com.moparisthebest.rcrdit.autorec.AutoRec; +import com.moparisthebest.rcrdit.scheduler.StartStop; + import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -27,7 +32,7 @@ import java.util.Objects; //@XmlRootElement(name="programme") //@JsonIgnoreProperties(value={"previously-shown", "lang", "", "system", "date"}) //@JsonIgnoreProperties(ignoreUnknown = true) -public class Program { +public class Program implements Comparable { /* @@ -60,6 +65,9 @@ public class Program { private final String channelId, channelName, title, subTitle, desc, episodeNum, date, category; private final boolean previouslyShown; + private final List 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) { this.start = start; this.stop = stop; @@ -118,6 +126,39 @@ public class Program { 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 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 public boolean equals(final Object o) { if (this == o) return true; @@ -155,6 +196,26 @@ public class Program { ", date='" + date + '\'' + ", category='" + category + '\'' + ", 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 + '}'; } } diff --git a/src/main/java/com/moparisthebest/rcrdit/xmltv/Tv.java b/src/main/java/com/moparisthebest/rcrdit/xmltv/Tv.java index bd7b63e..8e5259b 100644 --- a/src/main/java/com/moparisthebest/rcrdit/xmltv/Tv.java +++ b/src/main/java/com/moparisthebest/rcrdit/xmltv/Tv.java @@ -21,10 +21,7 @@ package com.moparisthebest.rcrdit.xmltv; import com.moparisthebest.sxf4j.impl.AbstractXmlElement; import com.moparisthebest.sxf4j.impl.XmlElement; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; +import java.time.*; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.ResolverStyle; @@ -78,7 +75,7 @@ public class Tv { '}'; } - public static Tv readSchedule(final List resources, final Set allChannels) throws Exception { + public static Tv readSchedule(final List resources, final Set allChannels, final LocalDateTime topOfHour) throws Exception { //try /*(InputStream is = new FileInputStream(resource))*/ { /* ObjectMapper mapper = new XmlMapper(new XmlFactory()); // create once, reuse @@ -128,7 +125,6 @@ public class Tv { break; } } - final LocalDateTime topOfHour = LocalDateTime.now().withMinute(0); final Instant now = topOfHour.toInstant(ZoneOffset.systemDefault().getRules().getOffset(topOfHour)).truncatedTo(ChronoUnit.MINUTES); //final Instant now = Instant.now()..truncatedTo(ChronoUnit.MINUTES); for (final XmlElement prog : tv.getChildren("programme")) {