/* * Copyright (c) Henrik Niehaus & Lazy Bones development team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. Neither the name of the project (Lazy Bones) nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package lazybones; import static org.hampelratte.svdrp.responses.highlevel.Timer.ENABLED; import static org.hampelratte.svdrp.responses.highlevel.Timer.VPS; import java.awt.Cursor; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.Observable; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.swing.JOptionPane; import org.hampelratte.svdrp.Response; import org.hampelratte.svdrp.commands.LSTE; import org.hampelratte.svdrp.commands.LSTT; import org.hampelratte.svdrp.parsers.EPGParser; import org.hampelratte.svdrp.parsers.TimerParser; import org.hampelratte.svdrp.responses.highlevel.Channel; import org.hampelratte.svdrp.responses.highlevel.EPGEntry; import org.hampelratte.svdrp.responses.highlevel.Timer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import devplugin.Date; import devplugin.Program; import lazybones.ChannelManager.ChannelNotFoundException; import lazybones.actions.CreateTimerAction; import lazybones.actions.DeleteTimerAction; import lazybones.actions.ModifyTimerAction; import lazybones.actions.responses.ConnectionProblem; import lazybones.conflicts.Conflict; import lazybones.conflicts.ConflictFinder; import lazybones.conflicts.ConflictResolver; import lazybones.gui.components.timeroptions.TimerOptionsDialog; import lazybones.gui.timers.TimerSelectionDialog; import lazybones.logging.LoggingConstants; import lazybones.logging.PopupHandler; import lazybones.programmanager.ProgramDatabase; import lazybones.programmanager.ProgramManager; import lazybones.utils.Utilities; /** * Class to manage all timers. * * @author <a href="hampelratte@users.sf.net">hampelratte@users.sf.net</a> */ public class TimerManager extends Observable { private static transient Logger logger = LoggerFactory.getLogger(TimerManager.class); private static transient Logger conLog = LoggerFactory.getLogger(LoggingConstants.CONNECTION_LOGGER); private static transient Logger epgLog = LoggerFactory.getLogger(LoggingConstants.EPG_LOGGER); private static transient Logger popupLog = LoggerFactory.getLogger(PopupHandler.KEYWORD); private static final int UNKNOWN = -1; /** * Stores all timers as Timer objects */ private final List<LazyBonesTimer> timers = new ArrayList<LazyBonesTimer>(); private final Lock timerListLock = new ReentrantLock(); /** * The VDR timers from the last session, which have been stored to disk */ private List<LazyBonesTimer> storedTimers = new ArrayList<LazyBonesTimer>(); /** * Stores mappings the user has made for later use. The user has to map one Program only once. Later the mapping will be looked up here */ private TitleMapping titleMapping = new TitleMapping(); private final Cursor WAITING_CURSOR = new Cursor(Cursor.WAIT_CURSOR); private final Cursor DEFAULT_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); private ConflictFinder conflictFinder = new ConflictFinder(); private RecordingManager recordingManager; private void addTimer(LazyBonesTimer timer, boolean calculateRepeatingTimers, boolean notifyObservers) { if (!timer.isRepeating() || !calculateRepeatingTimers) { timerListLock.lock(); timers.add(timer); timerListLock.unlock(); } else { Calendar startTime = timer.getStartTime(); Calendar endTime = timer.getEndTime(); long duration = endTime.getTimeInMillis() - startTime.getTimeInMillis(); if (timer.hasFirstTime()) { Calendar tmp = timer.getFirstTime(); startTime.set(Calendar.DAY_OF_MONTH, tmp.get(Calendar.DAY_OF_MONTH)); startTime.set(Calendar.MONTH, tmp.get(Calendar.MONTH)); startTime.set(Calendar.YEAR, tmp.get(Calendar.YEAR)); } if (calculateRepeatingTimers) { for (int j = 0; j < 21; j++) { // next 3 weeks, more data is never available in TVB Calendar tmpStart = (Calendar) startTime.clone(); tmpStart.add(Calendar.DAY_OF_MONTH, j); if (timer.isDaySet(tmpStart)) { LazyBonesTimer oneDayTimer = (LazyBonesTimer) timer.clone(); oneDayTimer.setStartTime(tmpStart); long start = tmpStart.getTimeInMillis(); oneDayTimer.getEndTime().setTimeInMillis(start + duration); timerListLock.lock(); timers.add(oneDayTimer); timerListLock.unlock(); } } } } if (notifyObservers) { setChanged(); notifyObservers(new TimersChangedEvent(TimersChangedEvent.TIMER_ADDED, timer)); } } /** * Removes this timer from the internal list. This will result in a disappearence of this timer in any GUI element. * * <string>Note!</strong>This method will not delete the timer on the VDR. To achieve that, use {@link #deleteTimer(Timer)} * * @param timer */ public void removeTimer(LazyBonesTimer timer) { timerListLock.lock(); timers.remove(timer); timerListLock.unlock(); setChanged(); notifyObservers(new TimersChangedEvent(TimersChangedEvent.TIMER_REMOVED, timer)); } /** * @return a List of Timer objects */ public List<LazyBonesTimer> getTimers() { // return a copy of the timer list to avoid ConcurrentModificationExceptions timerListLock.lock(); List<LazyBonesTimer> timersCopy = new ArrayList<>(timers); timerListLock.unlock(); return timersCopy; } /** * @param vdrTimers * an List of Timer objects */ public void setTimers(List<LazyBonesTimer> vdrTimers, boolean calculateRepeatingTimers) { for (LazyBonesTimer timer : vdrTimers) { addTimer(new LazyBonesTimer(timer), calculateRepeatingTimers, false); } // try to mark programs ProgramManager.getInstance().markPrograms(this); List<LazyBonesTimer> notAssigned = getNotAssignedTimers(); if (notAssigned.size() > 0) { handleNotAssignedTimers(); } // handle conflicts, if some have been detected Set<Conflict> conflicts = conflictFinder.findConflictingTimers(getTimers()); if (!conflicts.isEmpty()) { // clear old conflicts, which were stored on timers timerListLock.lock(); for (LazyBonesTimer timer : timers) { timer.getConflicts().clear(); } timerListLock.unlock(); // save conflicts on timers for (Conflict conflict : conflicts) { for (LazyBonesTimer timer : conflict.getInvolvedTimers()) { timer.getConflicts().add(conflict); } } boolean showTimerConflicts = Boolean.parseBoolean(LazyBones.getProperties().getProperty("timer.conflicts.show", "true")); if (showTimerConflicts) { ConflictResolver conflictResolver = new ConflictResolver(conflicts.iterator().next(), getTimers()); conflictResolver.handleConflicts(); } } // notify observers, that the timers have changed setChanged(); notifyObservers(new TimersChangedEvent(TimersChangedEvent.ALL, getTimers())); } /** * * @param prog * a devplugin.Program object * @return the timer for this program or null * @see Program */ public LazyBonesTimer getTimer(Program prog) { String progID = prog.getUniqueID(); if (progID == null) { // this really should not happen logger.warn("Unique program ID is null. Unable to find a timer for this program [" + prog.toString() + "]"); return null; } timerListLock.lock(); try { for (LazyBonesTimer timer : timers) { List<String> tvBrowserProdIDs = timer.getTvBrowserProgIDs(); for (String curProgID : tvBrowserProdIDs) { if (progID.equals(curProgID)) { return timer; // disabled, what is this condition for? // LazyBonesTimer bufferless = timer.getTimerWithoutBuffers(); // Calendar cal = prog.getDate().getCalendar(); // if (tvBrowserProdIDs.size() == 1 || Utilities.sameDay(cal, bufferless.getStartTime())) { // return timer; // } } } } } finally { timerListLock.unlock(); } return null; } /** * * @param timerNumber * The number of the timer * @return The timer with the specified number */ public LazyBonesTimer getTimer(int timerNumber) { timerListLock.lock(); try { for (LazyBonesTimer timer : timers) { if (timer.getID() == timerNumber) { return timer; } } } finally { timerListLock.unlock(); } return null; } /** * Returns all timers, which couldn't be assigned to a program * * @return an ArrayList with Timer objects */ public List<LazyBonesTimer> getNotAssignedTimers() { ArrayList<LazyBonesTimer> list = new ArrayList<LazyBonesTimer>(); timerListLock.lock(); try { for (LazyBonesTimer timer : timers) { if (!timer.isAssigned()) { list.add(timer); } } } finally { timerListLock.unlock(); } return list; } /** * For DEBUG only - print all timers to System.out * */ public void printTimers() { System.out.println("########## Listing timers #################"); for (LazyBonesTimer timer : timers) { System.out.println(timer); } System.out.println("################ End ######################"); } public List<LazyBonesTimer> getStoredTimers() { return storedTimers; } public void setStoredTimers(List<LazyBonesTimer> storedTimers) { this.storedTimers = storedTimers; } /** * Checks storedTimers, if this timer has been mapped to Program before. In case it has been mapped before, we return a list of all the program ids, this * timer has been assigned to. * * @param timer * @return a list of program ids */ public List<String> hasBeenMappedBefore(LazyBonesTimer timer) { for (LazyBonesTimer storedTimer : storedTimers) { if (timer.getUniqueKey().equals(storedTimer.getUniqueKey())) { if (storedTimer.getReason() == LazyBonesTimer.NO_PROGRAM) { List<String> timers = new ArrayList<String>(); timers.add("NO_PROGRAM"); return timers; } else { return storedTimer.getTvBrowserProgIDs(); } } } return Collections.emptyList(); } public void replaceStoredTimer(LazyBonesTimer timer) { for (LazyBonesTimer storedTimer : storedTimers) { if (timer.getUniqueKey().equals(storedTimer.getUniqueKey())) { storedTimers.remove(storedTimer); storedTimers.add(timer); return; } } // timer couldn't be found -> this is a new timer storedTimers.add(timer); } public TitleMapping getTitleMapping() { return this.titleMapping; } /** * Fetches the timer list from vdr */ public synchronized void synchronize() { LazyBones.getInstance().getParent().setCursor(WAITING_CURSOR); LazyBones.getInstance().getMainDialog().setCursor(WAITING_CURSOR); // unmark all tvbrowser programs ProgramManager.getInstance().unmarkPrograms(); // clear timer list timerListLock.lock(); this.timers.clear(); timerListLock.unlock(); // fetch current timer list from vdr Response res = VDRConnection.send(new LSTT()); if (res != null && res.getCode() == 250) { logger.info("Timers retrieved from VDR"); String timersString = res.getMessage(); List<Timer> vdrtimers = TimerParser.parse(timersString); List<LazyBonesTimer> timers = new ArrayList<LazyBonesTimer>(); for (Timer vdrtimer : vdrtimers) { timers.add(new LazyBonesTimer(vdrtimer)); } setTimers(timers, true); // update recording list if necessary boolean updateRecordings = false; for (Timer timer : vdrtimers) { if (timer.isRecording()) { updateRecordings = true; break; } } if (updateRecordings) { recordingManager.synchronize(); } } else if (res != null && res.getCode() == 550) { // no timers are defined, do nothing logger.info("No timer defined on VDR"); setChanged(); notifyObservers(new TimersChangedEvent(TimersChangedEvent.ALL, getTimers())); } else { // something went wrong, we have no timers -> load the stored ones conLog.error(LazyBones.getTranslation("using_stored_timers", "Couldn't retrieve timers from VDR, using stored ones.")); List<LazyBonesTimer> vdrtimers = getStoredTimers(); setTimers(vdrtimers, false); } LazyBones.getInstance().getParent().setCursor(DEFAULT_CURSOR); LazyBones.getInstance().getMainDialog().setCursor(DEFAULT_CURSOR); } /** * Deletes a timer on the VDR * * @param timer * timer to delete */ public void deleteTimer(final LazyBonesTimer timer) { deleteTimer(timer, null); } /** * Deletes a timer on the VDR * * @param timer * timer to delete * @param callback * a Runnable object, which is run after the delete process is finished */ public void deleteTimer(final LazyBonesTimer timer, final Runnable callback) { VDRCallback<DeleteTimerAction> _callback = new VDRCallback<DeleteTimerAction>() { @Override public void receiveResponse(DeleteTimerAction cmd, Response response) { if (!cmd.isSuccess()) { logger.error(LazyBones.getTranslation("couldnt_delete", "Couldn't delete timer:") + " " + cmd.getResponse().getMessage()); } else { synchronize(); } if (callback != null) { callback.run(); } } }; DeleteTimerAction dta = new DeleteTimerAction(timer, _callback); dta.enqueue(); } public void createTimerFromScratch() throws ChannelNotFoundException { LazyBonesTimer timer = new LazyBonesTimer(); timer.setChannelNumber(1); Program prog = ProgramDatabase.getProgram(timer); // in this situation it makes sense to show the timer options // so we override the user setting (hide options dialog) boolean showTimerOptions = Boolean.TRUE.toString().equals(LazyBones.getProperties().getProperty("showTimerOptionsDialog")); LazyBones.getProperties().setProperty("showTimerOptionsDialog", Boolean.TRUE.toString()); createTimer(prog, false); LazyBones.getProperties().setProperty("showTimerOptionsDialog", Boolean.toString(showTimerOptions)); } /** * Creates a new timer on the VDR * * @param prog * the Program to create a timer for * @param automatic * supresses all user interaction */ public void createTimer(Program prog, boolean automatic) { if (prog.isExpired()) { if (!automatic) { logger.error(LazyBones.getTranslation("expired", "This program has expired")); } return; } Channel c = ChannelManager.getChannelMapping().get(prog.getChannel().getId()); if (c == null) { logger.error(LazyBones.getTranslation("no_channel_defined", "No channel defined", prog.toString())); return; } int id = c.getChannelNumber(); long middleOfProgramTimeInMillis = determineMiddleOfProgramTime(prog); Response res = VDRConnection.send(new LSTE(id, middleOfProgramTimeInMillis / 1000)); if (res != null && res.getCode() == 215) { List<EPGEntry> epgList = new EPGParser().parse(res.getMessage()); if (epgList.size() <= 0) { noEPGAvailable(prog, id, automatic); return; } EPGEntry vdrEPG = epgList.get(0); LazyBonesTimer timer = new LazyBonesTimer(); timer.setChannelNumber(id); timer.addTvBrowserProgID(prog.getUniqueID()); if (vdrEPG != null) { setStartAndEndTime(vdrEPG, timer); timer.setFile(vdrEPG.getTitle()); timer.createTimerDescription(prog, vdrEPG); } else { // VDR has no EPG data noEPGAvailable(prog, id, automatic); return; } boolean showOptionsDialog = !automatic && Boolean.TRUE.toString().equals(LazyBones.getProperties().getProperty("showTimerOptionsDialog")); if (showOptionsDialog) { TimerOptionsDialog tod = new TimerOptionsDialog(this, recordingManager, timer, prog, TimerOptionsDialog.Mode.NEW); if (tod.isAccepted()) { commitTimer(tod.getTimer(), tod.getOldTimer(), tod.getProgram(), false, false); } } else { commitTimer(timer, null, prog, false, automatic); } } else if (res != null && res.getCode() == 550 & "No schedule found\n".equals(res.getMessage())) { noEPGAvailable(prog, id, automatic); } else { if (res instanceof ConnectionProblem) { conLog.error(LazyBones.getTranslation("couldnt_create", "Couldn\'t create timer\n: ") + " " + res.getMessage()); } else { String msg = res != null ? res.getMessage() : "Reason unknown"; logger.error(LazyBones.getTranslation("couldnt_create", "Couldn\'t create timer\n: ") + " " + msg); } } } private long determineMiddleOfProgramTime(Program prog) { Calendar cal = GregorianCalendar.getInstance(); Date date = prog.getDate(); cal.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth()); cal.set(Calendar.MONTH, date.getMonth() - 1); cal.set(Calendar.YEAR, date.getYear()); cal.set(Calendar.HOUR_OF_DAY, prog.getHours()); cal.set(Calendar.MINUTE, prog.getMinutes()); if (prog.getLength() == UNKNOWN) { // if the length is unknown, we add 5 minutes to the starttime, so that we still get the right program, // if the vdr start time differs from the tvbrowser start time cal.add(Calendar.MINUTE, 5); } else { cal.add(Calendar.MINUTE, prog.getLength() / 2); } return cal.getTimeInMillis(); } private void setStartAndEndTime(EPGEntry vdrEPG, LazyBonesTimer timer) { // set start and end time Calendar calStart = vdrEPG.getStartTime(); timer.setStartTime(calStart); Calendar calEnd = vdrEPG.getEndTime(); timer.setEndTime(calEnd); setTimerBuffers(timer); } public static void setTimerBuffers(LazyBonesTimer timer) { Calendar calStart = timer.getStartTime(); Calendar calEnd = timer.getEndTime(); // if we have a vps timer, set the status to vps, otherwise add the recording time buffers boolean vpsDefault = Boolean.parseBoolean(LazyBones.getProperties().getProperty("vps.default")); if (vpsDefault) { timer.changeStateTo(VPS, ENABLED); } else { // start the recording x min before the beginning of the program int bufferBefore = Integer.parseInt(LazyBones.getProperties().getProperty("timer.before")); calStart.add(Calendar.MINUTE, -bufferBefore); // stop the recording x min after the end of the program int bufferAfter = Integer.parseInt(LazyBones.getProperties().getProperty("timer.after")); calEnd.add(Calendar.MINUTE, bufferAfter); } } /** * * @param prog * Program to create a timer for * @param channelNumber * the corresponding VDR channel * @param automatic * supresses all user interaction */ private void noEPGAvailable(Program prog, int channelNumber, boolean automatic) { int buffer_before = Integer.parseInt(LazyBones.getProperties().getProperty("timer.before")); int buffer_after = Integer.parseInt(LazyBones.getProperties().getProperty("timer.after")); boolean dontCare = automatic || Boolean.FALSE.toString().equals(LazyBones.getProperties().getProperty("logEPGErrors")); int result = JOptionPane.NO_OPTION; if (!dontCare) { result = JOptionPane.showConfirmDialog(null, LazyBones.getTranslation("noEPGdata", ""), "", JOptionPane.YES_NO_OPTION); } if (dontCare || result == JOptionPane.OK_OPTION) { LazyBonesTimer newTimer = new LazyBonesTimer(); newTimer.setState(Timer.ACTIVE); newTimer.setChannelNumber(channelNumber); int prio = Integer.parseInt(LazyBones.getProperties().getProperty("timer.prio")); int lifetime = Integer.parseInt(LazyBones.getProperties().getProperty("timer.lifetime")); newTimer.setLifetime(lifetime); newTimer.setPriority(prio); newTimer.setTitle(prog.getTitle()); newTimer.addTvBrowserProgID(prog.getUniqueID()); Calendar startTime = prog.getDate().getCalendar(); int start = prog.getStartTime(); int hour = start / 60; int minute = start % 60; startTime.set(Calendar.HOUR_OF_DAY, hour); startTime.set(Calendar.MINUTE, minute); Calendar endTime = (Calendar) startTime.clone(); endTime.add(Calendar.MINUTE, prog.getLength()); // add buffers startTime.add(Calendar.MINUTE, -buffer_before); newTimer.setStartTime(startTime); endTime.add(Calendar.MINUTE, buffer_after); newTimer.setEndTime(endTime); if (automatic) { commitTimer(newTimer, null, prog, false, true); } else { TimerOptionsDialog tod = new TimerOptionsDialog(this, recordingManager, newTimer, prog, TimerOptionsDialog.Mode.NEW); if (tod.isAccepted()) { commitTimer(tod.getTimer(), tod.getOldTimer(), tod.getProgram(), false, false); } } } } /** * Commits a new or changed timer to VDR * * @param timer * The new created / updated Timer * @param oldTimer * A clone of the timer with the old settings. Can be null for new timers. * @param prog * The according Program * @param update * If the Timer is a new one or if the timer has been edited * @param automatic * Supresses all user interaction */ private void commitTimer(final LazyBonesTimer timer, LazyBonesTimer oldTimer, final Program prog, boolean update, boolean automatic) { logger.debug("Comitting timer to VDR"); int id = -1; if (prog != null) { Channel chan = ChannelManager.getChannelMapping().get(prog.getChannel().getId()); if (chan == null) { logger.error(LazyBones.getTranslation("no_channel_defined", "No channel defined", prog.toString())); return; } id = chan.getChannelNumber(); } if (update) { logger.debug("Timer exists and will be modified"); VDRCallback<ModifyTimerAction> callback = new VDRCallback<ModifyTimerAction>() { @Override public void receiveResponse(ModifyTimerAction cmd, Response response) { TimerManager.this.synchronize(); if (!cmd.isSuccess()) { String mesg = LazyBones.getTranslation("couldnt_change", "Couldn\'t change timer:") + " " + cmd.getResponse().getMessage(); logger.error(mesg); } } }; ModifyTimerAction mta = new ModifyTimerAction(timer, oldTimer); mta.setCallback(callback); mta.enqueue(); } else { logger.debug("Creating a new timer"); if (timer.getTitle() != null) { int percentage; if (timer.getPath() != null && timer.getPath() != "") { percentage = Utilities.percentageOfEquality(prog.getTitle(), timer.getPath() + timer.getTitle()); } else { percentage = Utilities.percentageOfEquality(prog.getTitle(), timer.getTitle()); } if (timer.getFile().indexOf("EPISODE") >= 0 || timer.getFile().indexOf("TITLE") >= 0 || timer.isRepeating() || automatic) { percentage = 100; } int threshold = Integer.parseInt(LazyBones.getProperties().getProperty("percentageThreshold")); if (percentage > threshold) { CreateTimerAction cta = new CreateTimerAction(this, timer); cta.enqueue(); } else { logger.debug("Looking in title mapping for timer {}", timer); // lookup in mapping history String timerTitle = getTitleMapping().getVdrTitle(prog.getTitle()); if (timer.getTitle().equals(timerTitle)) { VDRCallback<CreateTimerAction> callback = new VDRCallback<CreateTimerAction>() { @Override public void receiveResponse(CreateTimerAction cmd, Response response) { if (cmd.isSuccess()) { timer.addTvBrowserProgID(prog.getUniqueID()); replaceStoredTimer(timer); } } }; CreateTimerAction cta = new CreateTimerAction(this, timer); cta.setCallback(callback); cta.enqueue(); } else { // no mapping found -> ask the user showTimerConfirmDialog(timer, prog); } } } else { // VDR has no EPG data noEPGAvailable(prog, id, automatic); } } } public void assignProgramToTimer(Program prog, LazyBonesTimer timer) { timer.addTvBrowserProgID(prog.getUniqueID()); replaceStoredTimer(timer); if (!prog.getTitle().equals(timer.getTitle())) { getTitleMapping().put(prog.getTitle(), timer.getTitle()); } } /** * If a Program can't be assigned to a VDR-Program, this method shows a dialog to select the right VDR-Program * * @param prog * the Program selected in TV-Browser * @param timerOptions * the timer from TimerOptionsDialog */ private void showTimerConfirmDialog(LazyBonesTimer timerOptions, Program prog) { // get all programs 2 hours before and after the given program Calendar cal = GregorianCalendar.getInstance(); Date date = prog.getDate(); cal.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth()); cal.set(Calendar.MONTH, date.getMonth() - 1); cal.set(Calendar.YEAR, date.getYear()); cal.set(Calendar.HOUR_OF_DAY, prog.getHours()); cal.set(Calendar.MINUTE, prog.getMinutes()); cal.add(Calendar.MINUTE, prog.getLength() / 2); devplugin.Channel chan = prog.getChannel(); TreeSet<LazyBonesTimer> programSet = new TreeSet<LazyBonesTimer>(); // get the program for the timer's time Calendar c = GregorianCalendar.getInstance(); c.setTimeInMillis(cal.getTimeInMillis()); LazyBonesTimer t = ProgramManager.getInstance().getTimerForTime(c, chan); if (t != null) { programSet.add(t); } for (int i = 10; i <= 120; i += 10) { // get the program before the given one c = GregorianCalendar.getInstance(); c.setTimeInMillis(cal.getTimeInMillis()); c.add(Calendar.MINUTE, i * -1); LazyBonesTimer t1 = ProgramManager.getInstance().getTimerForTime(c, chan); if (t1 != null) { programSet.add(t1); } // get the program after the given one c = GregorianCalendar.getInstance(); c.setTimeInMillis(cal.getTimeInMillis()); c.add(Calendar.MINUTE, i); LazyBonesTimer t2 = ProgramManager.getInstance().getTimerForTime(c, chan); if (t2 != null) { programSet.add(t2); } } Program[] programs = new Program[programSet.size()]; int i = 0; for (LazyBonesTimer timer : programSet) { Calendar time = timer.getStartTime(); TimerProgram p = new TimerProgram(chan, new Date(time), time.get(Calendar.HOUR_OF_DAY), time.get(Calendar.MINUTE)); p.setTitle(timer.getTitle()); p.setDescription(""); p.setTimer(timer); programs[i++] = p; } // reverse the order of the programs Program[] temp = new Program[programs.length]; for (int j = 0; j < programs.length; j++) { temp[j] = programs[programs.length - 1 - j]; } programs = temp; // show dialog new TimerSelectionDialog(programs, timerOptions, prog); LazyBones.getInstance().synchronize(); } public void deleteTimer(final Program prog) { LazyBonesTimer timer = getTimer(prog); logger.debug("Deleting timer {}", timer); VDRCallback<DeleteTimerAction> callback = new VDRCallback<DeleteTimerAction>() { @Override public void receiveResponse(DeleteTimerAction cmd, Response response) { if (cmd instanceof DeleteTimerAction) { if (!cmd.isSuccess()) { logger.error(LazyBones.getTranslation("couldnt_delete", "Couldn\'t delete timer:") + " " + cmd.getResponse().getMessage()); return; } prog.unmark(LazyBones.getInstance()); synchronize(); } } }; DeleteTimerAction dta = new DeleteTimerAction(timer, callback); dta.enqueue(); } public void editTimer(LazyBonesTimer timer) { logger.debug("Looking up program for timer {}", timer); Program prog = null; if (timer.getTvBrowserProgIDs().size() > 0) { logger.debug("Timer has {} assigned programs", timer.getTvBrowserProgIDs().size()); prog = ProgramDatabase.getProgram(timer.getTvBrowserProgIDs().get(0)); } else { logger.warn("Timer has no program IDs assigned."); } logger.debug("Creating timer options dialog"); TimerOptionsDialog tod = new TimerOptionsDialog(this, recordingManager, timer, prog, TimerOptionsDialog.Mode.UPDATE); if (tod.isAccepted()) { logger.debug("Timer options dialog has been accepted"); commitTimer(tod.getTimer(), tod.getOldTimer(), tod.getProgram(), true, false); } else { logger.debug("Timer options dialog has been canceled"); } } public boolean lookUpTimer(LazyBonesTimer timer, Program candidate) { logger.debug("Looking in storedTimers for: {}", timer.toString()); boolean found = lookUpMappedTimer(timer); if(!found) { logger.debug("No mapping found for: {}", timer.toString()); if (candidate != null) { logger.debug("Looking up old mappings"); String progTitle = getTitleMapping().getTvbTitle(timer.getTitle()); if (candidate.getTitle().equals(progTitle)) { candidate.mark(LazyBones.getInstance()); // wieso mark hier drin? lookup hört sich nicht danach an timer.addTvBrowserProgID(candidate.getUniqueID()); logger.debug("Old mapping found for: {}", timer.toString()); return true; } } } return false; } private boolean lookUpMappedTimer(LazyBonesTimer timer) { List<String> progIDs = hasBeenMappedBefore(timer); if (progIDs != null) { // we have a mapping of this timer to a program for (String progID : progIDs) { if (progID.equals("NO_PROGRAM")) { logger.debug("Timer {} should never be assigned", timer.toString()); timer.setReason(LazyBonesTimer.NO_PROGRAM); return true; } else { try { devplugin.Channel c = ChannelManager.getInstance().getTvbrowserChannel(timer); Date date = new Date(timer.getStartTime()); Iterator<Program> iterator = LazyBones.getPluginManager().getChannelDayProgram(date, c); while (iterator != null && iterator.hasNext()) { Program p = iterator.next(); if (p.getUniqueID().equals(progID) && p.getDate().equals(date)) { p.mark(LazyBones.getInstance()); timer.setTvBrowserProgIDs(progIDs); logger.debug("Mapping found for: {}", timer.toString()); return true; } } } catch (ChannelNotFoundException e) { // fail silently } } } } return false; } /** * Handles all timers, which couldn't be assigned automatically * */ public void handleNotAssignedTimers() { if (Boolean.TRUE.toString().equals(LazyBones.getProperties().getProperty("supressMatchDialog"))) { return; } Iterator<LazyBonesTimer> iterator = getNotAssignedTimers().iterator(); logger.debug("Not assigned timers: {}", getNotAssignedTimers().size()); while (iterator.hasNext()) { LazyBonesTimer timer = iterator.next(); switch (timer.getReason()) { case LazyBonesTimer.NOT_FOUND: // show message java.util.Date date = new java.util.Date(timer.getStartTime().getTimeInMillis()); SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm"); String dateString = sdf.format(date); String title = timer.getPath() + timer.getTitle(); Channel chan = ChannelManager.getInstance().getChannelByNumber(timer.getChannelNumber()); String channelName = "unknown channel"; if (chan != null) { channelName = chan.getName(); } String msg = LazyBones.getTranslation("message_programselect", "I couldn\'t find a program, which matches the vdr timer\n<b>{0}</b> at <b>{1}</b> on <b>{2}</b>.\n" + "You may assign this timer to a program in the context menu.", title, dateString, channelName); popupLog.warn(msg); break; case LazyBonesTimer.NO_EPG: logger.warn("Couldn't assign timer: ", timer); String mesg = LazyBones.getTranslation("noEPGdataTVB", "<html>TV-Browser has no EPG-data the timer {0}.<br>Please update your EPG-data!</html>", timer.toString()); epgLog.error(mesg); break; case LazyBonesTimer.NO_CHANNEL: mesg = LazyBones.getTranslation("no_channel_defined", "No channel defined", timer.toString()); epgLog.error(mesg); break; case LazyBonesTimer.NO_PROGRAM: // do nothing break; default: logger.debug("Not assigned timer: {}", timer); } } } public RecordingManager getRecordingManager() { return recordingManager; } public void setRecordingManager(RecordingManager recordingManager) { this.recordingManager = recordingManager; } }